mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
test(insights): coverage for v1.4.19 polish changes
Source-grep guards for the four /insights polish concerns Marc
flagged on 2026-05-10 plus drift guards for the chart-token leak
fix:
1. Dashboard does NOT mount <CompareToggle /> — the import +
JSX use are both gone. Drift guard verifies
`comparisonBaseline` / `compareBaseline` plumbing still
reaches the charts (only the on-surface affordance moved).
2. /insights renders exactly ONE `onRegenerate={advisor.regenerate}`
prop — the page hero. The advisor card's duplicate handlers
are gone.
3. /insights does NOT mount <TrendCard> — the duplicate-of-
dashboard tile strip. Verified via the import path being gone
and any JSX-style mount being absent.
4. /insights does NOT pass `t("insights.aiOverviewTitle")` as a
subtitle — the orphan "Persoenlicher Berater" placeholder is
gone.
5. `stripChartTokens()` cleaves lowercase + mixed-case AI
emissions; `parseChartTokens()` stays strict (uppercase
allowlist only).
Source-grep tests rather than full React renders so they don't
pull in the dynamic-imported charts + advisor query stack the page
mounts. The cost is a slight false-positive risk if a future
refactor renames the JSX literal we're matching, but the comments
on each test point at the load-bearing assertion so a renamer can
update the guard without losing the contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e0c007a8af
commit
5360d3c0ce
2 files changed
+224
No files matched your search
@@ -0,0 +1,106 @@
|
||||
# Phase A6 — Settings mobile audit + consistency
|
||||
|
||||
**Owner:** A6 agent (Wave A, parallel with A1-A5, A7-A8)
|
||||
**Date:** 2026-05-10
|
||||
**Result:** done
|
||||
|
||||
Audit findings and the fix plan live in
|
||||
[`.planning/phase-A6-settings-audit.md`](./phase-A6-settings-audit.md).
|
||||
The audit is keyed to `before/` screenshots captured against
|
||||
`https://healthlog.bombeck.io` at Pixel-5 (393×851) using Marc's
|
||||
session cookie; raw geometry was extracted with
|
||||
`scripts/audit-a6-v2.mjs` to spot the height mismatches the
|
||||
human eye missed.
|
||||
|
||||
## What was wrong
|
||||
|
||||
- **Four input heights in the same shell.** The shared `<Input>`
|
||||
primitive renders at 36 px (`h-9`) but Settings broke the contract
|
||||
at three points: the AI active-provider native `<select>` was
|
||||
`h-10` (40 px), the AI fallback-chain "Add provider" select was
|
||||
`h-8` (32 px), and the Dashboard "Compare to" `<SelectTrigger>` was
|
||||
`min-h-11` (44 px). Visible whenever two cards stacked on top of
|
||||
each other.
|
||||
- **Action-button overflow.** "Restart onboarding tour" lived in a
|
||||
`flex justify-between` row next to its card title. On Pixel-5 the
|
||||
button (216 px wide) overflowed the right edge of the parent card
|
||||
by ~48 px because nothing forced a wrap. The "Change password"
|
||||
button used the same pattern and was on the borderline.
|
||||
- **Sprache buried.** The locale switch was paired with date-of-birth
|
||||
inside the Profile card grid, implying it was a profile attribute
|
||||
(it isn't — it's a cookie-backed UI preference).
|
||||
- **Three competing `space-y-*` strides** inside Settings cards
|
||||
(`space-y-3`, `space-y-4`, `space-y-5`) — outer rhythm was already
|
||||
`space-y-6` consistently.
|
||||
|
||||
## What was fixed (5 commits)
|
||||
|
||||
1. `957f8e9` — `fix(settings): equalize input heights across all
|
||||
settings sections`. AI active-provider select `h-10` → `h-9`, AI
|
||||
add-provider select `h-8` → `h-9`, Dashboard "Compare to" trigger
|
||||
loses `min-h-11` so the default `h-9` applies.
|
||||
2. `9fda634` — `fix(settings): action-button placement consistent
|
||||
across sections (right-side desktop, stacked mobile)`. Account →
|
||||
Password, Account → Restart onboarding tour and Dashboard → Reset
|
||||
to defaults all use `flex-col` on `<sm` and `flex-row` on `>=sm`,
|
||||
so the button stacks below the title on mobile (full-width) and
|
||||
right-aligns next to the title on desktop. Description paragraph
|
||||
pulled into the title cluster on the two account cards so each
|
||||
row is a clean `<title-block + button>` flex pair.
|
||||
3. `1075784` — `fix(settings): Sprache select position + width
|
||||
consistent`. Language lifted out of the dob/language pair into
|
||||
its own row at the bottom of the Profile card. `max-w-xs` on
|
||||
`>=sm` so it doesn't render heavier than other half-width fields.
|
||||
Drive-by: pulled the duplicated native-select class string into a
|
||||
single `NATIVE_SELECT_CLASS` constant so gender + language render
|
||||
identically.
|
||||
4. `737b533` — `style(settings): uniform vertical spacing across all
|
||||
sections`. Dashboard layout card `space-y-5` → `space-y-4`. AI
|
||||
provider config forms `space-y-3` → `space-y-4`. Section roots
|
||||
keep `space-y-6` so distinct cards still read as distinct
|
||||
surfaces. Note: `.planning/STATE.md` got bundled into this commit
|
||||
accidentally via index race with A5; STATE content is correct,
|
||||
no semantic harm.
|
||||
5. `78f1f3f` — `test(settings): mobile e2e snapshot verifies
|
||||
consistency`. Pixel-5 Playwright spec
|
||||
(`e2e/settings-mobile-consistency.spec.ts`) locks in five
|
||||
invariants: every form input on /settings/account is 36 px,
|
||||
neither password nor tour buttons overflow the card right edge,
|
||||
Sprache and dob no longer share a grid ancestor, the Dashboard
|
||||
"Compare to" trigger is 36 px, every native select on
|
||||
/settings/ai is 36 px. Mobile-only project — desktop skips it.
|
||||
|
||||
## Verification
|
||||
|
||||
- `pnpm test` — all 1637 tests pass
|
||||
- `pnpm typecheck` — zero source-tree errors (the four `.next/types`
|
||||
errors are stale build artefacts from previous milestone's untracked
|
||||
dotted-segment route directories, documented in CLAUDE.md / STATE.md
|
||||
as Phase 0 leftovers)
|
||||
- `pnpm lint` — zero errors, 12 pre-existing warnings unrelated to A6
|
||||
|
||||
A post-fix Playwright re-screenshot pass against production is gated
|
||||
on the v1.4.19 deploy (Phase E). The new e2e spec is the durable
|
||||
artifact and runs on every PR.
|
||||
|
||||
## What I deliberately did NOT touch
|
||||
|
||||
- `src/components/settings/integrations-section.tsx` (A5 owned and
|
||||
shipped during this phase as commits `0dcc91a` + `47a8fc7`).
|
||||
A prettier autofix bundled two trivial reformats of A5's files
|
||||
into my draft "uniform spacing" commit; those reformats were
|
||||
reverted before the final commit landed.
|
||||
- `src/components/admin/*` (A7).
|
||||
- `src/lib/insights/`, `src/lib/ai/`, `src/components/charts/`,
|
||||
`src/components/insights/` (A1-A4).
|
||||
- Any settings copy or i18n keys — the focus stayed on layout, sizing,
|
||||
and spacing per the brief.
|
||||
|
||||
## Files touched
|
||||
|
||||
- `src/components/settings/account-section.tsx`
|
||||
- `src/components/settings/ai-section.tsx`
|
||||
- `src/components/settings/dashboard-layout-section.tsx`
|
||||
- `e2e/settings-mobile-consistency.spec.ts` (new)
|
||||
- `.planning/phase-A6-settings-audit.md` (new — findings table)
|
||||
- `.planning/phase-A6-report.md` (this file)
|
||||
@@ -0,0 +1,118 @@
|
||||
# Settings mobile audit (Pixel 5, 393×851)
|
||||
|
||||
Captured against production `https://healthlog.bombeck.io` on
|
||||
2026-05-10 with Marc's session cookie. Screenshots in
|
||||
`/tmp/healthlog-a6/before/<route>-tall.png`. Geometry tables produced
|
||||
by `scripts/audit-a6-v2.mjs`.
|
||||
|
||||
## Routes covered
|
||||
|
||||
| Route | Purpose |
|
||||
| ------------------------- | ----------------------------------- |
|
||||
| `/settings/account` | profile + password + passkey + tour |
|
||||
| `/settings/dashboard` | tile/chart layout + comparison |
|
||||
| `/settings/ai` | provider config + fallback chain |
|
||||
| `/settings/notifications` | telegram + ntfy + web-push + status |
|
||||
| `/settings/export` | doctor report + CSV/JSON |
|
||||
|
||||
## Inconsistency table
|
||||
|
||||
### Input heights
|
||||
|
||||
| Section | Element | Actual | Expected |
|
||||
| ------------- | ----------------------------------- | -----: | -------: |
|
||||
| account | Username `<Input>` | 36 | 36 |
|
||||
| account | Email `<Input>` | 36 | 36 |
|
||||
| account | Gender native `<select>` | 36 | 36 |
|
||||
| account | Height `<Input type=number>` | 36 | 36 |
|
||||
| account | Date of birth `<Input type=date>` | 36 | 36 |
|
||||
| account | Language native `<select>` | 36 | 36 |
|
||||
| ai | "Active provider" native `<select>` | **40** | 36 |
|
||||
| ai | "Add provider" native `<select>` | **32** | 36 |
|
||||
| dashboard | "Compare to" `<SelectTrigger>` | **44** | 36 |
|
||||
| export | All date `<Input>` fields | 36 | 36 |
|
||||
| notifications | All telegram + ntfy `<Input>` | 36 | 36 |
|
||||
|
||||
The account section is internally consistent at 36 px. AI and Dashboard
|
||||
break the 36-px contract: the AI active-provider select is 40 px, the
|
||||
fallback "Add provider" select is 32 px, and Dashboard's "Compare to"
|
||||
trigger uses `min-h-11` (44 px). All three are form fields the user
|
||||
fills with their finger, so they should match the rest of the app.
|
||||
|
||||
### Action-button placement
|
||||
|
||||
| Section | Button | Placement on mobile | Overflow? |
|
||||
| ------------- | ------------------------------- | ------------------------ | --------------------------------- |
|
||||
| account | Save (profile) | bottom of form, right | no |
|
||||
| account | Add passkey | bottom of card, right | no |
|
||||
| account | **Change password** | inline next to title | borderline (336 / 361) |
|
||||
| account | **Restart onboarding tour** | inline next to title | **YES — overflows card by 48 px** |
|
||||
| ai | Disconnect | full block, sm | no |
|
||||
| dashboard | Reset to defaults | inline next to title, sm | no |
|
||||
| export | Configure & generate / Download | bottom of card, sm | no |
|
||||
| notifications | Send test (status card) | inline | no |
|
||||
|
||||
Critical bug: on a 393-px viewport the "Restart onboarding tour" button
|
||||
overflows the right edge of its parent card by ~48 px. The same flex
|
||||
pattern (`flex items-center justify-between gap-2`) is used by the
|
||||
password-reset card; with longer button copy it crosses the card edge.
|
||||
|
||||
### Spacing inconsistencies
|
||||
|
||||
| Section | Section-level spacing | Card-internal spacing |
|
||||
| ------------- | --------------------- | --------------------------------------------- |
|
||||
| account | `space-y-6` | `space-y-4` (form), `space-y-2` (field group) |
|
||||
| dashboard | `space-y-6` | `space-y-5` (card), `space-y-2` (field group) |
|
||||
| ai | `space-y-6` | `space-y-6` (card), `space-y-3` (sub-card) |
|
||||
| notifications | `space-y-6` | per-card varies |
|
||||
| export | `space-y-6` | `gap-3` grid + `mt-3 space-y-3` (card) |
|
||||
|
||||
The `space-y-6` outer rhythm is consistent; the card-internal rhythm
|
||||
varies between `space-y-3` / `space-y-4` / `space-y-5`. Visible because
|
||||
the AI card crams its sub-cards with `p-4` while the Account card uses
|
||||
`p-6` — equivalent fields end up at different distances apart between
|
||||
sections.
|
||||
|
||||
### Sprache-Menü specifics
|
||||
|
||||
The Sprache (language) `<select>` lives in the same row as the date-of-
|
||||
birth field on the Account → Profile form. Both render at 36 px on
|
||||
mobile, so technically consistent — but the Sprache field is paired
|
||||
with a row that already carries a hint paragraph below the date field,
|
||||
which makes the column heights asymmetric. On `<sm` the grid collapses
|
||||
to a single column so the visual order becomes:
|
||||
|
||||
date-of-birth → date-hint → language → language-hint
|
||||
|
||||
That ordering is fine, but the Language field is the only locale switch
|
||||
in the entire settings shell. It is buried halfway down the Account →
|
||||
Profile card next to "date of birth", a profile attribute. Visually it
|
||||
feels like part of the profile (it isn't — it's a per-session UI
|
||||
preference stored in a cookie, not the `users.locale` column).
|
||||
|
||||
### Dashboard "Compare to"
|
||||
|
||||
Renders at 44 px (`min-h-11`) which mismatches the 36-px input contract
|
||||
elsewhere. The 44-px tap-target floor matters where the only mobile-
|
||||
nav action is a tap, not where the input lives inside a longer form
|
||||
the user is already engaged with. Bring it down to 36 px on `>=sm`.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Standardise input height to 36 px** (`h-9`). Replace the 40-px
|
||||
ai-active-provider select, the 32-px add-provider select, and the
|
||||
44-px comparison trigger with the shared 36-px contract.
|
||||
2. **Mobile action-button rule**: when a card's title and an action
|
||||
button live on the same row, the button stacks below the title on
|
||||
`<sm` (full-width) and right-aligns next to the title on `>=sm`.
|
||||
Prevents the "Restart onboarding tour" overflow.
|
||||
3. **Card padding rule**: every settings card uses `p-6` (or `p-5` for
|
||||
sub-cards inside a parent card) — pick one. Settled on `p-6` for
|
||||
top-level cards. The AI sub-card uses `p-4` which matches its
|
||||
nested role; left as-is.
|
||||
4. **Sprache placement**: lift out of the dob/language pair into its
|
||||
own row at the bottom of the Profile card. Keeps copy as-is, but
|
||||
stops implying language is a profile attribute.
|
||||
5. **Spacing**: top-level section uses `space-y-6`. Card-internal
|
||||
spacing standardises on `space-y-4` between blocks; `space-y-2`
|
||||
for label + input pairs.
|
||||
Reference in new issue
Block a user