Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Audit date: 2026-05-19

This repo is a manifest-driven static atlas of plot pages, generated homepage content, generated README inventory text, a shared dashboard, and Python-based plot generators. The manifest is the inventory source of truth; data and metadata live beside each plot; generated outputs live under each plot's `output/` directory.
This repo is a manifest-driven static atlas of plot pages, generated homepage content, generated README inventory text, a shared dashboard, a browser smoke harness, and Python-based plot generators. The manifest is the inventory source of truth; data and metadata live beside each plot; generated outputs live under each plot's `output/` directory.

## Confirmed Working Pieces

Expand All @@ -12,20 +12,22 @@ This repo is a manifest-driven static atlas of plot pages, generated homepage co
- The repo-level validator exists at `scripts/validate_repo.py`.
- The test suite includes bootstrap smoke checks for the build and validator entrypoints.
- The shared accessibility and link-check scripts are present.
- The browser smoke harness exists at `scripts/browser_smoke.py`.

## Commands

Status below reflects the current local verification pass.

- `python -m pip install -r requirements.txt` - passed
- `python build_all.py` - passed
- `uv run --with numpy --with pandas --with matplotlib --with plotly --with scipy python build_all.py` - passed and refreshed generated outputs
- `python scripts/generate_homepage.py` - passed
- `python scripts/generate_readme_links.py` - passed
- `python scripts/generate_sitemap.py` - passed
- `python scripts/validate_all.py` - passed
- `python scripts/validate_repo.py --check` - passed
- `python scripts/check_links.py` - passed
- `python scripts/check_accessibility_static.py` - passed
- `python scripts/browser_smoke.py` - passed for homepage, dashboard, and AI Compute Timeline
- `python -m pytest tests -q` - passed

## Important Files and Directories
Expand Down Expand Up @@ -59,14 +61,15 @@ Status below reflects the current local verification pass.

## Known Risks

- The dashboard loads D3 from a CDN.
- The dashboard loads D3 from a CDN, so the browser smoke harness is a preflight rather than an offline-safe guarantee.
- Some plot rows remain speculative or projection-based and should not be reworded into facts without source review.
- Generated outputs need to be rebuilt after data or source edits to stay fresh.
- The repo depends on the Python packages listed in `requirements.txt`.

## Immediate Next Moves

1. Follow `docs/agentic-overhaul/two-prompt-buildout-plan.md` for the next feature branch.
2. Run `python build_all.py` when changing data or plot generators.
3. Run `python scripts/validate_repo.py --check` after any substantive change.
4. Keep `CURRENT_STATE.md` and `docs/agentic-overhaul/2026-05-audit.md` up to date when the repo shape changes.
1. Use `python scripts/browser_smoke.py --html /tmp/browser-smoke/index.html` before browser QA for the homepage, dashboard, and AI Compute Timeline.
2. Follow `docs/agentic-overhaul/two-prompt-buildout-plan.md` for the next feature branch.
3. Run `python build_all.py` when changing data or plot generators.
4. Run `python scripts/validate_repo.py --check` after any substantive change.
5. Keep `CURRENT_STATE.md` and `docs/agentic-overhaul/2026-05-audit.md` up to date when the repo shape changes.
2 changes: 1 addition & 1 deletion docs/agentic-overhaul/2026-05-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Scores are subjective and reflect this audit pass.

- Decide whether to prune or summarize more of the archival task briefs under `.agent-tasks/`.
- Tighten provenance notes for the most speculative plot rows where source review is still needed.
- Consider a browser-level smoke test for the dashboard and one representative plot page.
- Browser smoke harness now covers the dashboard, homepage, and one representative plot page; keep the target list aligned with the manifest as pages change.

### P2

Expand Down
2 changes: 2 additions & 0 deletions docs/agentic-overhaul/two-prompt-buildout-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This repo is already in the self-orienting, self-validating state. The remaining work should be done in small, independently mergeable features that fit a two-prompt loop:

Completed in this branch: item 1, the browser smoke harness for homepage + dashboard + one representative plot. The next unfinished item is order 2, offline-safe dashboard loading.

1. Build prompt: create a feature branch, implement one feature, add or update tests, update docs, commit, and push.
2. QA prompt: open the branch in the browser, take screenshots, verify behavior, update docs if needed, commit, push, and merge.

Expand Down
187 changes: 187 additions & 0 deletions scripts/browser_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""Browser smoke harness for the homepage, dashboard, and one representative plot.

The script is intentionally stdlib-only so the repo has a non-UI pass/fail signal
without depending on Playwright or another browser automation runtime.

It validates the canonical smoke targets, prints a concise terminal report, and
can optionally write a self-contained HTML receipt for browser QA.
"""

from __future__ import annotations

import argparse
import json
import sys
from html import escape
from pathlib import Path

from manifest_utils import ROOT

SMOKE_VIEWPORTS = [
{"name": "desktop", "width": 1440, "height": 1200},
{"name": "mobile", "width": 390, "height": 844},
]

SMOKE_TARGETS = [
{
"name": "homepage",
"title": "Plots homepage",
"path": "index.html",
"checks": [
'data-page="home"',
"plots_manifest.json",
"published entries",
],
},
{
"name": "dashboard",
"title": "Unified Dashboard",
"path": "dashboard/index.html",
"checks": [
"dashboard.css",
"dashboard.js",
"dashboard-container",
],
},
{
"name": "representative-plot",
"title": "AI Compute Timeline",
"path": "ai-compute-timeline/index.html",
"checks": [
"output/ai_compute_timeline_interactive.html",
"output/ai_compute_timeline_highres.png",
"output/ai_compute_timeline.svg",
],
},
]
Comment on lines +26 to +57
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The smoke targets and their associated content checks are currently hardcoded. Since the repository is manifest-driven, consider dynamically generating the targets (especially the representative-plot) by querying the manifest via manifest_utils.plot_entries. This would make the harness more resilient to directory renames or plot deletions in the future.



def _relative_url(root: Path, rel_path: str) -> str:
return (root / rel_path).as_uri()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve root path before converting to file URI

Using Path.as_uri() on (root / rel_path) crashes when --root is passed as a relative path (for example --root .), because relative paths cannot be expressed as file URIs. In that scenario the script raises ValueError before any smoke checks run, so the CLI option documented as a repository-root override is unusable unless callers manually pass an absolute path.

Useful? React with 👍 / 👎.



def evaluate_target(root: Path, target: dict) -> dict:
path = root / target["path"]
text = path.read_text(encoding="utf-8", errors="ignore") if path.is_file() else ""
missing = [needle for needle in target["checks"] if needle not in text]
result = {
"name": target["name"],
"title": target["title"],
"path": target["path"],
"url": _relative_url(root, target["path"]),
"file_exists": path.is_file(),
"missing_checks": missing,
"status": "pass" if path.is_file() and not missing else "fail",
"viewports": SMOKE_VIEWPORTS,
}
return result


def run_smoke_checks(root: Path = ROOT) -> list[dict]:
return [evaluate_target(root, target) for target in SMOKE_TARGETS]


def render_text_report(results: list[dict], root: Path) -> str:
lines = [
"Browser smoke harness",
f"Root: {root}",
"",
]
for result in results:
lines.append(f"[{result['status'].upper()}] {result['title']} -> {result['path']}")
if result["missing_checks"]:
for check in result["missing_checks"]:
lines.append(f" - missing: {check}")
viewport_summary = ", ".join(
f"{viewport['name']} {viewport['width']}x{viewport['height']}"
for viewport in result["viewports"]
)
lines.append(f" - browser viewports: {viewport_summary}")
lines.append("")
lines.append("Pass/fail: " + ("PASS" if all(result["status"] == "pass" for result in results) else "FAIL"))
return "\n".join(lines)


def render_html_report(results: list[dict], root: Path) -> str:
rows = []
for result in results:
missing = ", ".join(result["missing_checks"]) if result["missing_checks"] else "—"
viewports = ", ".join(
f"{vp['name']} {vp['width']}×{vp['height']}" for vp in result["viewports"]
)
rows.append(
f"""<tr>
<td>{escape(result['title'])}</td>
<td><a href=\"{escape(result['url'])}\">{escape(result['path'])}</a></td>
<td class=\"status-{escape(result['status'])}\">{escape(result['status'].upper())}</td>
<td>{escape(viewports)}</td>
<td>{escape(missing)}</td>
</tr>"""
)

overall = "PASS" if all(result["status"] == "pass" for result in results) else "FAIL"
return f"""<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>Browser Smoke Receipt</title>
<style>
body {{ background: #0f1117; color: #e8eaf6; font-family: system-ui, sans-serif; padding: 32px; line-height: 1.5; }}
a {{ color: #7dd3fc; }}
table {{ border-collapse: collapse; width: 100%; margin-top: 20px; }}
th, td {{ border-bottom: 1px solid #2d3148; padding: 12px 10px; text-align: left; vertical-align: top; }}
th {{ color: #cbd5e1; }}
.status-pass {{ color: #4ade80; font-weight: 700; }}
.status-fail {{ color: #f87171; font-weight: 700; }}
.badge {{ display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 999px; background: #1c1f2e; color: #cbd5e1; }}
</style>
</head>
<body>
<h1>Browser smoke receipt <span class=\"badge\">{overall}</span></h1>
<p>Root: <code>{escape(str(root))}</code></p>
<p>This receipt validates the canonical browser-smoke targets before opening them in a real browser session. Capture desktop and mobile screenshots for the three linked pages after this preflight passes.</p>
<table>
<thead>
<tr><th>Target</th><th>Path</th><th>Status</th><th>Viewports</th><th>Missing checks</th></tr>
</thead>
<tbody>
{''.join(rows)}
</tbody>
</table>
</body>
</html>
"""


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Validate the browser smoke targets.")
parser.add_argument("--root", type=Path, default=ROOT, help="Repository root to validate.")
parser.add_argument("--html", type=Path, help="Optional path for a browser-openable HTML receipt.")
parser.add_argument("--json", dest="json_path", type=Path, help="Optional path for a JSON receipt.")
args = parser.parse_args(argv)

results = run_smoke_checks(args.root)
Comment on lines +163 to +165
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The --root argument should be resolved to an absolute path before use. The Path.as_uri() method (called via run_smoke_checks -> evaluate_target -> _relative_url) raises a ValueError if the path is not absolute. If a user provides a relative path via the CLI (e.g., --root .), the script will crash.

Suggested change
args = parser.parse_args(argv)
results = run_smoke_checks(args.root)
args = parser.parse_args(argv)
args.root = args.root.resolve()
results = run_smoke_checks(args.root)

ok = all(result["status"] == "pass" for result in results)

print(render_text_report(results, args.root))

if args.html:
args.html.parent.mkdir(parents=True, exist_ok=True)
args.html.write_text(render_html_report(results, args.root), encoding="utf-8")
print(f"HTML receipt: {args.html}")

if args.json_path:
args.json_path.parent.mkdir(parents=True, exist_ok=True)
args.json_path.write_text(
json.dumps({"root": str(args.root), "results": results}, indent=2),
encoding="utf-8",
)
print(f"JSON receipt: {args.json_path}")

return 0 if ok else 1


if __name__ == "__main__":
raise SystemExit(main())
1 change: 1 addition & 0 deletions scripts/validate_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"plots_manifest.json",
"build_all.py",
"requirements.txt",
"scripts/browser_smoke.py",
"scripts/validate_repo.py",
"docs/agentic-overhaul/2026-05-audit.md",
".github/workflows/validate.yml",
Expand Down
56 changes: 56 additions & 0 deletions tests/test_browser_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Tests for the browser smoke harness."""

from __future__ import annotations

import sys
from pathlib import Path

import pytest


def _load_browser_smoke():
sys.path.insert(0, "scripts")
import browser_smoke

return browser_smoke


class TestBrowserSmokeTargets:
def test_targets_are_canonical(self):
browser_smoke = _load_browser_smoke()

names = [target["name"] for target in browser_smoke.SMOKE_TARGETS]
assert names == ["homepage", "dashboard", "representative-plot"]

paths = [target["path"] for target in browser_smoke.SMOKE_TARGETS]
assert paths == [
"index.html",
"dashboard/index.html",
"ai-compute-timeline/index.html",
]

def test_viewports_are_desktop_and_mobile(self):
browser_smoke = _load_browser_smoke()

viewport_names = [viewport["name"] for viewport in browser_smoke.SMOKE_VIEWPORTS]
assert viewport_names == ["desktop", "mobile"]

def test_run_smoke_checks_pass_for_repo_root(self, repo_root: Path):
browser_smoke = _load_browser_smoke()

results = browser_smoke.run_smoke_checks(repo_root)
assert len(results) == 3
assert all(result["status"] == "pass" for result in results)
assert all(result["file_exists"] for result in results)

def test_render_html_report_mentions_targets(self, repo_root: Path):
browser_smoke = _load_browser_smoke()

results = browser_smoke.run_smoke_checks(repo_root)
html = browser_smoke.render_html_report(results, repo_root)

assert "Browser smoke receipt" in html
assert "homepage" in html
assert "dashboard/index.html" in html
assert "ai-compute-timeline/index.html" in html
assert "PASS" in html
Comment on lines +46 to +56
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test is an integration test that depends on the repository being in a 'built' state. If the build hasn't been run, the test will fail on the assert "PASS" in html line. To make the test suite more robust, consider splitting this into a unit test that uses mock result data to verify the HTML rendering logic, while keeping the integration check separate.

Loading