Skip to content

feat(site): the explainer's opening, a CSP on every page, and the calculator fixes (phase 7) - #70

Merged
TMHSDigital merged 2 commits into
mainfrom
site/phase-7-explainer
Sep 23, 2026
Merged

TMHSDigital merged 2 commits into
mainfrom
site/phase-7-explainer

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

Stacked on #69, which is stacked on #68. Its base is site/phase-6-search, so the diff shows only this phase. Two commits: the first moves the explainer's inline <style> and <script> into explainer.css and explainer.js with no change in behaviour, so the second reads as a diff of real changes.

Fixes #25. Fixes #53. Fixes #55. Fixes #57.
(These close when the stack lands on main.)

The explainer's opening

  • The page now opens with a card showing the example report's result: the measured ECE against the floor's 95th percentile on one gauge, and the verdict.
  • build_site.py writes the card from the report's own ECE line, into a <!-- site:example-card --> slot. So it says exactly what the report says, it reads the same with scripts off, and it flips wording if the example ever becomes distinguishable.
  • Two calls to action follow: "Check your numbers" and "Read the methodology".
  • From 64rem the "On this page" list becomes a sticky rail beside the text, with the same section highlighting as the doc pages.

A Content-Security-Policy on every page

  • The policy is default-src 'self', with script-src, style-src, connect-src and worker-src all 'self', img-src 'self' data: (for the inline favicon), object-src, frame-src, base-uri and form-action set to 'none', and no 'unsafe-inline'.
  • The build refuses an explainer without the tag.
  • check_site_links.mjs now fails any page that lacks the tag or carries an inline <script>, a <style> block, or a style= attribute. I confirmed it catches an injected style= on a doc page.

Calculator and planner

Checked

  • ruff, mypy --strict, and pytest pass. The new tests cover:
    • explainer slots and CSP refusal;
    • the committed page having both slots and the tag;
    • the card saying what the report says, in both verdict branches.
  • floor_golden --check, check_floor_parity (now including the 1e-9 floor), check_site_links (with CSP), check_search, and render_docs --self-test all pass.
  • In the browser (Playwright, 1440 and 375 px), with no console messages and no CSP violations:
    • rows 0 shows the real message, marks the field aria-invalid with aria-describedby="calc-status", and focuses it;
    • a run writes ?n=300&bins=10&accuracy=0.8&measured=0.09#calculator, and opening that link reproduces the result;
    • a planner search can be cancelled and reports "Cancelled.";
    • #planner lands 68 px from the top, just under the header, with its rail entry highlighted.

Still open in #54: a browser smoke test in CI. It needs a package install in a workflow that has none today.

🤖 Generated with Claude Code

Base automatically changed from site/phase-6-search to main September 23, 2026 15:03
TMHSDigital and others added 2 commits September 23, 2026 11:03
…files

No change in behaviour. This is the first step toward a Content Security
Policy with no 'unsafe-inline', and it makes the fixes that follow read as
diffs of explainer.js rather than of an HTML file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…culator fixes (phase 7)

The explainer opens with the example report's result as a card: the
measured ECE against its floor's 95th percentile on one gauge, and the
verdict. build_site.py writes it from the report's own ECE line, so it
says what the report says and reads the same with scripts off. The
contents list becomes a rail beside the text on wide screens.

Every page now carries a Content-Security-Policy that allows only the
site's own files. The explainer's inline style and script moved out in
the previous commit, so nothing needs 'unsafe-inline' or a hash. The
build refuses an explainer without the tag, and check_site_links.mjs
fails any page that lacks it or carries an inline script, style block,
or style attribute the policy would block.

The calculator and planner:
- validation messages said "[object Object]"; they now say what is
  wrong, set aria-invalid and aria-describedby on the field, and focus
  it (fixes #25);
- the result blocks are no longer live regions; the status line
  announces one summary line (fixes the rest of #53);
- each tool has its own worker and a Cancel button, so the planner's
  long search never queues the calculator;
- inputs are kept in the query string, a link carrying them runs the
  tool on load, and each result has Copy link and Copy result (#57).

The worked example says whether its rows were missing or failed to
draw; floor.js refuses a bin count of 0 as the Python does, where it
used to turn it into 10; and the parity check holds the example's floor
to 1e-9, not only the four decimals the report prints (fixes #55). A
link to a later section now lands on it after the example has grown the
page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@TMHSDigital
TMHSDigital force-pushed the site/phase-7-explainer branch from fce54d7 to 32ae325 Compare September 23, 2026 15:03
@TMHSDigital
TMHSDigital merged commit 36f043d into main Sep 23, 2026
16 of 26 checks passed
@TMHSDigital
TMHSDigital deleted the site/phase-7-explainer branch September 23, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant