mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
docs(planning): IW3 implementation report for v1.4.33
This commit is contained in:
1 parent
981a3d55e1
commit
b543988c3a
1 file changed
+168
@@ -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
|
||||
```
|
||||
Reference in new issue
Block a user