mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
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:
1 parent
6c1a4b7f45
commit
4d05bcf8d9
7 files changed
+971
-3
No files matched your search
@@ -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,
|
||||
);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { cn } from "@/lib/utils";
|
||||
import { HeroStrip } from "@/components/insights/hero-strip";
|
||||
import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor";
|
||||
import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query";
|
||||
import { useDashboardSnapshot } from "@/lib/queries/use-dashboard-snapshot";
|
||||
import { useDashboardDerived } from "@/components/insights/derived/use-dashboard-derived";
|
||||
// v1.4.41 W-ORG — shared shape lives in `src/types/analytics.ts` as
|
||||
// `InsightsAnalyticsData`; aliased back to the local name to keep the
|
||||
@@ -275,6 +276,20 @@ export default function InsightsPage() {
|
||||
const analyticsQuery = useAnalyticsQuery();
|
||||
const analytics = analyticsQuery.data as AnalyticsData | undefined;
|
||||
|
||||
// v1.21.2 (A4 / A5 / A6) — the dashboard snapshot is the single server source
|
||||
// for the ambient narrative the hero + briefing surface: the Tension Verdict
|
||||
// and return-to-baseline ride its `healthScore`, the briefing recall +
|
||||
// forward-look rides `briefingMemory`. The hero score number itself stays on
|
||||
// the analytics payload (unchanged); only these three additive, server-
|
||||
// resolved DTO fields are lifted from the snapshot. Reads the same shared
|
||||
// `queryKeys.dashboardSnapshot()` cell the dashboard warms, so the insights
|
||||
// visit pays at most one extra round-trip and usually a warm cache hit.
|
||||
const snapshotQuery = useDashboardSnapshot(isAuthenticated);
|
||||
const snapshotHealthScore = snapshotQuery.data?.healthScore ?? null;
|
||||
const heroTension = snapshotHealthScore?.tension ?? null;
|
||||
const heroReturnToBand = snapshotHealthScore?.returnToBand ?? null;
|
||||
const briefingMemory = snapshotQuery.data?.briefingMemory ?? null;
|
||||
|
||||
// v1.12.6 — the page owns the ONE overview derived batch. The wellness
|
||||
// strip (above the briefing) and the vitals grid (below it) both read this
|
||||
// single query instance, so the wellness lift adds no second request.
|
||||
@@ -432,6 +447,10 @@ export default function InsightsPage() {
|
||||
// briefing is actually rendered (the empty-state path owns the
|
||||
// `noProvider` branch above).
|
||||
noProviderStale={briefingPayload !== null && !advisor.hasProvider}
|
||||
// v1.21.2 (A4) — server-resolved recall + forward-look off the dashboard
|
||||
// snapshot. Both strings are already localised; null leaves the card's
|
||||
// memory block unrendered.
|
||||
memory={briefingMemory}
|
||||
/>
|
||||
) : null,
|
||||
vitals: <VitalsDashboard batch={dashboardDerived} layout={layout} />,
|
||||
@@ -491,6 +510,11 @@ export default function InsightsPage() {
|
||||
// <relative>" line with a discreet connect-provider hint, matching
|
||||
// the briefing card footer. Only when a briefing is actually shown.
|
||||
noProviderStale={briefingPayload !== null && !advisor.hasProvider}
|
||||
// v1.21.2 (A5 / A6) — server-resolved Tension Verdict + return-to-
|
||||
// baseline off the dashboard snapshot. The hero localises the keys and
|
||||
// forwards them to the score card; null leaves the card quiet.
|
||||
tension={heroTension}
|
||||
returnToBand={heroReturnToBand}
|
||||
/>
|
||||
|
||||
{/* v1.15.18 — the inline "Anpassen" toggle was removed. Customising the
|
||||
|
||||
Reference in new issue
Block a user