Consolidate the report bundle into one report with a table of contents#75
Conversation
The bundle shipped nine separate pages plus a digest index that summarized the
executive summary — so the same story was told two or three times, the decisive
comparison was scattered, and you had to hunt for the right page. And the
row-level ("by-twin") audit table explodes file size when inlined.
The bundle entry point is now a single scrollable report.html (= index.html)
with a sticky table of contents:
- Decision & Evidence (executive summary), Technical Validation (twin), Survey
Profile, and One-Shot Marginals fold in as sections;
- the digest index is gone;
- row-level / reference material (twin run audit, twin comparison, per-twin
microdata) is *linked* in a Downloads section, never inlined — so the file
stays lean by construction (the bundle never generates microdata inline).
New consolidated_report.py composes the existing standalone pages by lifting
each page's <main> body into a titled section and merging the page-specific CSS
once into a shared head (the shared base CSS is deduped). When there is no ready
twin validation yet, the bundle falls back to the readiness/next-steps index so
it stays navigable.
Follow-ups (noted, not done): de-duplicate the generated narrative that can
appear in both Decision and Technical Validation; give the twin-validate
workflow report the same consolidated shape; fix relative download links in the
secondary report/ copy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR replaces the old multi-page digest bundle with a single scrollable
Confidence Score: 4/5Safe to merge; the structural consolidation is correct and well-tested, with two minor edge cases that do not affect normal operation. The greedy _MAIN_RE regex is only a risk if source pages ever contain a second closing main tag, which they currently do not. The report.html staleness concern requires an unusual re-run sequence and only affects that file's disk copy, not the live index.html users typically land on. zwill/report_bundle.py around the else-branch where report.html is not cleared on a non-ready re-run; zwill/consolidated_report.py line 19 for the greedy _MAIN_RE pattern. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[build_report_bundle] --> B{twin-validation ready?}
B -- No --> C[render_report_bundle_index]
C --> D[index.html = readiness fallback]
D --> E[report.html untouched - may be stale]
B -- Yes --> F[build_consolidated_report_html]
F --> G[read executive_summary HTML]
F --> H[read twin-validation / survey-profile / one-shot HTML]
F --> I[build downloads links for audit, comparison, microdata]
G & H & I --> J[render_consolidated_report]
J --> K[report.html written]
K --> L[index.html written = same content]
L --> M[copy to report/index.html]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[build_report_bundle] --> B{twin-validation ready?}
B -- No --> C[render_report_bundle_index]
C --> D[index.html = readiness fallback]
D --> E[report.html untouched - may be stale]
B -- Yes --> F[build_consolidated_report_html]
F --> G[read executive_summary HTML]
F --> H[read twin-validation / survey-profile / one-shot HTML]
F --> I[build downloads links for audit, comparison, microdata]
G & H & I --> J[render_consolidated_report]
J --> K[report.html written]
K --> L[index.html written = same content]
L --> M[copy to report/index.html]
Reviews (1): Last reviewed commit: "Consolidate the report bundle into one r..." | Re-trigger Greptile |
| from .reporting import EP_REPORT_CSS, copy_markdown_control, report_display_title | ||
|
|
||
| _STYLE_RE = re.compile(r"<style>(.*?)</style>", re.S) | ||
| _MAIN_RE = re.compile(r"<main[^>]*>(.*)</main>", re.S) |
There was a problem hiding this comment.
The
_MAIN_RE pattern uses a greedy (.*) with re.S, so it matches from the first <main…> opener to the last </main> in the entire document. For the current well-structured pages this is harmless, but if any source page ever gains a second </main> — e.g. inside a <template>, <script>, or a non-collapsed conditional block — the captured body would silently include everything between the two closing tags. Non-greedy is the semantically correct choice here.
| _MAIN_RE = re.compile(r"<main[^>]*>(.*)</main>", re.S) | |
| _MAIN_RE = re.compile(r"<main[^>]*>(.*?)</main>", re.S) |
| consolidated_report = build_consolidated_report_html( | ||
| output_dir, pages, survey, (manifest.get("executive_summary") or {}).get("path") | ||
| ) | ||
| if consolidated_report: | ||
| (output_dir / "report.html").write_text(consolidated_report) | ||
| (output_dir / "index.html").write_text(consolidated_report) | ||
| else: | ||
| (output_dir / "index.html").write_text(render_report_bundle_index(manifest)) |
There was a problem hiding this comment.
Stale
report.html after re-run in non-ready state
When consolidated_report is None (twin not yet ready), only index.html is refreshed; report.html is left untouched on disk. If a user ran report render once with a ready twin (writing report.html), then re-ran after the twin became un-ready (e.g. data reset, path moved), index.html correctly shows the readiness fallback but report.html silently serves the old, stale consolidated report. Clearing or not creating report.html in the else-branch would prevent this.
| def _page_style(html: str) -> str: | ||
| match = _STYLE_RE.search(html) | ||
| return match.group(1) if match else "" |
There was a problem hiding this comment.
_page_style uses _STYLE_RE.search() which stops at the first </style> tag. If a source page ever emits a second <style> block (e.g. dynamically injected component styles), those rules are silently dropped and won't appear in the consolidated report's <head>. A small comment here would flag the intentional limitation so future page authors know to keep all CSS in one block.
| def _page_style(html: str) -> str: | |
| match = _STYLE_RE.search(html) | |
| return match.group(1) if match else "" | |
| def _page_style(html: str) -> str: | |
| # Only the first <style> block is extracted; all page CSS must live there. | |
| match = _STYLE_RE.search(html) | |
| return match.group(1) if match else "" |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The structural half (B) of the report cleanup we scoped.
Problem
The bundle shipped nine pages plus a digest index that re-summarized the executive summary — the same story told two or three times, the decisive comparison scattered, and constant "which page do I open?". And the row-level "by-twin" audit table explodes file size when inlined.
Change
The bundle entry point is now one scrollable
report.html(=index.html) with a sticky table of contents:consolidated_report.pycomposes the existing standalone pages by lifting each page's<main>body into a titled section and merging the page-specific CSS once into a shared head (shared base CSS deduped). No ready twin yet → falls back to the readiness/next-steps index so the bundle stays navigable.This also makes the report more robust: previously every section was a separate page the index linked to; now the substance is inline and only the audit/comparison links point out.
Testing
pytest— 258 passed (newtest_consolidated_report.pyfor the composer; updated the incremental-bundle integration test to assert the TOC + folded-in sections + linked audit instead of the old digest markers).ruffclean.Deliberate follow-ups
twin-validateworkflow report the same consolidated shape.report/copy (pre-existing behavior; main entry is correct).🤖 Generated with Claude Code