diff --git a/docs/doctoring/DESIGN_TOKEN_REFERENCES.md b/docs/doctoring/DESIGN_TOKEN_REFERENCES.md index 717954045..c22317628 100644 --- a/docs/doctoring/DESIGN_TOKEN_REFERENCES.md +++ b/docs/doctoring/DESIGN_TOKEN_REFERENCES.md @@ -1,8 +1,8 @@ # Design-token and Storybook traceability **Status:** Active PR evidence; not protected-main truth until merge. -**Scope:** `frontend/src/styles/tokens.css`, repeated chip/close modules, and -the Storybook inventory. +**Scope:** `frontend/src/styles/tokens.css`, repeated chip/close modules, the +Ask evidence dialog, and the Storybook inventory. ## Standards mapped to implementation @@ -11,6 +11,7 @@ the Storybook inventory. | W3C Design Tokens Format Module 2025.10 | Name color, space, type, and radius once; consume those names from repeated objects. | `frontend/src/styles/tokens.css` defines `--color-*`, `--space-*`, `--size-control-min`, `--radius-chip`, `--radius-control`, `--radius-panel`, and `--font-*`. `CitationChip`, `PopupCloseButton`, `CutoffKnownBody`, and `LineageEntityPicker` read those names through `App.css`. | | Storybook for React & Vite | Catalog repeated controls so a buyer can try the next click without reading `App.tsx`. | `frontend/src/components/*.stories.tsx` and `docs/storybook-inventory.md`. | | WCAG 2.2 | Give interactive controls programmatic names and announce an asynchronous evidence failure instead of leaving a perpetual loading state. | Component interaction tests exercise the named controls; `EvidencePanel` exposes its terminal failure with `role="alert"`. This is targeted evidence, not a claim of complete WCAG conformance. | +| WAI-ARIA APG Dialog (Modal) Pattern | A surface marked `aria-modal="true"` must behave modally: focus moves inside, `Tab` and `Shift+Tab` remain inside, and `Escape` closes the layer. | `AskEvidenceLayerPopup` moves initial focus inside the dialog and explicitly cycles forward/backward keyboard focus between its actionable controls; component tests cover both focus-loop directions and Escape. Its evidence lists use dialog-specific accessible labels so assistive technology can distinguish the modal list from the still-rendered inline answer. | ## APA 7th references @@ -23,3 +24,7 @@ https://storybook.js.org/docs/get-started/frameworks/react-vite World Wide Web Consortium. (2024). *Web Content Accessibility Guidelines (WCAG) 2.2* (W3C Recommendation). https://www.w3.org/TR/WCAG22/ + +World Wide Web Consortium. (n.d.). *Dialog (modal) pattern*. WAI-ARIA +Authoring Practices Guide. Retrieved August 22, 2026, from +https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/ diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index 28c59bd48..a92dccf02 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -6,6 +6,7 @@ buyer-facing control you can click before changing product CSS. | Story | Buyer next action | Token / module | |---|---|---| | `Evidence/CitationChip` | Click a cited title to open that source post. | `--color-chip-border`, `--radius-chip`, `CitationChip` | +| `Evidence/AskEvidenceLayerPopup` | Inspect one citation without leaving the answer; close to continue the answer or open the complete source post. Stories cover text/image evidence, no-evidence, missing OCR, null caption, and blank-caption fallback states. | shared popup tokens through `App.css`, `PopupCloseButton`, `AskEvidenceLayerPopup` | | `AnalysisRun/CutoffKnownBody` | Read the cutoff-known sentence, then compare it with the live body below. | `--color-accent-border`, `--space-panel-block`, `--radius-panel`, `CutoffKnownBody` | | `Analysis/LineageEntityPicker` | Choose which corp to reconstruct, then click Request a lineage reconstruction. | `--space-control-gap`, `--size-control-min`, `--radius-control`, `LineageEntityPicker` | | `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` | diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index de8d2821f..8bbf6acce 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1712,6 +1712,27 @@ describe("App, authenticated", () => { expect(screen.queryByText(/Image evidence:/)).not.toBeInTheDocument(); }); + it("opens a cited post's evidence in a Layer Popup without leaving the answer", async () => { + stubBackend({ askImageCitation: true }); + render(); + expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument(); + await userEvent.click(screen.getByRole("button", { name: "Ask Agent" })); + await userEvent.type(screen.getByRole("textbox", { name: "Ask a question" }), "Which project?"); + await userEvent.click(screen.getByRole("button", { name: "Ask" })); + + await userEvent.click(await screen.findByRole("button", { name: "View evidence" })); + + const dialog = await screen.findByRole("dialog", { name: "Linked post" }); + expect(dialog).toBeInTheDocument(); + expect(within(dialog).getByText(/project: Semantic project/)).toBeInTheDocument(); + expect(within(dialog).getByText("Screenshot of the checkout error")).toBeInTheDocument(); + + await userEvent.click(screen.getByRole("button", { name: "Close evidence panel" })); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + // The answer itself is still on screen -- the layer never navigated away. + expect(screen.getByRole("button", { name: "View evidence" })).toBeInTheDocument(); + }); + it("labels the Customer Master entity level and Keymen side, never the raw lookup code", async () => { // Live UI finding (2026-08-19): read_customer_master() skipped the // common_lookup_value join both endpoints elsewhere already use, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e5c6d4bee..8fff1651b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -85,7 +85,9 @@ import { import { CitationChip } from "./components/CitationChip"; import { CutoffKnownBody } from "./components/CutoffKnownBody"; import { LineageEntityPicker } from "./components/LineageEntityPicker"; +import { AskEvidenceLayerPopup } from "./components/AskEvidenceLayerPopup"; import { PopupCloseButton } from "./components/PopupCloseButton"; +import { chatEvidenceKindLabel } from "./evidenceKindLabels"; import { BuyerNav, type BuyerDestination } from "./components/BuyerNav"; import { LineageDag } from "./LineageDag"; import { PostBody } from "./PostBody"; @@ -777,16 +779,6 @@ function projectProvenanceLabel(provenance: string): string { return t(PROJECT_PROVENANCE_LABELS[provenance] ?? "Recorded evidence"); } -const CHAT_EVIDENCE_KIND_LABELS: Record = { - source_field: "Source field hint", - semantic_project: "Semantic project", - semantic_role: "Semantic role", - semantic_keyman: "Semantic Keyman", -}; - -function chatEvidenceKindLabel(kind: string): string { - return t(CHAT_EVIDENCE_KIND_LABELS[kind] ?? "Evidence"); -} const VERIFICATION_BADGE: Record = { verify_pending: "Not yet checked", @@ -4477,6 +4469,7 @@ function AskAgentPanel({ const [answer, setAnswer] = useState(null); const [error, setError] = useState(null); const [asking, setAsking] = useState(false); + const [evidenceLayerPostId, setEvidenceLayerPostId] = useState(null); async function handleAsk() { const normalized = question.trim(); @@ -4525,6 +4518,13 @@ function AskAgentPanel({ + {answer.cited_post_evidence?.find((item) => item.post_id === post.post_id)?.facts.length ? (
    {answer.cited_post_evidence @@ -4555,6 +4555,23 @@ function AskAgentPanel({ )} )} + {evidenceLayerPostId && answer ? ( + post.post_id === evidenceLayerPostId)?.post_title ?? + evidenceLayerPostId + } + facts={ + answer.cited_post_evidence?.find((item) => item.post_id === evidenceLayerPostId)?.facts ?? [] + } + images={ + answer.cited_post_images?.filter((image) => image.post_id === evidenceLayerPostId) ?? [] + } + onClose={() => setEvidenceLayerPostId(null)} + onOpenPost={onOpenPost} + /> + ) : null} ); } @@ -4621,7 +4638,8 @@ export default function App({ showLabPanels = false }: { showLabPanels?: boolean
    - {destination === "admin" ? : null}