diff --git a/docs/assets/personal-workspace/steward-reports/masthead/after-desktop.png b/docs/assets/personal-workspace/steward-reports/masthead/after-desktop.png new file mode 100644 index 0000000000..12e2f482ab Binary files /dev/null and b/docs/assets/personal-workspace/steward-reports/masthead/after-desktop.png differ diff --git a/docs/assets/personal-workspace/steward-reports/masthead/after-narrow.png b/docs/assets/personal-workspace/steward-reports/masthead/after-narrow.png new file mode 100644 index 0000000000..28c29c76b6 Binary files /dev/null and b/docs/assets/personal-workspace/steward-reports/masthead/after-narrow.png differ diff --git a/docs/assets/personal-workspace/steward-reports/masthead/before-desktop.png b/docs/assets/personal-workspace/steward-reports/masthead/before-desktop.png new file mode 100644 index 0000000000..792a1dfcdc Binary files /dev/null and b/docs/assets/personal-workspace/steward-reports/masthead/before-desktop.png differ diff --git a/loopx/capabilities/periodic_report/private_generation.py b/loopx/capabilities/periodic_report/private_generation.py index d83c78bbe4..30d4546964 100644 --- a/loopx/capabilities/periodic_report/private_generation.py +++ b/loopx/capabilities/periodic_report/private_generation.py @@ -254,6 +254,10 @@ def validate_private_generation(folder: Path) -> dict[str, Any]: # Rendering can improve after a window was frozen. Reconstruct only the # exact prior presentation; never rewrite or regenerate it. # Both candidates still require full bundle, source and body equality. + checked = build_periodic_report_generation_bundle(document=bundle["document"], artifacts=[ + render_periodic_report_markdown(bundle["document"]), + render_periodic_report_html(bundle["document"], _legacy_masthead=True)]) + if checked != bundle: checked = build_periodic_report_generation_bundle(document=bundle["document"], artifacts=[ render_periodic_report_markdown(bundle["document"], _legacy_layout=True), render_periodic_report_html(bundle["document"], _legacy_layout=True)]) diff --git a/loopx/presentation/renderers/periodic_report_html.py b/loopx/presentation/renderers/periodic_report_html.py index 60510e3b28..416e218c86 100644 --- a/loopx/presentation/renderers/periodic_report_html.py +++ b/loopx/presentation/renderers/periodic_report_html.py @@ -458,7 +458,7 @@ def _render_source_references(references: Mapping[str, int], labels: Mapping[str .shell { width: min(1220px, calc(100% - 40px)); margin: 0 auto; padding: 40px 0 80px; } .masthead { border-top: 8px solid var(--ink); border-bottom: 1.5px solid var(--ink); padding: 27px 0 26px; } -.mast-grid { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 28px; +.mast-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, 360px); gap: 28px; align-items: end; } .eyebrow { margin: 0 0 8px; color: var(--muted); font: 650 10px/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase; } @@ -663,13 +663,20 @@ def _render_source_references(references: Mapping[str, int], labels: Mapping[str def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_links: bool = False, - _legacy_layout: bool = False) -> dict[str, Any]: + _legacy_layout: bool = False, + _legacy_masthead: bool = False) -> dict[str, Any]: """Render one dense, self-contained report without changing its semantics.""" if document.get("schema_version") != DOCUMENT_SCHEMA: raise ValueError(f"document must use {DOCUMENT_SCHEMA}") validate_periodic_report_editorial(document) _legacy_layout = _legacy_layout or _legacy_source_links + css = _CSS + if _legacy_masthead or _legacy_layout: + # Exact read-only reconstruction of already frozen presentations. + css = css.replace( + "grid-template-columns: minmax(0, 1fr) minmax(240px, 360px)", + "grid-template-columns: minmax(0, 1fr) auto", 1) references = {} if _legacy_layout else _source_references(document) sections = _items(document.get("sections")) primary_sections = [ @@ -757,7 +764,7 @@ def render_periodic_report_html(document: Mapping[str, Any], *, _legacy_source_l '" - f"{title}" + f"{title}" '
' '
' diff --git a/tests/capabilities/test_periodic_report_private_generation.py b/tests/capabilities/test_periodic_report_private_generation.py index 0f7e428b66..2d3a1833f4 100644 --- a/tests/capabilities/test_periodic_report_private_generation.py +++ b/tests/capabilities/test_periodic_report_private_generation.py @@ -191,7 +191,7 @@ def host_codec_read_text(path: Path, *args, **kwargs): @pytest.mark.parametrize("tamper", [None, "report", "html", "source", "response"]) -@pytest.mark.parametrize("legacy_links", [False, True]) +@pytest.mark.parametrize("legacy_links", [False, True, None]) def test_frozen_report_keeps_prior_presentations_across_renderer_upgrade(tmp_path, monkeypatch, tamper, legacy_links): from loopx.presentation.renderers import periodic_report_html as html from loopx.presentation.renderers import periodic_report_markdown as markdown @@ -206,14 +206,20 @@ def previous_html_source(value, **_): prior_markdown = markdown.render_periodic_report_markdown prior_html = html.render_periodic_report_html - # Seed through the real freezer, retaining both shipped inline layouts. + # Seed through the real freezer, retaining all three shipped layouts. # The old source rules remain independently specified, not inferred from # the new link-eligibility helper or the validator's selected fallback. with monkeypatch.context() as previous: - previous.setattr(markdown, "render_periodic_report_markdown", lambda document, **kwargs: - prior_markdown(document, **{**kwargs, "_legacy_layout": True})) - previous.setattr(html, "render_periodic_report_html", lambda document, **kwargs: - prior_html(document, **{**kwargs, "_legacy_layout": True})) + # Independently reproduce the shipped CSS. Its automatic side column + # predates bounded prose highlights in all three presentations. + previous.setattr(html, "_CSS", html._CSS.replace( + "grid-template-columns: minmax(0, 1fr) minmax(240px, 360px)", + "grid-template-columns: minmax(0, 1fr) auto", 1)) + if legacy_links is not None: + previous.setattr(markdown, "render_periodic_report_markdown", lambda document, **kwargs: + prior_markdown(document, **{**kwargs, "_legacy_layout": True})) + previous.setattr(html, "render_periodic_report_html", lambda document, **kwargs: + prior_html(document, **{**kwargs, "_legacy_layout": True})) if legacy_links: previous.setattr(markdown, "_markdown_source", lambda value, labels, **_: f"[{labels['source']}]({markdown._safe(value, maximum=500)})") @@ -221,14 +227,17 @@ def previous_html_source(value, **_): receipt = freeze_private_report(root=root, window=window, response=response) original = {path.name: path.read_bytes() for path in folder.iterdir() if path.is_file()} # Independent historical renderer oracles: parent of #7413 for old links, - # e20e4b684 for safe inline references. The input/window above is fixed. + # e20e4b684 for safe inline references, be1dc37a0 for indexed sources. + # The input/window above is fixed. expected_text_digest = ( "509ff2e53bbce2f703049f1684e1a9f2048826ebb8c386891650b52841a1ed23" if legacy_links else "8d97d55e80f759e7eaadb73c5df93afea4d5cfbc071301a9726cb47ff37f9755" + if legacy_links is False else + "0041149af93d653431f5a70e0b98ad388462b1b93b81bc7a718a39f0b54dec8d" ) assert hashlib.sha256(original["report.md"]).hexdigest() == expected_text_digest - assert ("[来源](sha256:" in original["report.md"].decode("utf-8")) is legacy_links + assert ("[来源](sha256:" in original["report.md"].decode("utf-8")) is bool(legacy_links) assert "[来源](sha256:" not in markdown.render_periodic_report_markdown( json.loads(original["generation-bundle.json"])["document"])["content"]