From 6a1c91d5d462b1431e11a40640cc9ac3929ade3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 11 Nov 2026 21:50:09 +0100 Subject: [PATCH] perf(insights): mount the medication efficacy summary when near Insights > Medications sent two requests per medication on entry: the compliance calendar inside each card and the efficacy read behind the summary card, which sits below every card and shows one line per medication. The summary now mounts through `DeferUntilNear`, like the overview's sections, so the efficacy reads go out only when the reader scrolls toward it. The cards and what the summary shows are unchanged. The comment that called the summary "never a second computation" goes: the efficacy route builds the view on every request. --- .../medications/__tests__/page.test.tsx | 81 +++++++++++++++++++ src/app/insights/medications/page.tsx | 13 +-- 2 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 src/app/insights/medications/__tests__/page.test.tsx diff --git a/src/app/insights/medications/__tests__/page.test.tsx b/src/app/insights/medications/__tests__/page.test.tsx new file mode 100644 index 000000000..7503f44fd --- /dev/null +++ b/src/app/insights/medications/__tests__/page.test.tsx @@ -0,0 +1,81 @@ +import { describe, expect, it, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import type { ReactNode } from "react"; + +/** + * Insights > Medications: the efficacy summary sits below every medication + * card and asks one efficacy read per medication. On entry it holds its slot + * with a sentinel and mounts only when scrolled near, so opening the page + * sends the cards' reads and not the summary's. + */ + +vi.mock("@tanstack/react-query", () => ({ + useQuery: ({ queryKey }: { queryKey: readonly unknown[] }) => + queryKey[1] === "comprehensive" + ? { + data: { + medications: [ + { + id: "m1", + name: "Ramipril", + dose: "5 mg", + category: "BLOOD_PRESSURE", + compliance7: 100, + compliance30: 95, + streak: 7, + taken7: 7, + skipped7: 0, + missed7: 0, + }, + ], + }, + isLoading: false, + isError: false, + } + : { data: undefined, isLoading: false, isError: false }, +})); +vi.mock("@/hooks/use-auth", () => ({ + useAuth: () => ({ isAuthenticated: true }), +})); +vi.mock("@/hooks/use-ai-capability", () => ({ + useAiCapability: () => ({ available: false }), + useAiProviderState: () => ({ responseTimeoutMs: null }), +})); +vi.mock("@/components/insights/medication-efficacy-summary", () => ({ + MedicationEfficacySummary: () =>
, +})); +vi.mock("@/components/insights/sub-page-shell", () => ({ + SubPageShell: ({ children }: { children: ReactNode }) => <>{children}, +})); +vi.mock("@/components/insights/glp1-substance-curve-summary", () => ({ + Glp1SubstanceCurveSummary: () => null, +})); +vi.mock("@/components/insights/metric-target-summary", () => ({ + MetricTargetSummary: () => null, +})); +vi.mock("@/components/insights/insight-status-card", () => ({ + InsightStatusCard: () => null, +})); +vi.mock("@/components/insights/coach-launch-button", () => ({ + CoachLaunchButton: () => null, +})); +vi.mock("@/components/charts/compliance-heatmap", () => ({ + ComplianceHeatmap: () => null, +})); + +import { I18nProvider } from "@/lib/i18n/context"; +import InsightsMedikamentePage from "../page"; + +describe("/insights/medications on entry", () => { + it("renders the cards and holds the efficacy summary until scrolled near", () => { + const html = renderToStaticMarkup( + + + , + ); + + expect(html).toContain("Ramipril"); + expect(html).toContain('data-deferred-section="medication-efficacy"'); + expect(html).not.toContain("efficacy-summary-mounted"); + }); +}); diff --git a/src/app/insights/medications/page.tsx b/src/app/insights/medications/page.tsx index bd17dc53f..efe9abb03 100644 --- a/src/app/insights/medications/page.tsx +++ b/src/app/insights/medications/page.tsx @@ -24,6 +24,7 @@ import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { InsightStatusCard } from "@/components/insights/insight-status-card"; import { MetricTargetSummary } from "@/components/insights/metric-target-summary"; import { MedicationEfficacySummary } from "@/components/insights/medication-efficacy-summary"; +import { DeferUntilNear } from "@/components/insights/defer-until-near"; import { Glp1SubstanceCurveSummary } from "@/components/insights/glp1-substance-curve-summary"; import { SubPageShell } from "@/components/insights/sub-page-shell"; import { TileHeader } from "@/components/insights/tile-header"; @@ -321,12 +322,12 @@ export default function InsightsMedikamentePage() { })}
- {/* v1.28 — compact efficacy summary: reuses the same server-computed - efficacy DTO the detail-page "Wirkung" tab reads (never a second - computation), association-only, linking through to the full view. */} - ({ id: m.id, name: m.name }))} - /> + {/* v1.28 — one efficacy read per medication, below every card: mounted when near. */} + + ({ id: m.id, name: m.name }))} + /> + {/* v1.28.20 — estimated active-substance curve for GLP-1 medications (additive; renders nothing when the user has no GLP-1 medication). */}