Files
school_compare/docs/superpowers/specs/2026-07-02-ux-audit-notes/journey-3-compare.md
T
2026-07-02 11:49:39 +01:00

126 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Journey 3: Building a comparison (`/compare`) — audit notes
**Pages visited:** `/compare` (empty state, direct entry), `/compare?urns=…` (populated, 13 schools), add-school modal, `/school/142161-…` (persistence round-trip)
**Viewports:** 390×844 (primary), 1440×900 (+720×450 proxy for 200% reflow)
**Site:** live https://schoolcompare.co.uk only. Read-only: searched, added/removed schools in the compare basket (client/localStorage + URL state), opened/closed the modal, hovered the chart. Submitted no data-modifying forms. `selectedSchools` was cleared from localStorage only to reproduce a genuine empty state and to test the share-link defect.
## Task attempt log
**Direct entry premise:** 20% of sessions enter on `/compare`, so the empty state is judged as a cold arrival.
**Mobile (390×844) — empty state.**
- Navigating to `/compare` directly first showed a *persisted* prior selection (The Castle School, carried in `localStorage.selectedSchools` from an earlier session) — the URL self-rehydrated to `?urns=136916&metric=…`. This is persistence working (see Works well) but means the "empty" state is only seen by first-ever visitors. Cleared storage to capture the true empty state (`j3-compare-empty-mobile.png`).
- Empty state is **instructive and actionable**: heading "No schools selected", body "Add schools from the home page or search to start comparing", and a primary button **"+ Add Schools to Compare"** that opens an in-page search modal. A cold arrival can start without leaving the page.
**Mobile — building a two-school comparison. Taps counted from empty state:**
1. Tap **"+ Add Schools to Compare"** → opens modal, auto-focuses the "School name or postcode" search box.
2. **Type** "St Mary" → live result list appears (name + LA + school type + "+ Compare" per row).
3. Tap **"+ Compare"** on a result → school added; **modal stays open, field clears** ("Comparing 1 school" behind it).
4. **Type** "Ottery St Mary" → result list.
5. Tap **"+ Compare"** → second school added (`?urns=142161,113105`).
6. Tap **"Close modal"** → reveals the comparison (`j3-compare-two-schools-mobile.png`).
**6 distinct interactions (4 taps + 2 text-entry sequences)** to a two-school comparison. Critically, both schools had to be **found by name/postcode** — there is no browse / "schools near this one" / "similar schools" on-ramp inside compare (ties to Journey 2 F2). A parent who knows only one school name cannot build a comparison here.
**Mobile — comparison output assessment.**
- Per-metric **school cards stack vertically** and are fully legible; each shows name, LA, type, the selected metric value (e.g. 77.0% vs 51.0%), colour-keyed to the chart. Good on 390px.
- **Chart** ("Performance Over Time"): renders on real scroll (blank in full-page captures = lazy-render artifact, confirmed by element screenshot `j3-compare-chart-mobile.png`). Legend labels each line by **colour + full school name** — colour is not the sole differentiator (WCAG 1.4.1 satisfied). `canvas` has `role="img"` but **`aria-label` = null** (no text alt).
- **"Detailed Comparison" table**: wrapper 626px inside a 324px column → only **Year + the first school column fit**; the second school is off-screen behind horizontal scroll (`overflow-x:auto`, no visible scroll affordance). The whole point of the table (side-by-side) can't be seen at once on the primary viewport.
- Metric selector carries a **plain-English caption** ("% meeting expected standard in reading, writing and maths") — jargon defined in place (the thing school-detail pages lacked, Journey 2 F3).
**Mobile — remove / add / persistence.**
- **Remove (×)** is immediate: no confirm, no toast, no undo; URL updates instantly.
- **Postcode search works**: "TA1 5AU" → The Castle School (advertised feature, functional).
- **Add beyond two / phase mixing**: adding a secondary school to a primary comparison keeps the header at "Comparing 2 schools" but renders **only the primary card**; the secondary sits hidden behind a "Secondary (1)" tab with no explanation.
- **Persistence**: navigated to a school detail page, then back to bare `/compare` — both schools restored and URL rehydrated to `?urns=142161,136916`. State survives real navigation via localStorage; comparisons are URL-encoded / deep-linkable.
**Desktop (1440×900).**
- Table fits with **both/all school columns visible** (no page overflow). Layout clean (`j3-compare-two-schools-desktop.png`).
- **Keyboard add/remove**: "+ Add School" reachable and Enter-activatable; modal **opens with focus moved into the search input**; Tab reaches a result's "+ Compare" with a visible **2px solid orange (#E07256)** focus outline; Enter adds it. **But** after adding, focus **drops to `document.body`**; after **Escape** (which does close the modal) focus is again on `body`, **not** returned to the "+ Add School" trigger.
- Modal has **no `role="dialog"` and no `aria-modal`** — not announced as a dialog.
- **Chart tooltip** (`j3-compare-chart-tooltip-desktop.png`): hover shows year + per-school values with colour swatch + name — **mouse-only** (no touch/keyboard equivalent), but the same values are present as text in the table below.
- **200% zoom (720×450 proxy)**: no page-level horizontal overflow — content reflows (WCAG 1.4.10 pass); the detailed table retains its own contained horizontal scroll.
### Is 32% of exits task-complete satisfaction or abandonment?
**Mixed, with concrete abandonment drivers — it is not safe to read the exits as pure satisfaction.**
- *Points to satisfaction:* compare is a terminal "results" tool — the natural next step (visit/apply) is off-site, so a high exit share is partly expected; persistence + shareable URLs mean some exits are "saved for later"; the core output (cards + chart + table) renders and metrics are explained.
- *Points to abandonment:* (a) **20% ENTER on `/compare`**, often via a shared link — and the **localStorage-overrides-URL defect (F4)** silently shows a returning recipient *their own* schools instead of the shared ones, a confusing dead end; (b) a 2-school comparison needs 6 interactions **and prior knowledge of both school names** (no discovery on-ramp) — a parent with one school hits a wall; (c) on mobile the **detailed table hides the second school off-screen** behind an unaffordanced scroll (F1), so the comparison can look incomplete; (d) low-contrast primary buttons (F6) degrade the mobile-daylight path. F1/F4/F6 are each capable of turning an intended task-complete exit into premature abandonment, especially for the 20% arriving on shared links.
## Friction points
- **F1. "Detailed Comparison" table hides the second/third school off-screen on mobile**
- Evidence: at 390px the table is 626px inside a 324px `.tableWrapper` (`overflow-x:auto`); only Year + the first school column are visible, second column cut off with no scroll affordance (`j3-compare-two-schools-mobile.png`).
- Criterion violated: mobile-usability standard (horizontal scrolling of primary content is a known antipattern) + Nielsen #6 (Recognition rather than recall) — a parent must remember school A's numbers while scrolling to school B; the tool's core "side-by-side" promise is defeated on the primary viewport.
- Argument: the whole reason a parent opens compare is to see schools next to each other; on mobile they can't, and with no scroll cue may believe the second school's data is missing and leave.
- Severity guess: P2 (the stacked metric cards + chart do show both, softening it)
- **F2. Comparison table scroll region is not keyboard-accessible**
- Evidence: axe `scrollable-region-focusable` (serious, 1 node) on `.ComparisonView-module__…tableWrapper`; the overflowing wrapper has no `tabindex`.
- Criterion violated: WCAG 2.2 **2.1.1 Keyboard (A)**.
- Argument: a keyboard-only parent cannot scroll the detailed table to reveal columns beyond the first school — data is literally unreachable without a mouse/touch.
- Severity guess: P2
- **F3. Trend chart exposes no text alternative to assistive tech**
- Evidence: axe `role-img-alt` (serious) on `canvas`; `aria-label` = null on both mobile and desktop.
- Criterion violated: WCAG 2.2 **1.1.1 Non-text Content (A)**.
- Argument: a screen-reader parent gets nothing from "Performance Over Time"; the redundant table softens this but the chart's at-a-glance trend story is lost. Recurs from Journey 2 F5 — systemic to the chart component.
- Severity guess: P2
- **F4. Share link is silently overridden by the visitor's own localStorage**
- Evidence: navigating to `/compare?urns=142161,113105` while `selectedSchools` held a different set resolved the page (and URL) back to the stored `urns=142161,136916`; only after clearing localStorage did the shared URL load its intended schools.
- Criterion violated: Nielsen #1 (Visibility of system status) / consistency; task-flow obstruction of an **advertised primary feature** ("Share" button).
- Argument: a parent shares their shortlist with a partner; if the recipient has ever used compare, they silently see *their own* schools with no error or notice — the shared comparison is unreproducible and the collaboration breaks. This directly hits the 20% who enter on `/compare` via links.
- Severity guess: P1
- **F5. Add-school modal lacks dialog semantics and loses focus on add/close**
- Evidence: modal has no `role="dialog"` and no `aria-modal`; after a keyboard "+ Compare" add, `document.activeElement` = `BODY`; after Escape-close (Escape does dismiss it), focus is again on `BODY`, not the "+ Add School" trigger.
- Criterion violated: WCAG 2.2 **4.1.2 Name, Role, Value (A)** + **2.4.3 Focus Order (A)**.
- Argument: screen-reader users aren't told a dialog opened; keyboard users lose their place after every add (focus jumps to page top) and after closing, so building a multi-school comparison means re-tabbing from the top repeatedly — friction on the add flow every comparison depends on.
- Severity guess: P2 (P1-candidate for keyboard/AT users building 3+ school comparisons)
- **F6. Colour-contrast failures on the primary compare controls**
- Evidence: axe `color-contrast` (serious, 5 nodes populated) — sample targets `.btn-primary` (the "+ Add School" / "+ Compare" buttons), `.…phaseTabActive` (active Primary/Secondary tab), `.…tabActive .tabLabel` (active bottom-nav label).
- Criterion violated: WCAG 2.2 **1.4.3 Contrast (Minimum) (AA)**.
- Argument: the exact orange buttons and active tabs a parent must use to build and read a comparison have insufficient text contrast — hard to read on a phone in daylight (the mobile-primary audience). Recurs site-wide (Journey 1/2).
- Severity guess: P1
- **F7. Mixed-phase selection reports "Comparing 2 schools" while showing one**
- Evidence: with a primary + a secondary selected, header reads "Comparing 2 schools" but only the primary card/chart/table render; the secondary is hidden behind a "Secondary (1)" tab with no explanatory copy.
- Criterion violated: Nielsen #1 (Visibility of system status) & #2 (Match between system and real world).
- Argument: a parent who added two schools sees one and may think the second was dropped; nothing explains the phase split. (Separating KS2 vs GCSE metrics is itself correct — see Works well — only the count/label is misleading.)
- Severity guess: P3
- **F8. Remove is instant with no undo or confirmation**
- Evidence: tapping the × removed a school immediately — no confirm, no toast, no undo; URL updated instantly.
- Criterion violated: Nielsen #3 (User control & freedom — support undo).
- Argument: an accidental tap on the small (28px) × on mobile silently loses a school the parent may have spent a name-search to add; recovery means re-searching. A one-tap "undo" would prevent the loss.
- Severity guess: P3
## Works well — keep
- **Empty state is instructive *and* actionable**: clear "No schools selected" message plus a "+ Add Schools to Compare" button that opens an in-page search modal — the 20% who enter cold on `/compare` can start immediately (answers Journey 2's "no on-ramp" concern *at the compare page itself*).
- **Metric selector carries a plain-English caption** for the chosen metric (e.g. "% meeting expected standard in reading, writing and maths") — jargon defined in place, exactly what school-detail pages omitted.
- **Search accepts name and postcode** (TA1 5AU resolved to the correct school) — the advertised postcode feature works.
- **Efficient multi-add**: the modal stays open and clears the field after each add, so adding several schools needs no reopen.
- **Robust persistence + deep-linking**: selection survives real navigation away and back (localStorage), and comparisons are fully URL-encoded/shareable.
- **Chart accessibility basics done right for sighted users**: legend labels each series by colour **and** full school name (WCAG 1.4.1 satisfied); desktop hover tooltip gives precise per-year values; the table repeats those values as text.
- **Phase separation** (Primary/Secondary tabs) sensibly prevents nonsensical KS2-vs-GCSE metric comparisons.
- **Desktop keyboard + reflow**: visible 2px orange focus outlines on controls; modal opens with focus moved into the search field; Escape dismisses it; all columns visible without overflow; content reflows at 200% zoom with no page-level horizontal scroll (WCAG 1.4.10 pass).
## Axe results
- `/compare` (empty) @ 390×844: **1 violation**`color-contrast` (serious, 2 nodes: active nav tab label, `.btn-primary`).
- `/compare?urns=142161,113105` (populated, 2 schools) @ 390×844: **3 violations**`color-contrast` (serious, 5 nodes: `.btn-primary`, active phase tab, active nav tab), `role-img-alt` (serious, 1 node: `canvas`), `scrollable-region-focusable` (serious, 1 node: `.tableWrapper`).
- Desktop 1440×900 populated: `canvas` `aria-label` still null (role-img-alt persists); `color-contrast` on `.btn-primary`/active tabs persists (viewport-independent).
## Manual WCAG spot checks
- Touch targets ≥24px on interactive elements (WCAG 2.5.8): **pass** — "+ Add School" 139×38, Share 110×40, metric select 324×41, phase tabs ~130×40. Smallest is the **× remove at 28×28** — above the 24px minimum but the tightest target and below the 44px comfortable norm.
- Keyboard: tab order / focus visibility (desktop): **pass with a gap** — logical order and visible 2px orange outlines on controls; modal opens with focus moved into the search box; **fail on focus restoration** — focus drops to `body` after an add and after Escape-close rather than returning to a sensible place (F5).
- Zoom 200% text reflow (desktop, 720×450 proxy): **pass** — no page-level horizontal scroll; only the detailed table retains its contained scroll.
## Screenshots
- `j3-compare-empty-mobile.png`: true empty state (localStorage cleared) — "No schools selected" + "+ Add Schools to Compare".
- `j3-compare-addmodal-mobile.png`: add-school modal with search field.
- `j3-compare-two-schools-mobile.png`: mobile 2-school comparison (cards stack; detailed table shows only first school column).
- `j3-compare-chart-mobile.png`: element capture proving the chart renders with a colour+name legend.
- `j3-compare-two-schools-desktop.png`: desktop 2-school comparison (both columns visible).
- `j3-compare-chart-tooltip-desktop.png`: mouse-hover tooltip with per-school values (3-school legend, colour + label).