diff --git a/.planning/research/v1427-r3c-mobile-medications.md b/.planning/research/v1427-r3c-mobile-medications.md new file mode 100644 index 000000000..28744c728 --- /dev/null +++ b/.planning/research/v1427-r3c-mobile-medications.md @@ -0,0 +1,189 @@ +--- +file: .planning/research/v1427-r3c-mobile-medications.md +purpose: Mobile capability audit — medications surface (list, form, schedule editor, drug-level chart, therapy timeline) +created: 2026-05-15 +auditor: MA5 +--- + +# Mobile audit — medications surface + +## Summary + +Reviewed 12 components across `/medications` and `/medications/[id]/history` plus the medication form, schedule editor, DrugLevelChart standalone, three GLP-1 detail sections (Scheduling, Titration, SideEffects), Inventory disclosure, intake history (table + mobile cards), and three modal dialogs (IntakeImport, ApiEndpoint, PhaseConfig). Found **18 issues**: 3 Critical, 6 High, 6 Medium, 3 Low. Headline pattern: the GLP-1 surface (well-iterated since v1.4.25 W4d) is mobile-aware (44 pt tap targets, mobile cards, `touch-pan-y`), but the schedule editor's day grid breaks at 320 px, three dialogs ignore the global `max-w-[calc(100%-2rem)]` rule, the DrugLevelChart standalone has a broken YAxis label, and several action rows (intake table, side-effect delete, inventory pen actions) ship 28-32 pt icon buttons that fall below the WCAG 2.5.5 floor. No bottom-sheet primitive is used anywhere on the surface — every entry point is a centred dialog. + +## Findings + +### F1 — Schedule day-of-week row overflows / squishes at 320 px +- Severity: Critical +- Axis: visual +- File: `src/components/medications/medication-form.tsx:869-903` +- Symptom: The advanced schedule editor renders the daily-toggle button (`min-w-24` = 96 px) plus seven weekday buttons (`flex-1`) in one `flex w-full gap-1` row. At 320 px viewport minus dialog padding (≈ 288 px usable), the seven weekday buttons each get ≈ 27 px width and the labels (`Mo`/`Di`/…) crowd against each other; on iOS Safari with translated labels (`Mo`, `Di`, `Mi`, `Do`, `Fr`, `Sa`, `So`) the row is borderline; with the umlauts on `Mär`/`Mai`-style labels in other contexts the row already wraps badly. +- Evidence: 96 px (Daily) + 7 × 27 px + 6 × 4 px (gap) ≈ 288 px. The weekday buttons are min-h-11 (44 pt) tall but only ~27 px wide — fails the WCAG 2.5.5 width axis. +- Recommended fix: Switch to `grid grid-cols-7` for the weekday buttons on mobile, with the "Daily" button on its own row above (`grid grid-cols-1 sm:grid-cols-[6rem_1fr]`). Match the existing `intervalWeeks` grid pattern at line 845. +- Effort: S + +### F2 — GLP-1 weekly preset day row has the same squish at 320 px +- Severity: High +- Axis: visual +- File: `src/components/medications/medication-form.tsx:653-674` +- Symptom: The weekly-cadence preset renders seven weekday buttons in a `flex w-full gap-1` row inside the form dialog. Same arithmetic as F1 — each button is ≈ 36 px (no leading "Daily" button) but the column still gets a 16 px left+right inner padding from the wrapping `p-3.5` card. Visually OK on 375 px+ but cramped at 320 px. +- Evidence: Tailwind `gap-1` = 4 px. 7 buttons × 36 px + 6 × 4 px = 276 px, fits, but with iOS Safari's safe-area insets removed the column gets ~256 px leaving ~32 px per button. +- Recommended fix: Same pattern as F1 — `grid grid-cols-7 gap-1`. Drop `flex w-full` so the buttons size evenly under the grid math. +- Effort: S + +### F3 — DrugLevelChart YAxis label rendered in `width={1}` column — never visible +- Severity: High +- Axis: code +- File: `src/components/medications/DrugLevelChart.tsx:549-563` +- Symptom: The YAxis is configured `width={1}` (so the chart area has no left gutter), but the same axis sets `label={{ value: axisLabel, position: "insideLeft", angle: -90 }}`. Recharts paints the label inside the 1-px wide column; the SVG label is effectively clipped and invisible. The axis caption above the chart body (line 485-490) is what users actually read; the SVG label is dead code that bloats the SSR snapshot. +- Evidence: Recharts `width` on YAxis governs the gutter the label paints into. `width={1}` collapses that gutter. The `axis-caption` `

` above the chart is the working label. +- Recommended fix: Drop the `label={…}` prop from `` entirely; keep the visible `

` only. Same applies to the `` child of `` (line 535-544) — empty `` element with no `value` / `children`, also dead code. +- Effort: S + +### F4 — Three dialogs lack the standard `sm:max-w-md` cap and rely on the v4 default +- Severity: High +- Axis: visual +- File: `src/components/medications/SideEffectsSection.tsx:334`, `src/components/medications/intake-history-list.tsx:535,678`, `src/components/medications/inventory-section.tsx:434`, `src/app/medications/page.tsx:261,423,667` +- Symptom: Most medication dialogs set `sm:max-w-md` or `sm:max-w-lg` — good. Two do not: the intake create/edit `` (lines 535 & 678) ships with the default `sm:max-w-lg` from `dialog.tsx:64`. The InventorySection add-pen dialog (line 434) likewise. On large desktop these become unnecessarily wide for the 3-field form; on narrow mobile the global `max-w-[calc(100%-2rem)]` saves them. Consistency-wise the medications surface is split. +- Evidence: Grep across medication dialogs — only some carry `className="sm:max-w-md"`. +- Recommended fix: Add `className="sm:max-w-md"` (or `sm:max-w-lg` where the schedule grid earns the extra width) to every medication `` for consistency. Document the rule in a comment on `dialog.tsx`. +- Effort: S + +### F5 — Intake history "Mobile cards" use 32-pt edit/delete tap targets +- Severity: High +- Axis: visual +- File: `src/components/medications/intake-history-list.tsx:482-497, 433-449, 805-812` +- Symptom: The mobile-card view (`md:hidden` block at line 458) shows edit + delete icon buttons at `h-8 w-8` (32 px). Below the WCAG 2.5.5 44 px minimum. The desktop-only table uses the same 32 px sizes — acceptable for mouse, but the `` is shared between the desktop table cell and the mobile card row, so the mobile path inherits the wrong size. +- Evidence: `medication-card.tsx` raised the analogous icon buttons to `min-h-11 min-w-11` in v1.4.15 phase-A5 (see comment line 396-399). The intake history surface skipped the same fix. +- Recommended fix: Pass a `size` prop into `` or split the desktop/mobile renderers so the mobile card uses `min-h-11 min-w-11`. The edit button on the mobile card path (line 486) similarly needs `min-h-11 min-w-11`. +- Effort: S + +### F6 — SideEffects delete button is 28 pt (h-7 w-7) +- Severity: High +- Axis: visual +- File: `src/components/medications/SideEffectsSection.tsx:308-326` +- Symptom: The per-entry delete button uses `h-7 w-7` (28 pt). Below WCAG 2.5.5. The aria-label is present, but the touch target is dangerous next to long notes — a thumb hit on the entry body is one column away. +- Evidence: 28 px < 44 px floor. The new W19d UI shipped this in v1.4.25 W19d but does not match the v1.4.15-era 44 pt rule applied elsewhere. +- Recommended fix: Bump to `min-h-11 min-w-11`; keep the inner icon at `h-3.5 w-3.5` for visual density. +- Effort: S + +### F7 — Inventory section per-pen action buttons are 28 pt +- Severity: High +- Axis: visual +- File: `src/components/medications/inventory-section.tsx:332-362, 405-414` +- Symptom: "Mark as in use" / "Mark as used up" buttons are `h-7 px-2 text-[10px]` (28 pt tall). The 10 px font is also below the 11 px minimum the rest of the surface targets. The trash button on the "past pens" sub-list is `h-6 w-6` (24 pt) — even smaller, below the WCAG 2.5.8 24 px floor. +- Evidence: Lines 332-362 (live items) + 405-414 (past pens delete). +- Recommended fix: Live actions → `min-h-11 px-2.5 text-xs`. Past-pen delete → `min-h-11 min-w-11` icon button (the destructive ghost variant already exists on `medication-card.tsx`). +- Effort: S + +### F8 — Schedule input height-pinning fights system font size on iOS +- Severity: Medium +- Axis: code +- File: `src/components/medications/medication-form.tsx:781, 799, 813, 825` +- Symptom: Each per-schedule input ships `className="h-11 text-xs md:text-xs"`. iOS Safari triggers an automatic zoom when an `` font-size is < 16 px on focus. The schedule inputs are 12 px (`text-xs`), so tapping any field on iOS Safari triggers a zoom-and-scroll that is jarring inside the modal. +- Evidence: WebKit policy: any focusable text input below 16 px font-size triggers a viewport zoom. Same applies to the doseAmount input on the form (line 610 — uses default `Input` which is `text-sm` = 14 px → also zooms). +- Recommended fix: Use `text-base` (16 px) on focusable inputs inside dialogs, or apply `text-sm @sm:text-xs` (the v4 container-query pattern). The codebase already uses `text-base` on inputs elsewhere — bring the schedule inputs in line. +- Effort: M + +### F9 — IntakeImport dialog lacks `max-h-[90vh] overflow-y-auto` +- Severity: Medium +- Axis: visual +- File: `src/app/medications/page.tsx:423` +- Symptom: The create/edit medication dialog (line 261) caps height at `max-h-[90vh] overflow-y-auto`. The IntakeImport (line 423) and ApiEndpoint (line 667) dialogs do not. On 320 × 568 mobile viewports (iPhone SE landscape, smallest active iOS class), the import dialog's textarea (`rows={8}`) + JSON sample block overflows behind the on-screen keyboard with no scroll. +- Evidence: `` — no max-h. Radix Dialog does not cap height by default. +- Recommended fix: Apply the same `className="max-h-[90vh] overflow-y-auto sm:max-w-lg"` to both dialogs. Better: lift the cap into the shared `` defaults via a Tailwind plugin or document the rule. +- Effort: S + +### F10 — Therapy timeline (Titration step list) wraps cells with 7-rem `min-w-` +- Severity: Medium +- Axis: visual +- File: `src/components/medications/TitrationSection.tsx:152` +- Symptom: Each ladder step is `flex flex-1 flex-col rounded-md border px-2.5 py-2 sm:min-w-[7rem]`. The `sm:` prefix means the `min-w-[7rem]` only applies at `sm` and above — so at 320-414 px the cells use only `flex-1` and stack vertically (`flex-col gap-2 sm:flex-row sm:flex-wrap`). That works. **But** on 768 px (iPad portrait inner tablet column) the steps wrap into rows of 3 because 7 rem × 3 + gaps = ~22 rem, then the 4th step wraps below alone, breaking the "horizontal ladder" reading. Also: at the 414-px to 640-px range the chevron continuation between steps is implicit (no visual connector) — readers see disconnected cards. +- Evidence: `flex flex-1 … sm:min-w-[7rem]` — the breakpoint cliff is at `sm` (640 px). Between 640 and 768 the row wraps awkwardly. +- Recommended fix: Either (a) keep `flex-col` until `md` (768) so iPad-portrait gets the vertical ladder same as mobile, or (b) replace the implicit "ladder" with a real visual connector (a left-side rail with step dots) that scales gracefully. Option (a) is the lower-effort patch. +- Effort: M + +### F11 — SchedulingSection cadence grid wraps cells with 44×44 tap targets, but cells crowd at 320 px +- Severity: Medium +- Axis: visual +- File: `src/components/medications/SchedulingSection.tsx:194-226` +- Symptom: The 30-day cadence timeline renders `