diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 8ce690ffc..be754d79b 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -882,7 +882,9 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-10 | PR #1803 / claude/codex-m4b-shadow-tight-migration-53a8kn | b778a56e9c3fa7642a783dde85e1130559d71e24 | shadow-tight token migration onto the e1 elevation tier and alias retirement (#262 part 1) | Migrated all 150 var(--shadow-tight) occurrences across 71 files to var(--e1) (90 gated production sites across 48 files, 60 mockup); deleted all three alias declarations (:root, .dark, forced-colors); pinned legacyShadowAliases 220 to 127 with exact per-path counts, closing 3 aliases of re-accumulated slack; added a whole-stylesheet absence assertion (mutation-verified); updated GATES.md section 3 plus a new section 6, TOKENS.md section 6, design-system.md, both redesign direction docs, .design-sync/conventions.md and ledger #262. Verified in Chromium that the ckb-v2 tier override is picked up by the alias substitution, so the change is value-preserving; that check is recorded as a prerequisite for the remaining six aliases. | npm run verify:cheap (30 static gates plus lint plus typecheck green; design-system contract passed, legacy shadow aliases 127; unit suite 553/554 files, 6024 tests passed, 1 pre-existing root-permission failure in tests/pr-handoff-stop.test.ts reproduced on untouched base a16dd26); npm run format:check whole tree; targeted Chromium computed-style measurement. verify:ui not run, Playwright browser revision drift #255, delegated to CI Production UI. No provider-backed gates. | | 2026-08-11 | codex/answer-loading-ui-20260811 | 6758f8156f9d1b3e893981dfd7a1f6563aa90da0 | answer creation loading UI | No high-confidence findings | UI 3 passed; unit 8 passed; lint, typecheck, build, design-system and offline RAG passed; full suite 6022 passed with 16 unchanged baseline failures | | 2026-08-11 | claude/codex-m4c-retire-shadow-nliak3 | 448a0d084c4cd2cda6153dd7f03dcb67c43a8df0 | DS Track A2 (#261): retire --shadow-focus; composer focus onto sanctioned outline; contract guard; baseline ratchet; design-system docs + ledger | Approved — PR #1807. Token deleted in both themes; .chat-composer-shell-delta:focus-within uses outline 2px var(--focus) at offset 2px and no longer overrides box-shadow. Reach premise corrected: 0 of 37 production routes render the class (only /mockups/calculators-search). legacyShadowAliases 127->125, globals.css pin 3->1. | check:design-system-contract PASS; design-token-contract.test.ts PASS + mutation-verified both ways; verify:pr-local PASS except pre-existing tests/pr-handoff-stop.test.ts failure baselined on untouched base e8b61d8; build PASS; check:rag:fixtures PASS (36 cases); Chromium look both themes on the mockup route (inspection only, rev 1194 vs pinned 1234 #255); verify:ui/verify:phone-chrome NOT run — delegated to CI | -| 2026-08-11 | HEAD | 45fd05c8c3947835c0368666ff576c7a38b33ee4 | answer sources sheet and extracted answer text | Fixed raw PDF navigation/list artifacts and simplified source verification UX | answer-content unit; focused Chromium source flow; PR-local lint/typecheck reached full test | +| 2026-08-11 | work | 45fd05c8c3947835c0368666ff576c7a38b33ee4 | mobile evidence sheet UX, accessibility, and feedback logic | fixed unexplained claim marker, excess panel reserve, unclear purpose and feedback copy; no remaining high-confidence defects | focused DOM 7/7; Chromium evidence journey 1/1; offline RAG 23 suites/574 tests | | 2026-08-11 | codex/answer-ecg-animation-20260811 | 12279a8309c225aa957ef1e65afc37545a0ce04c | answer ECG progress variants | No high-confidence findings; physical Safari/PWA remains residual acceptance | design contract, typecheck, focused unit 8/8, trace token 33/33, Chromium 4/4, offline RAG 574/574; full suite baseline/platform failures | | 2026-08-11 | work | 6dcd695076d630d16aae594577763e8004361893 | Codex Cloud setup and local parity | P2 fixed: cache-friendly locked Cloud npm install; parity limitations documented | check:codex-cloud; codex-cloud-setup 24/24; full suite 6059 pass, 7 unrelated timeout/state failures | +| 2026-08-11 | 1820 | 897ff11a4cdb13ae1c01f5eb149007847028f5aa | review-and-fix | fixed | Semgrep:IN_PROGRESS, Gitleaks:IN_PROGRESS, Semgrep ingestion gate:IN_PROGRESS, Static PR checks:QUEUED, Safety and config checks:QUEUED, Unit coverage:QUEUED, Build:QUEUED, Production UI critical:QUEUED, Lighthouse budget:QUEUED | +| 2026-08-11 | HEAD | 45fd05c8c3947835c0368666ff576c7a38b33ee4 | answer sources sheet and extracted answer text | Fixed raw PDF navigation/list artifacts and simplified source verification UX | answer-content unit; focused Chromium source flow; PR-local lint/typecheck reached full test | | 2026-08-11 | 1821 | d76e90547dbdb104256b66a508c15c74302002fc | review-and-fix | dispositioned | PR policy:success; PR mergeability:success; Gitleaks:success; Semgrep:success; Semgrep ingestion gate:success; Safety and config checks:success; Build:success; Production UI critical:success; Production UI (1):success; Production UI (2):success; Production UI (3):failure test not reproduced outside this PR; PR required:failure | diff --git a/src/components/clinical-dashboard/answer-result-surface.tsx b/src/components/clinical-dashboard/answer-result-surface.tsx index e8576a4d1..c485a3a8e 100644 --- a/src/components/clinical-dashboard/answer-result-surface.tsx +++ b/src/components/clinical-dashboard/answer-result-surface.tsx @@ -393,7 +393,7 @@ function StagedAnswerResultSurfaceImpl({ open={evidenceOpen} onClose={closeEvidenceReview} title="Evidence" - description="Review by evidence type." + description="Check how well sources support this answer." titleAccessory={{evidenceTrustLabel}} closeLabel="Close evidence" headerLeading={ diff --git a/src/components/clinical-dashboard/evidence-panels.tsx b/src/components/clinical-dashboard/evidence-panels.tsx index a180b1927..897d53ae1 100644 --- a/src/components/clinical-dashboard/evidence-panels.tsx +++ b/src/components/clinical-dashboard/evidence-panels.tsx @@ -1152,9 +1152,10 @@ export function AnswerFeedbackPanel({ >
-

Answer review

+

Is the answer supported?

- Capture misses for retrieval and RAG evals without changing the answer. + Record whether the linked evidence supports the answer. This sends feedback for review; it does not change + the answer.

{pending ? ( @@ -1164,7 +1165,7 @@ export function AnswerFeedbackPanel({ ) : null}
-
+
{answerFeedbackOptions.map((item) => { const Icon = item.icon; return ( @@ -1174,7 +1175,7 @@ export function AnswerFeedbackPanel({ disabled={Boolean(pending)} onClick={() => onSubmit(item.type)} className={cn( - "inline-flex min-h-tap items-center gap-1.5 rounded-lg border px-2.5 text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60 lg:min-h-10", + "inline-flex min-h-tap items-center justify-center gap-1.5 rounded-lg border px-2.5 text-center text-xs font-semibold transition disabled:cursor-not-allowed disabled:opacity-60 lg:min-h-10", feedbackToneClass(item.tone), )} > diff --git a/src/components/clinical-dashboard/visual-evidence.tsx b/src/components/clinical-dashboard/visual-evidence.tsx index d63bbf0ab..4025d84d1 100644 --- a/src/components/clinical-dashboard/visual-evidence.tsx +++ b/src/components/clinical-dashboard/visual-evidence.tsx @@ -6,6 +6,7 @@ import { CircleAlert, BookOpen, CircleCheck, + CircleDashed, ChevronDown, Copy, ExternalLink, @@ -346,33 +347,47 @@ const evidenceTabIconMap: Record = { Gaps: CircleAlert, }; -function supportDotClass(supportLevel: string) { - const normalized = supportLevel.toLowerCase(); - if (normalized.includes("unsupported") || normalized.includes("none")) return "bg-[color:var(--danger)]"; - if (normalized.includes("partial") || normalized.includes("limited") || normalized.includes("nearby")) { - return "bg-[color:var(--warning)]"; - } - return "bg-[color:var(--clinical-accent)]"; -} - function supportLabel(supportLevel: string) { const normalized = supportLevel.toLowerCase(); - if (normalized.includes("unsupported") || normalized.includes("none")) return "Unsupported"; + if (normalized.includes("unsupported") || normalized.includes("not classified") || normalized.includes("none")) { + return "Unsupported"; + } if (normalized.includes("partial") || normalized.includes("limited") || normalized.includes("nearby")) return "Partial"; return "Direct"; } +function SupportStatusIcon({ supportLevel }: { supportLevel: string }) { + const label = supportLabel(supportLevel); + const Icon = label === "Direct" ? CircleCheck : label === "Partial" ? CircleDashed : CircleAlert; + + return ( + + + ); +} + function claimRowsForEvidencePanel(rows: AnswerEvidenceMapRow[], renderModel: AnswerRenderModel) { if (rows.length) return rows.slice(0, 6); return renderModel.primarySources.slice(0, 6).map((source, index) => ({ id: source.id, section: source.label || cleanDisplayTitle(source.title || source.file_name) || `Source ${index + 1}`, detail: source.snippet || source.reason || "Open source passage to review the cited evidence.", - supportLevel: source.sourceStrength === "none" ? "partial" : source.sourceStrength, + supportLevel: supportLabel(source.sourceStrength ?? "not classified"), citationCount: 1, - sourceStatus: - source.sourceStrength === "none" ? "Source requires review" : `${source.sourceStrength} source support`, + sourceStatus: source.sourceStrength ? `${source.sourceStrength} source support` : "Source requires review", bestSourceLabel: source.label, bestLinkedPassage: source.snippet || source.reason, href: source.href, @@ -384,7 +399,7 @@ function EvidenceClaimsList({ rows, renderModel }: { rows: AnswerEvidenceMapRow[ const directCount = claimRows.filter((row) => supportLabel(row.supportLevel) === "Direct").length; const partialCount = claimRows.filter((row) => supportLabel(row.supportLevel) === "Partial").length; const claimRowClassName = - "grid min-h-[76px] grid-cols-[auto_auto_minmax(0,1fr)_auto] items-center gap-3 border-b border-[color:var(--border)] px-3 py-3 text-left last:border-b-0"; + "grid min-h-18 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 border-b border-[color:var(--border)] px-3 py-2.5 text-left last:border-b-0"; if (!claimRows.length) { return ( @@ -399,7 +414,7 @@ function EvidenceClaimsList({ rows, renderModel }: { rows: AnswerEvidenceMapRow[ return (
-
+

Claims checked

@@ -426,8 +441,7 @@ function EvidenceClaimsList({ rows, renderModel }: { rows: AnswerEvidenceMapRow[ {claimRows.map((row, index) => { const content = ( <> - - + {row.section} @@ -463,7 +477,7 @@ function EvidenceClaimsList({ rows, renderModel }: { rows: AnswerEvidenceMapRow[ claimRowClassName, "transition hover:bg-[color:var(--surface-subtle)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[color:var(--focus)]", )} - aria-label={`Open source for ${row.section}`} + aria-label={`${supportLabel(row.supportLevel)} support. Open source for ${row.section}`} > {content} @@ -581,7 +595,11 @@ export function MobileEvidenceSheetContent({ } return ( -
+
+

+ Check which claims are backed by the retrieved sources, open the supporting passages, and flag evidence problems + before relying on the answer. +

-
+
{order.map((tab) => { const selected = tab === activeTab; return ( @@ -641,7 +659,6 @@ export function MobileEvidenceSheetContent({ aria-labelledby={tabIdFor(tab)} data-testid={`mobile-evidence-panel-${tab.toLowerCase()}`} hidden={!selected} - className="min-h-[220px]" > {selected ? ( { + it("explains the review purpose and identifies claim support without decorative placeholders", () => { + render(); + + expect(screen.getByText(/check which claims are backed by the retrieved sources/i)).toBeVisible(); + expect(screen.getByRole("img", { name: "Partial support" })).toBeVisible(); + expect(screen.getByText("Is the answer supported?")).toBeVisible(); + expect(screen.getByText(/this sends feedback for review; it does not change the answer/i)).toBeVisible(); + }); + it("uses unique stable tab and panel ids with reciprocal ARIA associations", () => { const props = evidenceSheetProps(); const { rerender } = render();