17 KiB
Cross-cutting cohesion pass — audit notes (Task 7)
Pages compared: /, /compare, /rankings, /admissions, /school/136916-the-castle-school (secondary), /school/146678-welland-primary-school (primary)
Viewport: 1440×900 default; mobile 390×844 for the /compare 3-school check.
Method: Ran a browser_evaluate typography+colour collector on each of the six live pages (headings, body, buttons, links, chips — computed fontSize/fontWeight/fontFamily, color/backgroundColor/borderRadius/padding). Diffed the results into the tables below. For every ambiguous drift, grepped nextjs-app/ (app/globals.css + components/*.module.css) to decide token exists & bypassed (→ enforce) vs no token (→ create).
Criterion used throughout: Nielsen #4 "Consistency and standards" unless a WCAG SC is named. No taste-only findings — every claim below carries computed-style and/or source evidence.
Task attempt log
Collected computed styles on all six pages in sequence, then verified against source. The dominant story is two axes of drift: (a) the two school-detail pages are parallel component implementations (SchoolDetailView vs SecondarySchoolDetailView) whose tokens have diverged; (b) a rich design-token system exists in globals.css (--radius-*, --accent-*) but is pervasively bypassed with hardcoded px/hex in the module CSS.
OPEN item — /compare with THREE same-phase schools at 390×844
Loaded /compare?urns=142161,113105,124613&metric=rwm_expected_pct at 390×844 — three primary schools (St Mary & St Thomas CofE, Ottery St Mary, Trimley St Mary), same phase, confirmed via the "Primary (3) / Secondary (0)" phase tabs. Handling is sound: the three school cards stack vertically (each showing name, LA, type, and the selected metric value in its series colour — 77.0% teal, 51.0% coral, 44.0% blue), so no card is squeezed. The "Performance Over Time" chart canvas renders all three series (verified: canvas has drawn content — 9,720 non-white pixel samples in a 324×300 canvas; a full-page screenshot showed it momentarily blank, which is a Chart.js/fullPage capture artifact, not a real defect). The "Detailed Comparison" table becomes 4 columns (metric label + 3 schools) at 869px inside a 324px container: it scrolls horizontally within its own overflow-x:auto wrapper, and the page body itself does not overflow (document.scrollWidth = 390 = window.innerWidth). So a third same-phase school is legible and contained — no layout break. Only nit (already a general finding, not compare-specific): at 390px only the first school's column is visible without scrolling, so a 3-way visual scan of the table requires swiping. Screenshot: cohesion-compare-3school-mobile.jpeg.
Typography comparison (per page, desktop 1440)
| Page | Page-title H1 | H1 family | Section H2/H3 | Notable body sizes | Off-scale / cross-page flags |
|---|---|---|---|---|---|
/ |
48px /700 Playfair | Playfair | H2 28px & 21.6px; H3 16px & 20px Playfair | 16.8, 14.72, 14, 13.76, 13.12px | Search CTA button 20px /600 (see F1); many near-dup body sizes |
/rankings |
36px /700 Playfair | Playfair | — | table TH 12px, TD 15px/16px | H1 smaller than home; table type 12/15 |
/compare |
36px /700 Playfair | Playfair | H2 18px & 24px Playfair (mixed) | table TH 12px, TD 15px | 3× BUTTON 20px Arial (off-family, F7); mixed H2 |
/admissions |
44px /700 Playfair | Playfair | H2 24px & 21.6px Playfair; H3 15.2px /700 DM Sans | 14.08, 14.4, 14, 13.12px | H3 rendered in body font not Playfair (F7); 3rd distinct H1 size |
/school/…primary (Welland) |
52px /700 Playfair | Playfair | H2 18px Playfair ×5; H3 14px DM Sans | table TH 11px, TD 13px | largest H1; table type 11/13 (F8) |
/school/…secondary (Castle) |
52px /700 Playfair | Playfair | H2 18px Playfair ×4; H3 14px DM Sans | table TH 11px, TD 13px | matches Welland (good); table 11/13 (F8) |
Diff summary: Four distinct page-title sizes across five pages — 36 / 44 / 48 / 52px. Tool pages (rankings, compare) agree on 36; the three "hero" pages (home 48, admissions 44, school 52) each pick a different size. Source: each is a hardcoded clamp() in its own module, not a shared token — home .hero-title clamp(2rem,5vw,3.5rem); both school .schoolName clamp(2rem,5vw,3.25rem); admissions title 2.75rem. → no page-title/hero token exists → CREATE. Body copy shows a cloud of near-duplicate sizes (0.875/0.88/0.9/0.92rem → 14/14.08/14.4/14.72px) that no single scale explains.
Fonts are otherwise disciplined: DM Sans body + Playfair Display display everywhere. Exceptions: (a) admissions/school H3 elements render in DM Sans while H2 stays Playfair (F7); (b) Arial/Helvetica Neue buttons+links on chart and map pages are third-party (Chart.js legend, Leaflet zoom/attribution) — noted, not a first-party defect.
Colour comparison (buttons / links / chips)
| Role | Colour(s) observed | Token | Verdict |
|---|---|---|---|
| Primary CTA bg | rgb(224,114,86) #e07256 everywhere (search, +Add School, active phase tab, btn-primary) |
--accent-coral |
Consistent ✓ |
| Primary CTA hover | #c45a3f (btn-primary) vs #d4654a (.btn-compare:hover, globals.css:754) |
--accent-coral-dark = #c45a3f |
Near-dup / same-role different colour (F6) — #d4654a hardcoded, bypasses token |
| Secondary / supporting | rgb(45,125,125) #2d7d7d (teal outline btn, teal chips, "Near me", info) |
--accent-teal |
Consistent ✓ |
| Nav active | coral text on rgba(224,114,86,0.12) tint |
--accent-coral-bg |
Consistent ✓ |
| Gold accent (admissions cross-link badge on school page) | rgb(184,146,14) #b8920e |
tokens are --accent-gold #c9a227 / --accent-gold-text #7a6800 |
Third gold — matches neither token (F6 family) |
| Chip series colours (countdown / SATs) | coral #e07256, teal #2d7d7d, blue chart-5 |
--chart-* |
Consistent ✓ (data encoding) |
| Map furniture links | rgb(0,120,168) #0078a8 blue |
none (Leaflet/OSM) | Third-party — off-palette blue leaks into school hero (see hero verdict) |
Cluster flags: coral resolves to three values doing hover/pressed work — #e07256, #c45a3f, #d4654a; the middle two are the same semantic role (pressed coral) at different hex. Gold has a third off-token value #b8920e. Teal is clean. No different-role/same-colour collisions found (coral=primary, teal=secondary is held consistently).
Component variant table
| Component / role | / |
/rankings |
/compare |
/admissions |
school (primary) | school (secondary) | Drift |
|---|---|---|---|---|---|---|---|
| Segmented / phase switcher | search-mode toggle: rounded (container radius-lg, btn radius-md), coral active | phaseTab: radius 0, coral fill, bordered | phaseTab: radius 0 (matches rankings ✓) | sub-nav: radius 0, underline, teal active | section-nav pills radius 4/999 | tab btns radius 4 | F5 — 3+ different treatments for "switch view/phase" |
| Primary "+Compare/+Add" button | btn-primary radius 8, pad 20×40 (F1) | btn radius 8, pad 12×24 | btn radius 8, pad 12×24 | — | btnAdd radius 8, pad 12×20 | btnAdd radius 6, pad 8×16 | F1 / F2 |
| Back link | — | — | — | — | topBack coral, radius 0 | topBack coral, radius 0 | consistent ✓ |
| Data table type | — | TH 12 / TD 15 | TH 12 / TD 15 | — | TH 11 / TD 13 | TH 11 / TD 13 | F8 |
| Small badge/pill | ofsted badge radius 4 | rank badge 50% | — | deadline chip radius 12 | SATs natPill radius 4; nav pill 999 | badge radius 3; att8 badge radius 3 | F4 |
| Nav header / footer | identical | identical | identical | identical | identical | identical | consistent ✓ |
Radius scale audit across all *.module.css: 15 distinct raw-px radius values in use — 4px(30×), 8px(28×), 999px(21×), 12px(19×), 3px(16×), 6px(14×), 10px(13×), 2px(8×), 16px(4×), 14px(3×), 9999px(2×), 1px(2×), 9px, 5px, 25px — against a token scale of only --radius-sm/md/lg/xl = 4/8/16/24. Tokens exist and are widely bypassed; there is no pill token for the 999/9999 values.
Friction points
-
F1. Duplicate
.btnrule set inglobals.css— small-button padding is dead code, sizes drift- Evidence:
globals.cssdefines.btntwice — line 151 (radius 6px, pad0.5rem 1rem, font 0.875rem, 1px border) and again line 1558 (radius--radius-md, pad0.75rem 1.5rem, font 0.9rem,border:none)..btn-sm(line 218, pad0.3rem 0.625rem) is declared between them, so the later.btn(equal specificity, source order wins) overrides it. Live proof: rankings.btn.btn-smcomputes to padding 12px 24px, not the intended 4.8×10px. Meanwhile the home search.btn.btn-primarycomputes to pad 20px 40px / font 20px (a FilterBar override on top). - Criterion violated: Nielsen #4; touch-target predictability.
- Argument: "small" buttons aren't small, and the base button geometry depends on which of two conflicting blocks wins — any future button edit has a 50/50 chance of hitting the dead rule. Silent, repo-wide.
- Source verdict: token/rule conflict → ENFORCE (dedupe to one
.btndefinition; restore.btn-sm). - Severity: P2.
- Evidence:
-
F2. Two parallel school-detail components have drifted on the same controls
- Evidence:
SchoolDetailView.module.cssvsSecondarySchoolDetailView.module.cssimplement the same UI with divergent hardcoded values:.btnAddradius 8px / pad 0.75rem 1.25rem (primary) vs radius 6px / pad 0.5rem 1rem (secondary); national-average marker.natPillradius 4px (primary) vs.badge/.att8radius 3px (secondary); section-tab padding4.8px 10pxvs4.8px 12px. Both files hardcode px rather than referencing--radius-md. - Criterion violated: Nielsen #4.
- Argument: a parent moving from a primary school page to a secondary one (the compare flow explicitly mixes phases) meets the "+ Compare" button and nav tabs rendered at subtly different sizes/corners — the classic "two things that should be one" tax, and double the maintenance surface.
- Source verdict: token EXISTS (
--radius-md:8px) but bypassed → ENFORCE (both should use the token; ideally share one component). - Severity: P2.
- Evidence:
-
F3. Page-title (H1) sizing is unsystematic across page types
- Evidence: H1 computes to 36px (rankings, compare), 44px (admissions), 48px (home), 52px (school). Source: each is a separate hardcoded
clamp()/rem in its own module (homeclamp(2rem,5vw,3.5rem); schoolclamp(2rem,5vw,3.25rem); admissions2.75rem); tool pages fall back to smaller local values. - Criterion violated: Nielsen #4 (visual hierarchy consistency).
- Argument: page-to-page the "you are here" title jumps size with no rule a user could infer; hero pages don't even agree with each other.
- Source verdict: no shared page-title/hero token → CREATE (
--title-hero,--title-section) and apply. - Severity: P2 (hierarchy), leaning P3 in isolation.
- Evidence: H1 computes to 36px (rankings, compare), 44px (admissions), 48px (home), 52px (school). Source: each is a separate hardcoded
-
F4. Radius scale is bypassed system-wide (15 raw values vs 4 tokens; no pill token)
- Evidence: radius audit above — 3/5/6/9/10/12/14/25px and 999/9999px all appear hardcoded despite
--radius-sm/md/lg/xl. Same-role badges differ (natPill 4 vs secondary badge 3; ofsted badge 4 vs att8 badge 3). - Criterion violated: Nielsen #4.
- Argument: corner rounding is a primary "family resemblance" cue; with 15 values it reads as many hands, not one system.
- Source verdict: mixed — tokens exist for 4/8/16 (ENFORCE); pill radius has no token → CREATE
--radius-pill: 999px. - Severity: P2.
- Evidence: radius audit above — 3/5/6/9/10/12/14/25px and 999/9999px all appear hardcoded despite
-
F5. "Switch view / phase" control has 3+ different treatments
- Evidence: home search-mode toggle is a rounded segmented control (container
--radius-lg, coral active); rankings & compare phase tabs are square (radius 0) bordered coral-fill tabs; admissions sub-nav is an underline tab strip (radius 0, teal active). Same job, three shapes and two accent colours. - Criterion violated: Nielsen #4.
- Argument: the segmented switch is a recurring interaction; users re-learn it on each page. (Rankings↔compare agreeing is the one bright spot.)
- Source verdict: no shared segmented-control component → CREATE/CONSOLIDATE.
- Severity: P2.
- Evidence: home search-mode toggle is a rounded segmented control (container
-
F6. Near-duplicate accent colours for the same role
- Evidence: pressed/hover coral is
--accent-coral-dark #c45a3fon.btn-primarybut a hardcoded#d4654aon.btn-compare:hover(globals.css:754); gold appears as#b8920eon the school-page admissions cross-link badge, matching neither--accent-gold #c9a227nor--accent-gold-text #7a6800. - Criterion violated: Nielsen #4.
- Argument: two hovers for the same "coral button being pressed" is exactly the "two blues doing the same job" consistency defect.
- Source verdict: token EXISTS → ENFORCE (
--accent-coral-dark); the off-token gold → ENFORCE--accent-gold-text. - Severity: P3.
- Evidence: pressed/hover coral is
-
F7. Heading font-family and off-family buttons break the type system locally
- Evidence: on
/admissionsand both school pages,H3elements render in DM Sans /700 whileH2stays Playfair — an inconsistent semantic-heading treatment. Separately,/compareshows 3×BUTTONin Arial 20px and school pages show Arial/Helvetica-Neue controls. - Criterion violated: Nielsen #4 (the H3 case). The Arial/Helvetica cases are third-party (Chart.js legend toggles, Leaflet zoom/attribution) — recorded as environmental, not a first-party fix.
- Argument: an H3 in body font reads as a bold paragraph, weakening the Playfair hierarchy the rest of the site sells.
- Source verdict: H3 font-family is set locally, no heading-family token discipline → ENFORCE Playfair for display headings (or intentionally reclass those H3s as labels).
- Severity: P3.
- Evidence: on
-
F8. School-detail data tables use a smaller type scale than the shared data tables
- Evidence: rankings & compare tables compute TH 12px / TD 15px; both school-detail tables compute TH 11px / TD 13px.
- Criterion violated: Nielsen #4; borderline WCAG 1.4.4 (13px data is small but resizable).
- Argument: the same kind of KS2 figures appear one size on compare and a size smaller on the school page — inconsistent, and the smaller variant is the one a parent studies most.
- Source verdict: table type is set per-component, no shared table-type token → CREATE and apply.
- Severity: P3.
Recent-additions verdicts (native vs bolted-on)
- Map-blended hero (
SchoolHeroMapon school pages): mostly native, with a third-party seam. The framing is on-brand — coral back-link, coral "+ Compare", cream surround, Playfair title over the map. But the embedded Leaflet layer imports off-palette blue#0078a8attribution links and Arial zoom controls straight into the hero (F7), the one place they're most visible. Verdict: native design, bolted-on furniture — worth restyling the Leaflet attribution/controls to the palette. - Characteristic chips (school rows + detail badges): native. Tints use
--accent-teal-bg/gold tints that belong to the palette, tone is quiet per the recent commit. Only blemish is radius drift (badge 3px vs natPill 4px, F4) — a token issue, not a stylistic mismatch. - Admissions cross-links: native in colour, inconsistent in treatment. The teal
stepTool/navLinklinks match the accent system, but the same "go to a tool" intent is a plain underlined text link on/admissionsyet a gold badge (#b8920e) on the school page (F6) — and journey-5 already logged one such cross-link at a 39px tap target. Verdict: native palette, slightly bolted-on because the cross-link component isn't unified.
Works well — keep
- Header + footer are pixel-identical on all six pages (logo Playfair, coral nav-active tint, dark footer with faded links) — the strongest cohesion anchor on the site.
- Coral = primary / teal = secondary is held consistently for button and link roles (no role/colour collisions).
- Rankings and compare phase tabs are genuinely shared (radius 0, pad 10×24, coral active) — the model for what F5 should become everywhere.
- Both school-detail H1s agree at 52px, and the deadline countdown chip is byte-for-byte identical between the homepage widget and
/admissions(radius 12px, pad 16px 17.6px 14.4px) — a correctly reused component.
Self-review
- No taste-only findings: every F cites either a computed-style diff (F1/F2/F3/F4/F6/F7/F8 all carry live px/hex) or a shared-component absence (F5), plus a criterion.
- Token-vs-no-token recorded for every source-checked finding: ENFORCE — F1, F2, F4(4/8/16), F6, F7; CREATE — F3 (hero title token), F4 (pill radius token), F5 (segmented-control component), F8 (table-type token).
- Third-party styling (Chart.js Arial, Leaflet blue/Arial) is explicitly separated from first-party defects rather than filed as findings.
- Severity spread: P2 ×5 (F1, F2, F3, F4, F5), P3 ×3 (F6, F7, F8). No P0/P1 — nothing blocks a task; the journey-5 contrast WCAG failure is already logged there and not re-filed here.
Screenshots (referenced, not committed)
cohesion-compare-3school-mobile.jpeg:/compareat 390×844 with three same-phase primary schools — cards stack, chart renders, table scrolls within its container.