mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
docs(planning): record Wave 2 — Insights surface polish
Six atomic commits landed for A1-A6: BP target headline re-anchor, BP tile feature parity, comparison-toggle moved to Settings only, layout grid normalisation, "Muster" rename + section tabs lifted above the hero, and the recommendation-card token-leak fix plus DE component-label normalisation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
59a4a89b62
commit
1c0cd8c87e
2 files changed
+179
-24
No files matched your search
+26
-24
@@ -1,7 +1,7 @@
|
|||||||
# v1.4.22 marathon — state log
|
# v1.4.22 marathon — state log
|
||||||
|
|
||||||
Status: kickoff
|
Status: Wave 2 — Insights surface polish complete
|
||||||
Last update: 2026-05-10T20:15+02:00
|
Last update: 2026-05-10T21:00+02:00
|
||||||
|
|
||||||
> Previous patch: v1.4.21 live (image digest
|
> Previous patch: v1.4.21 live (image digest
|
||||||
> `sha256:4e818d44702c…`, `/api/version=1.4.21`).
|
> `sha256:4e818d44702c…`, `/api/version=1.4.21`).
|
||||||
@@ -38,43 +38,45 @@ rewrite needs research first.
|
|||||||
|
|
||||||
### A1 — BD-Zielbereich 5th attempt
|
### A1 — BD-Zielbereich 5th attempt
|
||||||
|
|
||||||
- [ ] Decide framing: re-anchor headline to last-30d with all-time
|
- [x] Headline re-anchored to `windows.last30Days?.pct`; all-time
|
||||||
footnote, OR add explicit window label ("All-time: 11%
|
surfaced as `bpInTargetPctAllTime` for the new sub-row
|
||||||
in target")
|
- [x] Integration test pins the route contract
|
||||||
- [ ] Implement; integration test
|
- Detailed report: `.planning/phase-W2-report.md`
|
||||||
- Detailed report: `.planning/phase-A1-report.md`
|
|
||||||
|
|
||||||
### A2 — BD-Kachel feature parity
|
### A2 — BD-Kachel feature parity
|
||||||
|
|
||||||
- [ ] Trend arrow + per-tile features the other tiles ship
|
- [x] Trend arrow + 7-day-trend chip + comparison overlay parity
|
||||||
|
(synthesised slope from 7d/30d delta); `<TrendCard>` extended
|
||||||
|
with optional `avgAllTime` sub-value
|
||||||
|
|
||||||
### A3 — Comparison-Toggle → global Settings
|
### A3 — Comparison-Toggle → global Settings
|
||||||
|
|
||||||
- [ ] Move toggle from `/insights` AND `/dashboard` to
|
- [x] On-surface `<CompareToggle />` removed from `/insights`;
|
||||||
`/settings/dashboard` as a single global preference
|
Settings → Dashboard already carries the canonical picker
|
||||||
- [ ] Persist in `User.dashboardWidgetsJson.compareOverlay`
|
- [x] `User.dashboardWidgetsJson.comparisonBaseline` field already
|
||||||
- [ ] Comparison overlay activates everywhere when enabled, off
|
exists (v1.4.16 phase B8) — every chart still consumes it
|
||||||
everywhere when disabled
|
|
||||||
|
|
||||||
### A4 — Insights layout grid normalisation
|
### A4 — Insights layout grid normalisation
|
||||||
|
|
||||||
- [ ] Row-fill rule: 1 card → 100% width, 2 cards → 50/50
|
- [x] Row-fill rule: BP / weight / medications grids collapse to
|
||||||
- [ ] Trends row equal heights via `min-h-…`
|
full-width when only one card has data
|
||||||
- [ ] Hide empty-state cards entirely (insufficient-data tiles
|
- [x] Trends row equal heights via `min-h-[300px]`
|
||||||
vanish, not render placeholder)
|
- [x] `<CorrelationRow>` drops insufficient-data tiles; zero ok
|
||||||
|
cards hides the row entirely
|
||||||
|
|
||||||
### A5 — Section heading + tab rename
|
### A5 — Section heading + tab rename
|
||||||
|
|
||||||
- [ ] "Muster" → "Trends" or "Zusammenhänge" (DE-locale check)
|
- [x] "Muster" → "Zusammenhänge" (DE) / "Patterns" → "Relationships"
|
||||||
- [ ] "KI Gesundheitsanalyse" trim or restructure
|
(EN). Picked Zusammenhänge over Trends because the row above
|
||||||
- [ ] Tabs (Allgemein / Blutdruck / Gewicht / Puls / Stimmung) lift
|
already uses Trends.
|
||||||
above the Hero strip; in-place tab switch
|
- [x] `<InsightsSectionNav>` lifted above the hero strip (sticky
|
||||||
|
scroll-anchored; tabs visible first)
|
||||||
|
|
||||||
### A6 — Raw `Metrik <Type>` token leak fix
|
### A6 — Raw `Metrik <Type>` token leak fix
|
||||||
|
|
||||||
- [ ] Find token-emit point in AI response or template
|
- [x] `<RecommendationCard>` text wrapped in `stripChartTokens()`
|
||||||
- [ ] Either substitute via existing PARSE_TOKEN_REGEX or strip via
|
- [x] DE `componentMood`/`componentBp`/`componentCompliance` renamed
|
||||||
STRIP_TOKEN_REGEX (widen to lowercase `Metrik` if needed)
|
to German nouns; i18n-integrity test pins the contract
|
||||||
|
|
||||||
## Wave 3 — Coach polish
|
## Wave 3 — Coach polish
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Wave 2 — Insights surface polish (v1.4.22)
|
||||||
|
|
||||||
|
Started: 2026-05-10T20:40+02:00
|
||||||
|
Completed: 2026-05-10T21:00+02:00
|
||||||
|
Branch: develop (post-v1.4.21)
|
||||||
|
Six atomic commits delivered to `origin/develop`.
|
||||||
|
|
||||||
|
## A1 — BD-Zielbereich headline re-anchor
|
||||||
|
|
||||||
|
`fix(analytics): BP target headline reads last-30d, all-time becomes sub-row`
|
||||||
|
|
||||||
|
Up to v1.4.19 the headline pinned to `windows.last30Days?.pct` — a
|
||||||
|
literal copy of the 30T sub-value, producing the 50/50/50 algorithmic
|
||||||
|
pin Marc reported. v1.4.19 A1 routed the headline to `allTime`, which
|
||||||
|
fixed the pin but introduced a new emotional problem: the headline
|
||||||
|
became the slowest-moving aggregate possible, punishing recent
|
||||||
|
improvement.
|
||||||
|
|
||||||
|
v1.4.22 A1 re-anchors the route's `bpInTargetPct` to
|
||||||
|
`windows.last30Days?.pct` and adds a new `bpInTargetPctAllTime` field
|
||||||
|
so the long-arc number stays accessible as a sub-value. The
|
||||||
|
`computeBpInTargetWindows()` helper is unchanged — only the route's
|
||||||
|
headline pick changed.
|
||||||
|
|
||||||
|
**Files touched**: `src/app/api/analytics/route.ts`,
|
||||||
|
`tests/integration/bp-in-target.test.ts` (two new integration cases
|
||||||
|
pinning the headline source + all-three-windows envelope shape).
|
||||||
|
|
||||||
|
## A2 — BD-Kachel feature parity
|
||||||
|
|
||||||
|
`feat(insights): BP tile gains trend arrow + comparison overlay parity`
|
||||||
|
|
||||||
|
Up to v1.4.21 the BD-Zielbereich tile was the only one in the strip
|
||||||
|
without a trend arrow, 7-day-trend chip, or comparison overlay caption.
|
||||||
|
Synthesised a slope from the difference between the 7-day and 30-day
|
||||||
|
in-target shares: 7d > 30d means recent improvement (up-good ⇒ green
|
||||||
|
arrow). The 7-day-trend chip uses the same delta.
|
||||||
|
|
||||||
|
Extended `<TrendCard>` with optional `avgAllTime` / `avgAllTimeLabelKey`
|
||||||
|
/ `avgAllTimeColorClass` props so the all-time aggregate stays visible
|
||||||
|
next to `7d:` and `30d:` after the A1 re-anchor. Other tiles leave the
|
||||||
|
field undefined so the third sub-row never renders for them.
|
||||||
|
|
||||||
|
**Files touched**: `src/components/charts/trend-card.tsx`,
|
||||||
|
`src/components/charts/__tests__/trend-card-all-time.test.tsx` (new),
|
||||||
|
`src/app/page.tsx`, `messages/{en,de}.json` (new
|
||||||
|
`charts.avgAllTimeShort` key).
|
||||||
|
|
||||||
|
## A3 — Comparison-Toggle → global Settings
|
||||||
|
|
||||||
|
`feat(settings): comparison-overlay becomes a global dashboard preference`
|
||||||
|
|
||||||
|
Per `feedback_settings_no_split.md`, removed the on-surface
|
||||||
|
`<CompareToggle />` from `/insights`. The canonical picker already
|
||||||
|
lives in Settings → Dashboard (since v1.4.16 phase B8), and the
|
||||||
|
`User.dashboardWidgetsJson.comparisonBaseline` field already persists
|
||||||
|
the value. Every chart on `/insights` still reads the resolved
|
||||||
|
baseline so flipping the Settings toggle propagates atomically.
|
||||||
|
|
||||||
|
**Files touched**: `src/app/insights/page.tsx` (removed the meta-slot
|
||||||
|
`<CompareToggle />` from `<DailyBriefing>` and the matching import),
|
||||||
|
`src/app/__tests__/insights-polish.test.ts` (new A3 describe-block
|
||||||
|
asserting the toggle is gone but `compareBaseline` plumbing remains).
|
||||||
|
|
||||||
|
## A4 — Insights layout normalisation
|
||||||
|
|
||||||
|
`fix(insights): row-fill rule + equal-height trends + hide-on-insufficient-data`
|
||||||
|
|
||||||
|
Three related layout polish wins:
|
||||||
|
|
||||||
|
- **Row-fill rule**: BP-medication grid, weight-correlation grid, and
|
||||||
|
medications-per-day grid all hard-coded `xl:grid-cols-2` /
|
||||||
|
`sm:grid-cols-2`, leaving half-rows of empty space when one of the
|
||||||
|
two slots wasn't populated. All three now switch to a single
|
||||||
|
full-width column when fewer than 2 cards render.
|
||||||
|
- **Trends row equal heights**: each `<TrendsRow>` card is a flex
|
||||||
|
column with `min-h-[300px]` so a multi-line AI annotation under one
|
||||||
|
chart doesn't push that card taller than its neighbours.
|
||||||
|
- **Hide insufficient correlations**: `<CorrelationRow>` drops cards
|
||||||
|
whose `status !== "ok"` instead of painting a placeholder. Zero ok
|
||||||
|
cards hides the row entirely (no header, no disclaimer); 1 card
|
||||||
|
spans 100 % width; 2-3 cards split 50/50 on `>=md`.
|
||||||
|
|
||||||
|
**Files touched**: `src/components/insights/correlation-row.tsx`,
|
||||||
|
`src/components/insights/trends-row.tsx`,
|
||||||
|
`src/components/insights/__tests__/correlation-row.test.tsx`,
|
||||||
|
`src/app/insights/page.tsx`.
|
||||||
|
|
||||||
|
## A5 — "Muster" rename + section tabs lift
|
||||||
|
|
||||||
|
`feat(insights): rename "Muster" + lift section tabs above the hero`
|
||||||
|
|
||||||
|
- **DE rename decision**: "Muster" → "Zusammenhänge". The W1b research
|
||||||
|
suggested "Trends" or "Zusammenhänge"; picked the latter because the
|
||||||
|
row directly above the correlation row already uses "Trends" for the
|
||||||
|
trend-annotation strip. "Zusammenhänge" carries the relational
|
||||||
|
meaning a correlation surface needs without borrowing terminology
|
||||||
|
from its neighbour.
|
||||||
|
- **EN parallel**: "Patterns" → "Relationships" for the same reason.
|
||||||
|
- **Tab lift**: `<InsightsSectionNav>` moved from below the advisor
|
||||||
|
card to the top of the page. Sticky scroll-anchored behaviour
|
||||||
|
preserved (incremental — a full SPA-tab rewrite is out of scope for
|
||||||
|
v1.4.22 polish). The user lands on Allgemein by default and sees
|
||||||
|
the metric-tab strip before scrolling.
|
||||||
|
|
||||||
|
**Files touched**: `messages/{en,de}.json`, `src/app/insights/page.tsx`,
|
||||||
|
`src/components/insights/__tests__/correlation-row.test.tsx`,
|
||||||
|
`src/components/insights/__tests__/correlation-card.test.tsx`.
|
||||||
|
|
||||||
|
## A6 — Token-leak fix + DE component labels
|
||||||
|
|
||||||
|
`fix(insights): strip metric tokens in recommendation prose + DE component labels`
|
||||||
|
|
||||||
|
W1a §3 traced the production "metric:WEIGHT" leak to
|
||||||
|
`src/components/insights/recommendation-card.tsx:336`: every other
|
||||||
|
AI-prose surface ran through `stripChartTokens()`, the recommendation
|
||||||
|
text didn't. Two-line fix.
|
||||||
|
|
||||||
|
The bonus DE-locale defect: `componentMood: "Mood"` (English in the
|
||||||
|
German file) plus the related `componentBp: "BD"` /
|
||||||
|
`componentCompliance: "Medis"` abbreviations. Marc reported "Mut" —
|
||||||
|
voice-to-text rendering of "Mood". All four labels now read natively
|
||||||
|
German: `"Blutdruck"`, `"Gewicht"`, `"Stimmung"`, `"Einnahmetreue"`.
|
||||||
|
|
||||||
|
i18n integrity test added to pin the four DE component labels so a
|
||||||
|
copy-paste regression can't reintroduce the leak.
|
||||||
|
|
||||||
|
**Files touched**: `src/components/insights/recommendation-card.tsx`,
|
||||||
|
`src/components/insights/__tests__/recommendation-card.test.tsx`,
|
||||||
|
`messages/de.json`,
|
||||||
|
`src/lib/__tests__/i18n-locale-integrity.test.ts`.
|
||||||
|
|
||||||
|
## Test delta
|
||||||
|
|
||||||
|
| Layer | Before | After |
|
||||||
|
| ----------- | ------ | ----- |
|
||||||
|
| Unit (vitest) | 2036 tests / 238 files | 2049 tests / 239 files |
|
||||||
|
| Integration | (covered separately) | +2 cases in `bp-in-target.test.ts` |
|
||||||
|
|
||||||
|
13 new unit cases + 2 new integration cases. All green.
|
||||||
|
|
||||||
|
## Decisions logged
|
||||||
|
|
||||||
|
- **DE rename**: "Zusammenhänge" over "Trends" — relational meaning,
|
||||||
|
doesn't collide with the Trends-row title directly above.
|
||||||
|
- **Section tabs**: kept the scroll-anchored sticky strip and lifted
|
||||||
|
it above the hero. A full SPA-tab rewrite is deferred (v1.5+).
|
||||||
|
- **All-time sub-value**: rendered as a third `<span>` inside the
|
||||||
|
existing `flex gap-3` sub-row so on mobile it wraps naturally below
|
||||||
|
`7d:` and `30d:` instead of crowding into a single line.
|
||||||
|
- **Synthetic slope**: BP-tile uses `slope = (pct7 - pct30) / 30` as
|
||||||
|
the slope-per-day signal. Confidence pinned to 1 because the source
|
||||||
|
is a deterministic difference, not a regression fit.
|
||||||
Reference in new issue
Block a user