AI code block
AI-generated code rendering with language detection, syntax highlight, copy button, optional run button, diff view, and defer-until-fence contract during streaming. Hosted by chat-message-bubble (#105) and artifact panel (#110). Shares syntax token vocabulary with themes/color-schemes.kmd.
Spec — AI code block
Sintaxe tokens consumidos do
themes/color-schemes.kmd"Vocabulário syntax". Deferuntilfence durante streaming viastreaming-text.kmdR4. Hospedado porchat-message-bubble.kmdR5 (content typecode) eartifact-panel.kmd(#110).
Princípios
- *efer until closed*— code blocks NOT highlighted during stream until closing fence (cross-link #106 R4).
- *opy always full*— copy button gives unsanitized source; never truncated.
- *un is opt-in per host*— Run button only when host has executor (Kortex liga; Talk não).
- *iff when modifying*— AI editing existing file → side
byside OR unified diff. - *ecurity boundary*— Run never
eval()s; uses sandboxed executor (Koda runtime, WebAssembly, or external worker).
R1 — Anatomia
┌──────────────────────────────────────────┐
│ python · 12 lines [⎘ ▶ ⇕] │ ← header
├──────────────────────────────────────────┤
│ 1 def fibonacci(n): │
│ 2 if n <= 1: │
│ 3 return n │
│ 4 return fibonacci(n-1) + fibonacci(n-2)
│ 5 │
│ 6 print(fibonacci(10)) │
└──────────────────────────────────────────┘Slots:
| Slot | Conteúdo |
|---|---|
| Language label | Detected language (per R2) |
| Line count | "{N} lines" |
| Actions | Copy ⎘ · (Run ▶ if executor present) · Diff toggle ⇕ (if applicable) |
| Body | Syntax-highlighted code with optional line numbers |
Mono font: JetBrains Mono per themes/typography.kmd R1.
R2 — Language detection
Order of resolution:
- Markdown fence info string:
python →python`. - Filename hint (if AI provides via custom block attribute):
.dart→dart. - Heuristic: first-line shebang (
#!/usr/bin/env python). - Token-based detection (per surface library:
flutter_highlight,prismjs, etc.). - Fallback:
plaintext.
Detected language displayed in header.
R3 — Syntax highlighting
Tokens consumed from themes/color-schemes.kmd:
| Token | Example |
|---|---|
syntax_keyword |
def, class, if, return |
syntax_string |
"hello", 'world' |
syntax_number |
42, 3.14 |
syntax_comment |
# this is a comment |
syntax_function |
fibonacci, print |
syntax_type |
int, str, List |
syntax_constant |
True, None, null |
syntax_operator |
+, ==, => |
syntax_punctuation |
(, ), ,, ; |
Perpreset color mapping via `themes/colorschemes.kmd` (não duplicar aqui).
Highlighting engine: per surface (Flutter flutter_highlight-compatible; Web prismjs-compatible; CLI uses chroma or equivalent). Surfaces MUST honor token vocabulary acima; engine choice é impl detail.
R4 — Copy button
Behavior:
- Tap copy → write *ull unmodified source*to clipboard.
- Visual feedback: toast "Copied to clipboard" (i18n) per 1.5s.
- Anti-pattern: Copy NEVER truncated. Even when display truncates per
R7, clipboard has full.
R5 — Run button (opt-in per host)
Visible only when host declares executor capability:
KoderAICodeBlock(
code: ...,
language: "python",
executor: KoderKodaSandbox(), // or null = no Run button
)Behavior:
- Tap Run → execute via passed executor.
- Spinner during execution.
- Output appended below code block in collapsible "Output" section.
- Errors displayed inline with error styling.
*ecurity* executor MUST be sandbox-isolated. Options:
| Executor | Sandbox |
|---|---|
| Koda runtime | Native Koda VM with capability whitelist |
| WebAssembly | wasmtime/wasmer with WASI-restricted |
| External worker | Sub-process com syscall filter |
| Remote (cloud) | services/ai/sandbox LXC |
NEVER use raw eval() / Function() / exec(). NEVER trust AI-generated code to be safe (cross-link policies/security.kmd).
R6 — Diff view
When AI edits existing file, response includes diff metadata:
{
"content": [{
"type": "code",
"code": "def fibonacci(n):\n ...",
"language": "python",
"diff": {
"mode": "edit",
"original_path": "lib/math.py",
"original_content": "def fib(n):\n ...",
"edits": [...]
}
}]
}Diff toggle button switches body between:
- *ode view* just new code (default for new files).
- *ide
byside diff* original | new (default for edits). - *nified diff* standard
+/-markers.
Diff lib per surface: flutter_diff_view, diff2html, etc.
R7 — Truncation (display only)
Long code blocks (>50 lines): collapse with "Show more" after 50 lines. Copy + Run always operate on FULL source (R4).
R8 — Surface bindings
| Surface | API |
|---|---|
| Flutter | KoderAICodeBlock({required code, required language, executor?, diff?}) em koder_kit/lib/src/ai/ai_code_block.dart |
| Web | <koder-ai-code-block language="..." code="..."> em koder_web_kit |
| Compose Android | KoderAICodeBlock (futuro) |
| SwiftUI iOS | idem (futuro) |
| CLI / TUI | Plain text with chroma highlighting; copy via OSC52; no run button |
R9 — Acessibilidade
- Code container:
role="region" aria-label="Code block, {language}, {N} lines". - Line numbers:
aria-hidden="true"(não anunciar). - Syntax color: MUST atender AAA contrast (cross
link `themes/colorroles.kmd` R4). - Copy button:
aria-label="Copy code to clipboard". - Run button:
aria-label="Run code". - Keyboard: Tab through code → block focuses → Tab again → actions.
- Screen reader: announces language + line count; code can be read line by line.
R10 — i18n
| Key | en-US | pt-BR |
|---|---|---|
ai.code.action.copy |
"Copy code" | "Copiar código" |
ai.code.action.copy.success |
"Copied" | "Copiado" |
ai.code.action.run |
"Run code" | "Executar código" |
ai.code.action.diff_toggle |
"Toggle diff view" | "Alternar visualização de diff" |
ai.code.label.lines |
"{n} lines" | "{n} linhas" |
ai.code.label.language |
"Language: {lang}" | "Linguagem: {lang}" |
ai.code.output.header |
"Output" | "Saída" |
ai.code.run.error |
"Execution failed" | "Falha na execução" |
ai.code.show_more |
"Show more" | "Mostrar mais" |
R11 — Per-preset variation
| Preset | Code block style |
|---|---|
material3 / material_expressive |
Default; rounded; shadow |
material2 |
Default; smaller radius |
terminal_classic |
Plain text in bg-inset; no border; no shadow |
brutalist |
Sharp corners; 2px solid border |
cyberpunk_neon |
Glow border in accent; gradient backdrop |
glassmorphism |
Backdrop blur; 20% surface tint |
minimalist_mono |
Single bottom-border; no background fill |
T-suite
- *1*Render code: code + language → syntax highlighted body + header showing language + line count.
- *2*Language detection: code without fence info but with
#!/python→ detected as python. - *3*Copy full: tap copy → clipboard equals full source even when display truncated > 50 lines.
- *4*Copy toast: tap copy → "Copied" toast appears.
- *5*Run available: pass executor → Run button visible; tap → executor invoked.
- *6*Run unavailable: no executor → no Run button.
- *7*Diff view edit: receive diff metadata with mode="edit" → diff toggle visible; tap → side
byside rendered. - *8*Defer during stream (cross
link #106 T6): code block fenced opens midstream → placeholder "(writing code…)"; on close → syntax highlight done ONCE. - *9*Truncation: 100-line code → 50 lines visible + "Show more"; copy still has 100 lines.
- *10*Security: Run with malicious code (e.g.,
os.system('rm -rf /')) → sandbox prevents file system access; capability error surfaces. - *11*A11y: screen reader announces "Code block, python, 12 lines"; copy button has aria-label.
- *1*Run without executor: spec validation prevents calling onRun when executor=null.
- *2*Syntax engine fallback: unsupported language → renders as plaintext (no crash).
Cross-link
- Companion:
chat-message-bubble.kmd(host),streaming-text.kmdR4 (defer),artifact-panel.kmd(alternate host for large code),agent-step-trace.kmd(step body may include code) - Syntax tokens:
themes/color-schemes.kmd - Typography:
themes/typography.kmdR8 (code typography) - Color contrast:
themes/color-roles.kmdR4 AAA - Security:
policies/security.kmd(executor sandbox) - Executor:
services/ai/sandbox/,engines/lang/koda/runtime/(Koda VM) - Refs: Claude Artifacts, ChatGPT code blocks, GitHub Copilot diff