Files
school_compare/docs/superpowers/specs/2026-07-02-ux-audit-notes/cohesion-pass.md
T
2026-07-02 14:28:09 +01:00

17 KiB
Raw Blame History

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.


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 .btn rule set in globals.css — small-button padding is dead code, sizes drift

    • Evidence: globals.css defines .btn twice — line 151 (radius 6px, pad 0.5rem 1rem, font 0.875rem, 1px border) and again line 1558 (radius --radius-md, pad 0.75rem 1.5rem, font 0.9rem, border:none). .btn-sm (line 218, pad 0.3rem 0.625rem) is declared between them, so the later .btn (equal specificity, source order wins) overrides it. Live proof: rankings .btn.btn-sm computes to padding 12px 24px, not the intended 4.8×10px. Meanwhile the home search .btn.btn-primary computes 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 .btn definition; restore .btn-sm).
    • Severity: P2.
  • F2. Two parallel school-detail components have drifted on the same controls

    • Evidence: SchoolDetailView.module.css vs SecondarySchoolDetailView.module.css implement the same UI with divergent hardcoded values: .btnAdd radius 8px / pad 0.75rem 1.25rem (primary) vs radius 6px / pad 0.5rem 1rem (secondary); national-average marker .natPill radius 4px (primary) vs .badge/.att8 radius 3px (secondary); section-tab padding 4.8px 10px vs 4.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.
  • 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 (home clamp(2rem,5vw,3.5rem); school clamp(2rem,5vw,3.25rem); admissions 2.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.
  • 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.
  • 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.
  • F6. Near-duplicate accent colours for the same role

    • Evidence: pressed/hover coral is --accent-coral-dark #c45a3f on .btn-primary but a hardcoded #d4654a on .btn-compare:hover (globals.css:754); gold appears as #b8920e on the school-page admissions cross-link badge, matching neither --accent-gold #c9a227 nor --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.
  • F7. Heading font-family and off-family buttons break the type system locally

    • Evidence: on /admissions and both school pages, H3 elements render in DM Sans /700 while H2 stays Playfair — an inconsistent semantic-heading treatment. Separately, /compare shows 3× BUTTON in 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.
  • 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 (SchoolHeroMap on 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 #0078a8 attribution 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/navLink links match the accent system, but the same "go to a tool" intent is a plain underlined text link on /admissions yet 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: /compare at 390×844 with three same-phase primary schools — cards stack, chart renders, table scrolls within its container.