fix(dashboard): mount InsightsCardPreview on root page (wire B1b dashboard preview to live route)

The polished InsightsCardPreview component shipped in B1b commit 5
(d2cdf9d) was an orphan with zero non-test imports. The dashboard read
no advisor payload, so the top severity-ordered AI recommendation +
ring confidence meter + "View all" CTA was unreachable from any live
route. Wave-D design review flagged this as CRITICAL C2.

Wires the preview into the root dashboard page above the chart row,
sharing the advisor cache key with /insights so a regenerate on either
surface refreshes the other without a second LLM call. The preview
self-hides when the user has no provider configured (route 422 → null)
or the advisor returns zero recommendations.

Adds insightsPreview to DASHBOARD_WIDGET_IDS so the layout toggle
persists through Settings → Dashboard. Pins the widget into
DEFAULT_DASHBOARD_LAYOUT (visible: true, tileVisible: false) so users
discover the feature without hunting through settings, and adds a
contract test mirroring the A5 widget-enum-drift guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 03:33:01 +02:00
1 parent aae968a6d5
commit 8a5b6de27e
7 files changed
+233

No files matched your search

+32
View File
@@ -46,6 +46,8 @@ import { GettingStartedChecklist } from "@/components/onboarding/getting-started
import { TourLauncher } from "@/components/onboarding/tour-launcher";
import { RecentAchievementsCard } from "@/components/gamification/recent-achievements-card";
import { CompareToggle } from "@/components/comparison/compare-toggle";
import { InsightsCardPreview } from "@/components/insights/insights-card";
import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor";
const HealthChart = dynamic(
() =>
@@ -208,6 +210,17 @@ export default function DashboardPage() {
enabled: isAuthenticated,
});
// v1.4.16 phase D reconcile (CRITICAL C2) — pull the rich advisor
// payload so the dashboard can surface a compact preview of the top
// severity-ordered AI recommendations + ring confidence meter +
// "View all" CTA. Shares the cache with /insights via the
// queryKeys.insightsAdvisor() key so a regenerate on either surface
// refreshes the other without a second LLM round-trip. Returns null
// when the user has no provider configured (route 422 → null), so
// the preview self-hides without burning rate-limit tokens on
// unconfigured accounts.
const advisor = useInsightsAdvisorQuery(isAuthenticated);
const w = data?.summaries?.WEIGHT;
const sys = data?.summaries?.BLOOD_PRESSURE_SYS;
const dia = data?.summaries?.BLOOD_PRESSURE_DIA;
@@ -297,6 +310,12 @@ export default function DashboardPage() {
// the layout-toggle gate here. No data-floor check (the empty card is
// intentional — Marc wants the user to discover the feature).
const showAchievementsCard = isChartVisible("achievements");
// v1.4.16 phase D reconcile (CRITICAL C2) — gate the dashboard
// insights preview by the layout toggle. The component itself
// returns null when the advisor payload is missing or has no
// recommendations, so the gate is sufficient — no extra data-floor
// check needed.
const showInsightsPreview = isChartVisible("insightsPreview");
// Glucose widget — visible iff layout enables it AND at least one reading exists.
// Glucose has no separate chart slot today, so the tile flag is the
@@ -1094,6 +1113,19 @@ export default function DashboardPage() {
))}
</div>
)}
{/* v1.4.16 phase D reconcile (CRITICAL C2) — dashboard
preview of the polished AI recommendations surface.
Pinned above the chart row (out-of-band from the sorted
charts[] array) so it stays at the top regardless of
widget reorder operations. Self-hides when the user has
no provider configured OR no recommendations to surface
(`<InsightsCardPreview>` returns null), so the preview
doesn't paint an empty card on first visit. */}
{showInsightsPreview && (
<InsightsCardPreview
insight={advisor.payload?.insights ?? null}
/>
)}
{charts.map((entry) => (
<div key={entry.id} className="space-y-2">
{entry.node}
@@ -43,6 +43,10 @@ const WIDGET_LABEL_KEYS: Record<DashboardWidgetId, string> = {
bpInTarget: "dashboard.bpInTarget",
oxygenSaturation: "measurements.typeOxygenSaturation",
achievements: "achievements.title",
// v1.4.16 phase D reconcile (CRITICAL C2) — dashboard preview tile
// for the polished `<InsightsCardPreview>` (top severity-ordered AI
// recommendations + ring confidence meter + "View all" CTA).
insightsPreview: "dashboard.insightsPreview",
};
export function DashboardLayoutSection({ id }: { id: string }) {
@@ -141,6 +141,15 @@ describe("DEFAULT_DASHBOARD_LAYOUT contract", () => {
expect(ids).toContain("achievements");
});
it("includes the insightsPreview widget (v1.4.16 phase D reconcile C2)", () => {
// CRITICAL C2: dashboard preview of the polished AI recommendations
// surface. Same Zod-enum-drift trap as A5 — if the widget id isn't
// listed here, the API PUT silently 422s and the layout toggle
// never persists. Pin the contract to catch a regression early.
const ids = DEFAULT_DASHBOARD_LAYOUT.widgets.map((w) => w.id);
expect(ids).toContain("insightsPreview");
});
it("survives a round-trip through serialize → resolve unchanged", () => {
// The API persists `serializeDashboardLayout(parsed.data)` and
// reads back via `resolveDashboardLayout(row.dashboardWidgetsJson)`.
+16
View File
@@ -29,6 +29,11 @@ export const DASHBOARD_WIDGET_IDS = [
"bpInTarget",
"oxygenSaturation",
"achievements",
// v1.4.16 phase D reconcile (CRITICAL C2) — dashboard preview of the
// top severity-ordered AI recommendations + "View all" CTA. Has only
// a `visible` (chart-row) surface; tileVisible is forced false because
// there's no tile-strip representation for it.
"insightsPreview",
] as const;
export type DashboardWidgetId = (typeof DASHBOARD_WIDGET_IDS)[number];
@@ -124,6 +129,17 @@ export const DEFAULT_DASHBOARD_LAYOUT: DashboardLayout = {
// `tileVisible` is forced false because there is no tile surface
// for this widget; only the chart-row card.
{ id: "achievements", visible: true, tileVisible: false, order: 13 },
// v1.4.16 phase D reconcile (CRITICAL C2) — AI insights preview
// anchored at the top of the chart row. Default visible so users
// discover the feature without hunting through Settings →
// Dashboard; self-gates on having a generated payload AND at least
// one recommendation (`<InsightsCardPreview>` returns null
// otherwise). `tileVisible` forced false because the preview lives
// in the chart row, not the tile strip. The dashboard page
// renders this entry independently of the sorted `charts[]` array
// so it stays pinned above charts even if the user reorders other
// widgets; only the boolean visibility flag is consumed.
{ id: "insightsPreview", visible: true, tileVisible: false, order: 14 },
],
};