Commit Graph
1160 Commits
Author SHA1 Message Date
Marc-André Bombeck ab4529fcb3 chore(settings,admin): consolidate Withings credentials, SettingsToggle stacking, and feedback tabs
v1.4.27 MB7 — four settings/admin polish items in one commit:

- CF-53: the Withings credentials grid drops from a 3-column row
  (client-id / secret / save) to a 2-column row of inputs with the
  Save button lifted into its own row below. The previous invisible-
  label hack collided on Galaxy Fold; the new row places the Save
  button at `justify-end` on every viewport and fills the column
  on `<sm`.
- CF-55: the feedback-inbox TabsList wraps in an `overflow-x-auto`
  strip so the 4 status pills + count badges scroll horizontally on
  Pixel 5 instead of squashing below the legibility floor.
- CF-56: the shared `SettingsToggle` row stacks vertically on `<sm`
  so the German description copy doesn't compete with the switch for
  horizontal space. The inline duplicate in
  `general-settings-section.tsx` (default-language row) picks up
  the same contract.
- CF-57: the integrations action row floors each button to a
  `min-w-[10rem]` on `<sm` so the four AlertDialog + test-connection
  triggers stack two per row at most and fill their column cleanly.
2026-05-15 19:19:14 +02:00
Marc-André Bombeck e1451f840f chore(medications): tighten titration ladder, cadence grid, and phase-config layout on mobile
v1.4.27 MB7 — four medications-surface polish items in one commit:

- CF-48: the titration ladder keeps its vertical (`flex-col`) layout
  until `md:` instead of breaking horizontal at `sm:`. The German
  dose + typical-weeks labels wrapped to 3 lines per step on Pixel 5,
  which read denser than the vertical layout below.
- CF-49: the 30-day cadence grid tightens from `gap-1` to `gap-0.5`
  on `<sm` so more cells fit per row without sacrificing the 44 px
  tap target per cell. The original 4 px gap returns at `sm:`.
- CF-50: the PhaseConfig dialog rows stack on `<sm` so the input +
  mode toggle + suffix caption fit inside the 280-300 px dialog
  inner width on Galaxy Fold. The suffix wraps below at `<sm` via
  `basis-full`; `sm:` keeps the single-line layout.
- CF-66: the DrugLevelChart wrapper drops the `md:p-6` lift so its
  padding matches the surrounding trend cards (`p-4` everywhere).
2026-05-15 19:16:45 +02:00
Marc-André Bombeck 7bfd5beeb2 chore(dashboard): horizontal-scroll the tile strip on narrow viewports and wrap the onboarding header
v1.4.27 MB7 / CF-42 + CF-44 — the dashboard tile strip switches to a
`flex overflow-x-auto` row at `<sm` so the tiles scroll horizontally
instead of wrapping to 3-4 rows on Pixel 5 / Galaxy Fold. From `sm:`
upwards the strip falls back to the canonical grid `auto-fit +
minmax(9rem, 1fr)` layout the maintainer pinned in v1.4.4. The
getting-started checklist header wraps onto a second row when the
title + dismiss button overflow, and `min-w-0` + `truncate` keep
the title from pushing the chevron off-screen on Galaxy Fold.
2026-05-15 19:15:20 +02:00
Marc-André Bombeck 071f26bf0f chore(insights): polish VO2 strip, status show-more, and trend-card secondary row
v1.4.27 MB7 — three related insights surface polish items in one
commit:

- CF-38: the VO2 stat strip extends the 2-column phone grid all the
  way to `<lg` so tablets keep the 2×2 layout, and every chip floors
  at `min-h-[64px]` so the row paints at a consistent height even
  when one stat has no trend arrow.
- CF-39: the insight-status-card body collapses to `line-clamp-3`
  with a "Show more" toggle when the assessment text is long
  enough to actually clip. Short bodies render plain so the user
  never sees a useless toggle.
- CF-41 + CF-67: the trend-card mobile-secondary row retires and the
  all-time aggregate renders inline alongside the `7d` / `30d`
  pair on every viewport. The flex-wrap already handles the overflow
  on narrow tiles, and the standalone comparison-delta block drops
  its `hidden sm:block` gate so the callout reads on every screen.
2026-05-15 19:14:08 +02:00
Marc-André Bombeck 53c5163901 chore(charts): apply responsive aspect-ratio and height var across the chart surface
v1.4.27 MB7 / CF-37 + CF-43 — the scatter correlation chart now uses
an `aspect-square` ratio at `<sm` so the plot area stays a centred
square on phones (matches Apple Health's correlation viz); from `sm:`
upwards a 3:2 ratio recovers the legacy ~250 px shape. The three
HealthChart-family components (`HealthChart`, `MoodChart`,
`MedicationComplianceChart`) drop the hard-coded `h-[240px]` floor
for a `var(--chart-height, 240px)` token plus a `md:` lift to 280 px
so taller viewports get a more legible chart without tweaking each
consumer.
2026-05-15 19:11:39 +02:00
Marc-André Bombeck f8b407da56 docs(planning): R3d MB6 regression and auth bucket report 2026-05-15 19:10:54 +02:00
Marc-André Bombeck 07c9d01faf feat(ui): extract a shared NativeSelect primitive and migrate three call sites
v1.4.27 MB7 / CF-52 — three settings/admin surfaces each declared a
local `NATIVE_SELECT_CLASS` constant tracing the same visual contract
as the shadcn `<SelectTrigger>`. The copies had drifted slightly so
`AccountSection`, `TimezonePicker`, and `GeneralSettingsSection`
rendered at subtly different focus-ring weights. The new
`<NativeSelect>` primitive owns the contract; all three call sites
import it and the in-file constants retire. The native picker remains
the right experience for the 400-entry timezone list and the
admin-only locale dropdowns, so the primitive deliberately stays on a
styled `<select>` rather than wrapping the radix popover-driven
`<Select>`.
2026-05-15 19:10:13 +02:00
Marc-André Bombeck e52b64bf34 fix(insights): point the empty-state CTAs at the existing measurements route
The B4 insights empty-state CTAs (blutdruck, gewicht, puls, bmi)
linked to `/measurements/new`, a route that does not exist; a tap
landed on a Next.js 404. Re-route via `/measurements?add=<TYPE>`,
the parameter the measurements page now consumes. Each CTA picks a
sensible default: BLOOD_PRESSURE for the BP page, WEIGHT for the
weight and BMI pages, PULSE for the pulse page. The mood, sleep,
and medication empty-states already pointed at their own dedicated
routes and need no change.

Add a `?add=<TYPE>` consumer to the measurements page. The consumer
opens the add-measurement dialog with the right `defaultType` on
first render, then `router.replace` strips the param so the back
button returns to a clean `/measurements`. The allow-list mirrors
MEASUREMENT_TYPES inside `measurement-form.tsx` so a stale link
cannot trap the user on an unknown form. The render-driven set-state
pattern follows account-section.tsx and satisfies the
`react-hooks/set-state-in-effect` rule.
2026-05-15 19:09:34 +02:00
Marc-André Bombeck 7ad894b535 docs(planning): R3d MB4 coach reachability bucket report 2026-05-15 19:08:48 +02:00
Marc-André Bombeck 0fb0235d17 fix(admin): move pagination and CSV export out of the table scroll containers on login-overview and app-log-preview
CF-21: the audit-log prev/next controls and summary row used to live
inside the same `overflow-x-auto` wrapper as the table. On narrow
viewports admins had to scroll the table back to its starting offset
before the pagination became visible. Same problem for the app-log
preview summary. Both rows now sit in a sibling block under the
scroll container, while the table itself keeps panning horizontally
on touch. The login-overview CSV export was already outside the
wrapper and stays put. The recent B3 carrier-chip layout is
unchanged.
2026-05-15 19:08:40 +02:00
Marc-André Bombeck 2bd659f66d feat(ui): EmptyState gains a ctaSize prop and lifts to a full-width tap target on mobile
v1.4.27 MB7 / CF-36 — the empty-state CTA used to render at the
button primitive's default density, which on Pixel 5 left a ~32 px
tap target inside a ~280 px column. The new `ctaSize="lg"` lifts
the inner button (or `asChild` link) to the 44 px floor and stretches
the wrapper to `w-full sm:w-auto` so the CTA fills the column on
mobile and centres on tablet+ viewports. Existing call sites that
omit the prop keep the previous compact density.
2026-05-15 19:07:54 +02:00
Marc-André Bombeck 650d4f8e45 feat(coach): re-pin the message-thread scroll when the soft keyboard opens
Subscribe to window.visualViewport.resize so the last bubble stays
visible when the phone's soft keyboard slides in. Without the listener
the scroller's scrollHeight reflects the layout viewport rather than
the visible region, leaving the tail of the thread hidden behind the
keyboard. The handler only re-pins when the user was already at the
bottom, matching the existing auto-scroll contract.
2026-05-15 19:07:42 +02:00
Marc-André Bombeck 40916d3127 feat(coach): swap the sources-rail toggle to a shadcn Checkbox
Replace the raw <input type="checkbox"> in the sources rail with a
shadcn-style <Checkbox> backed by radix-ui. The new primitive ships
a real focus ring, a Space-toggles keyboard contract, and a touch-
friendly hit target so the rail behaves correctly on the right-edge
side-tray surface mobile users open via the chevron button.
2026-05-15 19:07:07 +02:00
Marc-André Bombeck 2fb964ef9d feat(insights): gate the sub-page-shell focus-on-mount
v1.4.27 MB7 / CF-35 — the routed insights sub-pages used to focus the
heading on mount so a screen-reader landing announced the page title.
On mobile that fought the soft-keyboard: navigating between sub-pages
while a form was open dismissed the keyboard mid-typing. The
`focusOnMount` prop defaults to false, so only sub-pages that
explicitly want the screen-reader landing opt in. The `scrollTo({ top: 0 })`
reset stays unconditional because that's a sighted-user affordance,
not an a11y one.
2026-05-15 19:06:10 +02:00
Marc-André Bombeck f9558ce097 feat(insights): widen the Health Score card on tablets and rebalance the hero split
v1.4.27 MB7 / CF-34 — the score card swaps the fixed
`lg:w-[360px] xl:w-[400px]` width for a basis-based column so the hero
strip can split at `md+` and let the score occupy a generous-but-fluid
share of the band. The hero strip's `flex-row` flips activate one
breakpoint earlier (`md:`, alongside the legacy `lg:` modifiers) so
tablets receive the side-by-side layout instead of stacking the score
below the narrative copy.
2026-05-15 19:02:50 +02:00
Marc-André Bombeck 79dbdfbd2b feat(coach): hide the drawer window pill on phone viewports and pin title truncation
The bottom-sheet header already carries the avatar, title, new-chat,
settings, and close buttons on <sm viewports — adding the four-state
window pill there shrunk the title to a single character before
truncation. Wrap the pill in a hidden sm:block container so phone
users reach the same override via the sources-rail picker. Pin
min-w-0 on the SheetTitle so very long conversation titles always
clip cleanly inside the header.
2026-05-15 19:02:35 +02:00
Marc-André Bombeck 117eb87f75 fix(medications): widen the schedule day-of-week grid for narrow viewports and drop the DrugLevelChart dead axis labels
The medication-form schedule editor's day-of-week row packed the
wide "Daily" pill (min-w-24) into the same single-flex row as the
seven weekday pills; at 320 px the row overflowed and the last
weekday clipped off-screen. Stack the Daily pill above a fixed
seven-column grid so every weekday keeps the 44 px tap-target floor
regardless of container width.

Drop two dead axis decorations from the GLP-1 DrugLevelChart: an
empty `<text>` child beneath the XAxis that rendered an invisible
SVG node, and a duplicate Recharts `label={…}` prop on the YAxis
that painted the unit-less caption inside a 1 px-wide axis where it
could never be read. The external `<p>` above the chart remains the
single source of truth for the caption.
2026-05-15 19:02:07 +02:00
Marc-André Bombeck 9b6dbbf6ae feat(charts): pin the compliance heatmap tooltip on tap and float the cell
v1.4.27 MB7 / CF-10 — touch-pin tooltip via pointer events runs in
parallel with the existing mouse hover, so a tap on a cell holds the
day-level breakdown open until the user taps outside or on a different
cell. Pen + mouse keep the hover affordance unchanged. The heatmap now
clamps the stretch-branch cell size to a 14 px floor and wraps the
non-stretch render in overflow-x-auto so narrow viewports horizontal
scroll instead of compressing the calendar below the touch target.
2026-05-15 19:00:54 +02:00
Marc-André Bombeck 246c1def21 feat(coach): mount the drawer at the insights layout with a launch context
Promote the Coach drawer mount from the mother /insights page up to
/insights/layout.tsx so every routed sub-page can reach it. A new
CoachLaunchProvider exposes useCoachLaunch() for the hero strip,
suggested-prompt chips, and the seven sub-pages; the layout renders
the drawer beside the provider so navigation across Insights surfaces
no longer unmounts the conversation.

Each sub-page (blutdruck, gewicht, puls, stimmung, medikamente, bmi,
schlaf) mounts a new CoachLaunchButton that pins to the bottom on
narrow viewports and renders as an inline header action on lg+.
2026-05-15 18:59:55 +02:00
Marc-André Bombeck de0c16338f fix(auth): lift the register submit button and add a branded not-found page
Promote the register page's submit button to the 44 px tap-target
floor (size="lg" + min-h-11 + w-full) so the primary action stays
finger-tap reachable on a narrow viewport.

Add a new branded not-found.tsx page. The previous implicit fallback
was Next.js's bare framework text; this introduces a lighter wrapper
than ErrorDetails (404 is not an application error) — Logo, headline,
explanatory paragraph, and a single "Back to dashboard" link. The
wrapper carries min-h-dvh and safe-area-inset-top so the panel
anchors centred under iOS Safari's animated URL bar and stays clear
of the notch.

Apply the same dvh + safe-area pattern on the root-layout global
error boundary and lift its inline button heights to 44 px. Wrap the
ErrorDetails panel in a min-h-dvh flex column so server-error pages
also centre instead of hugging the notch.
2026-05-15 18:58:35 +02:00
Marc-André Bombeck fc8a58557a fix(auth): unblock the about page for unauthenticated visitors
The /about page (GeoLite2 attribution) was listed in proxy.ts
PUBLIC_PATHS but missed in AuthShell's client-side PUBLIC_PATHS, so
unauthenticated visitors bounced through /auth/login. Add /about to
the AuthShell list and treat it as a standalone public page (same
edge-to-edge render as /privacy).

Apply safe-area-inset-top on the privacy and about sticky headers so
iOS notch / Dynamic Island viewports don't overlap the brand row. Add
a default-closed Contents <details> TOC at the top of the privacy
policy and wrap every HealthKit identifier <code> with break-all so
long camelCase strings (heartRateVariabilitySDNN,
environmentalAudioExposure) wrap inside narrow viewports.
2026-05-15 18:56:49 +02:00
Marc-André Bombeck c77d5252a6 feat(settings): pair the API section tables with a mobile card-list rendering
CF-19 follow-up: the endpoint catalogue, active-token table, and
revoked-token table on `/settings/api` each gain a `md:hidden`
card-list partner so phones never have to horizontal-scroll the
tabular layout. Desktop tables stay verbatim behind `hidden md:block`.
The active-token cards expose a full-width revoke button that clears
the 44pt tap-target floor.
2026-05-15 18:56:08 +02:00
Marc-André Bombeck 79c84097ad docs(planning): R3d MB2 tap-target bucket report 2026-05-15 18:51:12 +02:00
Marc-André Bombeck 4464d2c9b9 chore(charts,errors): apply the tap-target floor across charts and error chrome
Final surface sweep for the v1.4.27 tap-target work: the chart band
overlay, the error-details action row, and the public-page header
links.

- `health-chart.tsx`: band overlay used to inset `right: 18px` while
  the underlying ComposedChart margin is `right: 8`, so the colour
  band drifted left of the plotted line by 10 px on every viewport.
  Pin the overlay to the chart's actual right edge.
- `error-details.tsx`: drop `size="sm"` from the retry, copy, and
  report-issue buttons so they sit at the default 40 px primitive
  height — clears the WCAG 2.5.5 floor on touch.
- `auth/login/page.tsx`: back-to-passkey toggle wraps in `inline-flex
  min-h-11 w-full` so the form-mode swap is reachable on touch.
- `privacy/page.tsx` and `about/page.tsx`: header HealthLog + Sign-in
  links wrap in `inline-flex min-h-11` so the public-page chrome
  clears the floor.
2026-05-15 18:48:07 +02:00
Marc-André Bombeck e9b159755f docs(planning): R3d MB3 input-attributes bucket report 2026-05-15 18:46:02 +02:00
Marc-André Bombeck 17aed374eb chore(dashboard): apply the tap-target floor across the dashboard tile and onboarding chrome
Lift the GLP-1 tile range strip + tab pills, the getting-started
checklist dismiss buttons, and the settings reorder + source-ladder
arrow buttons so every interactive element clears the 44 px WCAG
2.5.5 floor.

- `glp1-tile.tsx`: range-strip radio buttons gain `inline-flex
  min-h-11 items-center justify-center px-3` so each preset sits at
  44 px; the tab pills (level / weight) lift the same way.
- `getting-started-checklist.tsx`: dismiss-all CTA drops `size="sm"`
  so it sits at the default 40 px primitive height; per-row dismiss
  `X` button wraps in `inline-flex h-11 w-11`.
- `dashboard-layout-section.tsx`: reorder up + down arrows `h-5 w-5`
  → `size-11`; column-header spacer widens to `w-11` to keep the
  layout grid aligned.
- `sources-section.tsx`: metric source ladder + device-type ladder
  up/down arrows stack vertically on `<sm` so each button keeps the
  `h-11 w-11` floor without crowding the row; sit side-by-side again
  from `sm:` up to preserve the desktop layout.
2026-05-15 18:45:08 +02:00
Marc-André Bombeck 1ca7022538 chore(i18n): fan filterByType across the remaining locale files
`measurements.filterByType` shipped in en + de with the
v1.4.27 measurement-list aria-label wiring; the locale-integrity
test failed because the four English-fallback locales (es, fr, it,
pl) had not been backfilled. Add the placeholder English string so
each locale file is key-parity with `messages/en.json` again; real
translations follow in the v1.4.28 locale sweep.
2026-05-15 18:44:10 +02:00
Marc-André Bombeck bfb5ba7290 feat(coach): tap-friendly composer hint and accurate evidence aria-expanded
- New `<Popover>` primitive (Radix-backed, Dracula surface tokens)
  mirrors the existing `<Tooltip>` wrapper. Tooltips do not
  tap-toggle reliably on touch; popovers do.
- The Coach composer info-icon swaps off `<Tooltip>` onto
  `<Popover>` so the long-form "Enter to send, Shift+Enter for a new
  line" copy is reachable via a tap on mobile while the keyboard
  hover-preview semantic is preserved on desktop. The `aria-label`
  on the trigger keeps screen-reader users announced.
- The composer textarea now carries `enterKeyHint="send"`,
  `autoCapitalize="sentences"`, and an opt-in `autoFocusOnOpen` prop.
  The drawer flips that prop on when it mounts the fallback
  composer, so the textarea claims the cursor the first time the
  drawer opens; subsequent re-renders of an already-open drawer do
  not steal focus back.
- Coach evidence `<details>` is now controlled by local state. The
  summary carries an accurate `aria-expanded` that flips in
  lock-step with the disclosure; the native Enter / Space toggle
  semantics are preserved.
2026-05-15 18:42:15 +02:00
Marc-André Bombeck bba8921ec9 chore(measurements,medications): apply the tap-target floor across list and form surfaces
Lift the icon-button and pagination controls on the list + form
surfaces under `/measurements` and `/medications` so every touch
target clears the 44 px WCAG 2.5.5 floor.

- `measurement-list.tsx`: mobile-row edit + delete buttons `size-8`
  → `size-11`; pagination chevrons `size="sm"` → `size="icon"
  size-11` with translated `aria-label`; edit-dialog kebab
  `h-9 w-9` → `size-11`; `DeleteButton` default `h-8 w-8` →
  `size-11`. Empty-state CTAs drop `size="sm"` so they sit at the
  default 40 px primitive height.
- `measurement-form.tsx`: reset kebab `h-9 w-9` → `size-11`.
- `intake-history-list.tsx`: desktop + mobile per-row edit + delete
  buttons `size-8` → `size-11`; pagination chevrons → `size-11`
  icon-buttons; edit-dialog kebab → `size-11`; shared `DeleteButton`
  in this file → `size-11`.
- `SideEffectsSection.tsx`: per-entry delete `h-7 w-7` → `size-11`.
- `inventory-section.tsx`: live-pen "mark as in use" / "mark as used
  up" pills drop `size="sm"`/`h-7` so they sit at `min-h-11`; past-pen
  delete `h-6 w-6` → `size-11` icon-button.

Adds `measurements.previousPage` / `measurements.nextPage` and
`medications.previousPage` / `medications.nextPage` translation keys
so the pagination icon-buttons carry accessible labels in every
locale.
2026-05-15 18:41:52 +02:00
Marc-André Bombeck 95bc87f5ab feat(auth): wire keyboard hints and aria error wiring on login and register
Bring the auth flows to parity with the rest of the v1.4.27 form
sweep so screen-reader users hear the form-level error banner tied
to the offending field and the mobile keyboard surfaces the right
on-screen affordance per step.

Login: email field flips to `inputMode="email"` +
`enterKeyHint="next"` and disables capitalize / spell-check;
password field carries `enterKeyHint="go"`. Both inputs reference
the error banner via `aria-describedby` and the banner itself gains
`aria-live="polite"`.

Register: same pattern across email, username, and the new-password
field. The submit key on password is `"go"` to fire the create-
account submit on virtual-keyboard return.
2026-05-15 18:38:28 +02:00
Marc-André Bombeck 7398761412 docs(planning): R3d MB1 ResponsiveSheet bucket report
Records the five MB1 commits, the `<ResponsiveSheet>` API, every
migrated form mount, two race-conditioned commit-message
mis-attributions (`bd7cb938`, `44554729`), and the optional
sticky-CTA pattern deferral.
2026-05-15 18:37:13 +02:00
Marc-André Bombeck 192170ee5f feat(forms): wire keyboard hints and aria attributes across settings and admin forms
Bring the same `inputMode`, `enterKeyHint`, `autoComplete`, and
`spellCheck` defaults to the settings and admin surfaces:

- Account: profile email gets `autoComplete="email"` +
  `enterKeyHint="next"`, the disabled username carries
  `autoComplete="username"` so the password manager can still bind
  the credential, and the height field hints the decimal keyboard.
- Telegram card: bot token and chat ID both opt out of autofill,
  disable spell-check + autocapitalize, and lead the chained next →
  done keyboard order.
- Integrations: Withings client-id / secret and the moodLog URL +
  API key share the off-spell + no-capitalize secret-input contract
  and chain into the test / save action via `enterKeyHint`.
- Notifications anchors: each card wrapper carries `scroll-mt-28`
  so direct anchor navigation to `#telegram`, `#ntfy`, `#web-push`
  lands the section header below the safe-area sticky header
  instead of under it.
- Thresholds editor: min/max numeric inputs derive `inputMode`
  ("numeric" for integer ACTIVITY_STEPS, "decimal" elsewhere) and
  chain next → done.
- Admin reminders: the late-window and missed-window minute inputs
  pin `inputMode="numeric"` and the next → done keyboard order.
2026-05-15 18:36:33 +02:00
Marc-André Bombeck 48261b6797 refactor(coach): retire the redundant settings-sheet close-X
The primitive `<SheetContent>` close-X is an absolutely positioned
`opacity-70 rounded-xs` button that clashes with the rest of the
sheet's in-header chrome. Hide it via `showCloseButton={false}` and
render a matching ghost-icon `<SheetClose>` inline in the header
so the close affordance shares the same baseline, ghost variant,
and 44 px tap floor as the coach-drawer header pattern. The
settings-sheet test mock picks up a `SheetClose` passthrough so the
existing skeleton + form assertions keep working.
2026-05-15 18:35:02 +02:00
Marc-André Bombeck beb2b40f7d chore(coach): lift the rail-tray chevrons out of the overlay
The mobile history + sources tray triggers used to mount as absolute
chevron buttons over the message thread, so they (a) overlapped the
first message bubble, (b) sat at a 28 px tap target (`h-7`), and (c)
relied on `backdrop-blur` to stay legible — which still failed on
busy threads.

Lift them into a sub-header strip above the thread: a flex row with
two ghost buttons at `min-h-11`. The strip itself carries `xl:hidden`
since both rails are inline at xl+; the history trigger keeps its own
`lg:hidden` because the history rail mounts inline already at lg+.

While the rail surface is in scope, drop the `opacity-0
group-hover:opacity-100` reveal on the history-rail per-row delete
button — touch surfaces have no hover, so the affordance was
invisible on mobile. The button now sits at `size-11` always-visible;
the confirming state still styles via `data-confirming`.

Test re-baselined to assert the new strip wrapper + `min-h-11` floor.
2026-05-15 18:34:53 +02:00
Marc-André Bombeck 2d8c5e906e feat(forms): wire keyboard hints and aria error wiring across measurement, medication, and mood forms
Pair every required field with `enterKeyHint`, `aria-required`,
`aria-invalid`, and `aria-describedby` so the form-level error banner
gets announced the moment validation fails, and so each input
surfaces the right virtual-keyboard hint (next, done) when the user
moves through the form.

- Measurement form (single + BP triple) and the inline edit dialog
  pin `aria-describedby` to the alert region, add `enterKeyHint` on
  every numeric and notes input, and pass the filter Select an
  explicit `aria-label`.
- Medication form gets the chained `enterKeyHint` flow on name →
  dose → unit → doses-per-pen, switches the schedule window inputs
  to `type="time"`, and forces `text-base` on the dense schedule
  cells so iOS Safari does not auto-zoom the field on focus.
- Intake create + edit dialogs reference the dialog-local error
  banner via `aria-describedby`.
- Inventory add-pen dialog wires the same error descriptor and adds
  `enterKeyHint` to the date and doses-total fields plus
  `autoCapitalize="sentences"` on the notes textarea.
- Side-effect log notes textarea gets `enterKeyHint="done"` and
  `autoCapitalize="sentences"`.
- Mood form pins the timestamp input to the error banner and lifts
  the tags input to `autoCapitalize="none"` so colour-coded chips do
  not get title-cased.

Adds the `measurements.filterByType` translation in de + en.
2026-05-15 18:33:37 +02:00
Marc-André Bombeck 4455472921 chore(insights): apply the tap-target floor across the insights surface
Lift every interactive element on the insights hero + advisor + coach
chrome to clear the 44 px WCAG 2.5.5 floor on touch.

- `hero-strip.tsx`: drop `size="sm"` from the action buttons (weekly
  report, ask the Coach, weekly banner read/share/export). The default
  primitive height now sits at 40 px so the explicit `sm` was the only
  thing keeping these below the floor.
- `suggested-prompts.tsx`: chip floor `min-h-9` → `min-h-11`.
- `insight-advisor-card.tsx`: regenerate icon buttons drop their
  `h-7 w-7` override so the `size="icon"` primitive default (40 px)
  takes over; legacy-payload CTAs drop their `size="sm"`/`h-7` overrides.
- `coach-drawer.tsx`: window pill `h-7` → `h-11`; the three header
  icon buttons (new chat, settings, close) `size-9` → `size-11`.
- `coach-input.tsx`: composer hint icon trigger `h-7 w-7` → `h-11
  w-11`. The Tooltip → Popover swap is owned by MB3 — distinct lines.
- `message-thread.tsx`: thumbs feedback row `py-0.5 text-[11px]` →
  `min-h-11 py-1.5 text-xs`. Same surface row, padding does the lift.
2026-05-15 18:32:28 +02:00
Marc-André Bombeck 04cce8d9c2 refactor(medications): mount the entry surfaces via ResponsiveSheet
Swaps the medication create/edit, intake create/edit, side-effect
log, and inventory add-pen mounts from the centred `<Dialog>` to
the shared `<ResponsiveSheet>` primitive so the same surfaces flip
to a bottom-anchored sheet on narrow viewports. The "log this
thing" mental model — measurement, mood, medication, intake,
side-effect, inventory — now lands on a consistent mobile shape.
Configuration surfaces (IntakeImport, ApiEndpoint, PhaseConfig)
stay centred per Decision G; the dialog primitive's new
`max-h-[calc(100dvh-2rem)]` default keeps them inside the viewport
on short / soft-keyboard screens without the previous per-callsite
`max-h-[90vh]` ceiling.
2026-05-15 18:30:15 +02:00
Marc-André Bombeck b25683404e refactor(ui): move PasswordInput into the shared UI layer with a 44 px toggle
Move `<PasswordInput>` from `src/components/settings/password-input.tsx`
to `src/components/ui/password-input.tsx` so the auth surfaces and
every settings card that masks a secret consume one primitive. The
two surfaces previously diverged on toggle markup and aria-label
wiring; the new home in `ui/` keeps them in lock-step.

While the primitive moves, lift the icon-only show/hide toggle to a
44 px hit area per WCAG 2.5.5 — `inline-flex h-11 w-11` wrapper with
the eye icon centred inside. The input itself gets `pr-12` so user
input never collides with the toggle glyph.

Consumer imports updated in:
- `src/components/settings/account-section.tsx`
- `src/components/settings/ai-section.tsx`
- `src/components/settings/integrations-section.tsx`
- `src/components/settings/ntfy-card.tsx`
- `src/components/settings/telegram-card.tsx`
- `src/components/admin/_shared.tsx`
2026-05-15 18:27:14 +02:00
Marc-André Bombeck 9036e71516 feat(ui): derive Input inputMode default from the type prop
Surface the right on-screen keyboard on iOS Safari and Android Chrome
without forcing every call site to repeat the attribute. The primitive
maps `type="number"` to `inputMode="decimal"` (most numeric fields in
HealthLog accept decimals — kg, mmol/L, sleep hours), `type="email"`
to `"email"`, `type="url"` to `"url"`, `type="tel"` to `"tel"`, and
`type="search"` to `"search"`. Integer-only call sites (steps, doses
per pen, reminder minutes) still opt into `inputMode="numeric"`
explicitly and the override wins.

Cover the derivation with seven additional unit tests on top of the
three existing autoComplete contracts; the suite now pins every
mapping plus the override path.
2026-05-15 18:26:46 +02:00
Marc-André Bombeck bd7cb938c9 feat(ui): lift the password toggle into a shared primitive
Move `<PasswordInput>` from `src/components/settings/password-input.tsx`
to `src/components/ui/password-input.tsx` so the auth surfaces (login
and register) and every settings card that masks a secret consume one
primitive. The two surfaces previously diverged on toggle markup and
aria-label wiring; the new home in `ui/` keeps them in lock-step.

While the primitive moves, lift the icon-only show/hide toggle to a
44 px hit area per WCAG 2.5.5 — `inline-flex h-11 w-11` wrapper with
the eye icon centred inside. The input itself gets `pr-12` so user
input never collides with the toggle glyph.

Consumer imports updated in:
- `src/components/settings/account-section.tsx`
- `src/components/settings/ai-section.tsx`
- `src/components/settings/integrations-section.tsx`
- `src/components/settings/ntfy-card.tsx`
- `src/components/settings/telegram-card.tsx`
- `src/components/admin/_shared.tsx`

The `<DialogContent>` close-X tap-target lift to `min-h-9 min-w-9`
landed in the parallel `ResponsiveSheet` commit because both buckets
share the same `dialog.tsx` file. See Decision I — 36 px is the
intentional exception for fixed icon-only close affordances.
2026-05-15 18:26:06 +02:00
Marc-André Bombeck 65fd0bffdf feat(ui): introduce the ResponsiveSheet primitive
Shared `<ResponsiveSheet>` flips between a bottom `<Sheet>` on narrow
viewports and the existing centred `<Dialog>` at `md+`, with a
sticky-pinned footer slot on the Sheet branch so primary CTAs stay
reachable when the keyboard collapses the viewport. Picks the
breakpoint via a new SSR-safe `useIsMobile` hook (`matchMedia`-driven,
default `false` on the server so SSR markup always matches the
desktop branch). Adds a `max-h-[calc(100dvh-2rem)] overflow-y-auto`
default to `<DialogContent>` so the existing centred-dialog forms
also scroll inside the viewport on short / soft-keyboard mobile
screens instead of spilling off-screen. Smoke suite locks both
branches, the header sr-only escape hatch, sticky-vs-flow footer
shape, and controlled-state plumbing.
2026-05-15 18:23:49 +02:00
Marc-André Bombeck fb6fb4f5e6 feat(ui): lift Button, Input, Select, DropdownMenu defaults to honour the WCAG 2.5.5 floor
Move primitive default heights up so any consumer that does not opt
into a specific size already meets the 40-44 px target-size minimum
without per-callsite overrides.

- `<Button>` default → `h-10` (40 px), `lg` → `h-11` (44 px), `icon`
  → `size-10`, `icon-lg` → `size-11`. `sm` and `xs` stay compact for
  explicit-dense contexts.
- `<Input>` default → `h-10`.
- `<Select>` trigger default → `h-10`.
- `<DropdownMenuItem>` → `min-h-11` so menu rows always clear the
  44 px floor on touch.

No same-line collisions with MB1 (`dialog.tsx` max-h cap) or MB3
(`input.tsx` inputMode derivation) — distinct touch zones.
2026-05-15 18:20:08 +02:00
Marc-André Bombeck 6e01f0e717 docs(planning): consolidated mobile fix-plan for v1.4.27 R3d 2026-05-15 18:15:22 +02:00
Marc-André Bombeck 39d779c7d9 docs(planning): mobile audit report for settings and admin 2026-05-15 18:05:03 +02:00
Marc-André Bombeck 24b67f85a0 docs(planning): mobile audit report for auth and public surfaces 2026-05-15 18:04:02 +02:00
Marc-André Bombeck e5ff434395 docs(planning): mobile audit report for measurements and workouts 2026-05-15 18:03:57 +02:00
Marc-André Bombeck 6a8e5c3482 docs(planning): mobile audit report for the insights surface 2026-05-15 18:03:40 +02:00
Marc-André Bombeck 1b8f03af86 docs(planning): mobile audit report for the coach surface 2026-05-15 18:02:15 +02:00
Marc-André Bombeck d5792a1486 docs(planning): mobile audit report for the medications surface 2026-05-15 18:02:03 +02:00
Marc-André Bombeck bbbb513bfe docs(planning): mobile audit report for the dashboard surface 2026-05-15 18:01:40 +02:00