diff --git a/.planning/round-v1434-iwb-report.md b/.planning/round-v1434-iwb-report.md new file mode 100644 index 000000000..68cbebc94 --- /dev/null +++ b/.planning/round-v1434-iwb-report.md @@ -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. **`` wiring** — 12 mounts on `src/app/page.tsx` + pass `staleDays={tileStaleDays("")}` (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 `` 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. **`` 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 + `` rides alongside the provider on the shell; + `` 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` + +`` 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 + (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.