Skip to content

feat(ask): cite persisted image evidence for cited posts - #419

Merged
seonghobae merged 5 commits into
mainfrom
worktree-ask-agent-image-citation
Aug 24, 2026
Merged

feat(ask): cite persisted image evidence for cited posts#419
seonghobae merged 5 commits into
mainfrom
worktree-ask-agent-image-citation

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Buyer-visible gap

An Ask answer citing a post whose evidence actually came from an
embedded picture (a screenshot, a diagram) read as an unmarked text
claim -- the reader had no way to tell the citation was image-sourced
rather than drawn from the post's written body.

Change

Raw image bytes are never sent to the client anywhere in this codebase
-- only the persisted OCR text, caption, and tags
(lineageweave/image_content.py). This reuses that same never-raw-bytes
description GET /api/posts/{id}/content already renders, scoped to
posts the Ask answer already cited:

  • cited_post_images (backend/app/post_chat_ingestion.py) reads
    post_content_image/post_content_image_tag for the cited post ids.
    No extra ABAC check needed: cited_post_ids only ever come from
    gather_global_chat_sources's already-authorized source set -- the
    same trust boundary cited_post_evidence/cited_post_summaries
    already rely on.
  • Wired into POST /api/ask as a new cited_post_images response
    field.
  • AskAgentPanel renders an "Image evidence" line under a cited post
    when present, with the persisted caption and OCR text.
  • Korean/Chinese/Japanese/Vietnamese translations added for the two new
    UI strings ("Image evidence", "Untitled image").

Part of a larger goal: Ask Agent should also understand Korean
relative-time expressions (#415), render cited lineage as git-branch-style
graphs (#418), and show evidence in a Layer Popup -- separate
checkpoints/PRs, independently based off main.

Verification

  • uv run --frozen python -m pytest -q -- 755 passed, 17 skipped
    (pre-existing, live-Keycloak/orchestrator-only integration tests).
  • pnpm run test (frontend) -- 142 passed, including 2 new cases: a
    cited image renders its caption/OCR text, and no image line appears
    when the answer cites no image.
  • pnpm run lint / tsc -b -- no new warnings/errors; the two
    pre-existing App.tsx errors are the unrelated main break already
    tracked against feat: show persisted image-region locations (v2.12.8) #405.

No real records, identifiers, or provider credentials are included.


Open in Devin Review

Summary by CodeRabbit

  • 새 기능

    • Ask Agent 답변의 인용 게시물에서 이미지 증거를 확인할 수 있습니다.
    • 이미지 캡션과 추출 텍스트를 포함한 증거 정보를 제공합니다.
    • 증거 확인 창에서 게시물 열기, 닫기, 키보드 탐색을 지원합니다.
    • 로그인 후 원래 페이지로 돌아옵니다.
  • 개선 사항

    • 증거가 없거나 이미지 정보가 부족한 경우 안내 문구를 표시합니다.
    • 증거 관련 문구를 여러 언어로 제공합니다.
  • 테스트

    • 이미지 인용, 증거 창 표시 및 접근성 동작을 검증하는 테스트를 추가했습니다.

An Ask answer citing a post whose evidence actually came from an
embedded picture (a screenshot, a diagram) read as an unmarked text
claim -- no way to tell the citation was image-sourced. Raw image bytes
are never sent to the client anywhere in this codebase (only persisted
OCR/caption/tags -- see lineageweave/image_content.py); this reuses that
same never-raw-bytes description GET /api/posts/{id}/content already
renders, scoped to already-cited posts.

Backend: cited_post_images (backend/app/post_chat_ingestion.py) reads
post_content_image/post_content_image_tag for the cited post ids, no
extra ABAC check needed (cited_post_ids only ever come from
gather_global_chat_sources's already-authorized source set, same trust
boundary cited_post_evidence/cited_post_summaries rely on). Wired into
POST /api/ask as a new cited_post_images response field.

Frontend: AskAgentPanel renders an "Image evidence" line under a cited
post when present, with the persisted caption and OCR text. Adds the ko
/ zh / ja / vi translations for the two new strings.

Part of the Ask Agent temporal/lineage/evidence goal (checkpoint 3 of 4).
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 589f4478-8995-47e7-b3ee-0ccdfd4125c2

📥 Commits

Reviewing files that changed from the base of the PR and between c7f4d6c and b51b97b.

📒 Files selected for processing (7)
  • backend/app/post_chat_ingestion.py
  • frontend/src/App.test.tsx
  • frontend/src/App.tsx
  • frontend/src/components/AskEvidenceLayerPopup.stories.tsx
  • frontend/src/components/AskEvidenceLayerPopup.test.tsx
  • frontend/src/components/AskEvidenceLayerPopup.tsx
  • tests/test_post_chat_ingestion.py
📝 Walkthrough

Walkthrough

Ask Agent 응답에 인용 게시물의 이미지 근거를 추가했습니다. 프런트엔드는 이미지와 텍스트 근거를 접근 가능한 팝업에서 표시합니다. 백엔드 조회, API 타입, 통합 테스트, Storybook 상태 및 관련 번역을 추가했습니다.

Changes

Ask Agent 근거 표시

Layer / File(s) Summary
인용 이미지 근거 조회 및 응답
backend/app/main.py, backend/app/post_chat_ingestion.py, tests/test_post_chat_ingestion.py
/api/ask가 인용 게시물의 이미지 MIME 유형, 상태, 추출 텍스트, 캡션, 태그를 cited_post_images로 반환합니다. 빈 인용 목록에서는 빈 배열을 반환합니다.
프런트엔드 근거 계약 및 라벨
frontend/src/api.ts, frontend/src/evidenceKindLabels.ts, frontend/src/i18n.ts
CitedPostImageAskAgentResponse.cited_post_images를 추가했습니다. 채팅 근거 라벨과 4개 언어의 팝업 번역 키를 추가했습니다.
근거 팝업 및 접근성 동작
frontend/src/components/AskEvidenceLayerPopup.tsx, frontend/src/components/AskEvidenceLayerPopup.test.tsx, frontend/src/components/AskEvidenceLayerPopup.stories.tsx, docs/doctoring/DESIGN_TOKEN_REFERENCES.md, docs/storybook-inventory.md
텍스트 및 이미지 근거를 표시하는 모달을 추가했습니다. 초기 포커스, 포커스 순환, 포커스 복원, Escape, 백드롭, 게시물 열기 동작을 구현하고 검증했습니다.
Ask Agent 인용 연결 및 검증
frontend/src/App.tsx, frontend/src/App.test.tsx
인용 게시물에 View evidence 버튼을 추가했습니다. 팝업에 게시물 사실과 이미지 근거를 전달합니다. 이미지 표시와 답변 유지 동작을 테스트했습니다. 로그인 return URL 저장과 중복 관리자 패널 제거도 포함합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🔵 Low · up to c7f4d

Cited image evidence can display a blank caption when the persisted caption is empty or whitespace instead of showing “Untitled image.” This is a bounded UI correctness issue; the change is otherwise mergeable with explicit owner follow-up.

Sequence Diagram(s)

sequenceDiagram
  participant AskAgent as Ask Agent
  participant AskAPI as /api/ask
  participant ImageQuery as cited_post_images
  participant App as App.tsx
  participant Popup as AskEvidenceLayerPopup

  AskAgent->>AskAPI: 질문 전송
  AskAPI->>ImageQuery: 인용 게시물 ID 전달
  ImageQuery-->>AskAPI: 이미지 근거 반환
  AskAPI-->>App: cited_posts 및 cited_post_images 반환
  App->>Popup: 선택한 게시물의 사실 및 이미지 전달
  Popup-->>App: 닫기 또는 원문 게시물 열기
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 인용된 게시물에 저장된 이미지 근거를 추가하는 주요 변경 사항을 간결하고 정확하게 설명합니다.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-ask-agent-image-citation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

* feat(ask): show cited-post evidence in a Layer Popup

Reading an Ask answer's evidence today means either scanning the inline
fact list or leaving the answer entirely to open the full post popup.
Add AskEvidenceLayerPopup (frontend/src/components/), a focused modal
layer -- opened via a new "View evidence" button per citation -- showing
that post's text evidence facts (checkpoint 3's cited_post_evidence) and
image evidence (checkpoint 3's cited_post_images) without navigating
away from the answer. Proper dialog semantics: role="dialog",
aria-modal, Escape-to-close, backdrop-click-to-close, initial focus on
the panel -- stricter accessibility than the existing PostDetailPopup,
which has none of these.

Extracted chatEvidenceKindLabel into evidenceKindLabels.ts so both
App.tsx and the new component share one label map instead of drifting.

Stacked on #419 (checkpoint 3): the popup's image-evidence section needs
that PR's cited_post_images field to be meaningful.

Part of the Ask Agent temporal/lineage/evidence goal (checkpoint 4 of 4).

* test(ask): cover evidence dialog edge cases

* fix(ask): contain evidence dialog focus

* docs(storybook): cover blank evidence caption

* docs(ask): trace modal accessibility standard

* docs(storybook): inventory Ask evidence layer

* docs(changelog): record Ask evidence layer

* fix(changelog): restore historical entries

* test(ask): cover modal exit focus and source transition

* fix(ask): restore focus when evidence modal exits

* fix(frontend): drop stray pre-login AdminPanel, restore return-URL persistence

The pre-login screen rendered AdminPanel with a possibly-undefined
accessToken (a TS6192/TS2322 build break) and never used the persisted
return-URL helpers on the login redirect. Same fix as LineageWeave#456
on main, applied here since this stack predates that fix.
devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@seonghobae
seonghobae enabled auto-merge (squash) August 23, 2026 06:41
@opencode-agent
opencode-agent Bot disabled auto-merge August 23, 2026 07:38
image.caption ?? t("Untitled image") only substitutes for null/undefined,
so a persisted empty-string caption rendered a blank label ("Image
evidence:  — ..."). Match the blank-aware fallback already used in
AskEvidenceLayerPopup.tsx (image.caption?.trim() ? image.caption : ...)
so the same evidence renders consistently in both surfaces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5M79L945DMyMs3sg5yJ14
@seonghobae
seonghobae enabled auto-merge (squash) August 23, 2026 07:50
@github-actions
github-actions Bot disabled auto-merge August 23, 2026 07:51
devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Fixed empty image citations at exact head 88b6ea30727748ab7656dae5dcfbdc75fc63356a: cited images now require persisted caption, OCR text, or tags, so unavailable/failed empty artifacts never render as evidence. Focused suite: 11 passed; diff check passed. The OIDC changes are the shared inherited root fix. @opencode-agent please review this exact head independently.

@seonghobae
seonghobae enabled auto-merge (squash) August 23, 2026 08:18
devin-ai-integration[bot]

This comment was marked as resolved.

@github-actions
github-actions Bot disabled auto-merge August 23, 2026 08:24

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread frontend/src/App.tsx
@seonghobae
seonghobae enabled auto-merge August 24, 2026 01:25
@seonghobae
seonghobae merged commit 83ae0d0 into main Aug 24, 2026
30 checks passed
seonghobae added a commit that referenced this pull request Aug 24, 2026
)

* test(e2e): add a Playwright harness for the Ask Agent capabilities

No Playwright config existed despite the package already being a
devDependency. Add playwright.config.ts (points at the running
docker-compose stack, not a Playwright-managed dev server -- the app
needs Postgres/Keycloak/Valkey/orchestrator alongside it), a real
Keycloak-login helper (drives the actual OIDC redirect form with the
synthetic demo.analyst credentials, not a token injected into storage),
a validated smoke spec, and ask-agent.spec.ts covering all four Ask
Agent capabilities (relative-time retrieval #415, multi-lineage graphs
#418, image citation #419, Layer Popup #420).

The login flow is verified passing against a live stack right now.
ask-agent.spec.ts needs #415/#418/#419/#420 merged and the images
rebuilt from main before it can pass -- verified during development
that the currently-running ad-hoc stack is built from an unrelated,
more advanced branch (its own conversation-history UI), not main, so
it cannot validate this spec; that requires a proper CI/deployment
rebuild, out of this checkpoint's scope.

Part of the Ask Agent temporal/lineage/evidence goal (checkpoint 5 of
6 -- e2e harness).

* fix(frontend): repair the inherited login/admin-panel build break

Two TypeScript build errors on main (blocking every open PR's
"Frontend lint, test, build" check, including this repo's own review
bot's ability to approve them):

- App.tsx imported rememberOidcReturnUrl/returnUrlFromLocation from
  oidcReturnUrl.ts but never called them -- the login button built its
  own unsanitized returnUrl inline instead of using the safe helper
  (oidcReturnUrl.ts's isSafeReturnUrl guard against an open-redirect-
  shaped value) or persisting it as the sessionStorage/localStorage
  fallback restoreOidcReturnUrl (already wired up on the callback side
  in main.tsx) reads when the OIDC state round-trip drops it.
- The unauthenticated login screen unconditionally rendered
  <AdminPanel accessToken={accessToken} /> when destination === "admin"
  -- accessToken is string | undefined here (always undefined while
  unauthenticated), a real type error, and the render was unreachable
  through normal navigation (destination only changes via the
  authenticated nav) -- dead code, removed.

uv run --frozen python -m pytest -q: 753 passed, 17 skipped.
pnpm run test: 140 passed. pnpm run lint / build: clean.

* test(frontend): keep Playwright specs out of Vitest

* fix(e2e): stop Ask Agent lineage/image tests silently skipping their assertions

The git-branch-lineage and image-citation Ask Agent tests wrapped their
only real assertions in an `if (count > 0)` guard, so a run where the
LLM's answer happened not to produce that citation shape would report
PASS without exercising the behavior the test claims to cover. Assert
the precondition itself so the test fails loudly instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B5M79L945DMyMs3sg5yJ14

* fix(e2e): stabilize Ask Agent locale and multi-match checks

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
seonghobae added a commit that referenced this pull request Aug 24, 2026
)

* docs: record ADRs for the Ask Agent temporal/lineage/evidence goal

Four new ADRs, one per checkpoint of the Ask Agent temporal/lineage/
evidence goal:

- 0119: Korean relative-time expression resolution (#415)
- 0120: multi-thread Event Lineage graphs in Ask answers (#418)
- 0121: image citation without a new image-serving surface (#419)
- 0122: the evidence Layer Popup (#420)

Update CHANGELOG.md's Unreleased section and add an "Ask Agent Gaps"
section to docs/product-technical-gap-baseline.md marking all four
gaps (plus e2e coverage, #421) resolved, following that file's
existing "(Resolved)" convention.

Part of the Ask Agent temporal/lineage/evidence goal (checkpoint 6 of
6 -- documentation).

* docs: renumber Ask Agent ADRs 0119-0122 to 0150-0153

Cross-session coordination surfaced a widespread ADR-numbering
collision: at least ten numbers between 0119 and 0143 are
independently claimed by concurrent unmerged branches across other
sessions (0127/0128/0129/0131/0132 each claimed 2-4x, per
`git log --all --diff-filter=A -- docs/adr`). This PR's own
0119-0122 was a three-way collision (also claimed by the TEPP
topic-lineage PR and a quantity-superscript PR). Since this PR only
holds 4 ADRs against another's 14 (0119-0132), renumbering here is
the smaller diff. Moved clear of every number seen across all
branches (highest observed: 0143), leaving buffer room.

No content changes -- only the ADR number in each file's title,
their mutual cross-references, and every CHANGELOG.md /
gap-baseline.md citation of the old numbers.

* docs: refresh product technical gap baseline

* docs: record armed acceptance queue

* fix(frontend): repair the inherited login/admin-panel build break

Two TypeScript build errors on main (blocking every open PR's
"Frontend lint, test, build" check, including this repo's own review
bot's ability to approve them):

- App.tsx imported rememberOidcReturnUrl/returnUrlFromLocation from
  oidcReturnUrl.ts but never called them -- the login button built its
  own unsanitized returnUrl inline instead of using the safe helper
  (oidcReturnUrl.ts's isSafeReturnUrl guard against an open-redirect-
  shaped value) or persisting it as the sessionStorage/localStorage
  fallback restoreOidcReturnUrl (already wired up on the callback side
  in main.tsx) reads when the OIDC state round-trip drops it.
- The unauthenticated login screen unconditionally rendered
  <AdminPanel accessToken={accessToken} /> when destination === "admin"
  -- accessToken is string | undefined here (always undefined while
  unauthenticated), a real type error, and the render was unreachable
  through normal navigation (destination only changes via the
  authenticated nav) -- dead code, removed.

uv run --frozen python -m pytest -q: 753 passed, 17 skipped.
pnpm run test: 140 passed. pnpm run lint / build: clean.

---------

Co-authored-by: seonghobae <seonghobae@users.noreply.github.com>
seonghobae added a commit that referenced this pull request Aug 24, 2026
…-source-detail-state-filter

origin/main moved 58 commits ahead mid-session, including a properly
reviewed and tested implementation of the same evidence-layer-popup
feature this branch had manually rebuilt (#419). Re-merged to pick it
up rather than ship a parallel, untested version.

Resolved 7 conflicts:
- backend/app/post_chat_ingestion.py: combined this branch's
  ABAC/candidate-id filtering with main's Korean relative-time
  date-range filter on the same source_post query; combined both
  sides' import additions (tepp_client + temporal_expressions).
- backend/app/lineage_ingestion.py: combined this branch's
  isolation_reason (ADR 0143) with main's include_isolated parameter
  and new lineage_graphs_for_posts merge function -- both needed
  together, not alternatives.
- backend/app/main.py: added the lineage_graph computation (from
  main's dropped PR #418) into this branch's persist_turn-based
  ask_agent flow, using response["cited_post_ids"] so it works for
  both the sources-empty and populated-sources paths.
- frontend/src/App.tsx: kept this branch's working multi-turn
  (exchanges.map()) Ask Agent implementation wholesale over main's
  incompatible pre-refactor single-answer fragment; added the missing
  <LineageDag> rendering for response.lineage_graph per exchange, and
  kept the richer TenantConfig AdminPanel wiring already established.
- tests/test_lineage_ingestion.py, tests/test_global_ask_sources.py,
  CHANGELOG.md: reconstructed interleaved additive conflicts as
  complete, non-overlapping test functions / changelog entries from
  both sides.

Also fixed real bugs surfaced along the way:
- cited_post_images() (backend/app/post_chat_ingestion.py) queried
  the pre-rename image.caption column; migration 0104 renamed it to
  image_caption. Fixed the SQL and row mapping, matching how
  /api/posts/{id}/content already does it.
- Two tenant-settings tests asserted the old single-brandName
  response shape instead of the current {brandName, systemName,
  copyrightYear, copyrightHolder} contract.
- A stale test fixture in tests/test_post_chat_ingestion.py still
  keyed its fake DB rows by the pre-rename "caption" column.
- Fixed a duplicate ADR 0119 number (retire-buyer-terminology ->
  0168; leftover-map-two-dimensional-distance already owned 0119).
- A LineageDag.test.tsx role="group" vs a newly-merged App.test.tsx
  role="img" mismatch: kept role="group" (this branch's existing,
  better-tested LineageDag.tsx choice, verified by 4 passing
  assertions) and updated the one new test instead.

Verified: tests/ (1037 passed, 11 skipped), backend/tests/ (156
passed, 5 skipped -- orchestrator-gated), frontend (469 passed),
tsc -b clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NpWw9SnPBaemFZmW3fdTVM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant