Reviewed the live staging homepage at 1440px, 390px and in dark mode. Everything below was measured on the deployed page or verified against the code, not inferred.
Claims that were false
On the page
Reality
"24,000+ schools" (×3)
API returns unique_schools: 27230
"Up to three schools side by side"
MAX_SCHOOLS = 5, and a card 400px below said five
"Class sizes, SEN support…"
Class size exists nowhere in the codebase — that line was the only grep hit
Compare preview columns
Invented results and Ofsted grades against two real, named schools
The coverage figure was a silent bug, not a typo. DataInfoResponse declared a total_schools field the API has never sent (it sends unique_schools), so the read was undefined on every request and the hardcoded "24,000+" fallback rendered while the fetch reported success. The interface, not the code, was wrong — which is why it type-checked cleanly and no test caught it.
Also: one feature under three names (Compare / shortlist / pin), and <title> was the bare string Home.
Cut
The trust line — repeated the coverage figure one paragraph after the hero gave it, behind three decorative dots (the concept's avatar stack, avatars removed).
"Start exploring" — three chips linking to two destinations already in the nav.
The six-row "Coverage at a glance" table, one row of which was wrong.
Three of four countdown cards. They gave the page's largest numeral to dates up to 245 days away, two of them offer days — dates you receive something on, which cannot be missed. All four dates survive as one supporting line.
Value-prop titles demoted <h2> → <p>; they were outranking the page's real headings in the document outline.
Why it read "disjointed"
Measured gaps between the seven landing bands: 24 / 32 / 24 / 16 / 48 / 32 / 16px — no scale, each band setting its own margin. Plus four different section-header treatments in one scroll. The page container now owns a single gap and there is one header pattern; an e2e test asserts every band gap is identical.
The illustration
Two separate defects, both of which shipped:
Dark mode. It kept a fixed light palette in both themes, so a pale sky slab was the brightest object on a near-black page, out-shouting the H1 and the search box. Now driven by --ill-* tokens with a dark re-grade — same scene, lit at dusk, with the pin as the only bright thing left.
Mobile crop. The hero slot ranges from 1.34:1 to 4.9:1 across breakpoints. Under slice, at 860×176 a 540×520 scene shows only its bottom 110 user units — so the schoolhouse (at y 218–324) was cropped away entirely on phones, leaving hills and a pin pointing at nothing. No single composition covers that range, so there are now two, each drawn against the crop window its own breakpoint actually produces, with CSS showing one. Both are static server-rendered SVG — no JS, no second request.
Detail also reduced per the brand guideline's "minimal detail, maximum clarity": the gable clock, flagpole pennant and four-window grid are gone.
One thing measuring caught
The deadline bar was originally computed in an effect (to dodge a hydration mismatch), which meant the section had to reserve its own height. One guessed number cannot cover a block whose supporting line wraps differently at every width — measured, the reservation was short at all four breakpoints, shifting the page by up to 108px on a phone. It now renders on the server, with suppressHydrationWarning on the two text nodes that can differ across a midnight boundary.
Tests
Seven new e2e journeys, each guarding a failure mode that is silent by nature:
the coverage figure matches /api/data-info and "24,000+" appears nowhere
the stated comparison limit matches MAX_SCHOOLS
no real school names in the illustrative preview
the page title is not "Home"
every landing band gap is identical
the illustration swaps composition rather than cropping its subject
the illustration's sky is substantially darker in the dark theme
Verification
No local server, so this was verified through an offline render harness: the real compiled CSS chunks plus real server-rendered markup, screenshotted in Playwright at 1440 / 1024 / 640 / 390 in both themes. tsc clean, 159/159 unit tests, build green.
Note on the base branch: this targets feat/brand-logo-artwork (PR #90) rather than main, because both touch Footer.tsx. Retarget to main once #90 merges.
Reviewed the live staging homepage at 1440px, 390px and in dark mode. Everything below was measured on the deployed page or verified against the code, not inferred.
## Claims that were false
| On the page | Reality |
|---|---|
| "24,000+ schools" (×3) | API returns `unique_schools: 27230` |
| "Up to **three** schools side by side" | `MAX_SCHOOLS = 5`, and a card 400px below said five |
| "**Class sizes**, SEN support…" | Class size exists nowhere in the codebase — that line was the only grep hit |
| Compare preview columns | Invented results and Ofsted grades against **two real, named schools** |
The coverage figure was a silent bug, not a typo. `DataInfoResponse` declared a `total_schools` field the API has never sent (it sends `unique_schools`), so the read was `undefined` on every request and the hardcoded "24,000+" fallback rendered while the fetch reported success. The interface, not the code, was wrong — which is why it type-checked cleanly and no test caught it.
Also: one feature under three names (Compare / shortlist / pin), and `<title>` was the bare string `Home`.
## Cut
- The trust line — repeated the coverage figure one paragraph after the hero gave it, behind three decorative dots (the concept's avatar stack, avatars removed).
- "Start exploring" — three chips linking to two destinations already in the nav.
- The six-row "Coverage at a glance" table, one row of which was wrong.
- Three of four countdown cards. They gave the page's largest numeral to dates up to **245 days** away, two of them offer days — dates you *receive* something on, which cannot be missed. All four dates survive as one supporting line.
- Value-prop titles demoted `<h2>` → `<p>`; they were outranking the page's real headings in the document outline.
## Why it read "disjointed"
Measured gaps between the seven landing bands: **24 / 32 / 24 / 16 / 48 / 32 / 16px** — no scale, each band setting its own margin. Plus four different section-header treatments in one scroll. The page container now owns a single gap and there is one header pattern; an e2e test asserts every band gap is identical.
## The illustration
Two separate defects, both of which shipped:
1. **Dark mode.** It kept a fixed light palette in both themes, so a pale sky slab was the brightest object on a near-black page, out-shouting the H1 and the search box. Now driven by `--ill-*` tokens with a dark re-grade — same scene, lit at dusk, with the pin as the only bright thing left.
2. **Mobile crop.** The hero slot ranges from 1.34:1 to 4.9:1 across breakpoints. Under `slice`, at 860×176 a 540×520 scene shows only its bottom 110 user units — so the schoolhouse (at y 218–324) was cropped away entirely on phones, leaving hills and a pin pointing at nothing. No single composition covers that range, so there are now **two**, each drawn against the crop window its own breakpoint actually produces, with CSS showing one. Both are static server-rendered SVG — no JS, no second request.
Detail also reduced per the brand guideline's "minimal detail, maximum clarity": the gable clock, flagpole pennant and four-window grid are gone.
## One thing measuring caught
The deadline bar was originally computed in an effect (to dodge a hydration mismatch), which meant the section had to reserve its own height. One guessed number cannot cover a block whose supporting line wraps differently at every width — measured, the reservation was short at **all four** breakpoints, shifting the page by up to 108px on a phone. It now renders on the server, with `suppressHydrationWarning` on the two text nodes that can differ across a midnight boundary.
## Tests
Seven new e2e journeys, each guarding a failure mode that is silent by nature:
- the coverage figure matches `/api/data-info` and "24,000+" appears nowhere
- the stated comparison limit matches `MAX_SCHOOLS`
- no real school names in the illustrative preview
- the page title is not "Home"
- every landing band gap is identical
- the illustration swaps composition rather than cropping its subject
- the illustration's sky is substantially darker in the dark theme
## Verification
No local server, so this was verified through an offline render harness: the real compiled CSS chunks plus real server-rendered markup, screenshotted in Playwright at 1440 / 1024 / 640 / 390 in both themes. `tsc` clean, 159/159 unit tests, build green.
**Note on the base branch:** this targets `feat/brand-logo-artwork` (PR #90) rather than `main`, because both touch `Footer.tsx`. Retarget to `main` once #90 merges.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The homepage made four statements that were not true, carried elements that
asked nothing of anyone, and had a hero illustration that broke in both the
places it had to work.
Claims, all verified against the code or the API:
* "24,000+ schools" (three places) against a real 27,230. The fact box meant
to show the live figure rendered its own fallback on every request, because
DataInfoResponse declared a `total_schools` field the API has never sent —
it sends `unique_schools`. The fetch succeeded; only that field was
undefined, so nothing threw and nothing failed. The interface, not the
code, was the thing that was wrong.
* "Up to three schools side by side" against MAX_SCHOOLS = 5, contradicting a
card 400px below it that correctly said five.
* "Class sizes" — data the codebase has never held. That copy line was the
only hit in a full-repo grep.
* Invented results and Ofsted grades attributed to two real, named schools
in the compare preview.
Also one feature, three words: Compare (nav), shortlist (footer), pin (cards).
Settled on Compare everywhere. And <title> was the bare string "Home".
Cut: the trust line (repeated the coverage figure one paragraph after the hero
gave it, behind three decorative dots), the "Start exploring" row (three links
to two destinations already in the nav), the six-row coverage table, and three
of the four countdown cards — which gave the page's largest numeral to dates up
to 245 days away, two of them offer days, which cannot be missed. All four
dates remain, at proportionate weight. Value-prop titles drop from <h2> to <p>;
they were outranking the page's real headings in the document outline.
Rhythm: the gaps between the seven landing bands were 24/32/24/16/48/32/16px,
each band setting its own margin, with four different section-header
treatments between them. The page container now owns one gap, and there is one
header pattern. An e2e test asserts the gaps are identical.
Illustration: it kept a fixed light palette in both themes, which left a pale
sky slab as the brightest object on a near-black page, out-shouting the H1 and
the search box. It now reads from --ill-* tokens with a dark re-grade. And the
hero slot ranges from 1.34:1 to 4.9:1 across breakpoints, which no single
composition survives under `slice` — at 860x176 a 540x520 scene shows only its
bottom 110 units, so the schoolhouse was cropped away entirely on phones,
leaving hills and a pin pointing at nothing. There are now two compositions,
each drawn against the crop window its own breakpoint produces, with CSS
showing one. Both are static server-rendered SVG.
The deadline bar renders on the server rather than on hydrate. The effect-based
version needed a reserved height, and one guessed number cannot cover a block
whose supporting line wraps differently at every width — measured, it was short
at all four, shifting the page up to 108px on a phone.
Verified on the built output through an offline render harness (no local
server): real compiled CSS, real rendered markup, four widths, both themes.
tsc clean, 159/159 unit tests, build green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tudor
merged commit 8636c96d35 into feat/brand-logo-artwork2026-08-14 16:16:25 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Reviewed the live staging homepage at 1440px, 390px and in dark mode. Everything below was measured on the deployed page or verified against the code, not inferred.
Claims that were false
unique_schools: 27230MAX_SCHOOLS = 5, and a card 400px below said fiveThe coverage figure was a silent bug, not a typo.
DataInfoResponsedeclared atotal_schoolsfield the API has never sent (it sendsunique_schools), so the read wasundefinedon every request and the hardcoded "24,000+" fallback rendered while the fetch reported success. The interface, not the code, was wrong — which is why it type-checked cleanly and no test caught it.Also: one feature under three names (Compare / shortlist / pin), and
<title>was the bare stringHome.Cut
<h2>→<p>; they were outranking the page's real headings in the document outline.Why it read "disjointed"
Measured gaps between the seven landing bands: 24 / 32 / 24 / 16 / 48 / 32 / 16px — no scale, each band setting its own margin. Plus four different section-header treatments in one scroll. The page container now owns a single gap and there is one header pattern; an e2e test asserts every band gap is identical.
The illustration
Two separate defects, both of which shipped:
--ill-*tokens with a dark re-grade — same scene, lit at dusk, with the pin as the only bright thing left.slice, at 860×176 a 540×520 scene shows only its bottom 110 user units — so the schoolhouse (at y 218–324) was cropped away entirely on phones, leaving hills and a pin pointing at nothing. No single composition covers that range, so there are now two, each drawn against the crop window its own breakpoint actually produces, with CSS showing one. Both are static server-rendered SVG — no JS, no second request.Detail also reduced per the brand guideline's "minimal detail, maximum clarity": the gable clock, flagpole pennant and four-window grid are gone.
One thing measuring caught
The deadline bar was originally computed in an effect (to dodge a hydration mismatch), which meant the section had to reserve its own height. One guessed number cannot cover a block whose supporting line wraps differently at every width — measured, the reservation was short at all four breakpoints, shifting the page by up to 108px on a phone. It now renders on the server, with
suppressHydrationWarningon the two text nodes that can differ across a midnight boundary.Tests
Seven new e2e journeys, each guarding a failure mode that is silent by nature:
/api/data-infoand "24,000+" appears nowhereMAX_SCHOOLSVerification
No local server, so this was verified through an offline render harness: the real compiled CSS chunks plus real server-rendered markup, screenshotted in Playwright at 1440 / 1024 / 640 / 390 in both themes.
tscclean, 159/159 unit tests, build green.Note on the base branch: this targets
feat/brand-logo-artwork(PR #90) rather thanmain, because both touchFooter.tsx. Retarget tomainonce #90 merges.🤖 Generated with Claude Code
The homepage made four statements that were not true, carried elements that asked nothing of anyone, and had a hero illustration that broke in both the places it had to work. Claims, all verified against the code or the API: * "24,000+ schools" (three places) against a real 27,230. The fact box meant to show the live figure rendered its own fallback on every request, because DataInfoResponse declared a `total_schools` field the API has never sent — it sends `unique_schools`. The fetch succeeded; only that field was undefined, so nothing threw and nothing failed. The interface, not the code, was the thing that was wrong. * "Up to three schools side by side" against MAX_SCHOOLS = 5, contradicting a card 400px below it that correctly said five. * "Class sizes" — data the codebase has never held. That copy line was the only hit in a full-repo grep. * Invented results and Ofsted grades attributed to two real, named schools in the compare preview. Also one feature, three words: Compare (nav), shortlist (footer), pin (cards). Settled on Compare everywhere. And <title> was the bare string "Home". Cut: the trust line (repeated the coverage figure one paragraph after the hero gave it, behind three decorative dots), the "Start exploring" row (three links to two destinations already in the nav), the six-row coverage table, and three of the four countdown cards — which gave the page's largest numeral to dates up to 245 days away, two of them offer days, which cannot be missed. All four dates remain, at proportionate weight. Value-prop titles drop from <h2> to <p>; they were outranking the page's real headings in the document outline. Rhythm: the gaps between the seven landing bands were 24/32/24/16/48/32/16px, each band setting its own margin, with four different section-header treatments between them. The page container now owns one gap, and there is one header pattern. An e2e test asserts the gaps are identical. Illustration: it kept a fixed light palette in both themes, which left a pale sky slab as the brightest object on a near-black page, out-shouting the H1 and the search box. It now reads from --ill-* tokens with a dark re-grade. And the hero slot ranges from 1.34:1 to 4.9:1 across breakpoints, which no single composition survives under `slice` — at 860x176 a 540x520 scene shows only its bottom 110 units, so the schoolhouse was cropped away entirely on phones, leaving hills and a pin pointing at nothing. There are now two compositions, each drawn against the crop window its own breakpoint produces, with CSS showing one. Both are static server-rendered SVG. The deadline bar renders on the server rather than on hydrate. The effect-based version needed a reserved height, and one guessed number cannot cover a block whose supporting line wraps differently at every width — measured, it was short at all four, shifting the page up to 108px on a phone. Verified on the built output through an offline render harness (no local server): real compiled CSS, real rendered markup, four widths, both themes. tsc clean, 159/159 unit tests, build green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>