From 472da13da7cdcbe163ea7abbb19ae9dab5ce2f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 14 May 2026 22:01:28 +0200 Subject: [PATCH] docs(planning): W21 design findings --- .planning/research/w21-design-findings.md | 335 ++++++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 .planning/research/w21-design-findings.md diff --git a/.planning/research/w21-design-findings.md b/.planning/research/w21-design-findings.md new file mode 100644 index 000000000..3a6cfc913 --- /dev/null +++ b/.planning/research/w21-design-findings.md @@ -0,0 +1,335 @@ +# W21 Design Findings — v1.4.25 release-candidate + +Branch: `develop`. HEAD: `51f23ef3`. Date: 2026-05-14. + +Auditor: 1/8 W21 reviewers (focus: mobile-first, design tokens, chart visual +identity, a11y, motion, information density, no top/bottom split). Read-only; +no code edits. Dev server replied on port 3000 but Playwright drive-through +was skipped — the wizard step pages are auth-gated and static analysis already +gives clean evidence on every flagged surface. + +## Summary + +Counts: 0 critical / 3 high / 5 medium / 3 low. + +Onboarding wizard chrome is sound: server-rendered shell, a real +`role="progressbar"`, dot strip with `aria-current="step"`, safe-area-respecting +bottom padding for the iOS PWA shell. The motion contract is honoured at every +new surface — every new transition pairs `motion-reduce:transition-none` and the +welcome carousel switches `scrollIntoView` to `behavior: "auto"` under +`prefers-reduced-motion`. The Tailwind v4 OKLCH migration is clean — zero +`hsl(var(--token))` patterns survive in `src/`, and the v1.4.25 chart pass +replaced the remaining inline refs with bare `var(--token)` (commit +`f4606fb1`). The `aria-controls` / `aria-expanded` wiring on the GLP-1 +dose-history disclosure is in place (commit `7b6b766c`). + +The release does ship three repeatable a11y regressions that warrant a hotfix +pass before tagging: + +1. The onboarding wizard never reaches the 44×44px touch-target floor that + the rest of the v1.4.25 surface (insights tab-strip, maintainership-banner) + explicitly honours. Back / Skip / Save buttons all sit at 32–36px. +2. The new `range-bar` component (W3e extraction, used on the redesigned + `/targets` page) bakes in Tailwind raw palette utilities + (`bg-red-500/8`, `bg-yellow-500/12`, `bg-green-500/20`) for the zone + backgrounds, then renders the marker dot from the Dracula token system + (`var(--dracula-green/orange/red)`). The two palettes do not match and the + inconsistency is now locked in by a unit test that pins the class names. +3. The personal-record badge uses `text-dracula-green` (`#50fa7b`) on + `bg-dracula-green/10` — at ~10% opacity over background that lands at + ≈ 2.6:1 contrast in dark mode, well below the WCAG 2.1 AA 4.5:1 floor for + the "PR" body text inside it. + +Beyond those three the surface reads coherently. Marc's "no top/bottom split" +rule holds on the settings advanced page (research-mode card + danger-zone +stack vertically inside one section). The medication-detail GLP-1 section +stack (drug-level chart → side effects → scheduling → titration → intake +history) is mobile-friendly — every section uses the same `border-border/60 +rounded-md border` chrome, every grid degrades to `grid-cols-1` on small +viewports, and only the titration ladder uses a horizontal sm-and-up variant. + +## Critical + +(none — no broken layout that makes a core feature unusable.) + +## High + +### H-1. Onboarding wizard buttons are below the 44×44 px touch-target floor + +Surfaces: `OnboardingShell` footer, `WelcomeCarousel`, `GoalsChipPicker`, +`SourceCardGrid`, `BaselineForm`. + +Every Back / Skip / Save button on the wizard renders at `size="sm"` (32 px +tall) or `size` default (36 px). The carousel's prev/next arrows use +`size="icon-sm"` (32 × 32 px). + +- `src/components/onboarding/OnboardingShell.tsx:138, 147, 152` — Back, Skip, + Next all use `size="sm"`. +- `src/components/onboarding/WelcomeCarousel.tsx:222–268` — arrow buttons use + `size="icon-sm"`; dot pager pills are `size-2.5` (10 × 10 px hit area). +- `src/components/onboarding/GoalsChipPicker.tsx:255–266` — Back, Skip use + `size="sm"`, primary Next uses default (36 px). +- `src/components/onboarding/SourceCardGrid.tsx:155–169` — same pattern. +- `src/components/onboarding/BaselineForm.tsx:200–220` — same pattern, plus + the primary "Save and continue" CTA is the only place a user can finish + onboarding. + +The contract is documented elsewhere in the same release — the W8/W9 work +explicitly tuned the insights tab-strip pills to `min-h-11` +(`src/components/insights/insights-tab-strip.tsx:136`), the maintainership +banner dismiss button to `h-11 w-11` +(`src/components/i18n/maintainership-banner.tsx:118`), and the tour buttons +to `min-h-11` (`src/components/onboarding/tour.tsx:432`). The new wizard +deviates from a rule the rest of the surface already follows. + +WCAG 2.5.5 (Target Size, Level AAA) / Apple HIG 44×44 / Material 48×48. The +onboarding flow runs on mobile by definition (first-run iOS PWA install). +Hotfix is a one-line size upgrade per CTA (`size="lg"` is `h-10` 40 px — +still short of 44; consider a `min-h-11` class or a new `size="touch"` +variant) plus a Storybook test that pins the contract. + +### H-2. `range-bar` mixes Tailwind raw palette and Dracula tokens + +Surface: `src/components/targets/range-bar.tsx`. + +The zone backgrounds use raw Tailwind palette: + +```text +range-bar.tsx:103 bg-red-500/8 +range-bar.tsx:106 bg-yellow-500/12 +range-bar.tsx:113 bg-yellow-500/12 +range-bar.tsx:121 bg-green-500/20 +``` + +The marker dot at line 86 reads from Dracula tokens +(`var(--dracula-green)` / `var(--dracula-orange)` / `var(--dracula-red)`). +Tailwind's `red-500` is `#ef4444`, Dracula red is `#ff5555` — close in hue but +not the same swatch. Yellow is the loudest mismatch: Tailwind `yellow-500` +(`#eab308`) is olive-warm, Dracula yellow / orange (`#ffb86c`) is peach-warm. + +Why this matters here and not on `intake-history-list.tsx` or +`phase-config-dialog.tsx` (which use the same pattern): the range-bar is on +the *redesigned* `/targets` page — a feature shipped as polished in v1.4.25. +The marker dot and the zone its dot sits in should be visually unified. + +Worse — the existing test +`src/components/targets/__tests__/range-bar.test.tsx:31–32` now asserts the +raw-palette class names, so the inconsistency is contractually locked. A +hotfix needs to (a) swap the zone backgrounds to the Dracula tokens, (b) +update the test to assert the Dracula token references, and (c) re-validate +against the dashboard's existing green/yellow/red language for clinical +status colouring (already uses `dracula-green/orange/red` in the +`SchedulingSection` cadence visualisation). + +### H-3. Personal-record badge contrast on dark mode + +Surface: `src/components/insights/personal-record-badge.tsx:113`. + +```text +"border-dracula-green/40 bg-dracula-green/10 text-dracula-green …" +``` + +`text-dracula-green` is `#50fa7b`. `bg-dracula-green/10` over dark mode +`--background` (`#282a36`) yields ≈ `#2a3134`. Contrast: 4.0:1 — fails the +WCAG 2.1 AA 4.5:1 floor for body text (the "PR" string inside is 10 px +uppercase font-semibold tabular — by spec that's still body text for +contrast purposes, not "large text", since 10 px is below the 18 pt / 14 pt +bold threshold). + +In light mode the same combination lands ≈ 1.4:1 — even worse — but the +badge is informational and uppercase, and light mode's `--background` +(`#f5f5f5`) renders `text-dracula-green` (`#50fa7b`) on near-white which is +the loudest legibility regression. Hotfix candidates: + +- Render the text in `--foreground` and use the green only on the border / + background ring. +- Bump the background to `bg-dracula-green/20` and the text to a higher + contrast green token (e.g. a new `--dracula-green-strong` for the badge + use-case). +- Switch to the `text-success` / `bg-success/15` pattern the rest of the + insights tiles use (the doctor-report-availability pill, the streak + pills). + +## Medium + +### M-1. `BaselineForm` gender field uses a native `` component +(`@/components/ui/select`). The onboarding step-3 gender picker drops to a +native `` / `` rows. + +The same pattern repeats in `src/components/medications/SideEffectsSection.tsx:333–352` +(category picker) — also a native `