From f96c998062e3ce075d93858745b0d5602e727939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 20:46:22 +0200 Subject: [PATCH] docs(planning): R4 design review report for v1.4.27 --- .planning/research/v1427-r4-design.md | 758 ++++++++++++++++++++++++++ 1 file changed, 758 insertions(+) create mode 100644 .planning/research/v1427-r4-design.md diff --git a/.planning/research/v1427-r4-design.md b/.planning/research/v1427-r4-design.md new file mode 100644 index 000000000..7a8daa7b6 --- /dev/null +++ b/.planning/research/v1427-r4-design.md @@ -0,0 +1,758 @@ +# v1.4.27 R4 — Design review report + +**Scope.** Read-only 7-axis design rubric across the v1.4.27 user-facing +surfaces at 375 / 768 / 1024 / 1440 px, plus regressions since v1.4.26. +Methodology: static code inspection of the changed surfaces against +`docs/ui-guidelines.md`, the project's stated 44 px tap-target floor +(WCAG 2.5.5), the Tailwind / shadcn primitives the codebase already +ships, and the v1.4.27 R3d "mobile-fix" decision log. + +**Branch / HEAD.** `develop` at `617d4518`. 428 commits since the +v1.4.26 tag, of which roughly 200 are user-facing R3a/b/c/d work. + +**Axes covered.** Visual hierarchy / contrast / spacing rhythm / +typography scale / responsive behaviour / accessibility (WCAG 2.1 AA) / +motion + feedback. + +**Verdict.** v1.4.27 is the most mobile-honest release of the v1.4 +arc. The `` + `` primitives, the routed +sub-page empty-states, the tap-to-pin compliance heatmap tooltip, the +Coach drawer's layout-level mount, and the new `not-found.tsx` all +land cleanly. Three categories of issue remain: a small set of +**blockers** (Sheet close-X tap target, base form-control heights), +**high-priority** layout / wording bugs (about page missing the +promised TOC, ResponsiveSheet footer slot unused by every page-level +consumer, double `overflow-x-auto` on the tab strip, fade overlay +covering the regenerate icon), and a longer tail of **medium-priority** +polish gaps (focus-ring vocabulary drift, stale tap-target comments, +unused `EmptyState ctaSize="lg"` prop, dashboard tile padding cascade +mismatch). + +--- + +## Severity legend + +| Tier | Meaning | Action | +|------|---------|--------| +| **[Blocker]** | Ships a contract violation (tap-target floor, anchored scroll, AA contrast) | Fix before v1.4.27 release | +| **[High]** | Visible regression vs. v1.4.26 OR unwired feature that the release notes will claim is wired | Fix this release | +| **[Medium]** | Inconsistency or polish gap that survives the release but should be queued for v1.4.28 | Backlog | +| **[Nit]** | Aesthetic preference or stale comment | Sweep when next in file | + +--- + +## Blockers (must fix before release) + +### B1. [Blocker] `` close-X tap target stays at 16 px on the bottom-sheet branch + +**File.** `src/components/ui/sheet.tsx` lines 77-82. + +**Surface.** The bottom-sheet branch of `` (quick-add +sheets on `/`, `/measurements`, `/mood`, `/medications`, the +intake-history and side-effects sheets, and the Coach history / +sources trays on `` is positioned `absolute top-4 right-4` +and carries only `rounded-xs opacity-70` + an ``. +No `min-h-11` / `min-w-11` (or even the 36 px floor the Dialog close-X +got at MB2). The tap area is the SVG-icon's 16 × 16 px box. Every +page-level consumer leaves `showCloseButton` at its `true` default, so +the same 16 × 16 affordance is the user's primary "dismiss this sheet" +path on `` / `` heights stay below the 44 px floor + +**Files.** `src/components/ui/input.tsx` line 70 (`h-10` = 40 px), +`src/components/ui/native-select.tsx` line 33 (`h-9` = 36 px), +`src/components/ui/select.tsx` line 40 (`h-10` default). + +**Surface.** Every form on every page. The two highest-visibility v1.4.27 +landings are the Settings → Account `DateInput` + `` rows +(DOB + language pairing) and the Coach `` composer footer +controls. + +**Problem.** The project's stated floor is 44 px (WCAG 2.5.5, +referenced in `docs/ui-guidelines.md` and quoted inline in every +v1.4.27 R3d MB2 / MB3 / MB7 / CF-52 commit message). Every other tap +target was lifted to 44 px or explicitly waived to 36 px (the Dialog +close-X exception, MB2 Decision I). The shared base primitives stayed +behind — every site that mounts a raw `` or `` is +shipping a 36-40 px control. + +The brief explicitly named "Settings DOB + language pairing" as a +v1.4.27 surface, but the pair is two `h-9` selects (gender, language) +and one `h-10` date input. None of the three is at the floor the rest +of the release advertises. + +**Impact.** Half the form controls in the app sit below the contract. +A v1.4.27 release-notes line about WCAG 2.5.5 conformance would not +survive an audit. + +**Phase.** 4 + 6 (Code Health — the primitives are the right place to +fix this once, not at every call site). + +### B3. About page is missing the promised TOC + +**File.** `src/app/about/page.tsx` (the entire file — no `
` +TOC block). + +**Surface.** `/about` (public, App-Store-reachable). + +**Problem.** The R4 brief lists "`/about` and `/privacy` TOC + safe-area +headers" as a v1.4.27 deliverable. `/privacy` got the collapsible +`
` block at lines 150-240. `/about` +did **not**: its body jumps straight from the heading block to the +"Project" + "Credits" sections with no contents affordance. The +brief's wording ("/about and /privacy TOC") and the MB6 commit +message for `not-found.tsx` both promise feature parity. + +The page is short (two sections), so the cost of the omission is +small — but the brief framed the two pages as a single deliverable +and a reader landing on `/about` from the in-app footer link to +`/privacy` will not see why two sibling pages diverge. + +**Impact.** Inconsistency with the stated v1.4.27 contract; minor App- +Store-reviewer confusion if they navigate between the two pages. + +**Phase.** 3 (Visual Polish) + 7 (Content). + +### B4. `/about` and `/privacy` sticky header swallows anchored sections + +**Files.** `src/app/about/page.tsx` line 45, `src/app/privacy/page.tsx` +line 51, headers at lines 59 / 110 respectively. + +**Surface.** `/about` and `/privacy` on iPhones with a notch / Dynamic +Island. + +**Problem.** Anchored `
` blocks use `scroll-mt-20` (5 rem = +80 px). The sticky header carries `pt-[env(safe-area-inset-top)] py-3` +plus an inner `` at `min-h-11`. On an iPhone 14 Pro +(`safe-area-inset-top ≈ 47 px`), the header is roughly +`47 + 12 + 44 + 12 = 115 px` tall — already 35 px past the 80 px +`scroll-mt-20`. Click "6. Your rights" in the privacy TOC and the +heading lands behind the sticky header. + +**Impact.** A user who taps the TOC entry on iOS reads "We collect…" +instead of "6. Your rights." — the section title is occluded. + +**Phase.** 2 (Responsiveness) + 4 (Accessibility — keyboard users hit +the same problem via in-page #anchor focus). + +--- + +## High-priority issues (should fix before release) + +### H1. `` footer slot is unused by every page-level consumer + +**Files.** `src/components/ui/responsive-sheet.tsx` lines 41-50 + +129-150 (the primitive's contract); consumers at +`src/app/page.tsx` line 523-542, `src/app/measurements/page.tsx` +line 92-111, `src/app/mood/page.tsx` line 51-60, +`src/app/medications/page.tsx` line 261-312. + +**Problem.** The primitive's docstring is unambiguous: "On the Sheet +branch we cap the content at `90 dvh`, scroll the body on overflow, +and **sticky-pin the footer so Save / Cancel stay reachable when the +keyboard pushes the bottom of the sheet up**." Every page-level +consumer renders the form (and the form's own Cancel + Save row, +e.g. `src/components/measurements/measurement-form.tsx` line 454-473) +inside `children` and passes nothing into `footer`. The form's actions +therefore scroll with the body. On iOS Safari with the keyboard up, +the user has no path to Save without dismissing the keyboard first. + +This is the exact bug the primitive was carved out of `` to +fix; the carve-out shipped but no consumer wired the footer slot. + +**Impact.** v1.4.27's most-touted mobile improvement (``) +silently regresses to v1.4.26 behaviour for the quick-add and intake +flows. + +**Phase.** 1 (Interaction) + 2 (Responsiveness). + +### H2. Tab strip fade overlay covers the regenerate icon + +**File.** `src/components/insights/insights-tab-strip.tsx` lines 193-198. + +**Surface.** `/insights` mother page on ``. The button lives at flex-end of the same parent; +the absolute overlay at `right-0` overlaps the rightmost ~24 px of the +44 × 44 px circle. `pointer-events-none` keeps the button clickable, +but the icon is now visibly washed against a gradient on `` sets `overflow-x-auto`. The inner pill- +wrapping `
` also sets `overflow-x-auto`. The result is two +horizontal scroll contexts nested inside each other — one of them +will lose mouse-wheel / trackpad lateral scroll, and the inner context +captures pointer events the outer one was supposed to handle. +Tailwind's `[scrollbar-width:none]` paint-suppressors are applied to +both, so visually the bug is invisible; behaviourally one of them is +dead code or fighting the other. + +**Phase.** 6 (Code Health). + +### H4. Coach drawer header carries a stale "size-9 / 36 × 36 px" comment + +**File.** `src/components/insights/coach-panel/coach-drawer.tsx` +lines 384-389. + +**Problem.** The header-action-cluster comment reads: "All three buttons +share the same `ghost / size-icon / size-9` shape … 36 × 36 px hit +target meets the WCAG 2.1 AA touch-target minimum on mobile." Every +button below uses `className="size-11"` (44 × 44 px) and `