AI citations / source attribution
ai-ui specs/ai-ui/citations.kmd
Footnote-style inline citations + hover/long-press preview cards + source list sidebar for AI responses backed by RAG or web search. Mandatory in any RAG/search-backed surface; compliance basis for EU AI Act transparency requirements on source disclosure.
When this spec applies
Primary triggers
- Display AI response that cites external sources
All triggers
- Render AI response with RAG-backed sources
- Implement Kortex research mode, Bot search-aware responses, or any RAG surface
- Audit AI surface for source attribution compliance
Specification body
Spec — AI citations / source attribution
RAG/search-backed AI responses MUST attribute sources. Sem attribution: viola transparency principle (
policies/security.kmdadjacente), EU AI Act art. 50 (high-risk applications), e LGPD art. 9. Consumer principal:chat-message-bubble.kmdR5 (multi-modal content).
Princípios
- Inline + sidebar dual — cada claim com superscript
[N]; sources list completa em sidebar/footer. - Click reveals source — não exige scroll: tap inline → preview card.
- Confidence transparente — quando RAG fornece relevance score, expose.
- Copy preserves citations — copy-to-clipboard mantém footnote refs.
- Workspace-scoped sources — RAG search scope obedece tenant boundaries.
R1 — Inline citation
Render: superscript [1] [2] em pontos onde claim is backed by source.
São Paulo has 12 million residents[1] and is the largest city in Brazil[2].
Visual:
- Superscript
vertical-align: super; font-size: 75%. - Color:
accent(perthemes/color-roles.kmd). - Cursor: pointer.
- Hover (desktop) / long-press (mobile): reveal preview card (R3).
- Click: scroll-to-source in sidebar OR open in new surface.
Inline placement is gateway-provided: response includes citations array with offsets, NOT inline-spliced by client.
R2 — Citation data model
Gateway response shape:
{
"content": [
{"type": "text", "text": "São Paulo has 12 million residents and is the largest city in Brazil."}
],
"citations": [
{
"id": "1",
"offsets": [{"text_index": 0, "start": 17, "end": 39}],
"source": {
"type": "web" | "doc" | "memory",
"url": "https://...", // for web
"doc_id": "...", // for doc
"memory_id": "...", // for memory
"title": "São Paulo - Population statistics",
"snippet": "...",
"favicon": "https://...",
"confidence": 0.92, // 0.0–1.0
"fetched_at": "2026-05-14T12:00:00Z"
}
},
...
]
}
Client renders superscript at each offsets[].start–end range,
linked to source by id.
R3 — Preview card (hover/long-press)
Reveal trigger:
- Desktop: hover 300ms.
- Mobile: long-press.
- Keyboard: focus → Enter or Space.
Card content:
┌─────────────────────────────────────────────┐
│ [favicon] Source title │
│ weather.com/sao-paulo · 2 hours ago │
├─────────────────────────────────────────────┤
│ "São Paulo is the largest city in Brazil │
│ with over 12 million residents..." │
├─────────────────────────────────────────────┤
│ Confidence: 92% · [Open source ↗] │
└─────────────────────────────────────────────┘
- Positioning: per
koder_kittooltip primitive; auto-flip se overflow. - Max width: 380dp (desktop) / 90% viewport (mobile).
- Z-index: above bubble; below modals.
- Dismiss: mouseout (desktop) / tap outside (mobile) / Escape.
R4 — Sources list (sidebar/footer)
Per chat-message-bubble (R5): citation_list rendered como content item no fim do bubble OU em sidebar lateral.
Footer mode (default):
— Sources
[1] weather.com/sao-paulo · São Paulo - Population statistics · 92%
[2] ibge.gov.br/cidades · IBGE 2024 census · 88%
Sidebar mode (Kortex research surface):
┌─ Sources (2) ──────────────────┐
│ 1 ▸ weather.com │
│ São Paulo Population │
│ Confidence: 92% │
│ │
│ 2 ▸ ibge.gov.br │
│ IBGE 2024 census │
│ Confidence: 88% │
└─────────────────────────────────┘
Sortable: por confidence desc (default), recency, alphabetical.
R5 — Confidence score
Display threshold (per consumer):
| Confidence | Badge |
|---|---|
| ≥ 90% | high (no badge — implicit) |
| 70–89% | "medium" badge (text-muted) |
| < 70% | "low" badge (warning); strong hint pra verify |
| missing | omit confidence display |
Confidence vem do RAG/search backend (services/ai/rag/ ou
services/ai/search/). Não computado client-side.
R6 — Copy preserves citations
When user copies text from bubble:
- Plain text mode: includes
[1][2]markers + appended footnotes:São Paulo has 12 million residents[1] and is the largest city in Brazil[2]. [1] São Paulo - Population statistics — https://weather.com/sao-paulo [2] IBGE 2024 census — https://ibge.gov.br/cidades - Markdown mode: standard footnote syntax (
[^1]…[^1]: ...). - Rich text mode: hyperlinks preserved.
Configurable per user preference (default markdown mode).
R7 — Surface bindings
| Surface | API |
|---|---|
| Flutter | KoderCitation({required source}) + KoderCitationList({required sources}) em koder_kit/lib/src/ai/citation.dart |
| Web | <koder-citation source-id="..."> + <koder-citation-list> |
| Compose Android | KoderCitation em koder-design-compose (futuro) |
| SwiftUI iOS | KoderCitation em koder-design-swift (futuro) |
| CLI / TUI | Inline: ^1 markers; sources block at end |
R8 — Acessibilidade
- Inline citation:
<sup><a href="#source-1" aria-label="Source 1: weather.com">[1]</a></sup>. - Preview card:
role="tooltip"quando aberto por hover;role="dialog"quando aberto por tap (focus management). - Sources list:
<ol>(ordered) com semantically correct numbering. - Keyboard nav: Tab cycle entre citations; Enter opens source.
- Screen reader: announces "Citation 1: weather.com, São Paulo Population, 92% confidence."
R9 — Multi-tenant + storage
- Source URLs MUST NOT leak cross-tenant: workspace A RAG search hits never visible em workspace B.
- Memory-sourced citations: cross-link
memory-drawer.kmd(#117); memory items scoped per workspace. - Audit log: every citation render event optional (controlado via
services/foundation/audit/config). - Source URLs in retention: respeita
policies/identity-data-retention.kmd.
R10 — i18n
| Key | en-US | pt-BR |
|---|---|---|
ai.citation.sources_header | "Sources" | "Fontes" |
ai.citation.confidence | "Confidence" | "Confiança" |
ai.citation.confidence_low | "Low confidence — verify carefully" | "Confiança baixa — verifique com cuidado" |
ai.citation.confidence_medium | "Medium confidence" | "Confiança média" |
ai.citation.open_source | "Open source" | "Abrir fonte" |
ai.citation.fetched_relative | "{relative_time}" | "{relative_time}" |
ai.citation.no_sources | "No sources cited" | "Nenhuma fonte citada" |
R11 — Per-preset variation
| Preset | Citation style |
|---|---|
material3 / material_expressive | Default superscript, card with shadow |
material2 | Superscript, card without shadow |
terminal_classic | Inline ^[1] text; preview is plain text below |
brutalist | Square brackets [1], sharp card border |
minimalist_mono | [1] mono, card minimal |
cyberpunk_neon | Glow superscript, neon outline preview |
glassmorphism | Frosted glass preview card |
T-suite
- T1 Inline render: response with 2 citations →
[1][2]superscripts visible at correct offsets. - T2 Hover preview (desktop): hover citation → card appears after 300ms.
- T3 Long-press preview (mobile): long-press → card appears.
- T4 Click navigates: tap inline → scrolls to source in sidebar/footer.
- T5 Sources list render: footer mode shows ordered list with confidence.
- T6 Confidence badge: source with 65% → "low" badge present.
- T7 Copy preserves: select bubble text + copy → clipboard includes
[1][2]+ footnotes. - T8 Sidebar sort: change sort to "recency" → list reorders.
- T9 Keyboard nav: Tab through citations + Enter opens source.
- T10 A11y screen reader: announce "Citation 1: ... 92% confidence."
- T11 Multi-tenant: citation referencing source from workspace A NOT renderable in workspace B context.
- N1 Missing citation in RAG-backed response: lint/policy violation (cross-link
chat-message-bubble.kmdR2 disclaimer doesn't replace citation). - N2 Citation overflow offset (start > text length): graceful fallback (omit citation, log warning).
Cross-link
- Companion:
chat-message-bubble.kmd(host),memory-drawer.kmd(memory-sourced citations),ai-disclaimer.kmd(parallel transparency mechanism) - Backend:
services/ai/rag/,services/ai/search/ - Policies:
multi-tenant-by-default.kmd,identity-data-retention.kmd,security.kmd - Color/typography:
themes/color-roles.kmd,themes/typography.kmd - Compliance: EU AI Act art. 50 (transparency), LGPD art. 9
References
meta/docs/stack/specs/ai-ui/chat-message-bubble.kmdmeta/docs/stack/specs/ai-ui/streaming-text.kmdmeta/docs/stack/policies/security.kmdmeta/docs/stack/policies/multi-tenant-by-default.kmd