mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
feat(insights): wire <HeroStrip> + <DailyBriefing> into /insights
Replaces `<InsightsPageHero>` with the new `<HeroStrip>` band and mounts a full-width `<DailyBriefing>` directly below it, ahead of the existing advisor + status cards which remain untouched. `useInsightsAdvisorQuery` now lifts the optional `dailyBriefing` block off the cached payload and validates it client-side via `dailyBriefingSchema.safeParse`. A malformed cached briefing falls back to null so the briefing card paints its empty-state CTA instead of a half-rendered card. Legacy v1.4.19 cached payloads (no `dailyBriefing` field) round-trip unchanged. The CompareToggle migrates from the hero meta band into the DailyBriefing's metaSlot — keeps the page-level comparison control adjacent to the briefing it scopes. The hero greeting reads the user's username (HealthLog has no separate display-name field) and stops at the first whitespace so "alice" / "alice.smith" both surface "Good morning, alice". `insights-polish.test.ts` v1.4.19 A3 ratchet updated: the original "exactly one onRegenerate prop on the page" assertion was a string-count check that mis-fires now that the briefing card's empty-state CTA legitimately wires the same handler. The load-bearing constraint stays — the advisor card must NOT carry onRegenerate — and is enforced via a tighter JSX-block check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
59e63a0641
commit
7d29596998
3 files changed
+81
-9
No files matched your search
@@ -2,6 +2,10 @@
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { InsightResult } from "@/lib/ai/types";
|
||||
import {
|
||||
dailyBriefingSchema,
|
||||
type DailyBriefing as DailyBriefingPayload,
|
||||
} from "@/lib/ai/schema";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
|
||||
/**
|
||||
@@ -26,6 +30,15 @@ export interface InsightAdvisorPayload {
|
||||
cached: boolean;
|
||||
cachedAt?: string | null;
|
||||
legacyPayload?: boolean;
|
||||
/**
|
||||
* v1.4.20 phase B1 — Daily Briefing block surfaced for the new hero
|
||||
* strip + briefing card. Lives on the cached payload alongside the
|
||||
* legacy `insights` shape (see `aiInsightResponseSchema` — the
|
||||
* `.passthrough()` lets the field round-trip through any provider).
|
||||
* Validated client-side via the schema's `safeParse` to keep a
|
||||
* malformed payload from poisoning the briefing card.
|
||||
*/
|
||||
dailyBriefing?: DailyBriefingPayload | null;
|
||||
}
|
||||
|
||||
async function fetchAdvisor(
|
||||
@@ -46,7 +59,25 @@ async function fetchAdvisor(
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const json = await res.json();
|
||||
return json.data as InsightAdvisorPayload;
|
||||
const payload = json.data as InsightAdvisorPayload;
|
||||
// The cached `insights` blob may carry a `dailyBriefing` from a fresh
|
||||
// PROMPT_VERSION 4.20.x generation. Lift it onto the payload so
|
||||
// consumers don't have to know the legacy shape.
|
||||
const briefingCandidate = (payload?.insights as Record<string, unknown>)
|
||||
?.dailyBriefing;
|
||||
if (briefingCandidate != null) {
|
||||
const parsed = dailyBriefingSchema.safeParse(briefingCandidate);
|
||||
if (parsed.success) {
|
||||
payload.dailyBriefing = parsed.data;
|
||||
} else {
|
||||
// Malformed cached briefing — keep null so the UI shows the
|
||||
// empty-state CTA instead of a half-rendered card.
|
||||
payload.dailyBriefing = null;
|
||||
}
|
||||
} else {
|
||||
payload.dailyBriefing = null;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
export interface UseInsightsAdvisorResult {
|
||||
|
||||
Reference in new issue
Block a user