Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/outstanding-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ Durable, cross-session memory of everything still outstanding for this repo: ope
| #012 | P3 | rec | Slim the lazy cross-mode differentials chunk | `cross-mode-differentials.ts` is dynamically imported (correctly code-split **out** of the initial/dashboard bundle — verified), but it pulls the full ~860 KB differentials snapshot (~125 KB gzip lazy chunk) just to build a tiny `{slug,title,clinicalHinge}` + presentations + aliases catalog. A precomputed lightweight index (generator + drift check, like the `specifiers-content` split / medications `fields=index`) would cut that lazy chunk ~5–10×. Not a bundle leak — an M-effort slim. | `src/lib/cross-mode-differentials.ts`; `src/components/clinical-dashboard/cross-mode-links.tsx:150`; session 2026-07-21 (build:analyze) | 2026-07-21 |
| #013 | P3 | rec | Route-chunk + mockup catalogue JSON weight | `build:analyze`: `/specifiers` ships `specifiers-search-index.json` (~180 KB parsed), `/forms` ships `forms-catalog.json` (~132 KB), `/formulation` ships `formulation-content.json` (~52 KB, client-side local search — needs index/full split or a search endpoint, architectural). All route-scoped (not initial bundle). Also `*-mockups.tsx` (~100 KB across chunks) build though `/mockups` 404s in prod — exclude from the prod artifact. | session 2026-07-21 (build:analyze) | 2026-07-21 |
| #014 | P3 | rec | Realize the `next/image` win on signed previews | `next.config` `images` (AVIF + `*.supabase.co` `remotePatterns` pinned to the project host, from #1024) is currently inert — signed document/image previews still render as raw `<img>`. Route them through `next/image` to actually get AVIF + lazy optimization. | #1024; `src/components/clinical-dashboard/signed-image.tsx`; session 2026-07-21 | 2026-07-21 |
| #015 | P3 | task | Content-first fallback regression tests | CodeRabbit nitpick on #1024: add focused tests for the medication + registry (services/forms) content-first transitions — fallback content during loading, live record swap-in, error/empty states, and that **no authoritative verification badge shows before live governance reconciles** (the neutralized fixture flag). | #1024 CodeRabbit review; `src/components/clinical-dashboard/medication-record-page.tsx`; `src/components/registry-record-loader.tsx` | 2026-07-21 |
| #016 | P3 | rec | "Big but not easy" structural + motion perf | Deferred larger levers: (a) nonce-CSP forces every product route to `ƒ Dynamic` (zero static generation) — evaluate Partial Prerendering / static shells for the static clinical catalogues (DSM/differentials/therapy/specifiers/formulation); (b) sidebar expand/collapse animates `grid-template-columns` (biggest smoothness cost, motion-gated — needs a transform-overlay rethink); (c) Therapy Compass fetches 692 KB / 2.5 MB JSON client-side (defer until interaction + confirm brotli); (d) settings/setup/admin dialogs static-imported into the home chunk (`next/dynamic` them). | session 2026-07-21 (build route table + design audit) | 2026-07-21 |
| #017 | P3 | task | Field Web-Vitals baseline via live Lighthouse | In-sandbox runtime vitals were blocked (prod server hard-requires Supabase secrets; dev-mode CLS measured excellent at 0.00–0.04, content-first pages 0.000). Run Lighthouse against `psychiatry.tools` for real LCP/INP/CLS to prioritize #012–#016 by measured impact rather than reasoning. | session 2026-07-21 (measurement pass) | 2026-07-21 |
| #018 | P2 | task | Monitoring retrieval-depth trio misses schedules | quality-lithium-monitoring-range, quality-antipsychotic-metabolic-monitoring, quality-adhd-medication-monitoring still fail targeting ("no schedule/interval") after the #1040 gate parity — their live chunk sets apparently surface no admissible schedule sentence. Diagnose via read-only chunk queries + run #61 dump artifact (30d retention) to split corpus-content vs retrieval-depth vs fact-extraction; fix shape TBD after diagnosis. A retrieval-side fix is a protected surface (canary pair, fresh ~$2-4 authorization). | run #61 targeting misses; branch-review-ledger 2026-07-21 | 2026-07-21 |
Expand All @@ -63,3 +62,4 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th
| #003 | task | Staging tenancy release evidence outstanding | Ran GitHub Action and validated isolation | 2026-07-21 |
| #002 | task | Process-ownership fix not yet isolated on `main` | Fixed process isolation using child.pid termination | 2026-07-21 |
| #008 | rec | Dead href builders in `document-flow-routes.ts` | Not dead code (false positive): `documentReaderHref`/`documentEvidenceHref` are live via the mock wrappers in `src/components/document-search-mockups.tsx` + `src/components/master-document-flow-mockups.tsx` (rendered under `src/app/mockups/document-search/`) and covered by `tests/document-flow-routes.test.ts`; removing breaks the build. Only the production non-mock hrefs are unlinked from prod UI — a wiring gap, not dead code. | 2026-07-22 |
| #015 | task | Content-first fallback regression tests | Added `tests/registry-record-loader.dom.test.tsx` (8) + `tests/medication-record-page.dom.test.tsx` (6) covering content-first fallback paint, live swap-in, spinner/skeleton, error + not-found/unauthorized states, and the invariant that no authoritative verification badge shows before live governance reconciles (registry fixture-flag neutralization + medication governance-drop-on-error). | 2026-07-22 |
94 changes: 94 additions & 0 deletions tests/medication-record-page.dom.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import { MedicationRecordPage } from "@/components/clinical-dashboard/medication-record-page";
import type { MedicationRecord } from "@/lib/medications";

// Controllable data-hook mock so each test drives one content-first state.
const { useMedicationDetail } = vi.hoisted(() => ({ useMedicationDetail: vi.fn() }));
vi.mock("@/components/clinical-dashboard/use-medication-catalog", () => ({ useMedicationDetail }));
// The two heavy sidebar panels carry their own data concerns; stub them so these
// tests isolate the page's content-first + governance-reconciliation logic.
vi.mock("@/components/clinical-dashboard/patient-profile-panel", () => ({ PatientProfilePanel: () => null }));
vi.mock("@/components/clinical-dashboard/medication-considerations", () => ({ MedicationConsiderations: () => null }));

function mockDetail(state: { data: unknown; loading: boolean; error: string | null }) {
useMedicationDetail.mockReturnValue(state);
}

// Minimal record with no `src` "...checked" text, so the "Reviewed" identity
// badge depends purely on governance — which lets us assert the governance-drop
// invariant cleanly (isReviewed() otherwise falls back to source-review text).
const fallbackDrug: MedicationRecord = {
slug: "test-med",
name: "Fallback Drug",
class: "Test class",
subclass: "",
category: "",
accent: "#0f766e",
tag: "",
schedule: "",
stats: [],
sections: [],
quick: [],
};
const liveDrug: MedicationRecord = { ...fallbackDrug, name: "Live Drug" };

describe("MedicationRecordPage content-first states", () => {
it("renders the SSR fallback record immediately during loading, not the skeleton", () => {
mockDetail({ data: null, loading: true, error: null });
render(<MedicationRecordPage slug="test-med" fallbackRecord={fallbackDrug} />);
expect(screen.getByRole("heading", { name: "Fallback Drug" })).toBeInTheDocument();
expect(screen.queryByText(/Loading medication reference/i)).not.toBeInTheDocument();
});

it("shows the skeleton when loading with no fallback record", () => {
mockDetail({ data: null, loading: true, error: null });
render(<MedicationRecordPage slug="owner-only" />);
expect(screen.getByText(/Loading medication reference/i)).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Fallback Drug" })).not.toBeInTheDocument();
});

it("live swap-in prefers the live record over the SSR fallback", () => {
mockDetail({ data: { record: liveDrug, governance: null }, loading: false, error: null });
render(<MedicationRecordPage slug="test-med" fallbackRecord={fallbackDrug} />);
expect(screen.getByRole("heading", { name: "Live Drug" })).toBeInTheDocument();
expect(screen.queryByRole("heading", { name: "Fallback Drug" })).not.toBeInTheDocument();
});

it("renders the error panel when nothing renderable exists", () => {
mockDetail({ data: null, loading: false, error: "Network unavailable" });
render(<MedicationRecordPage slug="test-med" />);
expect(screen.getByText("Network unavailable")).toBeInTheDocument();
});

it("drops fixture governance on error so a fixture 'Reviewed' badge does not persist as authoritative", () => {
mockDetail({ data: null, loading: false, error: "boom" });
render(
<MedicationRecordPage
slug="test-med"
fallbackRecord={fallbackDrug}
fallbackGovernance={{ validationStatus: "approved" }}
/>,
);
// Content-first still paints the record...
expect(screen.getByRole("heading", { name: "Fallback Drug" })).toBeInTheDocument();
// ...but the fixture's approved-governance "Reviewed" badge must not survive
// the error, because the authoritative status is now unknown.
expect(screen.queryByText("Reviewed")).not.toBeInTheDocument();
});

it("keeps the SSR fallback governance badge while the fetch is still in flight", () => {
mockDetail({ data: null, loading: true, error: null });
render(
<MedicationRecordPage
slug="test-med"
fallbackRecord={fallbackDrug}
fallbackGovernance={{ validationStatus: "approved" }}
/>,
);
// Contrast to the error case: while loading (no error) the SSR-provided
// governance is trusted, so the "Reviewed" badge shows.
expect(screen.getByText("Reviewed")).toBeInTheDocument();
});
});
141 changes: 141 additions & 0 deletions tests/registry-record-loader.dom.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";

import { RegistryRecordLoader } from "@/components/registry-record-loader";
import type { ServiceRecord } from "@/lib/services";

// Controllable hook mock: each test drives one state so we can exercise the full
// content-first state machine (loading fallback paint, live swap-in, empty
// states) without a real fetch. The sibling registry-retry.dom.test.tsx covers
// the error → Retry affordance; this file covers everything else.
const { useRegistryRecord } = vi.hoisted(() => ({ useRegistryRecord: vi.fn() }));
vi.mock("@/lib/use-registry-records", () => ({ useRegistryRecord }));

function mockHook(state: Record<string, unknown>) {
useRegistryRecord.mockReturnValue({
status: "loading",
record: null,
linkedDocuments: [],
demoMode: false,
governance: null,
refetch: vi.fn(),
...state,
});
}

const verified: ServiceRecord = {
slug: "cmhs",
title: "Community Mental Health Service",
verification: { locallyVerified: true },
};
const unverified: ServiceRecord = {
slug: "cmhs",
title: "Community Mental Health Service",
verification: { locallyVerified: false },
};

// The render-prop receives the reconciled record, so we assert exactly which
// verification flag the loader hands downstream (the badge input) per state.
const body = (record: ServiceRecord) => (
<div>
<span data-testid="title">{record.title}</span>
<span data-testid="verified">{String(record.verification?.locallyVerified)}</span>
</div>
);

describe("RegistryRecordLoader content-first states", () => {
it("paints the public fallback record immediately during loading instead of a spinner", () => {
mockHook({ status: "loading", record: null });
render(
<RegistryRecordLoader kind="service" slug="cmhs" fallbackRecord={verified}>
{body}
</RegistryRecordLoader>,
);
expect(screen.getByTestId("title")).toHaveTextContent("Community Mental Health Service");
expect(screen.queryByText(/Loading service record/i)).not.toBeInTheDocument();
});

it("neutralizes a fixture 'locally verified' flag on the provisional paint (no authoritative badge before governance reconciles)", () => {
mockHook({ status: "loading", record: null });
render(
<RegistryRecordLoader kind="service" slug="cmhs" fallbackRecord={verified}>
{body}
</RegistryRecordLoader>,
);
// The fixture is verified, but the loading paint must not assert it before
// live governance reconciles — the flag is neutralized to false so a stale
// "verified" badge cannot flash in.
expect(screen.getByTestId("verified")).toHaveTextContent("false");
});

it("shows a spinner (not content) when loading with no fallback record", () => {
mockHook({ status: "loading", record: null });
const { container } = render(
<RegistryRecordLoader kind="service" slug="owner-only">
{body}
</RegistryRecordLoader>,
);
expect(screen.getByText(/Loading service record/i)).toBeInTheDocument();
expect(container.querySelector('[aria-busy="true"]')).not.toBeNull();
expect(screen.queryByTestId("title")).not.toBeInTheDocument();
});

it("live swap-in reconciles the verified flag ON when governance is approved", () => {
mockHook({
status: "ready",
record: unverified,
governance: { sourceStatus: "current", validationStatus: "approved" },
});
render(
<RegistryRecordLoader kind="service" slug="cmhs" fallbackRecord={verified}>
{body}
</RegistryRecordLoader>,
);
expect(screen.getByTestId("verified")).toHaveTextContent("true");
});

it("live swap-in downgrades a stale fixture flag when governance is unverified", () => {
mockHook({
status: "ready",
record: verified,
governance: { sourceStatus: "current", validationStatus: "unverified" },
});
render(
<RegistryRecordLoader kind="service" slug="cmhs" fallbackRecord={verified}>
{body}
</RegistryRecordLoader>,
);
expect(screen.getByTestId("verified")).toHaveTextContent("false");
});

it("passes the record through unchanged when governance is absent", () => {
mockHook({ status: "ready", record: verified, governance: null });
render(
<RegistryRecordLoader kind="service" slug="cmhs">
{body}
</RegistryRecordLoader>,
);
expect(screen.getByTestId("verified")).toHaveTextContent("true");
});

it("renders a not-found empty state, not the record body", () => {
mockHook({ status: "not_found", record: null });
render(
<RegistryRecordLoader kind="form" slug="missing">
{body}
</RegistryRecordLoader>,
);
expect(screen.getByText(/No form record found/i)).toBeInTheDocument();
expect(screen.queryByTestId("title")).not.toBeInTheDocument();
});

it("renders the session-expired panel when unauthorized", () => {
mockHook({ status: "unauthorized", record: null });
render(
<RegistryRecordLoader kind="service" slug="cmhs">
{body}
</RegistryRecordLoader>,
);
expect(screen.getByText("Session expired")).toBeInTheDocument();
});
});
Loading