diff --git a/.claude/skills/gates/SKILL.md b/.claude/skills/gates/SKILL.md index bffcd9267f..8fa7850dcd 100644 --- a/.claude/skills/gates/SKILL.md +++ b/.claude/skills/gates/SKILL.md @@ -26,7 +26,7 @@ Check these before believing any result. for exactly this reason — if installed packages do not match `package-lock.json`, treat any test, lint, or typecheck result as void until `npm ci` has run. Its own failure message says as much. - **`verify:cheap` stops at the first failing check.** Everything after that point never ran. Do not - describe the change as broadly verified when the gate died at check 2 of 39. + describe the change as broadly verified when the gate died at check 2 of 40. - **Changed-file formatting is required in CI but is not part of `verify:cheap`.** A locally green `verify:cheap` can still fail CI on formatting. During iteration, format only task-owned files. Before a push, follow `AGENTS.md`: from an isolated or otherwise fully owned worktree run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0cbce0fad..9dfa335d0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -380,6 +380,10 @@ jobs: if: needs.changes.outputs.docs_changed == 'true' || needs.changes.outputs.static_heavy_changed == 'true' run: npm run sitemap:check + - name: Mockup retirement policy + if: needs.changes.outputs.docs_changed == 'true' || needs.changes.outputs.static_heavy_changed == 'true' + run: npm run check:mockups + - name: Repo awareness snapshot if: needs.changes.outputs.docs_changed == 'true' || needs.changes.outputs.static_heavy_changed == 'true' run: npm run check:repo-awareness-snapshot diff --git a/.prettierignore b/.prettierignore index 5a8dea64ea..8b4d1f8636 100644 --- a/.prettierignore +++ b/.prettierignore @@ -53,6 +53,15 @@ docs/outstanding-issues.md # tests/branch-review-index.test.ts gates the generator's escaping, ordering and # per-record coverage far more strictly than Prettier's alignment would. docs/branch-review-index.md +# Content-addressed immutable review records: the filename IS the SHA-256 of the row, so +# ANY reformatting breaks the record's own guard. Found 2026-09-02 — `npm run format`, which +# AGENTS.md requires before every ordinary push, silently rewrote a freshly appended record +# and `check:branch-review-ledger` then rejected it as "filename must be the SHA-256 content +# address for its record row". Existing records happened to be Prettier-clean, so the trap +# only fires on a row whose prose Prettier wants to touch. Structure is not lost by ignoring +# them — `check:branch-review-ledger` verifies the address, cell count, dates and duplicates +# far more strictly than Prettier's formatting ever could. +docs/branch-review-records/ # Verbatim user-provided prompt inputs retained for cloud review. docs/prompts/codex-cloud-review/ # Generated by `scripts/generate-outstanding-issues-snapshot.mjs` from diff --git a/CLAUDE.md b/CLAUDE.md index a7bc1994c6..416b83b770 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,7 +95,7 @@ Verification pyramid — run the **smallest gate that covers the change**, then | Gate | What it is | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `npm run test:focused -- --files ` | Source-only iteration. Fails closed for deleted files and test infrastructure — then run `npm run test`. | -| `npm run verify:cheap` | The broad local gate: 36 static/consistency gates + `lint` + `typecheck` + full offline unit suite; use for cross-module risk, not automatically | +| `npm run verify:cheap` | The broad local gate: 37 static/consistency gates + `lint` + `typecheck` + full offline unit suite; use for cross-module risk, not automatically | | `npm run verify:pr-local` | Risk-routed PR mirror: focused docs/workflow contracts for recognised light scope, fail-closed heavy checks for executable or unknown scope. `-- --dry-run --files ` shows selection. | | `npm run verify:ui` | Chromium production journeys. Run `npm run ensure` first. | | `npm run verify:phone-chrome` | Phone-chrome changes; selects affected owners/journeys before escalating to `verify:ui` | diff --git a/data/repo-awareness-snapshot.json b/data/repo-awareness-snapshot.json index 944e44fd59..cf8be0221b 100644 --- a/data/repo-awareness-snapshot.json +++ b/data/repo-awareness-snapshot.json @@ -1,8 +1,8 @@ { "version": "repo-awareness-snapshot-v2", "captured_revision": { - "sha": "a683a5a6af3d400574bb8ef55e199e0c8665c60f", - "committed_at": "2026-09-02T11:35:41+00:00" + "sha": "fce054c83d8ee57c0ba9701b6855b11516dc1cc2", + "committed_at": "2026-09-02T15:15:38+00:00" }, "routes": { "modes": [ @@ -344,16 +344,6 @@ "file": "src/app/mockups/answer-chat-redesign/page.tsx", "area": "mockup" }, - { - "path": "/mockups/answer-evidence-popups", - "file": "src/app/mockups/answer-evidence-popups/page.tsx", - "area": "mockup" - }, - { - "path": "/mockups/answer-home-proposal", - "file": "src/app/mockups/answer-home-proposal/page.tsx", - "area": "mockup" - }, { "path": "/mockups/answer-loading-redesign", "file": "src/app/mockups/answer-loading-redesign/page.tsx", @@ -649,36 +639,11 @@ "file": "src/app/mockups/document-navigation-final/page.tsx", "area": "mockup" }, - { - "path": "/mockups/document-navigation-final-review", - "file": "src/app/mockups/document-navigation-final-review/page.tsx", - "area": "mockup" - }, - { - "path": "/mockups/document-navigation-pane", - "file": "src/app/mockups/document-navigation-pane/page.tsx", - "area": "mockup" - }, { "path": "/mockups/document-navigation-perfected", "file": "src/app/mockups/document-navigation-perfected/page.tsx", "area": "mockup" }, - { - "path": "/mockups/document-phone-fused-directions", - "file": "src/app/mockups/document-phone-fused-directions/page.tsx", - "area": "mockup" - }, - { - "path": "/mockups/document-phone-title", - "file": "src/app/mockups/document-phone-title/page.tsx", - "area": "mockup" - }, - { - "path": "/mockups/document-phone-title-refined", - "file": "src/app/mockups/document-phone-title-refined/page.tsx", - "area": "mockup" - }, { "path": "/mockups/document-phone-zero-chrome", "file": "src/app/mockups/document-phone-zero-chrome/page.tsx", @@ -1461,9 +1426,9 @@ ], "counts": { "modes": 16, - "pages": 204, + "pages": 197, "product_pages": 67, - "mockup_pages": 137, + "mockup_pages": 130, "redirects": 18, "api": 60 } @@ -3035,6 +3000,11 @@ "section": "root", "catalogued": false }, + { + "path": "docs/mockup-retirement-policy.md", + "section": "root", + "catalogued": true + }, { "path": "docs/mode-aware-clinical-ask-local-handover.md", "section": "root", @@ -4447,8 +4417,8 @@ } ], "counts": { - "documents": 582, - "catalogued": 113, + "documents": 583, + "catalogued": 114, "uncatalogued": 469, "sections": 21 } @@ -10614,6 +10584,14 @@ "outcome": "implemented; focused + ci-workflow contracts green; Production UI wall-time confirmation pending first CI run", "checks": "vitest focused+ci-workflows; playwright-pr-shards --validate; check:playwright-browser-revision; check:outstanding-issues" }, + { + "date": "2026-09-02", + "ref": "claude/gate-audit-ujhkqb", + "head": "4dcd8ddece3b4ec3cbe75cace784214eb7e14be9", + "scope": "prlanded", + "outcome": "merged clean, content diff empty against branch tip", + "checks": "verify:pr-local green (multiple re-runs across 8 conflict resolutions), full CI green after one confirmed flake (Production UI privacy-sticky-chrome strict-mode double-render, unrelated to this PR's diff) re-ran and passed" + }, { "date": "2026-07-28", "ref": "PR #1298 / `codex/fix-p2-audit-20260719`", @@ -16822,6 +16800,14 @@ "outcome": "Re-check only: still CONFLICTING vs origin/main. Semantic conflicts include privacy/page.tsx, answer-render-policy.ts, answer-request.ts, source-authority-metadata.ts, upload/bulk routes, settings-dialog, drift-manifest (+ more). Merge aborted; no force-resolve.", "checks": "merge origin/main and/or conflict re-check only; no provider-backed checks run" }, + { + "date": "2026-09-02", + "ref": "claude/caring-contacts-vocabulary-tmnc89", + "head": "969cc7f8889181758c93eb725e8eab7be6dc5e1e", + "scope": "prlanded", + "outcome": "Merged and verified. Two-dot content diff between the squash commit and the branch tip 21f4ef3da was empty, so all fifteen commits landed and nothing was orphaned by the squash+auto-merge race. The ~20 queued inbox requests it carried are applied by PR #2559, which rebased onto this squash and absorbed them; #Z5P2BW, #0HYHTH and #AGRAKQ are archived there, and the two follow-ups this branch filed (#686WHW, #1NMMZS) are added.", + "checks": "prlanded content diff empty; full CI green on 21f4ef3da (PR required, Build, Unit coverage, Production UI 1/2/3 + critical, Caring Contacts database, Safety and config checks, Lighthouse, Static PR checks, PR policy, PR mergeability, Semgrep, Gitleaks, GitGuardian); one review thread, resolved" + }, { "date": "2026-07-29", "ref": "claude/latency-fixes-2026-07-29", @@ -17094,6 +17080,14 @@ "outcome": "supersedes prior #1160 row: final HEAD after prettier site-map; merge origin/main clean; stale sitemap fixed; no threads", "checks": "vitest site-map pass; sitemap:check pass; no provider-backed checks run" }, + { + "date": "2026-09-02", + "ref": "claude/mockup-retirement-xw0vmn", + "head": "9985c1709eff661f42a42be3744e585c831816a5", + "scope": "mockup retirement policy and sweep", + "outcome": "Reviewed by two fresh agents before push; both found real defects and all were fixed on this head. (1) An adversarial 'argue every candidate is still alive' pass withdrew two of nine retirement candidates. document-navigation-final is partly adopted, not superseded: production document-viewer/section-nav.tsx:143 renders its heading as the string 'flex items-baseline justify-between px-0.5 pb-2', which exists in exactly two files repo-wide (production and that draft at line 278) and is absent from document-navigation-perfected, so production is a hybrid of two drafts. document-phone-zero-chrome returned UNCERTAIN because the kept document-navigation-contract carries its 'zero new chrome, sheet not pane' thesis verbatim and both landed in the one squash 6230c4db, so authorship cannot be established. Both restored, along with their chrome-suppression branches in mockups-layout-client.tsx, which would otherwise have shipped a duplicate composer over both studies. Nine retirements became seven. That pass also confirmed the winner identification independently (flexGrow/weight/pending/Loader2 appear in perfected and in no other draft) and re-ran check:dead-code-candidate, reading every distinct refusal reason and pulling the pinning file for the two that could plausibly have been real; both were bare-name collisions. (2) A frontend-ui-reviewer pass on the full diff found that two doc corrections introduced by this branch were themselves false, both since verified with picomatch: mockups are NOT exempt from CodeRabbit (.coderabbit.yaml's '!mockups/**' is root-anchored and excludes only the repo-root notes directory) and NOT blind to knip (its ignore is a basename filter, so route page.tsx files and _/mockups/_* subtrees are still scanned; what suppresses findings is check:knip omitting unused-file analysis repo-wide). It also found the new gate advertised enforcement nothing invoked (no caller passed --diff), that the gate missed relative imports, dynamic imports, CSS composes and route-path literals, and that the sweep had consequently left four dead pathname branches in mockups-layout-client.tsx which the gate passed clean. All fixed with tests; check:mockups now runs --diff auto so all three modes are enforced in verify:cheap and CI. Two fail-open holes closed: listRouteSlugs returned [] on a missing route root (passing as '0 routes indexed'), and the Retired table's column order was trusted positionally. Three findings filed to the /issues inbox rather than fixed here: the calculators mockup still serving prescribing/ECT/admission directives that PR #2491 removed from production on clinical-safety grounds (those routes 404 in production, so not patient-facing); the loss of the repo's only source-text heading-hierarchy contract; and two pieces of pre-existing dead wiring. Note the heavy CI jobs are skipped on draft PRs by design in this repo, so server-side green is not yet demonstrated.", + "checks": "check:mockups (self-test + 72 routes indexed, 14 retired + 13 deleted files recorded and unreferenced); npm run test (947 files, 12098 passed, 4 skipped); lint; typecheck; check:gate-manifest (38 gates, 35 static, consistent); sitemap:check; docs:check-links (4742 refs); check:outstanding-issues; check:ledger-write-discipline; prettier --check on all changed files; bundle budget on two cold builds (mockups 611.6 KiB / 160 chunks / 133 routes, -0.3% vs baseline). check:dead-code-candidate --diff REFUSES (64/201) and is reported as refusing, not green: every refusal is a bare-symbol-name collision on a file-local or framework-convention identifier; no threshold or refusal-list entry was changed. Not run: verify:ui (only non-mockup change is the /mockups route-group shell, which 404s in production) and any provider-backed gate." + }, { "date": "2026-07-28", "ref": "PR #1297 / `motion-audit-fixes-clean`", diff --git a/docs/README.md b/docs/README.md index 8a78118a37..ecb39eb275 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,6 +27,7 @@ npm run docs:check-links - [frontend-architecture.md](frontend-architecture.md) — shell, routing, dashboard module structure - [wiring-conventions.md](wiring-conventions.md) — page/button wiring conventions and the dead-button / orphan-route gates - [search-chrome-behaviour.md](search-chrome-behaviour.md) — shared search-chrome contract: composer ownership, phone edge-to-edge dock, hide/reveal reserves +- [mockup-retirement-policy.md](mockup-retirement-policy.md) — when a mockup may be deleted, who decides, what evidence is required, and the three tiers that keep developer-gated prototypes out of cleanup scope - [search-results-bar-decisions.md](search-results-bar-decisions.md) — shared results-bar anatomy, why the filter shelf is scoped to two modes, and what is deliberately not done - [deployment-architecture.md](deployment-architecture.md) — app/worker/Supabase deployment topology - [ingestion-state-machine.md](ingestion-state-machine.md) — ingestion job lifecycle and states diff --git a/docs/agents/wiring-and-bundle-budget.md b/docs/agents/wiring-and-bundle-budget.md index 7c5faddb8a..fcaf7e7a18 100644 --- a/docs/agents/wiring-and-bundle-budget.md +++ b/docs/agents/wiring-and-bundle-budget.md @@ -31,11 +31,34 @@ action must perform one; a page that ships must be reachable. ` - ); -} - -function CloseButton({ label = "Close popup" }: { label?: string }) { - return ( - - ); -} - -function SourceCapsule() { - return ( - - ); -} - -function SourceRows() { - return ( -
- {sources.map(([title, meta, support, score], index) => ( -
- -
- ))} -
- ); -} - -function ActionRow({ children }: { children: ReactNode }) { - return ( -
- {children} -
- ); -} - -function ButtonText({ children }: { children: ReactNode }) { - return {children}; -} - -function DesktopSourcePreviewDemo() { - return ( -
-

- Clozapine monitoring should include FBC/ANC, myocarditis symptoms, metabolic checks, constipation prevention, - and shared-care communication. -

-
- -
- -
-
-

- The answer body stays in place. The preview floats above nearby content instead of pushing the support card - down. -

-
- ); -} - -function SourcePreviewPopover() { - return ( -
-
-
-

Source preview

-

- Check the best passage, status, and source action before opening the PDF. -

-
- 3 sources -
- -
- “Monitor FBC/ANC, myocarditis symptoms, metabolic risk, constipation, and shared-care communication during - clozapine initiation.” -
- - - - Open source - - - - Copy quote - - - - View cited section - - -
- ); -} - -function MobileSheetFrame({ - title, - description, - children, -}: { - title: string; - description: string; - children: ReactNode; -}) { - return ( -
-
-
-
-

{title}

-

{description}

-
- -
-
{children}
-
- ); -} - -function EvidenceTabs({ selected }: { selected: string }) { - return ( -
-
- {tabs.map(([label, count, Icon]) => { - const active = label === selected; - return ( - - ); - })} -
-
- ); -} - -function FeedbackPanel() { - return ( -
-

Clinical verification

-

- Mark whether the linked evidence is safe to use, needs correction, or is source-insufficient. -

-
- - - Verified - - - - Needs correction - - - - Source insufficient - -
-
- ); -} - -function EvidenceSummaryMini({ selected }: { selected: string }) { - return ( -
-
-
-

{selected} evidence

-

- Showing only the items used to support this answer. -

-
- Source-backed -
-
- ); -} - -function TablePreview({ expanded = false }: { expanded?: boolean }) { - return ( -
-
- Clozapine monitoring schedule - {expanded ? ( - Verify against source - ) : ( - - )} -
-
- - - - {["Domain", "Monitoring", "Action"].map((header) => ( - - ))} - - - - {tableRows.map((row) => ( - - {row.map((cell) => ( - - ))} - - ))} - -
- {header} -
- {cell} -
-
-
- ); -} - -function SourceCards() { - return ( -
- {sources.map(([title, meta, support, score]) => ( -
-
- -
-

{title}

-

{meta}

-
- - {support} - {score} - -
-

- Passage supports monitoring and escalation wording. Open source to inspect the highlighted PDF section. -

- - - - Open source - - - - Scope to this - - -
- ))} -
- ); -} - -function QuoteCards() { - return ( -
- {[1, 2].map((item) => ( -
-
-

Exact quote

- {item === 1 ? "p.12" : "p.14"} -
-
- “FBC/ANC monitoring, myocarditis symptoms, metabolic review and constipation prevention should be - checked during initiation and ongoing care.” -
- - - - Copy - - - - Ask about quote - - -
- ))} -
- ); -} - -function ImageEvidence() { - const items = [ - { label: "Table crop", icon: Table2, body: "Monitoring domains extracted from a table image." }, - { label: "PDF page region", icon: FileImage, body: "Page crop used to check source layout and nearby wording." }, - ] as const; - - return ( -
- {items.map(({ label, icon: Icon, body }) => ( -
-
-
-
-
-
-

{label}

-

{body}

-
-
- ))} -
- ); -} - -function PdfLinks() { - return ( -
- {["Clozapine physical health protocol", "Shared-care communication checklist"].map((title, index) => ( - - ))} -
- ); -} - -function EvidenceMap() { - const rows = [ - ["Monitoring", "Moderate", "2", "Current / locally reviewed"], - ["Escalation", "Strong", "3", "Current / locally reviewed"], - ["Metabolic review", "Partial", "1", "Review due"], - ] as const; - return ( -
- - - - {["Section", "Support", "Citations", "Source status"].map((header) => ( - - ))} - - - - {rows.map((row) => ( - - {row.map((cell) => ( - - ))} - - ))} - -
- {header} -
- {cell} -
-
- ); -} - -function MobileEvidencePanel({ selected }: { selected: string }) { - return ( - -
- {selected === "Tables" ? : } - - {selected === "Tables" ? : null} - {selected === "Sources" ? : null} - {selected === "Images" ? : null} - {selected === "Quotes" ? : null} - {selected === "PDFs" ? : null} - {selected === "Map" ? : null} -
-
- ); -} - -function DesktopEvidenceModal() { - return ( -
- - ); -} - -function TableDialog() { - return ( -
-
-
-

Clozapine monitoring table

-

- Expanded from visual evidence. Use the source PDF for final verification. -

-
- -
-
- -
-
- ); -} - -function WeakEvidencePopup() { - return ( -
-
- - - -
-
-

Evidence support is limited

- Do not copy into notes -
-

- Treat this as source finding only. The closest indexed passages are nearby, but they do not directly support - a clinical answer yet. -

- - - - Open closest passage - - - - Restrict to current local sources - - -
-
-
- ); -} - -export default function AnswerEvidencePopupsMockupPage() { - return ( - -
-
- -
- -
- - - -
- -
-
- {["Tables", "Sources", "Images", "Quotes", "PDFs", "Map"].map((tab) => ( -
-

- {tab} tab -

- -
- ))} -
-
- -
- -
- -
- -
- -
- -
-
-
- ); -} diff --git a/src/app/mockups/answer-home-proposal/page.tsx b/src/app/mockups/answer-home-proposal/page.tsx deleted file mode 100644 index 9103f0de9c..0000000000 --- a/src/app/mockups/answer-home-proposal/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { AnswerHomeProposalMockupsPage } from "@/components/answer-home-proposal-mockups"; - -export default function AnswerHomeProposalMockupRoute() { - return ; -} diff --git a/src/app/mockups/document-navigation-final-review/page.tsx b/src/app/mockups/document-navigation-final-review/page.tsx deleted file mode 100644 index 038106df68..0000000000 --- a/src/app/mockups/document-navigation-final-review/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Metadata } from "next"; - -import { DocumentNavigationFinalReviewMockups } from "@/components/document-navigation-final-review-mockups"; - -export const metadata: Metadata = { - title: "Document navigation, final review - PsychSift", - description: - "Two final document-navigation versions at desktop, tablet, and phone, with every issue from the study resolved.", -}; - -export default function DocumentNavigationFinalReviewMockupPage() { - return ; -} diff --git a/src/app/mockups/document-navigation-pane/page.tsx b/src/app/mockups/document-navigation-pane/page.tsx deleted file mode 100644 index 77242caf4e..0000000000 --- a/src/app/mockups/document-navigation-pane/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Metadata } from "next"; - -import { DocumentNavigationPaneMockups } from "@/components/document-navigation-pane-mockups"; - -export const metadata: Metadata = { - title: "Document navigation pane mockups - PsychSift", - description: - "Three navigation panes anchored to the universal header for the document page, shown on desktop, tablet, and phone.", -}; - -export default function DocumentNavigationPaneMockupPage() { - return ; -} diff --git a/src/app/mockups/document-phone-fused-directions/page.tsx b/src/app/mockups/document-phone-fused-directions/page.tsx deleted file mode 100644 index 9ec47701b1..0000000000 --- a/src/app/mockups/document-phone-fused-directions/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Metadata } from "next"; - -import { DocumentPhoneFusedDirectionsMockups } from "@/components/document-phone-fused-directions-mockups"; - -export const metadata: Metadata = { - title: "Document phone fused row directions - PsychSift", - description: "Three directions built on the fused phone title row for the document page.", -}; - -export default function DocumentPhoneFusedDirectionsMockupPage() { - return ; -} diff --git a/src/app/mockups/document-phone-title-refined/page.tsx b/src/app/mockups/document-phone-title-refined/page.tsx deleted file mode 100644 index 6d54c263fe..0000000000 --- a/src/app/mockups/document-phone-title-refined/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Metadata } from "next"; - -import { DocumentPhoneTitleRefinedMockups } from "@/components/document-phone-title-refined-mockups"; - -export const metadata: Metadata = { - title: "Document phone title refined mockups - PsychSift", - description: "Three corrected variants of the fused phone title row for the document page.", -}; - -export default function DocumentPhoneTitleRefinedMockupPage() { - return ; -} diff --git a/src/app/mockups/document-phone-title/page.tsx b/src/app/mockups/document-phone-title/page.tsx deleted file mode 100644 index cb68512498..0000000000 --- a/src/app/mockups/document-phone-title/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { Metadata } from "next"; - -import { DocumentPhoneTitleMockups } from "@/components/document-phone-title-mockups"; - -export const metadata: Metadata = { - title: "Document phone title mockups - PsychSift", - description: "Three ways to keep the document title visible on a phone alongside the navigation pane.", -}; - -export default function DocumentPhoneTitleMockupPage() { - return ; -} diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx index 7f427c1cf8..e28e1202d9 100644 --- a/src/app/mockups/mockups-layout-client.tsx +++ b/src/app/mockups/mockups-layout-client.tsx @@ -12,14 +12,14 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { const isFavouritesPageMockup = pathname.startsWith("/mockups/favourites-"); const isDocumentSearchMockup = pathname.startsWith("/mockups/document-search"); const isDocumentTopNavigationMockup = pathname === "/mockups/document-top-navigation"; - const isDocumentNavigationPaneMockup = pathname === "/mockups/document-navigation-pane"; - const isDocumentPhoneTitleMockup = pathname.startsWith("/mockups/document-phone-"); // Draws its own universal top bar and document composer inside every frame, so // the shared composer would read as a second, real search bar over the study. const isDocumentNavigationContractMockup = pathname === "/mockups/document-navigation-contract" || pathname === "/mockups/document-navigation-perfected" || - pathname.startsWith("/mockups/document-navigation-final"); + pathname === "/mockups/document-navigation-final"; + // The one surviving document-phone study; its three siblings were retired 2026-09-02. + const isDocumentPhoneZeroChromeMockup = pathname === "/mockups/document-phone-zero-chrome"; const isSourceOverlayRedesignMockup = pathname === "/mockups/document-search/source-overlays"; const isStandaloneDocumentFlow = pathname === "/mockups/document-search"; const isUniversalSearchRedesignMockup = pathname === "/mockups/universal-search-redesign"; @@ -58,7 +58,6 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { // Owns the also-matches panel as the subject; shared composer chrome would // sit on top of a study about results-card identity. const isAlsoMatchesAccentMockup = pathname === "/mockups/also-matches-accents"; - const isAnswerHomeProposalMockup = pathname === "/mockups/answer-home-proposal"; // Every direction in this study draws its own top bar, transcript and // composer inside phone/desktop frames — the reference system under review // sits directly between them, so shared chrome would read as a second real @@ -166,8 +165,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { ? "therapy-compass" : isDocumentSearchMockup || isDocumentTopNavigationMockup || - isDocumentNavigationPaneMockup || - isDocumentPhoneTitleMockup || + isDocumentPhoneZeroChromeMockup || isDocumentNavigationContractMockup ? "documents" : "answer" @@ -177,8 +175,7 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { !isFavouritesPageMockup && !isStandaloneDocumentFlow && !isDocumentTopNavigationMockup && - !isDocumentNavigationPaneMockup && - !isDocumentPhoneTitleMockup && + !isDocumentPhoneZeroChromeMockup && !isDocumentNavigationContractMockup && !isUniversalSearchRedesignMockup && !isCalculatorsSearchPageMockup && @@ -192,7 +189,6 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { !isWarningConsolidationMockup && !isWarningLineMockup && !isAlsoMatchesAccentMockup && - !isAnswerHomeProposalMockup && !isAnswerChatRedesignMockup && !isAnswerChatPerfectedMockup && !isAnswerLoadingRedesignMockup && @@ -225,7 +221,6 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { !isWarningConsolidationMockup && !isWarningLineMockup && !isAlsoMatchesAccentMockup && - !isAnswerHomeProposalMockup && !isAnswerChatRedesignMockup && !isAnswerChatPerfectedMockup && !isAnswerLoadingRedesignMockup && diff --git a/src/components/answer-home-proposal-mockups.tsx b/src/components/answer-home-proposal-mockups.tsx deleted file mode 100644 index eda7ead82b..0000000000 --- a/src/components/answer-home-proposal-mockups.tsx +++ /dev/null @@ -1,270 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { MessageSquareText, ShieldAlert, ShieldCheck } from "lucide-react"; - -import { cn } from "@/components/ui-primitives"; - -// Design-scratch: the concrete proposal, drawn as before/after of the whole -// answer-mode hero rather than of the notice alone — because the change moves -// information between two places. The scope badge is removed and the corpus -// name it carried moves into the subtitle that already exists. -// -// Pinned by tests/privacy-ui.test.ts and reproduced verbatim on both sides: -// "Do not enter patient-identifiable information." and the "Privacy and data -// processing" link to /privacy. The subtitle is ordinary product copy -// (src/lib/ui-copy.ts, sharedHomePresentation.answer.subtitle) and is not pinned. - -const OBLIGATION = "Do not enter patient-identifiable information."; -const PRIVACY_LINK = "Privacy and data processing"; - -const focusRing = - "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"; - -function Hero({ subtitle, phone }: { subtitle: React.ReactNode; phone?: boolean }) { - return ( -
- - - -

- How can I help? -

-

{subtitle}

-
- ); -} - -function Composer({ phone }: { phone?: boolean }) { - return ( -
- - + - - Ask a clinical question… - -
- ); -} - -/** Today: three notices, three type sizes, two opposing shields. */ -function BeforeFooter() { - return ( -
-

- - {OBLIGATION} -

-

- - {PRIVACY_LINK} - -

-

- - - Searches indexed clinical sources - - - Clinical Guide library -

-
- ); -} - -/** Proposed: one quiet line, words only. */ -function AfterFooter() { - return ( -

- {OBLIGATION} Verify answers against the - cited source.{" "} - - {PRIVACY_LINK} - -

- ); -} - -function Panel({ side, phone, children }: { side: "before" | "after"; phone?: boolean; children: React.ReactNode }) { - const isAfter = side === "after"; - return ( -
-
- - {isAfter ? "Proposed" : "Today"} - - {phone ? "390 px" : "1440 px"} -
-
-
{children}
-
-
- ); -} - -const changes: Array<{ what: string; why: string; pinned?: boolean }> = [ - { - what: "Remove the “Searches indexed clinical sources · Clinical Guide library” badge", - why: "Accent-blue, semibold, 14px — the loudest element on the screen, and a capability claim rather than a safety statement. Pre-query it can only assert; post-query the real sources appear as citations.", - }, - { - what: "Remove the ShieldCheck", - why: "A shield-and-tick reads as “verified”. The comment above this component in answer-status.tsx warns that pre-query copy must not assert every indexed source is verified/current (PT-06) — the wording obeys that, the icon undoes it. It also sat ~40px from a ShieldAlert meaning the opposite.", - }, - { - what: "Move “Clinical Guide library” into the subtitle", - why: "The one genuinely useful fact in the badge — that this searches a curated clinical corpus, not the open web. The subtitle already exists, so the fact keeps a home without a second voice. Ordinary product copy, not pinned by any test.", - }, - { - what: "Remove the ShieldAlert from the privacy line", - why: "With the competing shield gone, the amber icon is the only ornament left on a line that now carries weight-based hierarchy instead.", - }, - { - what: "Join the privacy link to the same line", - why: "It previously wrapped alone with no lead-in, reading as navigation rather than as the footnote to the warning above it.", - }, - { - what: "Add “Verify answers against the cited source.”", - why: "The statement the screen never made. Every other clinical mode carries an equivalent; answer mode — the only one that synthesises prose — carried none. This is new clinical-safety copy and needs governance sign-off.", - pinned: true, - }, -]; - -export function AnswerHomeProposalMockupsPage() { - return ( -
-
-
-

- Answer home — proposed change -

-

- Drop the scope badge. Keep the corpus name. One quiet line. -

-

- Three stacked notices in three type sizes with two opposing shields become one line of text, and the only - fact worth keeping from the badge moves into the subtitle that was already there. -

-
-
- -
-
-

- Phone — the surface in the screenshot -

-
- - - - - - - - Ask a clinical question or search your{" "} - Clinical Guide library. - - } - /> - - - -
-
- -
-

- Desktop -

-
- - - - - - - - Ask a clinical question or search your{" "} - Clinical Guide library. - - } - /> - - - -
-
- -
-

- What changed, and why -

-
    - {changes.map((c, i) => ( -
  1. - {i + 1} -
    -

    - {c.what} - {c.pinned ? ( - - Needs governance sign-off - - ) : null} -

    -

    {c.why}

    -
    -
  2. - ))} -
-

- Unchanged and reproduced verbatim on both sides: “{OBLIGATION}” and the “{PRIVACY_LINK}” link to{" "} - /privacy — APP-5 / PIA-5 copy pinned by tests/privacy-ui.test.ts. Adoption edits{" "} - PrivacyInputNotice, the single site-wide notice rendered on the answer, documents and - calculators composers, so all three move together and the change needs a full clinical governance preflight. -

-
-
-
- ); -} diff --git a/src/components/document-navigation-final-review-mockups.tsx b/src/components/document-navigation-final-review-mockups.tsx deleted file mode 100644 index 5eabf4e27e..0000000000 --- a/src/components/document-navigation-final-review-mockups.tsx +++ /dev/null @@ -1,694 +0,0 @@ -"use client"; - -import { - ArrowLeft, - ChevronDown, - Compass, - FileImage, - FileSearch, - FileText, - ListTree, - Menu, - MessageSquarePlus, - Plus, - Quote, - Search, - ShieldCheck, - Sparkles, - type LucideIcon, -} from "lucide-react"; -import { useId } from "react"; - -import { cn } from "@/components/ui-primitives"; - -type Device = "desktop" | "tablet" | "phone"; -type VersionId = "persistent" | "single"; -type PhoneState = "rest" | "pane" | "scrolled"; - -const documentTitle = "Clinical practice guideline for schizophrenia"; - -const sections: Array<{ label: string; detail: string; icon: LucideIcon; collapsible: boolean }> = [ - { label: "Overview", detail: "84 pages", icon: Compass, collapsible: false }, - { label: "High-yield summary", detail: "8 points", icon: Sparkles, collapsible: true }, - { label: "PDF preview", detail: "p. 12 / 84", icon: FileText, collapsible: false }, - { label: "Pinned evidence", detail: "27 passages", icon: Quote, collapsible: false }, - { label: "Indexed source text", detail: "312 chunks", icon: FileSearch, collapsible: true }, - { label: "Tables and diagrams", detail: "6 visuals", icon: FileImage, collapsible: true }, - { label: "Indexing details", detail: "v3 · OCR", icon: ShieldCheck, collapsible: true }, -]; - -const activeIndex = 3; -const activeSection = sections[activeIndex]; - -/** Every issue raised across the study and where it is answered in this build. */ -const ledger: Array<{ issue: string; resolution: string; source: string }> = [ - { - issue: "A second phone header row is not allowed", - resolution: - "Nothing new is sticky. The two-line row is the document header that already portals into the universal collapse row.", - source: "PhoneHeaderCollapsePortal · one collapse owner", - }, - { - issue: "Extra header height starves the hide budget", - resolution: - "Only the `· 4 of 7` numerals were added and then removed again; the row lands at 56 px, and the 2 px track is the sole addition.", - source: "readChromeCollapseMetrics", - }, - { - issue: "The phone bottom edge is already owned", - resolution: "The composer pill keeps the footer. Navigation never claims a persistent bottom bar.", - source: "PhoneFooterLayerPortal · 9rem visible / 0rem hidden", - }, - { - issue: "A dropdown hung from a row that scroll-hides", - resolution: - "The phone pane is a sheet, anchored to the viewport. Scrolling under it is not a question that arises.", - source: "Sheets are not viewport chrome", - }, - { - issue: "Grid labels truncated three of seven sections", - resolution: "The pane is a single-column list at every breakpoint. Every label renders in full.", - source: "Observed in the two-column study", - }, - { - issue: "Position competed with the title for width", - resolution: "Numerals became a seven-segment hairline on the row's bottom edge; the title keeps the full line.", - source: "Direction 02", - }, - { - issue: "A hairline cannot name the section", - resolution: "Line two keeps the section label in accent with its icon. The track places, the label names.", - source: "Your call, and the right one", - }, - { - issue: "Jumps landed on a collapsed accordion", - resolution: "The handler opens the target `
`, waits a frame, then scrolls.", - source: "document-viewer-section is exclusive", - }, - { - issue: "`scroll-mt-24` assumes a constant header", - resolution: "Anchor offsets read the live collapse-row height, so a hidden header does not strand headings.", - source: "Variable chrome height", - }, - { - issue: "A sticky pane leaves a dead band when the top bar hides", - resolution: "Sticky offsets switch from `header + safe-area` to `0` with the top-bar hide state.", - source: "Collapse-everywhere hosts rule", - }, - { - issue: "A live control could sit under an open pane", - resolution: "Opening the pane blurs the document composer and the scrim covers the `z-40` pill.", - source: "Blur the focused composer", - }, - { - issue: "Colour alone marked the active track segment", - resolution: "The active segment is 3 px against the others' 2 px, so forced-colors keeps it legible.", - source: "Forced-colors", - }, -]; - -const versions: Array<{ - id: VersionId; - title: string; - verdict: string; - summary: string; - perDevice: Record; - costs: string[]; -}> = [ - { - id: "persistent", - title: "Version 1 — Persistent index on wide screens", - verdict: "Fewest interactions", - summary: - "Desktop and tablet show the section list permanently, as the first card in the column the viewer already renders. Phone opens the identical list as a sheet, because it has no column to spare.", - perDevice: { - desktop: "Index card always visible, sticky, offset bound to the top-bar hide state.", - tablet: - "Same card in page flow above the document, so it scrolls away rather than becoming a third sticky layer.", - phone: "Row chevron opens the list as a bottom sheet.", - }, - costs: [ - "Zero clicks to see the whole structure on desktop and tablet.", - "Two interaction models: always-there on wide screens, on-demand on phone.", - "Consumes about 310 px of the shared column before the evidence panel starts.", - ], - }, - { - id: "single", - title: "Version 2 — One interaction model everywhere", - verdict: "Most document width", - summary: - "The row's chevron is the only way into the section list at every breakpoint. Desktop and tablet open a popover anchored under the row; phone opens the same list as a sheet. One mental model, one component, three placements.", - perDevice: { - desktop: "Popover anchored under the row, dismissed by outside click or Escape, focus returns to the row.", - tablet: "The same popover, width-capped so it never crosses the pinned search stack.", - phone: "The same list as a bottom sheet.", - }, - costs: [ - "One consistent gesture: the row is always the way in.", - "The document keeps the full column — the evidence panel starts at the top.", - "Costs one click on desktop where a permanent index would have been free.", - ], - }, -]; - -const focusRing = - "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"; - -/* ------------------------------------------------------------------ */ -/* Chrome */ -/* ------------------------------------------------------------------ */ - -function UniversalTopBar({ device }: { device: Device }) { - const phone = device === "phone"; - - return ( -
- {phone ? ( - -
- ); -} - -/** Active segment is taller as well as tinted, so forced-colors keeps it readable. */ -function SegmentTrack() { - return ( -