-
Notifications
You must be signed in to change notification settings - Fork 1
feat(ask): show cited-post evidence in a Layer Popup #420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
seonghobae
merged 11 commits into
worktree-ask-agent-image-citation
from
worktree-ask-agent-layer-popup
Aug 23, 2026
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
38bd9c9
feat(ask): show cited-post evidence in a Layer Popup
seonghobae 47185cb
test(ask): cover evidence dialog edge cases
seonghobae ef302ba
fix(ask): contain evidence dialog focus
seonghobae 9f5b0a0
docs(storybook): cover blank evidence caption
seonghobae 619ea7b
docs(ask): trace modal accessibility standard
seonghobae 51ae72f
docs(storybook): inventory Ask evidence layer
seonghobae 7a9481e
docs(changelog): record Ask evidence layer
seonghobae 4e4986d
fix(changelog): restore historical entries
seonghobae 5410f76
test(ask): cover modal exit focus and source transition
seonghobae 17c2f08
fix(ask): restore focus when evidence modal exits
seonghobae eb77477
fix(frontend): drop stray pre-login AdminPanel, restore return-URL pe…
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import type { Meta, StoryObj } from "@storybook/react-vite"; | ||
| import { AskEvidenceLayerPopup } from "./AskEvidenceLayerPopup"; | ||
|
|
||
| const meta = { | ||
| title: "Evidence/AskEvidenceLayerPopup", | ||
| component: AskEvidenceLayerPopup, | ||
| args: { | ||
| postId: "post-demo-public", | ||
| postTitle: "Checkout error follow-up", | ||
| facts: [ | ||
| { kind: "semantic_project", text: "project: Checkout revamp | evidence: Body evidence" }, | ||
| { kind: "semantic_keyman", text: "Keyman mention: Ada West | context: account lead" }, | ||
| ], | ||
| images: [ | ||
| { | ||
| unit_index: 1, | ||
| caption: "Screenshot of the checkout error", | ||
| extracted_text: "Error code 500 on checkout", | ||
| }, | ||
| ], | ||
| onClose: () => undefined, | ||
| onOpenPost: () => undefined, | ||
| }, | ||
| } satisfies Meta<typeof AskEvidenceLayerPopup>; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| export const Default: Story = {}; | ||
|
|
||
| export const TextEvidenceOnly: Story = { | ||
| args: { | ||
| images: [], | ||
| }, | ||
| }; | ||
|
|
||
| export const ImageEvidenceOnly: Story = { | ||
| args: { | ||
| facts: [], | ||
| }, | ||
| }; | ||
|
|
||
| // Edge case: a citation with no persisted evidence facts or images at all -- | ||
| // must show an explicit placeholder, never a blank panel. | ||
| export const NoEvidence: Story = { | ||
| args: { | ||
| facts: [], | ||
| images: [], | ||
| }, | ||
| }; | ||
|
|
||
| // Edge case: an image evidence entry whose OCR text was never extracted. | ||
| export const ImageWithoutExtractedText: Story = { | ||
| args: { | ||
| facts: [], | ||
| images: [ | ||
| { | ||
| unit_index: 0, | ||
| caption: "Architecture diagram", | ||
| extracted_text: null, | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
|
|
||
| // Edge case: an untitled/uncaptioned image. | ||
| export const UntitledImage: Story = { | ||
| args: { | ||
| facts: [], | ||
| images: [ | ||
| { | ||
| unit_index: 0, | ||
| caption: null, | ||
| extracted_text: null, | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
|
|
||
| // Edge case: some sources persist an empty caption rather than null. The buyer | ||
| // still needs a visible label that explains what to do with the evidence row. | ||
| export const BlankImageCaption: Story = { | ||
| args: { | ||
| facts: [], | ||
| images: [ | ||
| { | ||
| unit_index: 0, | ||
| caption: "", | ||
| extracted_text: "Diagram OCR text remains available", | ||
| }, | ||
| ], | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| import { render, screen } from "@testing-library/react"; | ||
| import userEvent from "@testing-library/user-event"; | ||
| import { describe, expect, it, vi } from "vitest"; | ||
| import { AskEvidenceLayerPopup } from "./AskEvidenceLayerPopup"; | ||
|
|
||
| const baseProps = { | ||
| postId: "post-demo-public", | ||
| postTitle: "Checkout error follow-up", | ||
| }; | ||
|
|
||
| describe("AskEvidenceLayerPopup", () => { | ||
| it("renders text and image evidence for the cited post", () => { | ||
| render( | ||
| <AskEvidenceLayerPopup | ||
| {...baseProps} | ||
| facts={[{ kind: "semantic_project", text: "project: Checkout revamp | evidence: Body evidence" }]} | ||
| images={[ | ||
| { | ||
| unit_index: 1, | ||
| caption: "Screenshot of the checkout error", | ||
| extracted_text: "Error code 500 on checkout", | ||
| }, | ||
| ]} | ||
| onClose={vi.fn()} | ||
| onOpenPost={vi.fn()} | ||
| />, | ||
| ); | ||
|
|
||
| expect(screen.getByRole("dialog", { name: "Checkout error follow-up" })).toBeInTheDocument(); | ||
| expect(screen.getByText(/project: Checkout revamp/)).toBeInTheDocument(); | ||
| expect(screen.getByText("Screenshot of the checkout error")).toBeInTheDocument(); | ||
| expect(screen.getByText("Error code 500 on checkout")).toBeInTheDocument(); | ||
| expect( | ||
| screen.getByRole("list", { name: "Checkout error follow-up Evidence facts" }), | ||
| ).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("shows an explicit placeholder when the citation has no persisted evidence", () => { | ||
| render( | ||
| <AskEvidenceLayerPopup {...baseProps} facts={[]} images={[]} onClose={vi.fn()} onOpenPost={vi.fn()} />, | ||
| ); | ||
| expect( | ||
| screen.getByText("No persisted evidence is available for this citation."), | ||
| ).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("uses the untitled fallback when an image caption is blank", () => { | ||
| render( | ||
| <AskEvidenceLayerPopup | ||
| {...baseProps} | ||
| facts={[]} | ||
| images={[{ unit_index: 0, caption: "", extracted_text: null }]} | ||
| onClose={vi.fn()} | ||
| onOpenPost={vi.fn()} | ||
| />, | ||
| ); | ||
| expect(screen.getByText("Untitled image")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("closes on backdrop click, close button click, and Escape, but not on panel click", async () => { | ||
| const onClose = vi.fn(); | ||
| const { container } = render( | ||
| <AskEvidenceLayerPopup {...baseProps} facts={[]} images={[]} onClose={onClose} onOpenPost={vi.fn()} />, | ||
| ); | ||
|
|
||
| await userEvent.click(screen.getByRole("dialog")); | ||
| expect(onClose).not.toHaveBeenCalled(); | ||
|
|
||
| const backdrop = container.querySelector(".popup-backdrop") as HTMLElement; | ||
| await userEvent.click(backdrop); | ||
| expect(onClose).toHaveBeenCalledTimes(1); | ||
|
|
||
| await userEvent.click(screen.getByRole("button", { name: "Close evidence panel" })); | ||
| expect(onClose).toHaveBeenCalledTimes(2); | ||
|
|
||
| await userEvent.keyboard("{Escape}"); | ||
| expect(onClose).toHaveBeenCalledTimes(3); | ||
| }); | ||
|
|
||
| it("closes the evidence layer before opening the cited post", async () => { | ||
| const onClose = vi.fn(); | ||
| const onOpenPost = vi.fn(); | ||
| render( | ||
| <AskEvidenceLayerPopup | ||
| {...baseProps} | ||
| facts={[]} | ||
| images={[]} | ||
| onClose={onClose} | ||
| onOpenPost={onOpenPost} | ||
| />, | ||
| ); | ||
| await userEvent.click(screen.getByRole("button", { name: "Open post: Checkout error follow-up" })); | ||
| expect(onClose).toHaveBeenCalledTimes(1); | ||
| expect(onOpenPost).toHaveBeenCalledWith("post-demo-public"); | ||
| expect(onClose.mock.invocationCallOrder[0]).toBeLessThan(onOpenPost.mock.invocationCallOrder[0]); | ||
| }); | ||
|
|
||
| it("moves initial focus onto the dialog panel", () => { | ||
| render( | ||
| <AskEvidenceLayerPopup {...baseProps} facts={[]} images={[]} onClose={vi.fn()} onOpenPost={vi.fn()} />, | ||
| ); | ||
| expect(screen.getByRole("dialog")).toHaveFocus(); | ||
| }); | ||
|
|
||
| it("contains Tab and Shift+Tab focus within the modal layer", async () => { | ||
| render( | ||
| <AskEvidenceLayerPopup {...baseProps} facts={[]} images={[]} onClose={vi.fn()} onOpenPost={vi.fn()} />, | ||
| ); | ||
| const closeButton = screen.getByRole("button", { name: "Close evidence panel" }); | ||
| const openPostButton = screen.getByRole("button", { name: "Open post: Checkout error follow-up" }); | ||
|
|
||
| openPostButton.focus(); | ||
| await userEvent.tab(); | ||
| expect(closeButton).toHaveFocus(); | ||
|
|
||
| closeButton.focus(); | ||
| await userEvent.tab({ shift: true }); | ||
| expect(openPostButton).toHaveFocus(); | ||
| }); | ||
|
|
||
| it("returns focus to the element that invoked the modal when the layer unmounts", () => { | ||
| const opener = document.createElement("button"); | ||
| opener.textContent = "View evidence"; | ||
| document.body.append(opener); | ||
| opener.focus(); | ||
|
|
||
| const { unmount } = render( | ||
| <AskEvidenceLayerPopup {...baseProps} facts={[]} images={[]} onClose={vi.fn()} onOpenPost={vi.fn()} />, | ||
| ); | ||
| expect(screen.getByRole("dialog")).toHaveFocus(); | ||
|
|
||
| unmount(); | ||
| expect(opener).toHaveFocus(); | ||
| opener.remove(); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.