From 34af56a6d373181d63f87257a5b50e9b665c0735 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:03:51 +0800 Subject: [PATCH 1/3] feat(settings): add a gated Development section that opens the Caring Contact prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings gains a Development section at the bottom of the rail, holding the in-progress surfaces while they are being built. Its first entry opens the Caring Contact linked prototype. - Gated on the same two conditions as `mockupsEnabled()` in src/lib/env.ts, read from bundler-inlined values rather than by importing that module: no client component imports the server env contract, and tests/client-secret-surface.test.ts holds that boundary. In a production deploy that has not opted in, the section does not render — which is exactly when /mockups/* would 404. - The desktop rail is filtered from the same predicate, so it cannot advertise a section the body does not render. The scroll-spy already reads section ids from the DOM, so a hidden section cannot desync it. - Navigation uses per docs/wiring-conventions.md and closes the sheet behind it. The card states plainly that the prototype is synthetic-only. Co-Authored-By: Claude Opus 5 --- .../clinical-dashboard/settings-dialog.tsx | 45 ++++++++++++++++++- tests/settings-dialog-actions.dom.test.tsx | 27 +++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index bc7d5e097b..f81d2453d5 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { type FormEvent, type ReactNode, type UIEvent, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ArrowLeft, @@ -8,6 +9,7 @@ import { Check, ChevronRight, CircleHelp, + FlaskConical, CircleUserRound, Globe2, Keyboard, @@ -63,7 +65,8 @@ type SettingsSectionId = | "notifications" | "privacy" | "keyboard" - | "help"; + | "help" + | "development"; const SETTINGS_SECTIONS: ReadonlyArray<{ id: SettingsSectionId; navLabel: string; icon: LucideIcon }> = [ { id: "account", navLabel: "Account", icon: CircleUserRound }, @@ -74,6 +77,7 @@ const SETTINGS_SECTIONS: ReadonlyArray<{ id: SettingsSectionId; navLabel: string { id: "privacy", navLabel: "Privacy", icon: ShieldCheck }, { id: "keyboard", navLabel: "Shortcuts", icon: Keyboard }, { id: "help", navLabel: "Help & About", icon: CircleHelp }, + { id: "development", navLabel: "Development", icon: FlaskConical }, ]; const APPEARANCE_OPTIONS: ReadonlyArray<{ value: ThemePreference; label: string; icon: LucideIcon }> = [ @@ -140,6 +144,9 @@ export function SettingsDialog({ }) { const closeButtonRef = useRef(null); const guideButtonRef = useRef(null); + const visibleSettingsSections = SETTINGS_SECTIONS.filter( + (item) => item.id !== "development" || caringContactPrototypeVisible, + ); const scrollRef = useRef(null); // The title bar is sticky inside the scroll region on every breakpoint, so its // height is the amount of the scroll port a section would otherwise land @@ -533,7 +540,7 @@ export function SettingsDialog({
+ + {caringContactPrototypeVisible ? ( + +
+

Caring Contact

+

+ Linked prototype of the caring-contacts workflow. Synthetic data only — no patient record, message + or schedule here is real. +

+ +
+
+ ) : null} @@ -1035,6 +1068,14 @@ export function SettingsDialog({ ); } +// Temporary developer affordance: a way into the Caring Contact prototype while +// it is being built. Mirrors `mockupsEnabled()` in src/lib/env.ts without +// importing that server-only module: `NODE_ENV` and `NEXT_PUBLIC_*` are both +// inlined at build time, so this evaluates to `false` in a production deploy +// that has not opted in — exactly when `/mockups/*` would 404 anyway. +const caringContactPrototypeVisible = + process.env.NODE_ENV !== "production" || process.env.NEXT_PUBLIC_MOCKUPS_ENABLED === "true"; + function SettingsSection({ id, title, diff --git a/tests/settings-dialog-actions.dom.test.tsx b/tests/settings-dialog-actions.dom.test.tsx index 00d76fc531..acb2cd509c 100644 --- a/tests/settings-dialog-actions.dom.test.tsx +++ b/tests/settings-dialog-actions.dom.test.tsx @@ -97,6 +97,33 @@ afterEach(async () => { }); describe("SettingsDialog — destructive and account actions", () => { + // A Development section carries the in-progress surfaces while they are being + // built. It is gated to development builds, matching `mockupsEnabled()`, and the + // rail must not advertise a section the body does not render. The route is a + // mockup, so the entry navigates through and closes the sheet behind it. + it("opens the Caring Contact prototype from a gated Development section", () => { + renderDialog(); + const development = document.querySelector('[data-settings-section="development"]'); + expect(development).not.toBeNull(); + expect(development).toHaveTextContent("Development"); + expect(development).toHaveTextContent("Synthetic data only"); + + const prototypeLink = screen.getByTestId("settings-row-caring-contact-prototype"); + expect(prototypeLink).toHaveAttribute("href", "/mockups/caring-contacts"); + expect(prototypeLink).toHaveTextContent("Open Caring Contact prototype"); + expect(prototypeLink).toHaveTextContent("Temporary"); + expect(development?.contains(prototypeLink)).toBe(true); + + // The desktop rail lists exactly the sections that render. + const railLabels = [...document.querySelectorAll("[data-settings-nav-target]")].map((el) => + el.getAttribute("data-settings-nav-target"), + ); + const renderedIds = [...document.querySelectorAll("[data-settings-section]")].map((el) => + el.getAttribute("data-settings-section"), + ); + if (railLabels.length) expect(railLabels).toEqual(renderedIds); + }); + it("clears recent searches through the privacy action", () => { renderDialog(); const button = screen.getByRole("button", { name: "Clear recent searches" }); From 5bda0941ea2b257af23f569a25e7659bcebcdf40 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 11:57:47 +0000 Subject: [PATCH 2/3] fix(settings): use --e2 elevation token, not the legacy --shadow-soft alias The new Development section's shadow used var(--shadow-soft), tripping the design-system-contract ratchet (legacyShadowAliases 3 -> 4 for this file). Switch to shadow-[var(--e2),var(--shadow-inset)], the same pattern already used in account-setup-dialog.tsx for an identical visual effect (--shadow-soft is itself just an alias for --e2). Fixes CI job "Static PR checks" on PR #2109. --- src/components/clinical-dashboard/settings-dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index f81d2453d5..f26f0f46b7 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -1042,7 +1042,7 @@ export function SettingsDialog({ title="Development" note="In-progress surfaces, reachable only in development builds. Not clinical content." > -
+

Caring Contact

Linked prototype of the caring-contacts workflow. Synthetic data only — no patient record, message From f7d20b1ea4ca19bf7d87d20424b6a9106c3efedc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 12:01:16 +0000 Subject: [PATCH 3/3] docs(ledger): record Run PR sweep review for PR #2109 --- ...efecca63e3f476dd4dfa1757c1297c491599e715bfbe3739a04.record.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/branch-review-records/5b03318c07763efecca63e3f476dd4dfa1757c1297c491599e715bfbe3739a04.record.md diff --git a/docs/branch-review-records/5b03318c07763efecca63e3f476dd4dfa1757c1297c491599e715bfbe3739a04.record.md b/docs/branch-review-records/5b03318c07763efecca63e3f476dd4dfa1757c1297c491599e715bfbe3739a04.record.md new file mode 100644 index 0000000000..b2a5233af3 --- /dev/null +++ b/docs/branch-review-records/5b03318c07763efecca63e3f476dd4dfa1757c1297c491599e715bfbe3739a04.record.md @@ -0,0 +1 @@ +| 2026-08-18 | claude/settings-development-section (PR #2109) | 5bda0941ea2b257af23f569a25e7659bcebcdf40 | Run PR sweep: CI fix + threads + drift | Before: Static PR checks failing (design-system-contract ratchet — legacyShadowAliases at settings-dialog.tsx increased 3 -> 4 via var(--shadow-soft) in the new Development section), 0 unresolved review threads, branch 1 commit behind main (clean merge-tree). After: synced origin/main into the branch via update_pull_request_branch (no conflicts, base now 5ae2bb6e), fixed the shadow-alias regression by switching to shadow-[var(--e2),var(--shadow-inset)] (same pattern as account-setup-dialog.tsx), pushed b1f9dac6..5bda0941. No review threads existed to action. | npm run check:design-system-contract -> 'Design-system contract passed'; node scripts/run-vitest.mjs run tests/settings-dialog-actions.dom.test.tsx tests/client-secret-surface.test.ts -> 'Test Files 2 passed (2) / Tests 13 passed (13)'; npx tsc -p tsconfig.typecheck.json --noEmit -> exit 0 no diagnostics; npx eslint settings-dialog.tsx -> exit 0; npx prettier --check settings-dialog.tsx -> 'All matched files use Prettier code style!'; no provider-backed checks run |