Grounded answers already show inline citation markers like [1, 2, 3, 4, 5, 6], but users cannot see what those numbers refer to — no titles, URLs, or dates in the chat UI.
Backend already emits and persists citations (chat_backend#62, shipped in Phases 1–3). Frontend does not handle the frame or Prompt.citations yet.
Related: activity-status frames live in chat_web_app#96 (same WebSocket envelope; citations explicitly out of scope there).
## Problem
Grounded answers already show inline citation markers like `[1, 2, 3, 4, 5, 6]`, but users cannot see what those numbers refer to — no titles, URLs, or dates in the chat UI.
Backend already emits and persists citations ([chat_backend#62](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/62), shipped in Phases 1–3). Frontend does not handle the frame or `Prompt.citations` yet.
Related: activity-status frames live in [chat_web_app#96](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/96) (same WebSocket envelope; citations explicitly out of scope there).
## Backend contract (already live)
WebSocket frame after stream end:
```json
{"v": 1, "type": "citations", "data": [
{"index": 1, "title": "…", "url": "https://…", "published_at": "2026-07-03"}
]}
```
Also persisted on the assistant `Prompt.citations` and returned from conversation/prompt APIs so reload can restore Sources.
## Proposed UX
1. **Sources block** under the assistant bubble when citations exist — numbered list: title (link), optional date, truncated domain/snippet if useful.
2. **Inline markers** — style `[1]` / `[1, 2]` in the message as links/hover that scroll/highlight the matching Sources row (or open the URL).
3. **Reload** — hydrate from `Prompt.citations` when loading conversation history, not only live WS frames.
4. **Empty / missing** — if no citations frame and no stored citations, render nothing (no empty Sources header).
5. Share the versioned-frame parser path with #96 (`MessageContext` JSON envelope); ignore unknown types safely.
## Acceptance
- [ ] Live grounded turn: after answer streams, Sources list shows with titles linking to URLs matching `[n]` in the prose
- [ ] Clicking / focusing an inline `[n]` highlights or opens the corresponding source
- [ ] Reload conversation: Sources still appear from persisted `Prompt.citations`
- [ ] Non-grounded turns: no Sources UI
- [ ] Theme + mobile safe; external links open safely (`rel="noopener noreferrer"`, new tab)
- [ ] Unit tests: WS citations frame attach; history hydrate; no-citations fallback
- [ ] Frame envelope compatible with #96 status frames (no conflicting `type` values)
## Out of scope
- Changing how the backend chooses / ranks sources (#62)
- Activity status labels (#96)
- Agentic step frames ([chat_backend#63](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/63))
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
Grounded answers already show inline citation markers like
[1, 2, 3, 4, 5, 6], but users cannot see what those numbers refer to — no titles, URLs, or dates in the chat UI.Backend already emits and persists citations (chat_backend#62, shipped in Phases 1–3). Frontend does not handle the frame or
Prompt.citationsyet.Related: activity-status frames live in chat_web_app#96 (same WebSocket envelope; citations explicitly out of scope there).
Backend contract (already live)
WebSocket frame after stream end:
Also persisted on the assistant
Prompt.citationsand returned from conversation/prompt APIs so reload can restore Sources.Proposed UX
[1]/[1, 2]in the message as links/hover that scroll/highlight the matching Sources row (or open the URL).Prompt.citationswhen loading conversation history, not only live WS frames.MessageContextJSON envelope); ignore unknown types safely.Acceptance
[n]in the prose[n]highlights or opens the corresponding sourcePrompt.citationsrel="noopener noreferrer", new tab)typevalues)Out of scope