feat(insights): wire the score-card tension + return-to-baseline and the briefing recall

The hero score card and the daily briefing accept tension, returnToBand, and
memory props, but nothing populated them server-side, so the lines never rendered.

Resolve all three server-authoritatively on the dashboard snapshot DTO (iOS rides
the same payload):

- tension — fires when the readiness composite's contributors disagree (a strongly
  favourable one alongside a strongly unfavourable one), reusing the readiness
  components the wellness ring grades and the shared disagreement detector.
  Suppressed under a clinical red-flag (a coincident-deviation that no illness
  explains) so the red-flag path is never reconciled into a calm verdict.
- returnToBand — runs the streak detector across the salient vitals and surfaces
  at most one metric that has come back inside its own personal range after a
  genuine prior out-of-band run.
- memory — maps the machine-derived coach memory block to the briefing's recall +
  forward-look.

The blocks are locale-agnostic (contributor keys + a metric type); the insights
hero localises them through the existing readiness-contributor and metric-name
labels before handing the cards their resolved strings. Each value omits rather
than guesses. The snapshot cache key now carries the locale for the recall prose.
This commit is contained in:
Marc-André Bombeck committed 2026-06-27 00:28:56 +02:00
1 parent 6c1a4b7f45
commit 4d05bcf8d9
7 files changed
+971 -3

No files matched your search

+10 -2
View File
@@ -41,6 +41,7 @@ import {
type DashboardSnapshot,
type SnapshotUserInput,
} from "@/lib/dashboard/snapshot";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
export const dynamic = "force-dynamic";
@@ -89,6 +90,13 @@ export const GET = apiHandler(async () => {
dashboardWidgetsJson: user.dashboardWidgetsJson,
};
// v1.21.2 (A4) — the briefing recall + forward-look is locale-specific prose,
// so the snapshot cache key carries the resolved locale: an EN and a DE
// session never share a snapshot cell (and never see each other's memory
// wording). The `${user.id}|` prefix still covers the key under the
// measurement-write stale-sweep.
const locale = await resolveServerLocale({ userLocale: user.locale });
// Stale-while-revalidate: a measurement write marks the analytics
// bucket stale rather than hard-evicting the snapshot, so a busy iOS
// sync serves the prior snapshot immediately (within the bucket's
@@ -98,8 +106,8 @@ export const GET = apiHandler(async () => {
// miss + synchronous rebuild here.
const body = await cachedSwr(
caches.analytics as ServerCache<DashboardSnapshot>,
`${user.id}|dashboard-snapshot`,
() => buildDashboardSnapshot(prisma, snapshotUser, { time }),
`${user.id}|dashboard-snapshot|${locale}`,
() => buildDashboardSnapshot(prisma, snapshotUser, { time, locale }),
annotate,
SNAPSHOT_CACHE_TTL_MS,
);