From 88d6c313a5bbd11171b03be68a16f3ee5ecc5148 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 5 Aug 2026 02:36:44 +0800 Subject: [PATCH 01/98] fix(answer): preserve phone verification safety Add an explicit responsive compact presentation for production answers while keeping the complete governed wording on larger screens, print, and default consumers. Restore the original phone geometry budgets and cover every model/extractive state with focused DOM tests (66 passed). --- .../answer-result-surface.tsx | 1 + src/components/ui/verification-notice.tsx | 60 ++++++++++++++++- tests/ui-smoke.spec.ts | 37 +++-------- tests/ui-v2-answer-safety.dom.test.tsx | 65 +++++++++++++++++++ 4 files changed, 133 insertions(+), 30 deletions(-) diff --git a/src/components/clinical-dashboard/answer-result-surface.tsx b/src/components/clinical-dashboard/answer-result-surface.tsx index 9364b5156..3c8b1a852 100644 --- a/src/components/clinical-dashboard/answer-result-surface.tsx +++ b/src/components/clinical-dashboard/answer-result-surface.tsx @@ -238,6 +238,7 @@ function StagedAnswerResultSurfaceImpl({ one kind that carries no count, hence the `in` guard. */} > = { + model: { + ready: "AI-generated. Verify every clinical claim in the cited sources before acting.", + stale_evidence: "AI-generated; some cited sources are overdue. Re-verify every clinical claim before acting.", + partial_retrieval: + "AI-generated from incomplete sources and may omit guidance. Verify against cited sources before acting.", + ungrounded: "AI-generated; citations do not support every claim. Check each dose, number, timing and threshold.", + source_only: "Copied from cited sources without model synthesis. Verify against the cited sources before acting.", + }, + extractive: { + ready: "Copied from cited sources without model synthesis. Verify against the cited sources before acting.", + stale_evidence: "Copied from cited sources; some are overdue. Re-verify every clinical claim before acting.", + partial_retrieval: + "Copied from incomplete sources and may omit guidance. Verify against cited sources before acting.", + ungrounded: "Copied from sources that do not support every claim. Check each dose, number, timing and threshold.", + source_only: "Copied from cited sources without model synthesis. Verify against the cited sources before acting.", + }, +}; + +const UNKNOWN_RESPONSIVE_COMPACT_WORDING = + "Answer provenance is unknown. Treat it as unverified and check every clinical claim in the cited sources before acting."; + /** * Wording for a state this build does not recognise — a newer producer, a bad * cast, a partially-deployed rollout. A verification notice is the one surface @@ -135,8 +163,19 @@ const UNKNOWN_STATE_WORDING: Record<"clinician" | "plain", string> = { */ const CAUTION_STATES = new Set(["stale_evidence", "ungrounded"]); +const MAX_RECORDED_UNKNOWN_STATES = 32; const loggedUnknownStates = new Set(); +function recordUnknownState(key: string): boolean { + if (loggedUnknownStates.has(key)) return false; + if (loggedUnknownStates.size >= MAX_RECORDED_UNKNOWN_STATES) { + const oldest = loggedUnknownStates.values().next().value; + if (typeof oldest === "string") loggedUnknownStates.delete(oldest); + } + loggedUnknownStates.add(key); + return true; +} + function wordingFor( audience: "clinician" | "plain", state: VerificationState, @@ -152,8 +191,7 @@ function wordingFor( if (wording) return wording; const key = `${audience}:${String(state)}`; - if (!loggedUnknownStates.has(key)) { - loggedUnknownStates.add(key); + if (recordUnknownState(key)) { if (typeof process === "undefined" || process.env?.NODE_ENV !== "test") { console.warn( JSON.stringify({ @@ -172,6 +210,7 @@ export function VerificationNotice({ state, audience = "clinician", attribution = "model", + presentation = "full", medium = "screen", sourceCount, printedAt, @@ -179,6 +218,9 @@ export function VerificationNotice({ className, }: VerificationNoticeProps) { const wording = wordingFor(audience, state, attribution); + const showResponsiveCompact = + presentation === "responsive-compact" && audience === "clinician" && medium === "screen"; + const compactWording = RESPONSIVE_COMPACT_WORDING[attribution][state] ?? UNKNOWN_RESPONSIVE_COMPACT_WORDING; // An unrecognised state is treated as caution: it wears the warning mark for // the same reason it gets the strongest wording. const caution = CAUTION_STATES.has(state) || !WORDING[audience][state]; @@ -191,6 +233,7 @@ export function VerificationNotice({ data-audience={audience} data-attribution={attribution} data-medium={medium} + data-presentation={presentation} // Deliberately not a live region and not focusable: it is standing // document text above the answer actions, not an event. Print CSS may // never hide or clamp it, so no line-clamp and no print:hidden here. @@ -210,7 +253,18 @@ export function VerificationNotice({ >