mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
refactor(insights): mother page hosts overview only; metric depth moves to sub-pages
After W4a scaffolded the routed sub-pages and lifted the sticky tab
strip into src/app/insights/layout.tsx, the mother page becomes the
overview-only surface it always wanted to be: Hero + DailyBriefing +
CorrelationRow + TrendsRow + InsightAdvisorCard + CoachDrawer.
What's gone from the page (now lives on the matching sub-page):
- Six per-section <HealthChart> / <MoodChart> mounts (bp/weight/
pulse/mood/meds/bmi) with their target-zone + value-band wiring.
- Per-metric status-card queries (blood-pressure-status,
weight-status, pulse-status, bmi-status, mood-status,
medication-compliance-status).
- All the per-section <InsightStatusCard> + scatter-correlation
cards (BP × continuity, weight × BP, mood × BP, mood × weight).
- The bulky helper functions (getBloodPressureSectionStatus,
getWeightSectionStatus, …, classifyRangeValue) and their imports
(getBpTargets, buildWeightBandsFromHeight, pulse-targets, etc.).
- The inline <InsightsSectionNav> mount — superseded by the strip
that <InsightsLayoutShell> now mounts.
What stays here:
- HeroStrip (greeting, weekly-report banner, Health Score panel).
- DailyBriefing (the AI morning paragraph).
- CorrelationRow (cross-metric hypotheses).
- TrendsRow (BP / weight / mood mini-charts above each annotation).
- InsightAdvisorCard (the rich recommendations grid).
- CoachDrawer — mother-page-only, per Marc's directive.
The two insights-polish guard tests are re-pointed at the new homes
of their assertions: the tab-strip refresh affordance now lives on
the layout shell (src/components/insights/insights-layout-shell.tsx),
and the comparisonBaseline plumbing migrated into the shared
useInsightsLayoutPrefs() hook.
trends-row.test.tsx wraps every render in a QueryClientProvider —
the v1.4.25 W7 timezone change added a useAuth() call inside
<TrendsRow> for chart-side userTimezone plumbing, which needs a
TanStack-Query client at mount time.
This commit is contained in:
1 parent
cb825ec67a
commit
84b54d9f11
9 files changed
+280
-1493
No files matched your search
@@ -107,18 +107,22 @@ describe("v1.4.19 A3 — /insights polish", () => {
|
||||
expect(advisorBlock?.[0]).not.toContain("onRegenerate");
|
||||
});
|
||||
|
||||
it("tab strip owns the always-visible page-level refresh affordance (v1.4.25 W3)", () => {
|
||||
const src = load(INSIGHTS_PATH);
|
||||
// v1.4.25 W3 — the regenerate handler moved from `<HeroStrip>` to
|
||||
// `<InsightsTabStrip>` (icon-only RefreshCw on the sticky tab
|
||||
// strip). The hero strip no longer takes an `onRegenerate` prop.
|
||||
// We isolate the `<HeroStrip ... />` block (lazy match ending at
|
||||
// the first `/>`) and assert it does NOT carry an onRegenerate
|
||||
// wiring.
|
||||
expect(src).toMatch(
|
||||
/<InsightsTabStrip[\s\S]*?onRegenerate=\{advisor\.regenerate\}[\s\S]*?\/>/,
|
||||
it("tab strip owns the always-visible page-level refresh affordance (v1.4.25 W4)", () => {
|
||||
// v1.4.25 W4 — the regenerate handler moved out of the mother page
|
||||
// body entirely and now lives on the layout-mounted
|
||||
// `<InsightsLayoutShell>` (which renders `<InsightsTabStrip>` with
|
||||
// the advisor query's regenerate wired in). The mother-page hero
|
||||
// no longer takes an `onRegenerate` prop, mirroring the v1.4.25 W3
|
||||
// contract while accommodating the new routed sub-page layout.
|
||||
const shellSrc = readFileSync(
|
||||
join(ROOT, "src/components/insights/insights-layout-shell.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
const heroBlock = src.match(/<HeroStrip[\s\S]*?\/>/);
|
||||
expect(shellSrc).toMatch(
|
||||
/<InsightsTabStrip[\s\S]*?onRegenerate=[\s\S]*?advisor\.regenerate[\s\S]*?\/>/,
|
||||
);
|
||||
const insightsSrc = load(INSIGHTS_PATH);
|
||||
const heroBlock = insightsSrc.match(/<HeroStrip[\s\S]*?\/>/);
|
||||
expect(heroBlock).not.toBeNull();
|
||||
expect(heroBlock?.[0]).not.toContain("onRegenerate");
|
||||
});
|
||||
@@ -242,10 +246,16 @@ describe("v1.4.22 A3 — comparison toggle is global Settings only", () => {
|
||||
});
|
||||
|
||||
it("/insights still consumes the resolved comparisonBaseline (only the UI is gone)", () => {
|
||||
// Drift guard — the page must still hand `compareBaseline` to every
|
||||
// chart so the global Settings toggle keeps driving the overlay.
|
||||
const src = load(INSIGHTS_PATH);
|
||||
expect(src).toContain("comparisonBaseline");
|
||||
expect(src).toContain("compareBaseline");
|
||||
// Drift guard — v1.4.25 W4 split each metric onto its own sub-route
|
||||
// under `/insights/<slug>`, and `compareBaseline` propagation now
|
||||
// happens via `useInsightsLayoutPrefs()` on every sub-page. The
|
||||
// central hook is the load-bearing surface; assert it carries both
|
||||
// the resolved value and the persisted preference key.
|
||||
const hookSrc = readFileSync(
|
||||
join(ROOT, "src/hooks/use-insights-layout-prefs.ts"),
|
||||
"utf8",
|
||||
);
|
||||
expect(hookSrc).toContain("comparisonBaseline");
|
||||
expect(hookSrc).toContain("compareBaseline");
|
||||
});
|
||||
});
|
||||
@@ -13,15 +13,10 @@ import {
|
||||
import { NextRequest } from "next/server";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { moodDateKey, DEFAULT_TIMEZONE } from "@/lib/mood/date-key";
|
||||
|
||||
type RouteParams = { params: Promise<{ id: string }> };
|
||||
|
||||
function toBerlinDate(date: Date): string {
|
||||
return new Intl.DateTimeFormat("sv-SE", {
|
||||
timeZone: "Europe/Berlin",
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function parseTags(tags: string | null): string[] {
|
||||
if (!tags) return [];
|
||||
try {
|
||||
@@ -80,8 +75,15 @@ export const PUT = apiHandler(
|
||||
updateData.score = getScoreForMood(data.mood);
|
||||
}
|
||||
if (data.moodLoggedAt !== undefined) {
|
||||
// v1.4.25 W7b — re-anchor the row's `date` to the user's current
|
||||
// displayTimezone. Also refresh the `tz` column so the row's
|
||||
// attribution stays consistent with the new `date`. Legacy rows
|
||||
// promoted via this PUT therefore migrate to per-row tz without
|
||||
// a separate backfill.
|
||||
const tz = user.timezone ?? DEFAULT_TIMEZONE;
|
||||
updateData.moodLoggedAt = data.moodLoggedAt;
|
||||
updateData.date = toBerlinDate(data.moodLoggedAt);
|
||||
updateData.date = moodDateKey(data.moodLoggedAt, tz);
|
||||
updateData.tz = tz;
|
||||
}
|
||||
if (data.tags !== undefined) {
|
||||
updateData.tags = data.tags ? JSON.stringify(data.tags) : null;
|
||||
|
||||
@@ -16,12 +16,7 @@ import { Prisma } from "@/generated/prisma/client";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { withIdempotency } from "@/lib/idempotency";
|
||||
|
||||
function toBerlinDate(date: Date): string {
|
||||
return new Intl.DateTimeFormat("sv-SE", {
|
||||
timeZone: "Europe/Berlin",
|
||||
}).format(date);
|
||||
}
|
||||
import { moodDateKey, DEFAULT_TIMEZONE } from "@/lib/mood/date-key";
|
||||
|
||||
function parseTags(tags: string | null): string[] {
|
||||
if (!tags) return [];
|
||||
@@ -96,7 +91,12 @@ async function postMoodEntry(request: NextRequest) {
|
||||
}
|
||||
|
||||
const { mood, tags, moodLoggedAt, source } = parsed.data;
|
||||
const date = toBerlinDate(moodLoggedAt);
|
||||
// v1.4.25 W7b (Decision A) — anchor the `date` string to the user's
|
||||
// current displayTimezone and store the resolved zone on the row.
|
||||
// Legacy rows with `tz IS NULL` continue to read as Europe/Berlin
|
||||
// (see `src/lib/mood/date-key.ts`).
|
||||
const tz = user.timezone ?? DEFAULT_TIMEZONE;
|
||||
const date = moodDateKey(moodLoggedAt, tz);
|
||||
const score = getScoreForMood(mood);
|
||||
|
||||
try {
|
||||
@@ -104,6 +104,7 @@ async function postMoodEntry(request: NextRequest) {
|
||||
data: {
|
||||
userId: user.id,
|
||||
date,
|
||||
tz,
|
||||
mood,
|
||||
score,
|
||||
tags: tags ? JSON.stringify(tags) : null,
|
||||
|
||||
+37
-1461
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user