minicode

Internal & Arsitektur (untuk kontributor)

Output Rendering Spec — boundary renderer

Status: implemented renderer boundary; canonical policy active, with streaming content transport and one ACP compatibility window. Tanggal: 2026-09-25 Kontrak aktif: renderer TUI/linear di src/presentation/ dan src/ui/.

#1. Prinsip boundary

Renderer berubah dari string consumer menjadi consumer dari semantic projection:

PresentationState + ContentStore
        ↓
ProjectionPolicy(mode, viewport, locale)
        ↓
RendererInput (nodes + source refs + interaction state)
        ↓
TUI / linear / machine renderer

Renderer boleh:

  • menghitung layout kolom, wrap, tinggi baris, dan posisi cursor;
  • memilih warna, glyph fallback, dan density;
  • menangani hover, selection, scroll, popup, dan keyboard;
  • memakai sanitizeAnsi sebagai defense-in-depth.

Renderer tidak boleh:

  • menentukan status dengan includes("denied"), regex, atau warna;
  • membangun turn summary sendiri;
  • menyimpan truth runtime/approval/durasi;
  • meng-clip source text berdasarkan pixel/frame yang sudah dirender;
  • menentukan bahwa machine consumer boleh membaca human string.

#2. Inventaris renderer aktual

ModuleTanggung jawab sekarangKeterkaitanTarget
src/ui/tui/transcript.tsStreaming content buffer, source map, markdown table, canonical tool ledger, evict, selectionRaw content bus + presentation snapshot/events + policy DIView adapter; no semantic status parsing
src/ui/tui/app.tsInput, viewport, layout, screen ownership, popup lifecycle, clipboardController + painterController/painter boundary unchanged
src/ui/assistant/simple.tsStream sanitizer, answer/tool state, collapse, table, copy, stdout/stderrPresentation events + policy DI + streaming textNormal/verbose projection writer
src/ui/assistant/turn-status.tsInfer active phase dan heartbeatPolicy-derived activity labelTransient view only
src/ui/runtime/screen.tsAlternate screen, atomic paint, regionTerminal primitiveTetap dipertahankan
src/ui/runtime/statusline.tsArbitrasi transient stderrTransport ownerTetap, plus writer policy
cli/commands/exec.tsCanonical lifecycle JSONL, text delta, versioned summaryPresentation events + text transportCanonical machine projection
cli/commands/acp.tsCanonical lifecycle ACP, sanitized text deltaPresentation events + compatibility fallbackCanonical lifecycle projection
src/ui/approval/prompt.tsHuman prompt/decisionDI/sink + policy-compatible fallbackSame decision event

#3. Kontrak input renderer

interface RendererInput {
  snapshot: PresentationSnapshot
  mode: "normal" | "verbose" | "debug" | "machine"
  viewport: { columns: number; rows: number; locale: "en" | "id" }
  content: ContentResolver
  clock: RendererClock
}

PresentationSnapshot harus read-only. ContentResolver mengembalikan ContentEntry dengan marker retention; tidak mengembalikan seluruh raw database tanpa batas. RendererClock adalah satu-satunya sumber waktu visual; Date.now() tidak boleh dipanggil dari node builder atau state reducer.

#Projection node

type ProjectionNode =
  | { kind: "user"; text: string; source: SourceRef }
  | { kind: "message"; text: string; source: SourceRef; streaming: boolean }
  | { kind: "activity"; activity: ActivityEntry }
  | { kind: "finding"; finding: FindingEntry; evidence: EvidenceRef[] }
  | { kind: "plan"; plan: PlanEntry }
  | { kind: "result"; result: ResultEntry }
  | { kind: "warning" | "error" | "diagnostic"; error: SemanticError }
  | { kind: "system"; text: string; source: SourceRef }

Node membawa semantic payload. priority, collapse, visibility, dan groupKey menentukan policy; node tidak membawa keputusan terminal.

#4. Projection policy

Node/statusNormalVerboseDebugMachine
Task runningsatu activity/current stateduration + targetevent IDstyped lifecycle
Tool completedreceipt singkatreceipt + durationfull metadatatyped completed
Tool deniedwarning + reasonpolicy pathraw category redactedtyped denied
Reasoningcollapsed/dotsexpandedraw redacted chunksstructured ref/status
Planupcoming stepsstep statusinternal linkstyped plan
Findingfinding + evidence ringkasevidence detailfull evidencetyped finding
Diagnosticactionable onlycategory + next steptrace/countertyped diagnostic
Context compactedsystem warningreason detailtracetyped event

Policy tidak boleh mengubah final status. machine boleh lebih ringkas secara visual, tetapi tidak boleh kehilangan terminal state atau category.

#5. TUI renderer

#Ownership

TuiApp tetap memiliki:

  • alternate-screen lifecycle;
  • keyboard/mouse decoder;
  • viewport scroll dan selection;
  • popup suspend/resume;
  • full-frame paint dan cursor parking.

TuiApp tidak lagi menjadi sumber status tool. Ia menerima status dari snapshot/ projection melalui TuiHost, dengan fallback deny bila DI tidak tersedia.

#Frame

Frame terdiri dari:

  1. transcript viewport;
  2. optional inline / dropdown;
  3. composer/prompt atau activity row;
  4. satu spacer;
  5. status bar.

screen.ts tetap satu-satunya alt-screen owner. paintRegion() hanya untuk popup; popup tidak boleh membuat listener stdin kedua.

#TUI semantic projection

  • Prompt percakapan: satu entry user dengan source range. Input command slash tetap tersedia di composer history, tetapi tidak ditambahkan sebagai pesan transkrip.
  • Assistant stream: live buffer yang di-cap; final node dipisah dari stream.
  • Whitespace jawaban dirapikan hanya pada layer tampilan: baris kosong tepi dibuang, jeda berulang di luar code fence dirapatkan, isi fenced code tetap literal.
  • Activity: started → running → terminal, dengan glyph + status word.
  • Tool child: default dikelompokkan ke parent delegate_task; child detail tersedia pada verbose/debug.
  • Context compaction: system entry, tidak menghapus history.
  • Summary: system node dari canonical turn summary, bukan hasil hitung ulang widget.
  • Error: node dengan category/cause/hint; tidak hanya string merah.
  • Pilihan provider/model dan status selesai dari popup pengelolaan tidak diarsipkan ke transkrip; state aktif dibaca dari footer/manager saat ini. Error tetap terlihat.

#Selection dan clipboard

Selection adalah view concern, tetapi sumbernya adalah SourceRef semantic:

  • anchor/focus menyimpan source ID + logical offset;
  • row visual menyimpan source range + display columns;
  • wrap, CJK/emoji, SGR, table border, dan evict diuji;
  • click prompt hanya memindahkan cursor;
  • Ctrl+C copy hanya jika selection valid; tanpa selection no-op;
  • Esc clear selection dulu, lalu abort/batal;
  • OSC52 menerima plain sanitized payload dan failure state eksplisit.

Selection tidak boleh menyalin terminal control sequence atau table padding. Terminal native selection bukan acceptance contract karena mouse tracking aktif.

#6. Linear renderer

Linear renderer menerima ProjectionNode[]/snapshot yang sama:

  • stdout: permanent output dan answer/result;
  • stderr: activity, reasoning (verbose), warning, error;
  • non-TTY: stripSgr, no cursor control, no alternate screen;
  • TTY: satu compact ledger per operation, parallel child group;
  • markdown table: semantic table hanya di TTY; non-TTY mempertahankan source sanitized;
  • copy buffer: isi yang benar-benar terlihat, dengan cap dan marker.

simple.ts tidak lagi menentukan status dari raw result. Ia menerima activity snapshot/event canonical, lalu hanya memilih glyph, stream, dan disclosure level.

#7. Machine renderers

#Exec

Exec tidak memakai human renderer. Mapper canonical:

  • canonical event → JSON object;
  • terminal summary → object type:summary;
  • scrub sebelum serialize/write;
  • setup failure → summary ok:false pada stdout + human error stderr;
  • tidak ada console.log dari builtin selama machine mode.

#ACP

ACP juga tidak mengurai terminal text. Lifecycle projection menggunakan event canonical, sedangkan text delta tetap transport-compatible. Jika lifecycle subscription tidak tersedia dalam compatibility window, hanya tool.started diemit sebagai type:"tool" legacy; consumer harus menandainya sebagai fallback dan tidak boleh menyusun status terminal dari raw text.

#8. Diagnostics dan direct writes

Direct write bukan otomatis salah; ia harus diklasifikasi:

WriterKategoriRoute target
TuiApp/screenTUI framestdout owner screen
simple permanenthumanstdout
turn-status/spinnertransientstatusline owner
approval/askLinehuman inputprompt/overlay owner
CLI setup/recovery/verifydiagnosticstderr atau transcript
exec/ACPmachineprotocol sink
debug busdebugstderr/trace

Writer baru harus mendaftarkan category dan owner. statusline.ts tetap menjadi arbitrator untuk transient, tetapi bukan pengganti semantic routing.

#9. Geometry, text, dan fallback

  • displayWidth() menghitung kolom terminal per grapheme dengan Intl.Segmenter("und", { granularity: "grapheme" }). Karakter CJK/fullwidth dihitung dua kolom; combining marks tidak menambah kolom. Cluster emoji yang ditampilkan sebagai emoji (Emoji_Presentation/VS16), skin-tone, ZWJ, regional indicator, dan keycap dihitung dua kolom sebagai satu unit. Kebijakan ini mengikuti perilaku terminal umum; MiniCode tidak mengukur lebar glyph per terminal, sehingga font/terminal yang menyimpang dapat tetap berbeda.
  • Wrapping, truncation/chunking, transcript selection, kursor prompt, dan geometri frame memakai helper lebar bersama. truncateToWidth() dan chunkByWidth() tidak memecah grapheme.
  • Sanitasi control bytes dilakukan sebelum output; SGR boleh hanya bila color gate.
  • Resize dibaca saat paint, bukan saat event.
  • NO_COLOR/legacy Windows memakai glyph ASCII atau status word.
  • Narrow terminal memakai truncation/wrap yang menjaga path/target penting.
  • TTY table boleh aligned; machine/non-TTY tidak boleh berubah makna.
  • Copy/paste tidak bergantung pada warna.

#10. Visual system dan accessibility

src/ui/render/theme.ts menyediakan token semantik (success, error, warning, info, accent, muted, faint) dengan getter runtime. Token warna hanya menegaskan status dan hierarki; status penting juga memakai kata atau glyph sehingga tetap dapat dibaca pada NO_COLOR/mono. Fallback ASCII dikendalikan oleh MINICODE_ASCII dan capability UTF-8 Windows.

Interaksi tetap keyboard-first: PgUp/PgDn dan Home/End untuk transkrip, Esc untuk membatalkan/menutup selection, Ctrl+D untuk keluar, dan popup menahan fokus sampai ditutup. Tidak ada mode screen-reader terpisah karena kontrak fullscreen terminal dibekukan. Navigasi keyboard dan jalur mono memiliki test; screen-reader announcement, kontras fisik, dan manual assistive-technology UAT belum diverifikasi. Tampilan antar terminal tetap dapat berbeda.

#11. Parity matrix

SemanticTUILinearExecACP
tool completedledger successledger successtool.completedtool.completed
tool denieddeny glyph/wordwarning + reasontool.deniedtool.denied
turn cancelledstopped/abort statestopped/errorturn.cancelled + summaryturn.cancelled/error
file receiptactivity/sourcereceipt linetyped receipttyped receipt
context compactedsystem entrywarningtyped system eventtyped system event
durationpinned/summarysuffixfieldfield
parent/childgroupedgroupedIDs/linkIDs/link

Parity diuji pada semantic object sebelum snapshot string. Test output tetap diperlukan untuk regressions visual dan protocol.

#12. Migration seams

  1. PresentationAdapter dapat tetap emit raw-compatible events.
  2. ProjectionBridge aktif untuk semua surface; flag MINICODE_PRESENTATION_V2 dihapus di Phase 8.
  3. TUI/linear dapat membandingkan canonical projection dengan legacy output pada test.
  4. Exec/ACP memakai canonical projection lebih dulu karena machine parity paling mudah diukur.
  5. After one release and green golden tests, raw semantic subscriptions were removed from user-facing TUI. Raw EventBus remains for streaming content and diagnostics/trace only.

#13. Definition of done renderer

  • Tidak ada renderer yang meng-status-kan teks.
  • TUI/linear/exec/ACP menghasilkan semantic fields yang sama.
  • Sanitasi, width, lifecycle, machine stream, dan clipboard invariants tetap hijau.
  • Tidak ada direct write baru yang tidak terkategori.
  • Legacy path removal tidak mengubah exit code atau terminal contract tanpa update docs/TERMINAL_CONTRACT.md dan peta test.