Skip to content

Phase 168: a test tier that can see the page, seams in the three big files, and a clean root - #212

Merged
StephenAbbott merged 4 commits into
mainfrom
feat/phase-168-tests-and-splits
Sep 3, 2026
Merged

Phase 168: a test tier that can see the page, seams in the three big files, and a clean root#212
StephenAbbott merged 4 commits into
mainfrom
feat/phase-168-tests-and-splits

Conversation

@StephenAbbott

Copy link
Copy Markdown
Owner

Three commits. Root litter: seven unreferenced files out of the repository root; unilever.json untracked (it was gitignored while still tracked). Test tier: jsdom component tests and a ten-test Playwright smoke over six routes and a curated report, real backend, production build, offline — it found four pages with no <h1> and a vite preview that proxied /sources to the backend instead of the SPA, both fixed; tsc -b now typechecks the tests it was skipping. Splits: App.tsx 4,152→3,107, lookup.py 3,214→2,543, mapper.py 10,824→8,512, everything re-exported.

Backend 3,874 tests, frontend 472 + 10 smoke.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YB1QPTL3x1HwpmcZc64xGv

StephenAbbott and others added 4 commits September 3, 2026 18:06
Eight files had accumulated in the root, and the giveaway that none of them
belonged there was that seven were unreferenced by anything in the repo.

The two node diagnostics join the frontend tooling they mirror:
cluster-eval.mjs and person-dup-diagnostic.mjs go to frontend/scripts/,
beside verify-reconciliation.mjs, which is the same kind of thing — the
precedent backend/scripts/ already sets, where eval harnesses live next to
the code they exercise. Their usage lines now give the path you would
actually type from the repo root.

PR_narrative_summaries.md is a design writeup for the Phase 53 narrative
work, so it becomes docs/narrative-summaries.md.

The four social cards and the explainer graphic go to docs/social/ with a
README, because one of them is not litter at all: opencheck-social-b.html
carries the live source count and is on the "add a new source" checklist in
CLAUDE.md (path updated). The only thing distinguishing it from the three
drafts beside it was that you had to know.

unilever.json is untracked rather than moved. It is 252 KB of one captured
/lookup answer, and it was already listed in .gitignore — listed there
while still tracked, which does nothing at all, and is exactly why it
survived. It stays on disk for whoever has it; both diagnostics document the
curl that makes one. The .gitignore entry now says so, and covers
*.lookup.json for the next one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YB1QPTL3x1HwpmcZc64xGv
The frontend suite was logic-only for 167 phases — no `.test.tsx`, no
browser — and the three regressions that cost the most all walked past it:
the v1/v2 component mix, the verdict rendered twice, the mode tabs
overflowing their strip at 390px. None of them was a wrong value, so no
amount of `lib/` testing could have seen any of them.

Two tiers are added above it, and the rule between them is which question
they can answer.

Component tests (`*.test.tsx`, jsdom + testing-library) pin what the markup
is: how many of a thing there are, an element's accessible name, what a
control does when pressed. `VerdictStrip` gets ten — including "states the
verdict exactly once", the double verdict pinned where it happened — and
`SourcesPage` gets eight, over the disclosure Phase 161 added. jsdom is
per-file, chosen by the extension: `environmentMatchGlobs` gives a DOM to
`.test.tsx` and leaves the fast `node` suite alone.

The Playwright smoke (`frontend/e2e/`, `npm run test:e2e`) pins what a whole
page is, over the six routes a reader can reach and a curated BP report:
one `<h1>`, one header, one footer, nothing overflowing sideways, no console
errors, and — at 390px — four check-mode tabs all inside the viewport with
44px targets. It runs a real backend and a production build, offline: no
keys are set, so adapters serve stubs or the committed bulk BODS the curated
examples already use, and external requests are answered empty so a font CDN
cannot fail a merge.

It found two things on its first run.

**Four pages had no `<h1>` at all.** /sources, /about, /api and /changelog
each carried a page of `<h2>`s under nothing, while a comment beside the
hero had claimed since Phase 122 that the homepage heading was the page's
h1 — true there, and only there. Each view that does not title itself now
renders an sr-only title from `PAGE_TITLES`, typed `Exclude<View,
SelfTitledView>` so a new view cannot be added without either naming its
page or declaring that it has its own heading, as /batch does.

**`vite preview` was not previewing production.** Vite defaults
`preview.proxy` to `server.proxy`, so the built preview proxied `/sources`
to the backend's JSON inventory and the SPA route of that name could not be
reached at all — while the deployed static site, which has no proxy, serves
it fine. `preview.proxy` is now empty, which is what production is.

Three smaller things fall out. `tsconfig.json` no longer excludes test files
and now includes `e2e/`, so `tsc -b` — already in CI — typechecks the tests
it was silently skipping. `PLAYWRIGHT_CHROMIUM_PATH` is honoured for
environments that ship a browser rather than download one. And a new `e2e`
CI job installs one browser and uploads traces on failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YB1QPTL3x1HwpmcZc64xGv
App.tsx 4,152 → 3,107. lookup.py 3,214 → 2,543. mapper.py 10,824 → 8,512.
Nothing here changes behaviour: code moves, and every name keeps its
address, because 62 files import from bods.mapper and sources/probes.py
resolves a mapper by getattr.

**App.tsx** gives up the two static pages and the homepage panels — none of
which read a line of its state. ApiPage and BehindTheScenesPage each mount
from one line of App and are documents, not components with a life;
HomePanels holds EXAMPLE_LEIS, the picker, BatchInvite and How It Works;
BtsCard holds the two primitives both pages are built from, which had been
sitting inside whichever page imported them first. The design-system
baseline is re-seeded so the moved literals follow their code — the totals
are unchanged at 79 hex and 382 arbitrary sizes, App.tsx dropping 12→7 and
117→66 with the four new files picking up exactly the difference, so the
ratchet is where it was.

**lookup.py** gives up the per-source hit builders. CLAUDE.md's "add a
source" checklist has said `routers/lookup.py — _bh_<name>() hit builder
(only this)` for a hundred phases; hit_builders.py is now a file where only
that is true. It takes the whole contract with it: the 36 builders, the
`_hit` factory they all go through, and `_LookupCtx`, which is half of
their signature.

**mapper.py** gives up its core and its two largest sections. statements.py
holds what every one of the 148 functions uses — the three statement
factories, `_stable_id`, the source and date blocks — and
`set_beneficial_ownership`, which had been living in the middle of the FtM
section although it decides a policy about every source. bods/mappers/ then
takes FtM (with its vendored edge-schema table) and Wikidata, the two
largest per-source blocks. The remaining sections are banner-separated the
same way and can move the same way.

Two things the move turned up, both fixed here: a bare string statement at
App.tsx module scope, left behind when the Neo4j download came off in Phase
122, along with the doc comment for the field it described; and
test_curated_leis_sync, which greps App.tsx for EXAMPLE_LEIS and would have
caught the move on its own — it now reads HomePanels.tsx. The fail-fast
guard for a missing hit builder now patches the module the collector
actually reads, which is the point of that test.

Backend 3,872 tests pass; frontend 472 vitest and 10 smoke tests pass; tsc,
build and the design lint are clean. Ruff on the new modules is down to the
nine findings that travelled with the code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YB1QPTL3x1HwpmcZc64xGv
Status row for Phase 168 (implementation commits cd1e790, ecc2599,
0c4edef), README latest line, spelled-out phase count, frontend footer
(472 vitest tests across 40 files, and the ten-test Playwright smoke),
regenerated changelog JSON. The backend figure is unchanged: the phase
added no backend tests — main and this branch both run 3,874 in the same
environment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YB1QPTL3x1HwpmcZc64xGv
@StephenAbbott
StephenAbbott merged commit 5fbc0dc into main Sep 3, 2026
12 checks passed
@StephenAbbott
StephenAbbott deleted the feat/phase-168-tests-and-splits branch September 3, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant