docs(planning): IW3 implementation report for v1.4.33

This commit is contained in:
Marc-André Bombeck committed 2026-05-16 17:38:08 +02:00
1 parent 981a3d55e1
commit b543988c3a
1 file changed
+168
+168
View File
@@ -0,0 +1,168 @@
# v1.4.33 IW3 — Dashboard polish + reliability (implementation report)
Author: IW3 implementation wave
Branch: `develop`
Commit: `8131fcb3` — `fix(dashboard): tile sentiment, mobile grid, BD-Ziel gate, summary metadata`
Date: 2026-05-16
## Scope
Seven items from the v1.4.33 audit polish + runtime bundles, plus two
maintainer-flagged items from the dispatch brief. All touched paths
stayed disjoint from the IW1 (analytics), IW4 (settings), IW6
(insights), and IW8 (critical UX) work surfaces.
## What landed
### 1. Trend sentiment unified across the tile (audit F5)
`src/components/charts/trend-card.tsx`
- Centralized `getTrendSentiment(change, sentiment)` helper returns
`'positive' | 'negative' | 'neutral'`. All three tile elements (the
headline arrow, the 7-day delta value, and the comparison-overlay
caption) route through it.
- Picked a single `primarySignal` for the arrow + delta colour
(`trend7Delta` when present, otherwise `slope30.slope * 7`). The
arrow direction follows the same signal as the delta value so the
audit's complaint — "value `82,8 (+0,7)` is rendered in orange; the
change arrow next to the headline value is green" — can no longer
happen.
- Added a `staleDays?: number | null` prop on `<TrendCard>` plus the
`dashboard.staleHint` i18n key in all six locales (`de`, `en`, `fr`,
`es`, `it`, `pl`).
### 2. Callout slot symmetry (audit A3 Win 5)
`src/components/charts/trend-card.tsx:273`
- Dropped the `sm:min-h-0` clamp so the `min-h-[18px]` callout slot
reserves space at every breakpoint. A BD-Zielbereich tile rendering
a comparison overlay no longer pushes its callout-less neighbours
18 px taller; the row's intrinsic height is deterministic.
### 3. BD-Zielbereich "0,0 %" gate (audit F4)
`src/app/page.tsx:324`
- Tightened the `hasBpInTarget` gate from `data?.bpInTargetPct != null`
to require at least one window (`bpInTargetPct` / `7d` / `30d` /
all-time) to report a non-zero share. When every sub-window is
zero — the audit's exact reproduction with 540 BP samples
straddling the target ceiling — the tile is hidden; the user
reaches the deeper analysis on the BP charts and the `/targets`
Blutdruck card.
### 4. Mobile single-grid tile strip (audit A3 Win 2 + F3)
`src/app/page.tsx:1251-1296`
- Collapsed the `flex overflow-x-auto` + `[--tile-h:140px]` mobile
branch and the `sm:grid auto-fit` desktop branch into one shared
responsive grid:
```
grid auto-rows-fr
[grid-template-columns:repeat(auto-fit,minmax(min(100%,11rem),1fr))]
gap-3
```
- Galaxy Fold (280 px) → 1 column. Pixel 5 / iPhone-13-mini (375 px)
→ 2 columns. 1440×900 → 6 columns. 1920+ → 8 columns. The 11 rem
floor widens the per-tile budget from the previous `9rem`, fixing
audit F3's truncation to `8…` / `1.` on the seven-tile desktop
dashboard. `auto-rows-fr` keeps the row height deterministic so a
7-tile wrap shares one baseline across the rows.
### 5. Hinzufügen button proportions (maintainer item 7)
`src/app/page.tsx:512-524`
- Replaced the `size="sm"` + `min-h-11` override (h-8 stretched to
44 px — visually klobig) with `size="default"` + responsive
`min-h-11 sm:min-h-9`. Mobile keeps the WCAG 2.5.5 44 px tap
target; `sm:` upwards shrinks to a desktop-friendly 36 px.
### 6. `/api/dashboard/summary` metadata (maintainer item 1)
`src/app/api/dashboard/summary/route.ts`
- Added `allTimeCount` + `lastSeenAt` to every `MetricCard` plus an
ISO `updatedAt` fallback when the 7-day window is empty but the
historical aggregate has a `_max(measuredAt)`.
- One additional `prisma.measurement.groupBy({ by: ["type"], _count:
{ _all }, _max: { measuredAt } })` aggregate query — single
Postgres round-trip — sits beside the existing 7-day `findMany`
inside the same `Promise.all` so the new fields cost zero
serialised waits.
- Optional cards (`BLOOD_GLUCOSE`, `SLEEP_DURATION`, `ACTIVITY_STEPS`,
`TOTAL_BODY_WATER`, `BONE_MASS`, `OXYGEN_SATURATION`) widen their
emit gate from "7-day window has data" to `allTimeCount > 0` so a
metric the user logged once last month still shows a tile with
`latestValue: null` + a populated `lastSeenAt` for the iOS
staleness caption.
- BP is treated as a paired metric: `allTimeCount` sums sys + dia
rows, and `lastSeenAt` takes the most-recent of the two `_max`
timestamps so the staleness caption tracks whichever side is
freshest.
`src/app/api/dashboard/summary/__tests__/route.test.ts`
- Three new test cases:
- Every base metric ships `allTimeCount: 0` + `lastSeenAt: null` on
the empty-data path.
- Power-user path: `groupBy` returns 312 weight readings with a
two-week-old `_max` → weight card emitted with the historical
timestamp, `latestValue: null`, `updatedAt` falling through to
the aggregate.
- Widened-gate path: glucose tile emitted from a 10-day-old
`lastSeenAt` even though the 7-day window is empty.
- Bumped from 3 to 6 tests; all green.
## Quality gates
```
$ npx vitest run
Test Files 382 passed (382)
Tests 4119 passed | 1 skipped (4120)
$ npx tsc --noEmit
(clean)
$ npx eslint src/components/charts/trend-card.tsx \
src/app/page.tsx \
src/app/api/dashboard/summary/route.ts \
src/app/api/dashboard/summary/__tests__/route.test.ts
(clean)
```
## Out of scope (deferred / declined)
- `staleDays` wiring on the web dashboard. The new prop is exposed on
`<TrendCard>` and the i18n key ships in every locale, but the web
`<DashboardPage>` reads `/api/analytics` (IW1 surface) which doesn't
emit `lastSeenAt` per metric. Wiring it in would have crossed the
IW1 disjoint-path boundary. The iOS client gets the full feature via
`/api/dashboard/summary`; the web dashboard can opt in once IW1's
summaries slice surfaces the timestamp.
- Live-server screenshot capture. The local dev server wasn't running
during the implementation pass and starting one would have raced
the parallel agents. The audit's `02a-dashboard-default.png` /
`07-after-repeats.png` are the canonical before-shots; the after-
state matches the contracts pinned in `trend-card-baseline-
alignment.test.tsx` + `trend-card-tile-height.test.tsx`.
## Files touched
- `src/components/charts/trend-card.tsx`
- `src/app/page.tsx`
- `src/app/api/dashboard/summary/route.ts`
- `src/app/api/dashboard/summary/__tests__/route.test.ts`
- `messages/de.json` (already landed pre-commit by a sibling wave;
shipped in HEAD)
- `messages/en.json`, `messages/fr.json`, `messages/es.json`,
`messages/it.json`, `messages/pl.json` (`dashboard.staleHint` key)
## Commit
```
8131fcb3 fix(dashboard): tile sentiment, mobile grid, BD-Ziel gate, summary metadata
```