Re-targets the work from #91 at main. #91 was merged into feat/brand-logo-artwork rather than into main, and main was cut from that branch without it — so none of this reached main. Same single commit, cherry-picked onto current main (clean, no conflicts, since #90 has since landed).
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.
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. Re-verified after the cherry-pick onto main: tsc clean, 159/159 unit tests, build green.
Re-targets the work from #91 at `main`. #91 was merged into `feat/brand-logo-artwork` rather than into `main`, and main was cut from that branch without it — so none of this reached main. Same single commit, cherry-picked onto current main (clean, no conflicts, since #90 has since landed).
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.
## 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. Re-verified after the cherry-pick onto main: `tsc` clean, 159/159 unit tests, build green.
🤖 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>
This PR reworks the SchoolCompare landing page: fixes a real bug where the DataInfoResponse type declared a nonexistent total_schools field (causing a hardcoded '24,000+' fallback to silently render instead of the live count), corrects mismatched copy ('three schools' vs the actual 5-school limit, 'shortlist' vs 'compare', invented school names/data in the how-it-works preview), adds a responsive two-composition hero illustration with dark-theme color tokens, and unifies section vertical rhythm via new CSS custom properties. New Playwright e2e tests guard each of these regressions. The change is frontend/test-only (no backend, CI, or deploy config touched), and cross-checking against the actual backend response shape, MAX_SCHOOLS, and CSS/class references confirms the fixes are accurate and structurally sound.
✅ No issues found.
## 🤖 AI Code Review (Claude Code)
This PR reworks the SchoolCompare landing page: fixes a real bug where the `DataInfoResponse` type declared a nonexistent `total_schools` field (causing a hardcoded '24,000+' fallback to silently render instead of the live count), corrects mismatched copy ('three schools' vs the actual 5-school limit, 'shortlist' vs 'compare', invented school names/data in the how-it-works preview), adds a responsive two-composition hero illustration with dark-theme color tokens, and unifies section vertical rhythm via new CSS custom properties. New Playwright e2e tests guard each of these regressions. The change is frontend/test-only (no backend, CI, or deploy config touched), and cross-checking against the actual backend response shape, `MAX_SCHOOLS`, and CSS/class references confirms the fixes are accurate and structurally sound.
✅ No issues found.
Main's staging E2E has been red since #90. Two of the four failures were the
staging container serving a stale image at the moment the gate ran — apple-icon
and the header lockup both check out against staging now. The other two were
real, and both are the same mistake: a colour pairing verified against one
ground while the element sits on another.
* .heroEyebrow --brand on its own 10% tint over Sand is 4.19:1. The token
clears AA on Sand, but the tint darkens the ground under the label, and
that composite was never the thing measured. --brand-strong is 5.85:1.
* .hiwVisual The preview panel was grounded in Sand while everything
inside it is a translucent status tint — and those tints are specced to
clear AA over --bg-card. Over Sand the report-card chips landed at 4.22:1
and 4.29:1. The ratio depended on a background two levels up. Moving the
panel to the card surface puts them at 5.69:1 and 5.81:1; a border keeps
it reading as an inset frame now that panel and card share a colour.
* Footer .sectionTitle --sage flips with the theme; the footer band does
not (it is teal in both). So the pairing only held in one of them — the
dark sage measured 4.08:1, on every page. Now --on-sunken-muted, which is
what the --on-sunken-* family exists for, as Footer.tsx's own header
comment already says.
* .compareHeadLabel --text-muted on the header row's tint is 4.45:1 in the
dark theme. Under by a hair, same cause. Now --text-secondary.
The harness gained the footer, because it had no footer and so could not have
caught the one failure that appeared on all three pages. Rewriting fragments
now uses each CSS module's own hash — the footer rewritten with HomeView's
prefix renders unstyled, which would have made any contrast measured on it
meaningless while still reporting a number.
Verified: 0 AA failures in both themes across the assembled page, measured with
the same probe the e2e test uses, against the real compiled CSS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This PR fixes a real data bug (the landing page was silently showing a hardcoded "24,000+" schools fallback because the frontend type declared a total_schools field the API never sends, instead of the actual unique_schools), unifies "shortlist"/"compare"/"pin" terminology, rewrites the admissions countdown UI, adds a second hero illustration composition for mobile crops, unifies section vertical rhythm via CSS variables, fixes several AA contrast failures, and adds substantial new e2e coverage for all of it. The change is unusually well self-documented and internally consistent — CSS classes, component props, and the backend/frontend DataInfoResponse shape all line up correctly, and no stale references to removed fields or copy were left behind.
🟡 Minor
e2e/tests/journeys.spec.ts: The new dark-theme illustration test builds its context with browser.newContext({ colorScheme }) instead of using the test's page/context fixtures. Manually created contexts do not inherit playwright.config.ts's use.baseURL (only the framework-provided context/page fixtures merge that in), so await p.goto('/') inside this context has no base to resolve the relative path against and will throw on every run — deterministically failing this test and re-breaking the same staging E2E gate the companion a11y commit in this PR chain was written to keep green.
## 🤖 AI Code Review (Claude Code)
This PR fixes a real data bug (the landing page was silently showing a hardcoded "24,000+" schools fallback because the frontend type declared a `total_schools` field the API never sends, instead of the actual `unique_schools`), unifies "shortlist"/"compare"/"pin" terminology, rewrites the admissions countdown UI, adds a second hero illustration composition for mobile crops, unifies section vertical rhythm via CSS variables, fixes several AA contrast failures, and adds substantial new e2e coverage for all of it. The change is unusually well self-documented and internally consistent — CSS classes, component props, and the backend/frontend `DataInfoResponse` shape all line up correctly, and no stale references to removed fields or copy were left behind.
### 🟡 Minor
- **e2e/tests/journeys.spec.ts**: The new dark-theme illustration test builds its context with `browser.newContext({ colorScheme })` instead of using the test's `page`/`context` fixtures. Manually created contexts do not inherit `playwright.config.ts`'s `use.baseURL` (only the framework-provided `context`/`page` fixtures merge that in), so `await p.goto('/')` inside this context has no base to resolve the relative path against and will throw on every run — deterministically failing this test and re-breaking the same staging E2E gate the companion a11y commit in this PR chain was written to keep green.
Checked the 🟡 finding on browser.newContext({ colorScheme }) and baseURL — it does not hold for this repo.
Three pre-existing tests in the same file use the identical pattern (journeys.spec.ts lines 795, 817, 1027), and the last staging run proves the paths resolve: the WCAG test at line 1027 creates its context that way and its failure output is tagged per-page —
Those are relative goto() calls that navigated successfully, so use.baseURL is being applied to manually created contexts in this Playwright version. The new test copies that pattern exactly. No change made.
The rest of the review matches what I found. Worth noting the four AA failures fixed in 97ac5c9 were already red on main before this PR — the gate has been failing since #90, so nothing on main is currently promotable. Two of those four turned out to be the staging container serving a stale image at gate time (apple-icon and the header lockup both verify against staging now); the other two were real.
Checked the 🟡 finding on `browser.newContext({ colorScheme })` and `baseURL` — it does not hold for this repo.
Three pre-existing tests in the same file use the identical pattern (`journeys.spec.ts` lines 795, 817, 1027), and the last staging run proves the paths resolve: the WCAG test at line 1027 creates its context that way and its failure output is tagged per-page —
```
/ → HomeView-module__…__compareHeadCell 4.45:1 …
/rankings → Footer-module__…__sectionTitle 4.08:1 …
/admissions → Footer-module__…__sectionTitle 4.08:1 …
```
Those are relative `goto()` calls that navigated successfully, so `use.baseURL` is being applied to manually created contexts in this Playwright version. The new test copies that pattern exactly. No change made.
The rest of the review matches what I found. Worth noting the four AA failures fixed in 97ac5c9 were **already red on main** before this PR — the gate has been failing since #90, so nothing on main is currently promotable. Two of those four turned out to be the staging container serving a stale image at gate time (apple-icon and the header lockup both verify against staging now); the other two were real.
tudor
merged commit f1819e9c4d into main2026-08-14 18:18:53 +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.
Re-targets the work from #91 at
main. #91 was merged intofeat/brand-logo-artworkrather than intomain, and main was cut from that branch without it — so none of this reached main. Same single commit, cherry-picked onto current main (clean, no conflicts, since #90 has since landed).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.
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. Re-verified after the cherry-pick onto main:
tscclean, 159/159 unit tests, build green.🤖 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>🤖 AI Code Review (Claude Code)
This PR reworks the SchoolCompare landing page: fixes a real bug where the
DataInfoResponsetype declared a nonexistenttotal_schoolsfield (causing a hardcoded '24,000+' fallback to silently render instead of the live count), corrects mismatched copy ('three schools' vs the actual 5-school limit, 'shortlist' vs 'compare', invented school names/data in the how-it-works preview), adds a responsive two-composition hero illustration with dark-theme color tokens, and unifies section vertical rhythm via new CSS custom properties. New Playwright e2e tests guard each of these regressions. The change is frontend/test-only (no backend, CI, or deploy config touched), and cross-checking against the actual backend response shape,MAX_SCHOOLS, and CSS/class references confirms the fixes are accurate and structurally sound.✅ No issues found.
🤖 AI Code Review (Claude Code)
This PR fixes a real data bug (the landing page was silently showing a hardcoded "24,000+" schools fallback because the frontend type declared a
total_schoolsfield the API never sends, instead of the actualunique_schools), unifies "shortlist"/"compare"/"pin" terminology, rewrites the admissions countdown UI, adds a second hero illustration composition for mobile crops, unifies section vertical rhythm via CSS variables, fixes several AA contrast failures, and adds substantial new e2e coverage for all of it. The change is unusually well self-documented and internally consistent — CSS classes, component props, and the backend/frontendDataInfoResponseshape all line up correctly, and no stale references to removed fields or copy were left behind.🟡 Minor
browser.newContext({ colorScheme })instead of using the test'spage/contextfixtures. Manually created contexts do not inheritplaywright.config.ts'suse.baseURL(only the framework-providedcontext/pagefixtures merge that in), soawait p.goto('/')inside this context has no base to resolve the relative path against and will throw on every run — deterministically failing this test and re-breaking the same staging E2E gate the companion a11y commit in this PR chain was written to keep green.Checked the 🟡 finding on
browser.newContext({ colorScheme })andbaseURL— it does not hold for this repo.Three pre-existing tests in the same file use the identical pattern (
journeys.spec.tslines 795, 817, 1027), and the last staging run proves the paths resolve: the WCAG test at line 1027 creates its context that way and its failure output is tagged per-page —Those are relative
goto()calls that navigated successfully, souse.baseURLis being applied to manually created contexts in this Playwright version. The new test copies that pattern exactly. No change made.The rest of the review matches what I found. Worth noting the four AA failures fixed in
97ac5c9were already red on main before this PR — the gate has been failing since #90, so nothing on main is currently promotable. Two of those four turned out to be the staging container serving a stale image at gate time (apple-icon and the header lockup both verify against staging now); the other two were real.