docs(planning): v1.4.34 IW-B close-out report

Captures the analytics lastSeenByType surface, the bfcache-friendly
Cache-Control directive, the dashboard tile-strip staleDays wiring,
the CoachLaunchProvider hoist, and the dashboard hero CTA — three
commits on develop with quality gates passing across 4205 tests.
This commit is contained in:
Marc-André Bombeck committed 2026-05-16 20:50:05 +02:00
1 parent 0729c2028b
commit 4d40454261
1 file changed
+109
+109
View File
@@ -0,0 +1,109 @@
# v1.4.34 IW-B — implementation report
Branch: `develop`. Three commits pushed:
| SHA | Title |
| ---------- | ------------------------------------------------------------------------ |
| `9a8a9e9a` | feat(analytics): expose per-type lastSeenByType + bfcache-friendly Cache-Control |
| `721e88df` | feat(i18n): add stale-hint week/month plurals and dashboard coach CTA |
| `0729c202` | feat(dashboard): hoist Coach launch provider and add hero CTA |
## Scope deltas vs the brief
1. **`/api/analytics` `lastSeenByType`** — additive field on both the default
thick path and the `?slice=summaries` slim slice. Default path captures
the freshest `measuredAt` per `MeasurementType` in the existing per-type
chunked walk (`.at(-1)` on the ascending-sorted series). Slim slice adds
`measured_at` to the existing `DISTINCT ON (type)` pass — zero extra
round-trips. Shape:
`lastSeenByType[type] = { lastSeenAt: string, daysAgo: number } | null`.
2. **`<TrendCard staleDays>` wiring** — 12 mounts on `src/app/page.tsx`
pass `staleDays={tileStaleDays("<TYPE>")}` (weight, BP sys/dia, pulse,
body-fat, sleep, steps, VO2 max, 4× glucose contexts). Mood and the
BD-Zielbereich tile keep the default `null` because they have no
underlying per-type freshness signal (mood reads `/api/mood/analytics`;
BD-Ziel is an aggregate). The `tileStaleDays` helper returns the
`daysAgo` value only when it crosses the 7-day floor — the same gate
the `<TrendCard>` uses to decide whether to paint the caption.
3. **`Cache-Control` on `/api/analytics`** — both slices stamp the typed
`NO_STORE_BUT_BFCACHE` constant (`private, max-age=0, must-revalidate`)
imported from IW-A's `src/lib/http/cache-headers.ts`. Replaces the
framework's stock `no-store, must-revalidate` which Chromium treats as
a hard bfcache breaker.
4. **`<CoachLaunchProvider>` hoist** — moved from
`src/app/insights/layout.tsx` to `src/components/layout/auth-shell.tsx`
so the drawer is reachable from every authenticated route. The
`<LayoutCoachMount>` rides alongside the provider on the shell;
`<LayoutCoachFab>` stays scoped to `/insights/**` because the floating
action would distract from the dashboard hero.
5. **Dashboard hero CTA** — new "Frag den Coach" / "Ask the coach" button
next to the existing "Hinzufügen" pill on `src/app/page.tsx`. Wires to
`coachLaunch.askCoach(null)`; mobile-first via `min-h-11` (WCAG 2.5.5)
with the matching `sm:min-h-9` desktop floor.
## Locale changes
Six locales (de, en, es, fr, it, pl) gained five additive keys each:
- `dashboard.staleHintWeeksOne` / `staleHintWeeksOther`
- `dashboard.staleHintMonthsOne` / `staleHintMonthsOther`
- `dashboard.coachCta`
`<TrendCard>` picks the right bucket based on `staleDays`:
- `8 <= staleDays <= 30` → existing `dashboard.staleHint` ("X d ago")
- `30 < staleDays <= 60` → `staleHintWeeks*` (singular at exactly 1 week)
- `staleDays > 60` → `staleHintMonths*` (singular at exactly 1 month)
Polish uses the genitive-plural form ("tygodni" / "miesięcy") that covers
every non-1 count the bucket math can produce.
## Files touched
Source:
- `src/app/api/analytics/route.ts`
- `src/app/api/analytics/__tests__/route.test.ts`
- `src/lib/analytics/summaries-slice.ts`
- `src/lib/analytics/__tests__/summaries-slice.test.ts`
- `src/lib/queries/use-analytics-query.ts`
- `src/components/charts/trend-card.tsx`
- `src/components/charts/__tests__/trend-card-stale-days.test.tsx` (new)
- `src/app/page.tsx`
- `src/components/layout/auth-shell.tsx`
- `src/components/layout/__tests__/auth-shell-coach-hoist.test.ts` (new)
- `src/app/insights/layout.tsx`
- `src/app/__tests__/insights-polish.test.ts` (existing mount-location pin updated)
Locales: `messages/{de,en,es,fr,it,pl}.json`.
## Quality gates
```
$ npx tsc --noEmit
(clean)
$ npx eslint <every touched file>
(clean)
$ npx vitest run
Test Files 396 passed (396)
Tests 4205 passed | 1 skipped (4206)
```
Eight new test cases pin the contracts:
- analytics route: `lastSeenByType` on both slices, bfcache header on both slices.
- slim slice unit: `lastSeenByType` derived from the `DISTINCT ON` pass's `measured_at`.
- trend-card: 6 cases covering the bucket-aware copy (silent, day-bucket, week-bucket, month-bucket, German singular/plural edge).
- auth-shell hoist: provider import, drawer mount, no double-mount on insights layout.
## Notes for downstream
- The dashboard hero CTA hides cleanly when `featureFlags.coach === false`
OR the provider context is null. Operators that disable the Coach surface
app-wide see the existing "Hinzufügen" pill in the hero unchanged.
- The slim slice's `lastSeenByType` is now consumed by `useAnalyticsQuery`
callers via the optional field on `AnalyticsRawPayload`; the dashboard
uses the thick slice today but the slim path is consumer-ready for the
sub-page surfaces that prefetch the lightweight payload.
- Three commits were retag-rebased mid-session because parallel agents
share the working tree on `develop`. Final state on `origin/develop`
carries `9a8a9e9a`, `721e88df`, and `0729c202` with my full commit
messages intact.