423b27140ceab53c5fb4243839e797964cb6c578
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
41d3f3b971 |
fix(school): never open a wrapped fact line with a separator
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 17s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 20s
At phone widths the fact line wraps, and each dot travelled with the value after it, so the second line read "· 1,478 pupils". The line now starts 1.125rem left of the column and clips that strip: a value that starts a line sits flush with the title and its dot is hidden. Measured on staging at 360, 390 and 430 px: no element past the gutter, no page overflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ca47d08186 |
feat(school): state the header's facts in one line, and flag what applies
The row of grey chips under the name mixed the council, a GIAS governance term, the age range and provision, with no labels. Parents could not tell what "Wandsworth" or "Nursery" meant, and selective, faith and fees were missing or buried in the details. The header now prints one fact line (phase pill · ages · type · pupils), then flags only for what applies, in a fixed order: who can apply or what it costs (Fee-paying, Selective, Girls'/Boys' school, the religious character as recorded), then what the school offers (Nursery class, Sixth form). Flags carry no hue: they are facts, not verdicts. The council joins the address unless the address already names it. The details keep headteacher, website, phone, academy trust (hidden when it has the school's own name) and capacity; county, constituency and religious character leave the header. The proposed-to-close strip is unchanged. lib/schoolFacts holds the type label and the flags so the search rows can print the same words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0571d1c0ff |
fix(web): stop the sheet-open rule stealing .sectionNav's layout
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m12s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 18s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m17s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m18s
Review catch, and a bad one: the previous commit anchored its insertion on `padding: 0.5rem 0.75rem;` and closed .sectionNav there. Everything that followed in the rule — margin-bottom, box-shadow, display: flex, align-items, gap — was orphaned into .sectionNavSheetOpen, which is only applied while the mobile jump sheet is open. So the sticky nav lost its flex layout, spacing and shadow in the closed state, which is virtually every page view on every school detail page. A site-wide regression introduced by a fix for one mobile menu. Redone by anchoring on the complete rule, closing brace included, so nothing can be orphaned. .sectionNav is now byte-identical to main and the diff is purely additive; .sectionNavSheetOpen carries the z-index and nothing else. The staging experiment that validated this fix set nav.style.zIndex = '1100' with every other declaration intact, so it was always testing this version rather than the broken one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
180d6e9b3e |
fix(web): lift the jump sheet above the bottom tab bar
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m14s
PR Checks / Backend Smoke (pull_request) Successful in 10s
PR Checks / Build Backend (no push) (pull_request) Successful in 17s
PR Checks / Build Frontend (no push) (pull_request) Successful in 1m19s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 37s
On mobile the last item in "Jump to section" was painted over by the fixed bottom tab bar and could not be tapped. Reported as nearby schools missing from the menu; it was there, underneath the bar. The sticky nav sets `position: sticky` with `z-index: 10`, which makes it a stacking context. The sheet's own `z-index: 1600` therefore orders it only inside that context — against the tab bar (z-index 1000) the nav's 10 is what counts, so the bar wins. Verified on staging: every menu item returns itself from elementFromPoint except the last, which returns the tab bar. Latent rather than new. With five sections the list stopped just above the bar; "Nearby schools" made six, and the sixth is the first to reach it. Any section added later would have done the same. Lifted only while the sheet is open, and only to 1100 — above the bar, below the comparison toast (2000), the fullscreen map (5000) and the info popover (9999). The backdrop rises with it, so tapping over the bar now dismisses the sheet instead of navigating away. The journey asks what a thumb asks: for each item, whether it is the topmost element at its own centre. A bounding-box check cannot see this — the item is in the viewport and the right size, just underneath something. Confirmed to fail against current staging, naming "Nearby schools", before the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8d50afef1e |
fix(design): meet AA on tinted surfaces, and stop the footer inverting in dark
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m11s
PR Checks / Backend Smoke (pull_request) Successful in 9s
PR Checks / Build Backend (no push) (pull_request) Successful in 12s
PR Checks / Build Frontend (no push) (pull_request) Successful in 52s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 11s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 3m6s
Full audit across home, search, rankings, admissions, compare, and primary / secondary / special / no-data school pages, in both themes, measuring computed styles rather than reading CSS. Contrast: the status hues were specced against --bg-primary, but they are used as chip text on their own tint, which sits on cards and secondary surfaces rather than the page ground. Measured in the wild they were 3.85–4.44:1 — under AA — on Ofsted badges, delta chips, report-card chips and metric values, i.e. most of the product's actual signal. Darkened the light hues (#0e6e66 → #0b625a, #9a5b00 → #7f4a00) and lifted the dark teal (#3fb3a4 → #4fc0b0) so each clears AA on its own tint, which is the worst case rather than the easy one. Chart and series ramps follow. The footer was painting itself with a text token and lettering itself with a background token: `background: var(--text-primary); color: var(--bg-secondary)`. That reads correctly in one theme and inverts in the other — in dark mode it became a light slab at the bottom of a dark page, with amber section headings at 1.98:1. Added --surface-sunken and its on-* companions, which stay dark in BOTH themes (deliberately not --surface-inverse, whose whole job is to flip), and moved the footer onto them. Section headings were also using a status hue purely as decoration; they are now a muted on-surface token. The "Open full map" pill was a fixed white background with a themed text colour, so in dark mode it rendered light violet on white at 1.84:1. Both sides are token-driven now. Tabular numerals are now the default for .main rather than per-component opt-in, with prose opting back out — a handful of figures (miniNatPill, compareRowVal, factVal) had been missed by the class-name-based pass. Added a two-theme AA gate over home, rankings and admissions. It waits for `transition: color` to settle first: an earlier measurement pass read mid-transition values and reported seven failures that did not exist at rest. Worth stating plainly — most of what a naive audit flags here is its own artifact, and the check has to account for that to be worth having. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8ab0ac0a04 |
feat(design): adopt the Cohort identity — new palette, type, mark and dark theme
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m6s
PR Checks / Backend Smoke (pull_request) Successful in 7s
PR Checks / Build Backend (no push) (pull_request) Successful in 12s
PR Checks / Build Frontend (no push) (pull_request) Successful in 49s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Failing after 3m13s
Implements the direction agreed from the identity board: Route C ("Cohort")
with the paper ground from C1, the Schibsted Grotesk / Literata pairing from
C2, and the iris accent from C3. Dark theme is in scope from the start rather
than retrofitted.
The audit found three things wrong beyond taste:
* No brand asset set. og:image was absent entirely, so every link shared into
a class WhatsApp group rendered as a bare grey card. apple-touch-icon pointed
at an SVG, which iOS ignores, and the manifest shipped no PNGs, so Android
installs had no icon. The header mark and the favicon had also drifted into
two different logos.
* No colour discipline. --primary and --trend-down were the same coral, so the
main CTA and "below average" shared a hue. 58 distinct hex values were spread
across component CSS, and the chart palette was still Chart.js's stock demo
colours.
* A dark theme that was declared but never built — themeColor announced a dark
variant with no dark styling behind it.
What changed:
Colour now has exactly three jobs that never borrow each other's hues: brand
(iris) for interactive and identity, status (teal/amber) for above/below a
comparison point, and phase for categories. Teal/amber rather than green/red
keeps the above/below signal readable for every form of colour blindness.
Every chromatic literal in component CSS is now a token, and the JS-painted
surfaces (Chart.js, Leaflet) read the tokens through lib/theme so they follow
the theme instead of ignoring it.
The mark is the five-bar cohort spread — the same object as the distribution
strip inside a school row, built from opacity steps so it inverts cleanly.
components/Logo.tsx is the single source; the favicon, apple-icon and share
card all derive from its geometry.
globals.css drops 123 dead global classes left over from the vanilla-JS app
(only the btn family, .skip-link and .main were still referenced), along with
the noise overlay. It also gains prefers-reduced-motion support, which was
missing entirely, and a type scale so the 54 ad-hoc font sizes have somewhere
to converge.
Verified: tsc clean, 159 unit tests pass, production build succeeds and
prerenders /icon.svg, /apple-icon and /opengraph-image. Three e2e journeys
added for the asset set, the themeColor/background match, and the dark theme
actually repainting — all silent failures that nothing on the page reveals.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e098ad4bd1 |
fix(detail): restore the shell's mobile header/nav CSS
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m6s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 36s
PR Checks / Build Frontend (no push) (pull_request) Successful in 46s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 1m19s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 9s
The server/client split moved the header and sticky nav markup into
SchoolDetailShell, but their responsive rules stayed behind in
schoolSections.module.css. CSS Modules hash class names per file, so those
rules stopped matching entirely: on mobile the header details block never
collapsed ("Show all details" e2e journey failed), the toggle itself never
appeared, and the section-nav mobile swap (menu button in, link row out)
never applied.
Ports the stranded blocks into SchoolDetailShell.module.css verbatim: the
768px header block (stacked header, wrapped meta pills, details toggle +
collapse) and the 640px section-nav blocks (scroll-fade mask, bottom-sheet
panel, control swap). Adds a jest guard that every class a components/school
component references is defined in the stylesheet that component imports —
it fails on the three classes that had no rule at all here
(headerDetailsOpen, atEnd, sectionNavBackLabel).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
752eb07310 |
refactor(detail): render sections on the server behind a client shell
page.tsx now composes the sections and passes them through SchoolDetailShell
as children, so ~1,300 lines of static markup stop shipping as client
JavaScript. The shell keeps what is genuinely interactive: back link, header
reveal, hero map, compare CTA, sticky nav and scroll-spy.
The scroll-spy already located sections via document.getElementById, so it
works unchanged against server-rendered children.
Charts needed a client wrapper: next/dynamic with ssr:false is illegal in a
Server Component, so components/school/charts.tsx is the boundary that keeps
Chart.js (64 KB gz) lazy and browser-only.
Measured on this build:
- school route client chunk: 8 KB gz (33 KB raw)
- total static JS across all chunks: 380.6 -> 350.7 KB gz
- section markup is absent from every client chunk ("Got their first choice",
"Ofsted reports", "Most deprived" etc. all return 0 hits); shell strings
still present, as expected
- shared baseline unchanged at 172 KB gz -- out of scope, as designed
The 14 characterization tests pass byte-identical to the commit that
introduced them. Only the render helper changed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|