test(examples): citations e2e coverage + fix ag-ui citation delivery#774
Conversation
The ag-ui example's attach_citations node only set additional_kwargs.citations, which the ag-ui protocol drops when streaming message text — so citations never reached the UI. Emit them as state.citations keyed by the AI message id, and re-apply bridgeCitationsState in the MESSAGES_SNAPSHOT reducer handler so the final message (whose id differs from the streamed chunk id) keeps its citations when the snapshot swaps the streamed messages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aimock fixtures + Playwright specs for chat and ag-ui: assert inline pill markers resolve, the Sources panel is collapsed-by-default and expands to the cited detail cards, and focusing a marker opens the portaled preview card. The real search_documents tool's no-match fallback yields a deterministic 3-source set. Twin specs keep chat + ag-ui in sync. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @blove's task in 2m 35s —— View job Code Review
The bug fix is correct and the reasoning is well-documented. A few things worth noting: Bug fix — reducer (
|
| const bubble = await sendPromptAndWait(page, PROMPT); | ||
| const markers = bubble.locator('.chat-citation-marker'); | ||
| await expect(markers.first()).toBeVisible(); | ||
| expect(await markers.count()).toBeGreaterThanOrEqual(3); |
There was a problem hiding this comment.
Raw expect(await markers.count()) doesn't auto-retry. If markers render fractionally after the first one is visible this can flake. The fixture has 4 inline references across 3 sources — prefer the retrying form:
| expect(await markers.count()).toBeGreaterThanOrEqual(3); | |
| await expect(markers).toHaveCount(4); |
| const markers = bubble.locator('.chat-citation-marker'); | ||
| await expect(markers.first()).toBeVisible(); | ||
| // 4 inline references across 3 distinct sources. | ||
| expect(await markers.count()).toBeGreaterThanOrEqual(3); |
There was a problem hiding this comment.
Same issue as the ag-ui twin — raw expect(await …count()) has no retry. Prefer:
| expect(await markers.count()).toBeGreaterThanOrEqual(3); | |
| await expect(markers).toHaveCount(4); |
Summary
Follow-up to #769 (citations & sources UI). Adds end-to-end coverage for the citations flow in both example apps — and, in doing so, uncovered and fixed a real bug: ag-ui citations never reached the UI.
Bug fix — ag-ui citation delivery
The ag-ui example's
attach_citationsnode only setAIMessage.additional_kwargs.citations. The ag-ui protocol dropsadditional_kwargswhen streaming message text, so the frontend (which readsstate.citations[messageId]viabridgeCitationsState) never received them — citations silently didn't render in ag-ui.examples/ag-ui/python/src/graph.py): add acitationschannel toStateand emitstate.citationskeyed by the AI message id.libs/ag-ui/src/lib/reducer.ts): re-applybridgeCitationsStatein theMESSAGES_SNAPSHOThandler. The final snapshot message id (str(AIMessage.id), e.g.resp-…) differs from the streaming chunk id the earlierSTATE_SNAPSHOTbridged against, so without re-bridging the citations were dropped on the message swap. Covered by a new reducer unit test reproducing that exact ordering.E2E coverage (chat + ag-ui twins)
Aimock fixtures + Playwright specs asserting the rendered system:
angular.devlinksThe real
search_documentstool's no-match fallback returns the first 3 corpus docs, so the citation set is deterministic (ng-signals-overview,ng-signals-rxjs,ng-control-flow) with no live LLM.Test Plan
examples/chatcitations e2e — 3/3 pass locallyexamples/ag-uicitations e2e — 3/3 pass locally (was 0/3 before the fix)npx nx test ag-uigreen (incl. new reducer test); 0 lint errors🤖 Generated with Claude Code