AI citations / source attribution
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.
Spec — AI citations / source attribution
RAG/search-backed AI responses *UST*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
- *nline + sidebar dual*— cada claim com superscript
[N]; sources list completa em sidebar/footer. - *lick reveals source*— não exige scroll: tap inline → preview card.
- *onfidence transparente*— quando RAG fornece relevance score, expose.
- *opy preserves citations*— copy
toclipboard mantém footnote refs. - *orkspace-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
tosource 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 chatmessagebubble (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 RAGsearch backend (`servicesairag ou
servicesaisearch/`). 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:/eather.com/sao-paulo [2] IBGE 2024 census — https:/bge.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: crosslinkmemory-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
- *1*Inline render: response with 2 citations →
[1][2]superscripts visible at correct offsets. - *2*Hover preview (desktop): hover citation → card appears after 300ms.
- *3*Long
press preview (mobile): longpress → card appears. - *4*Click navigates: tap inline → scrolls to source in sidebar/footer.
- *5*Sources list render: footer mode shows ordered list with confidence.
- *6*Confidence badge: source with 65% → "low" badge present.
- *7*Copy preserves: select bubble text + copy → clipboard includes
[1][2]+ footnotes. - *8*Sidebar sort: change sort to "recency" → list reorders.
- *9*Keyboard nav: Tab through citations + Enter opens source.
- *10*A11y screen reader: announce "Citation 1: ... 92% confidence."
- *11*Multi-tenant: citation referencing source from workspace A NOT renderable in workspace B context.
- *1*Missing citation in RAG
backed response: lint/policy violation (crosslinkchat-message-bubble.kmdR2 disclaimer doesn't replace citation). - *2*Citation overflow offset (start > text length): graceful fallback (omit citation, log warning).
Cross-link
- Companion:
chat-message-bubble.kmd(host),memory-drawer.kmd(memorysourced citations), [`aidisclaimer.kmd`](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 - Colortypography: `themescolor-roles.kmd
,themes/typography.kmd` - Compliance: EU AI Act art. 50 (transparency), LGPD art. 9