minicode

Internal & Arsitektur (untuk kontributor)

Output Rendering Spec — boundary renderer

Status: target renderer boundary; terminal primitives existing Tanggal: 2026-09-25 Audit sumber: docs/OUTPUT_ARCHITECTURE_AUDIT.md

#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.tsKoleksi raw event, source map, markdown table, ledger, evict, selectionRaw bus + presentation callbackProjection model + view adapter
src/ui/tui/app.tsInput, viewport, layout, screen ownership, popup lifecycle, clipboardController + painterController; painter behind TuiHost
src/ui/assistant/simple.tsStream sanitizer, answer/tool state, collapse, table, copy, stdout/stderrPolicy + state + writerNormal/verbose projection writer
src/ui/assistant/turn-status.tsInfer phase dari raw bus dan heartbeatIndependent semantic stateDerived active-task projection
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.tsRaw event JSONL dan summaryTransport/raw semanticsCanonical machine projection
cli/commands/acp.tsText/tool/lifecycle ACPPartial semantic + raw fallbackCanonical lifecycle projection
src/ui/approval/prompt.tsHuman prompt/decisionDI/sink + direct fallbackProjection-fed prompt, same 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

  • User message: satu entry user dengan source range.
  • Assistant stream: live buffer yang di-cap; final node dipisah dari stream.
  • 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.

#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, fail loudly pada diagnostic contract; jangan diam-diam kembali ke bentuk raw yang tidak lengkap.

#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 terminal columns; CJK/emoji = dua kolom.
  • 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. 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.

#11. Migration seams

  1. PresentationAdapter dapat tetap emit raw-compatible events.
  2. ProjectionBridge baru diaktifkan per surface dengan flag MINICODE_PRESENTATION_V2.
  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. Setelah satu release flag-on dan test golden hijau, raw user-facing subscriptions dihapus; raw bus tetap untuk diagnostics/trace.

#12. 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.