Perubahan v7.4 (P12 S2.2 WorkMode): invarian baru I35 — WorkMode (lapis intent DISCUSS/PLAN/EXECUTE) sebagai dimensi TERPISAH dari permission
mode. Command /work-mode <DISCUSS|PLAN|EXECUTE> (aksi operator eksplisit, transisi berlaku pada sesi yang sedang berjalan); footer menampilkan <WorkMode>·<permission mode> saat WorkMode ≠ EXECUTE (PLAN·auto, DISCUSS·auto, PLAN·readonly); EXECUTE (default) mempertahankan label permission mode seperti sebelumnya. Tab/Shift+Tab dan /mode TETAP khusus permission mode. Rincian di I35.
Perubahan v7.3 (P11 Presentation & UX): invarian baru I34 — subcommand exec dan daemon menerima argv SCOPE dari router (flag global dikenal yang mendahului token subcommand tetap berlaku; token subcommand tidak ikut ke handler). Kalimat lama I28 "Flag exec diparse dari argv SESUDAH token subcommand" sudah tidak lengkap sejak P11.2 menerima bentuk flag-mendahului, diganti rujukan ke I34. Registrasi peta proteksi: test/cli-subcommands.test.ts sudah ada SEBELUM P11.3 — yang ditambahkan hanya describe cli: daemon scoped argv (P11.3) di dalamnya, BUKAN file baru; test/exec-json-envelope.test.ts + test/cli-session.test.ts melindungi kontrak exec scoping (P11.2) dan verification summary (P11.1). Nol perubahan perilaku runtime — dokumen mengejar kontrak yang sudah diterima + diuji.
Perubahan v7.2 (2026-09-25): mouse tracking ?1002h + ?1006h, drag selection app-level, Ctrl+C copy selection, wheel tetap scroll, popup turun ke ?1000h, dan MINICODE_MOUSE_SELECTION=0 sebagai kill switch.
Perubahan v7.1 (2026-09-25): mouse wheel X10/SGR untuk scroll transkrip; klik mouse tetap diabaikan; mouse tracking dibatasi pada lifecycle TUI; /copy [n] menyalin 1–10 turn terakhir; abort turn TUI memakai Esc.
Perubahan v7 (temuan audit audit-tui): penanganan sinyal fatal SIGTERM/SIGHUP (I31), navigasi transkrip Home/End + Shift+PgUp/PgDn (I14), hint abort sekali saat busy (I14), idle guard form dua-tahap (I21), spark statis via MINICODE_MOTION=0 (I13), parkir kursor di dalam blok sync-update (I25).
Satu-satunya tampilan interaktif adalah TUI fullscreen: transkrip ala shell + status bar + popup komposit. REPL linier, footer lengket (chrome), overlay inline, dan jendela info DIHAPUS — tak ada mode, tak ada flag (--tui usang = no-op). Popup = satu kotak: daftar DAN input teks/pilihan terjadi di dalamnya (form); tak ada ketikan di luar kotak. Bahasa UI dwibahasa (en/id) via i18n nol-dependensi. Perubahan perilaku apa pun di bawah wajib: update dokumen ini + test yang menjaganya (lihat "Peta proteksi").
#Sifat produk
- Interaktif (TTY mampu): aplikasi fullscreen alternate-screen dari start sampai quit. Area transkrip bergaya shell (prompt
minicode ›, jawaban model, ledger›), status bar bawah, popup kecil di atas transkrip yang tetap terlihat (redup) di belakang. Keluar = buffer utama kembali persis (transkrip sesi tidak di-dump ke scrollback). - Non-interaktif (one-shot prompt,
exec, pipe/redirect/CI): cetak polos, tak pernah membuka TUI. - Tanpa panel/sidebar; mouse app-level untuk wheel scroll dan drag-selection transkrip (bukan native terminal selection); tanpa animasi dekoratif (pulse spark status bar dapat dimatikan
MINICODE_MOTION=0— I13).
#Contract stdout/stderr
| Stream | Isi | Catatan |
|---|---|---|
| stdout | Output PROGRAM yang bermakna: teks model, receipt perubahan (› write_file …), daftar/artefak perintah | Harus bersih dari cursor-control pada non-TTY |
| stderr | Human-facing progress/diagnostic: ledger tool (› … hijau/merah), reasoning (verbose), warning, error | Boleh transient rendering bila stderr TTY |
| keduanya | Warna hanya bila stream TTY (stdout.isTTY); NO_COLOR menang; TERM/COLORTERM env TIDAK menyalakan warna pada pipe/redirect | Lihat src/ui/render/theme.ts colorLevel() |
Non-TTY (pipe/redirect/CI/file): 0 cursor control, 0 alternate screen, 0 animasi spinner, 0 ANSI yang tidak diperlukan, output deterministic.
#Klasifikasi writer (wajib dipatuhi)
- permanent user-visible output — teks model, receipt, hasil perintah → stdout (idle non-interaktif).
- frame TUI — SATU-SATUNYA penulis layar saat interaktif adalah
App(src/ui/tui/app.ts): transkrip viewport + prompt + status bar + komposit popup, satu repaint sinkron per event. Modul lain DILARANG menulisprocess.stdoutlangsung saat App aktif (popup melukis regionnya viapaintRegion, bukan frame penuh). - diagnostic/logging —
[warn],[router],[mcp],[vector], dst. dari layer non-UI → bolehprocess.stderr.writementah. - machine-readable output — mis.
--json, protokol MCP server → stdout saja, tanpa apa pun yang lain. - debug-only output — guard
verbose/env; tidak pernah default.
#Ownership
Apppemilik tunggal layar (alt-screen enter saat start, exit saat quit — selalu berpasangan, termasuk exception/SIGINT via handlerprocess.on("exit")sinkron best-effort). Turn berjalan = indikator spark pada status bar.- Sinyal fatal (I31): saat sesi TUI hidup,
App.run()memasangprocess.on("SIGTERM")/process.on("SIGHUP")(handler terpisah karena Bun tak menyampaikan nama sinyal — satu handler bersama tak bisa membedakan) → restore terminal (alt-screen exit + raw mode pulih) laluprocess.exit(128+n). Pasangan ketat: keduanya dilepas di cleanuprun()— tanpa sesi TUI, sinyal TIDAK di-intercept (perilaku default proses tetap). Tanpa ini SIGTERM meninggalkan terminal di alt-screen + raw mode (user harusreset). - App me-repaint live mengikuti event bus (stream teks, ledger, thinking; coalesce 30ms) — TANPA ini layar buta selama turn.
?1002h + ?1006hmengaktifkan tracking drag;?1000h + ?1006hsaat popup;?1000l/?1002l/?1006lpada quit/child/sinyal. Suspend menahan repaint (popup melukis sendiri); quit menahan semua. - Popup komposit (
/model,/provider,/sessions, form, approval): controller/view memanggilapp.suspend()(lepas listener stdin + redupkan layar), view melukis HANYA region kotak viapaintRegion(tanpa clear — transkrip di belakang tetap tampil),app.resume()repaint penuh saat tutup. Tanpa suspend: byte masuk ke dua tempat (App + popup) dan frame App menimpa popup — dilarang. - Saat turn berjalan SEMUA input dibekukan kecuali abort (Esc) dan scroll (mouse wheel, PgUp/PgDn). Enter saat busy = sunyi (bukan antre, bukan petunjuk). Abort pertama menampilkan hint "tekan Esc lagi untuk keluar" di slot indikator SEKALI (anti keluar sesi tak sengaja saat double-tap "memastikan" — I14); abort kedua dalam 1,5 dtk = quit.
- Checkpoint = milik lifecycle sesi. Bus kernel memanggil handler
turn:completedsecara sinkron dan tak menunggu promise-nya, jadi operasi tulis checkpoint (async) harus DIMILIKI: operasi yang dimulai dicatat, kegagalannya ditangani saat lahir (bukan unhandled rejection, bukan sukses palsu) dan dilaporkan lewat diagnostic[warn] checkpoint failed: …ke stderr.close()= BARRIER: setelah ia kembali, tak ada pekerjaan checkpoint milik sesi itu yang masih berjalan (manifest sudah tertulis atau kegagalannya sudah tercatat).
#Popup, dialog, form & i18n
screen.ts= pemilik?1049h/?1049l+paintRegion(cursor-addressed, tanpa clear) +clearRegion. Refcount nested, idempoten, null-screen fail-closed di non-TTY/TERM=dumb. Siklus hidup region: render yang menyusut/bergeser membersihkan union lama+baru (tanpa baris hantu); tutup menghapus region sendiri sebelum pemilik repaint.- Isi popup: kotak terpusat (
dialog.ts: border, judul opsional dalam garis atas, TANPA bayangan) di atas konten redup;dialogBoxuntuk komposit,dialogFrame(backdrop░faint) hanya untuk layar yang memiliki buffer sendiri. - Lebar kotak popup TETAP per permukaan (model 64, picker 64, provider 76, form 64 — min=max): geometri stabil, tak bernapas saat filter/error. Nama/ID di DEPAN label (truncasi memakan ekor — tanggal/konteks boleh hilang, id selamat).
- Form dalam popup (
form.ts): field text/secret(mask•)/select/confirm, Tab/↑↓ pindah, ←/→ pilih atau geser kursor, Enter validasi+submit, Esc dua-tahap (tekan-1 bersihkan field aktif, tekan-2 batal — isi tidak pernah hilang sekali tekan); error validasi merah dalam box; kursor terminal diparkir di field aktif. Add/edit provider, delete confirm, dan wizard input WAJIB lewat ini (bukan askLine liar di luar kotak). - Dropdown
/tetap inline menempel prompt (harus mengikuti kursor ketik): berbingkai, terhapus di SEMUA jalur keluar. - Kursor diparkir di SEMUA permukaan (picker/manager/form/App): baris item/field aktif — bukan ditinggal di posisi paint terakhir.
- i18n dwibahasa (
src/ui/i18n/): kamus statisen.ts/id.ts(id WAJIB kunci = en — tsc mengawal),t(key, params)interpolasi{nama}, fallback kunci hilang → en. Prioritas:/lang(sesi) >MINICODE_LANG> state.jsonlang> locale OS (LANG/LC_ALLawalanid) > en. Semua string user-visible TUI lewatt()— literal Indonesia hardcode di luar dict dilarang (test anti-regresi). Getter runtime, bukan const beku. - Keybinding konsisten (kontrak I25):
Escdua-tahap di semua input teks;backspace/deleteselalu edit teks (aksi hapus-data viad/Del+ confirm dalam popup, tercatat); navigasi daftar =↑↓+ `pgup/ pgdn+home/end` di semua popup ber-daftar; hint footer tiap permukaan hanya tombol yang benar-benar berlaku di sana. - Approval tool & ask_user: blok pertanyaan + keputusan DICATAT di transkrip (jejak audit) dan layar di-repaint sebelum menjawab; input via askLine di baris kursor (aman: input App beku saat busy; Esc/Ctrl+C = abort + deny).
- Layar tak mampu (non-TTY,
rows < 10,TERM=dumb): view menolak BERSUARA + batal (fail-closed). TAK BOLEH diam. /status&/history= readout ke transkrip (bukan popup);/helpringkas (≤6 baris, penuh via/help tombol);/expandmembuka buffer isi tool; pilihan/modelpersist antar sesi + strukmodel: xdi transkrip.
#Invariant (nomor dipakai di peta proteksi)
- Satu tampilan interaktif: TUI fullscreen; frame penuh tiap repaint (tepat
rowsbaris); scrollback utama tak tersentuh selama sesi. - stdout/stderr contract seperti tabel di atas (TTY & non-TTY deterministic).
- Satu kepemilikan layar (
App); popup satu lifecycle (suspend/resume+paintRegion). - Permanent output tidak bergantung pada painter TUI.
- Tidak ada popup yatim / input bocor setelah success / failure / cancellation / SIGINT / session end. Tidak ada modal yatim.
- Non-TTY: 0 cursor control / alternate screen / spinner animation / ANSI tak diperlukan.
- Tool activity default compact; ledger
› nama targetsatu baris. - Streaming tanpa duplicate output / overlap / stale status.
- Foreign stderr writers boleh mentah di layer non-UI.
- (Dilebur ke I3 — nomor dipertahankan agar referensi lama tak patah.)
- Resize memakai lebar/tinggi SAAT PAINT (bukan saat event) — termasuk geometri popup, dialog, dan viewport TUI.
- Long session tetap readable (cap 5000 baris, tertua dibuang). Proyeksi presentasi mempertahankan cap yang sama dan menambahkan satu marker
… N baris awal di luar viewport — riwayat penuh di modelyang melacak jumlah baris yang di-evict;total()tetap monotonik. Buffer jawaban live dibatasi 1.000.000 karakter dan menampilkan marker truncasi; satu layout paint tidak boleh memproyeksikan transcript dua kali.
- Status bar: 1 baris dasar (
✦ 00.00.00 mode model cwd) dari sumber yang sama dengan angka sesi.✦tetap menjadi indikator proses hidup; timerHH.MM.SSberada tepat di sebelahnya. Idle=redup; saat aktif hanya detik putih, menit ikut terang setelah 60 dtk, dan jam setelah 1 jam. Reasoning memakai dots...→..→.→..→..., sedangkan teksWorking/Thinking/Runningtidak tampil. Bullet separator dihapus; field dipisahkan oleh spasi dan warna. Promptminicode ›tampil saat boot/idle, hilang saat busy, dan kembali otomatis setelah turn selesai; aksi edit/histori tetap dapat membuka composer.MINICODE_MOTION=0membuat dots dan sparkle statis tetapi tidak menghilangkan feedback; satu baris kosong memisahkan composer dari footer. - Binding TUI: mouse wheel (X10/SGR) scroll transkrip; drag kiri app-level membuat selection transkrip dan
Ctrl+Cmenyalinnya; PgUp/PgDn scroll; **Shift+PgUp/PgDn (ESC[5;2~/6;2, modifier 2) = setengah halaman; Home/End saat prompt KOSONG = lompat baris-teratas/ekor transkrip (baris berisi = editing awal/akhir baris; dropdown terbuka = jalur engine)**; Up/Down histori; Tab/Shift+Tab mode; Ctrl+O/T compact/thinking; Ctrl+D baris-kosong keluar; Esc membersihkan selection lebih dulu, lalu batal input / abort turn (busy: SEMUA input dibekukan kecuali abort, scroll, dan selection — Enter pun sunyi; berlaku juga saat layar MENCIUT: abort+scroll selalu lolos agar turn bisa dibatalkan tanpa kill -9; abort pertama menampilkan statusStopping+ hint keluar sekali, abort kedua dalam 1,5 detik menutup sesi). Ini adalah selection buffer TUI, bukan native terminal selection. Idle menampilkan timer redup di footer dan promptminicode ›; prompt hilang saat busy, lalu otomatis muncul kembali kosong setelah turn selesai. Scroll ke atas + stream masuk = indikator↓ N baris baru. - (Dihapus bersama jendela info — nomor dipertahankan.)
- Popup komposit: region tanpa clear + union-clear anti-hantu + clearRegion saat tutup + suspend/resume berpasangan; layar tak mampu = tolak bersuara + batal.
- Transkrip append-only di memori (cap 5000), viewport ikut ekor otomatis; ketikan baru kembali ke ekor; stream turn TIDAK merampas posisi baca; repaint live coalesce 30ms dan clock activity 200ms (layar tak buta saat turn). Composer memakai tinggi tetap: prompt saat editing, dots saat reasoning, atau tool target; satu baris kosong selalu memisahkannya dari footer. Proyeksi presentasi: baris running di-pin dari snapshot, elapsed tetap pada ledger tool, status tool final memakai glyph + kata (
completed/failed/denied/cancelled/interrupted), retry dan grup anak mengikuti model, serta ringkasan turn masuk sebagai system entry.
- Prompt multiline (Ctrl+J newline), histori memori-sesi (tak persist ke berkas histori lama).
/copy [n]menyalin 1–10 turn terakhir (default 1, oldest-to-newest) melalui OSC 52. - Dropdown
/inline berbingkai; Enter pada menu melengkapi + kirim. - Lebar kotak popup TETAP per permukaan (model 64, picker 64, provider 76, form 64 — min=max); nama/ID di depan label; seleksi murni warna; search selalu tampil.
- Form dalam popup: semua input teks/pilihan di dalam kotak (text/secret/ select/confirm + validasi inline + kursor diparkir); tak ada ketikan di luar kotak. Esc dua-tahap: tekan-1 KEMBALIKAN nilai bawaan field (prefill/edit tak hilang sekali tekan), tekan-2 batal. Idle guard dua-tahap (temuan audit TUI-004): 80 dtk tanpa input = peringatan statis di footer form ("batal dalam 10 dtk" — bukan countdown hidup), 10 dtk berikutnya = auto-batal (cancel), total semantik 90 dtk lama; SATU ketikan apa pun me-reset kedua timer.
- Thinking terlihat: composer memakai dots clock; transkrip minimized = bersih tanpa marker (
… thinking), expanded = alir redup; isi tetap tersedia di/expand; fase berakhir = commit (tak ada thinking yatim). - Approval & ask_user tercatat di transkrip (pertanyaan + keputusan) dan terlihat sebelum menjawab; non-TTY = deny/null fail-closed; stdout di-pipe tanpa sink TUI = deny/null (prompt tak terlihat + mencemari output program bila dipaksa).
/expandquery Content Store (buka-ulang identik, tidak sekali-habis); dengan id membaca satu tool, tanpa id membaca seluruh entry store yang masih hidup; miss → durable sqlite → penanda retensi. Pilihan/modelpersist antar sesi + strukmodel: x;/helpringkas (penuh via/help tombol).- Keybinding konsisten: Esc dua-tahap (isi tak hilang sekali tekan); backspace/delete edit-teks (hapus-data = confirm); navigasi daftar (
↑↓, pgup/pgdn, home/end) di semua popup; hint footer akurat per permukaan; kursor diparkir di semua permukaan. - i18n dwibahasa: semua string user-visible lewat
t(); id kunci = en (tsc mengawal); fallback kunci hilang → en; prioritas sesi > env > state.json > locale OS > en (di OS:LC_ALL>LANG, string kosong diabaikan); literal Indonesia hardcode dilarang. - Sinyal fatal saat TUI hidup (SIGTERM/SIGHUP): restore terminal dulu (alt-screen exit + raw mode pulih + handler exit sinkron) lalu exit
128+n(143/129). Pasangan ketat per-run — handler dipasang dirun()dan dilepas di cleanup; tanpa sesi TUI, sinyal tak disentuh. (Temuan audit TUI-001: handlerexitNode tak jalan pada SIGTERM default — terminal tertinggal alt-screen + raw.) - Proyeksi Presentasi V2.1 TUI memakai snapshot struktural dari composition root, bukan parser string.
view, cap, scroll, approval, transient, resize, cursor, dan PTY path tetap memakai kontrak lama. - Proyeksi linear dan ACP memakai snapshot/event presentasi yang sama: linear menambahkan status/duration/receipt/retry tanpa mengubah stdout/stderr split, sedangkan ACP mengirim lifecycle
turn.*,tool.*, danapproval.*terstruktur plus teks delta. Exit code, pipe safety, dan determinisme non-TTY tetap sama.
- Painter transient stderr (spinner setup/cek-update, garis status turn) DITAHAN selama layar interaktif memegang terminal:
beginInteractiveScreen(src/ui/runtime/statusline.ts) membisukanpaintWrite(nol byte + baris painter dibersihkan sekali saat layar mengambil alih), nested dihitung, release idempoten. Pemegang:askLine/askSecret/runPicker(raw-mode) danopenAltScreen(alt-screen, release diclose()). Tanpa ini tick\r\x1b[2Kspinner menghapus baris prompt wizard (laporan "macet di Menyiapkan sesi…"). - Kontrak exit code: 0 sukses/help, 1 gagal runtime/lookup, 2 salah pakai (argumen hilang/malformed, subcommand asing). Flag sebagai positional id = salah pakai (exit 2).
exec --jsonyang gagal di fase setup (tanpa provider) TETAP memancarkan satu baris `{"type":"summary", "ok":false,…}` (di-scrub) di stdout — pipeline CI tak pernah menerima stream kosong; jalur manusia (stderr + exit 1) tidak berubah. Flag handlerexec/daemondibaca dari argv scope — lihat I34. minicode acp= server JSON-RPC stdio satu-JSON-per-baris untuk IDE (subset:initialize/run/cancel/shutdown; BUKAN klaim ACP penuh): stdout murni mesin (semua di-scrub), diagnostik manusia ke stderr, approvaldeny-headless, satu run dalam satu waktu, shutdown/EOF = exit 0.askLine(input inline: dropdown/, prompt wizard/ask_user) menginvalidasi jangkar relatifnya saat geometri terminal berubah: render pertama sesudahcolumns/rowsberbeda dari geometri render TERAKHIR me-resetprevRows/prevInputRows/prevCursorRowlalu menggambar dari kursor saat ini (lastGeoCols/Rowsdisrc/ui/input/input.ts). Jangkar basi pasca-reflow mendarat di baris salah — menimpa area output dan menghapus baris yang salah (temuan snap kiri/kanan). SENGAJA tanpa listenerresizebaru (frame basi cukup ditimpa render berikutnya), danprintedWtidak ikut di-reset — nilainya konservatif; dikosongkan justru membiarkan ekor lama.- Routing argv-scope (P11.2/P11.3): handler
execdandaemonmenerima argv SCOPE dari router — flag global dikenal yang mendahului token subcommand tetap berlaku, token subcommand dibuang, urutan token sisanya utuh. Ditunjukkan tes:--json exec "t"identik denganexec --json "t"(prompt model = teks user persis, tanpa token routing maupun nilai flag);--json daemon statusmencapai aksistatusalih-alih error subcommand asing; aksi/opsi yang tidak dikenal TETAP salah pakai sesuai I28; posisi argumen pertama (prompt/aksi) tidak bergeser. Hanya kedua handler itu menerima scope — subcommand lain menerima args mentah. Invarian ini tidak mengubah semantik, aksi, atau exit code masing-masing aksi; yang berubah hanya invokasi flag-mendahului yang dulu salah rute menjadi rute benar. - WorkMode = lapis intent di ATAS permission mode (P12 S2.2) — dua dimensi yang BERJALAN BERSAMA, bukan pengganti: Nilai:
DISCUSS(read-only penuh) ·PLAN(ceiling rencana) ·EXECUTE(tanpa ceiling tambahan). DefaultEXECUTE= perilaku lama (kompatibel: sesi tanpa WorkMode yang eksplisit tetap EXECUTE). Gerbang penolakan: permission handler tetap satu-satunya yang menentukan keamanan eksekusi. WorkMode hanya MEMASANG CEILING di atasnya; ia tidak pernah memperluas kewenangan permission mode dasar (INV-WM1). Command/work-mode <DISCUSS|PLAN|EXECUTE>: aksi operator EKSPLISIT. Tanpa argumen → tampilkan mode aktif + usage; nilai invalid → pesan kesalahan dan mode AKTIF TIDAK berubah; sesi tanpa kontrol WorkMode (handler izin terinjeksi = policy, bukan mode) → ditolak, bukan diam-diam tampak berubah. Transisi berlaku pada SESI YANG SEDANG BERJALAN: ceiling permission dievaluasi per pemanggilan tool, daftar tool yang dilihat model ikut turun/naik, hooks dievaluasi terhadap WorkMode aktif, dan delegasi berikutnya mewarisi WorkMode parent yang aktif. WorkMode TIDAK bisa dinaikkan otomatis oleh model, anak, atau argumen delegasi. Pemisahan dari permission mode: Tab/Shift+Tab dan/modeTETAP khusus permission mode — WorkMode tak masuk siklus Tab. Permission mode yang memutuskan allow/deny per tool; WorkMode memilih postur (diskusi/rencana/bebas) dan hanya mempersempit. Hooks: gateshouldRunHooks(permissionMode, workMode)dievaluasi per pemanggilan dengan WorkMode AKTIF (transisi/work-modelangsung berlaku); sisi permission mode memakai nilai startup sesi (semantik lama dipertahankan —Tab/Shift+Tabmenggeser mode izin di dalam handler, bukan nilai gate hooks). WorkModeDISCUSStetap menahan hook walau permission modeauto. Footer: mode WorkMode aktif tampil sebagai<WorkMode>·<permission mode>hanya saat WorkMode ≠ EXECUTE (mis.PLAN·auto,DISCUSS·auto,PLAN·readonly); saat EXECUTE label permission mode apa adanya (mis.auto) — perilaku lama dipertahankan.
#Grammar (ringkas)
prompt minicode › tampil saat boot/idle dan hilang saat busy (dropdown / berbingkai di atasnya) · status bar ✦ 00.00.00 mode model cwd (timer redup idle; detik putih aktif, menit mulai terang di 60 dtk, jam di 1 jam; tanpa bullet separator; mode = permission mode, atau <WorkMode>·<permission mode> bila WorkMode ≠ EXECUTE) + satu spacer sebelum footer · ledger tool › name target (hijau) / merah saat error · error ✗ pesan sekali per kegagalan · pesan error TUI = formatter kanonik (formatError, src/ui/render/errors.ts): credential di-redaksi (bearer/key/query), escape ANSI dibuang, body provider diringkas + dipotong; e.message mentah tak pernah tampil · thinking: dots clock / alir redup · popup: konten redup di belakang + kotak terpusat (form di dalam) + Esc tutup · approval tercatat · /status, /history, /help ringkas & /expand mengalir ke transkrip · /mode = permission mode · /work-mode = WorkMode (intent layer) · Projection V2: › name target … running, ledger status ber-glyph/kata, retry/grup anak, ringkasan turn, dan marker evict sesuai flag ·
Esc saat turn = abort (+ hint sekali); busy = input beku total · Mouse wheel = scroll transkrip, PgUp/PgDn = scroll, Shift+PgUp/PgDn = setengah halaman, Home/End (prompt kosong) = lompat top/ekor · indikator ↓ N baris baru · tab dihitung 8 kolom (batas atas stop terminal — tak pernah undercount) · C1/bidi/tag dibuang dari teks tak-terpercaya (sanitasi) · truncate/chunk hanya menyalin SGR (non-SGR dibuang) · penanda (aktif) di luar budget truncasi (tak termakan URL panjang).
#Residual risk (DISENGAJA — jangan "perbaiki" tanpa keputusan)
- Foreign partial stderr write tanpa newline (non-interaktif).
kill -9di tengah sesi: ketikreset(pola standar). SIGTERM/SIGHUP SUDAH di-handle (I31 — restore + exit 128+n); hanya kill -9/terminal close yang masih menyisakan alt-screen.- Transkrip TUI hilang saat quit (disengaja — TUI fullscreen).
- Terminal purba tanpa alternate screen: pesan satu baris + keluar (bukan error).
- Approval saat user scroll ke atas: blok tercatat di ekor (tak terlihat sampai PgDn), tapi prompt jawaban + bell selalu terlihat di bawah.
- Area yang dibuka kembali saat kotak menyusut = kosong (bukan transkrip di belakangnya) sampai popup tutup + repaint penuh.
- Jalur end-to-end mouse SGR bergantung pada TRANSPORT PTY: kalau byte SGR tak sampai ke stdin anak, selection → OSC52 tak bisa diverifikasi lewat byte stream nyata. Pada lingkungan diagnostik Windows (ConPTY + node-pty 1.2.0-beta.15) byte SGR terbukti sampai ke anak yang membaca dalam raw mode — kegagalan lama yang disangka "ConPTY membuang SGR" adalah artefak line buffering probe, bukan keterbatasan transport. Perilaku platform lain belum diuji; capability gate mengukur kemampuannya secara langsung (bukan asumsi platform): di luar CI Linux, SGR yang memang tak diantarkan → SKIP beralasan; di CI Linux jalur SGR end-to-end WAJIB terverifikasi, jadi probe yang verdict-nya bukan "supported" → GAGAL dengan diagnosa (bukan skip senyap yang menghapus coverage).
#Peta proteksi (test → invariant)
Cara membaca peta ini. Setiap baris hanya boleh menyatakan perilaku yang
benar-benar di-assert oleh test yang disebut. Kehadiran nama file test
bukan bukti klaim. Baris yang hanya "nominal" (test ada, tapi assertion-nya
tak menyentuh perilaku yang diklaim) dipindah ke bagian
Gap proteksi di bawah — jangan
dipoles dengan mengganti kata-kata.
test/tui-app.test.ts— I1/I13/I14/I17-I20 (boot/exit pairing, transcript viewport, mouse-wheel/drag-selection/PgUp/PgDn scroll + indikator, prompt editing, dots/timer, status bar, Esc-only abort, quit, suspend/resume, busy-freeze, live repaint; I14: Home/End jump + Shift+PgUp/PgDn + hint abort pertama ada ditest/tui-audit-fixes.test.ts, bukan di file ini; I17: kunci posisi baca saat stream masuk; basis monotonik ada ditest/tui-transcript.test.ts+test/presentation-projections.test.ts; P0: jalur error TUI meredaksi credential — sentinel bearer tak pernah tampil di layar).test/prompt-engine.test.ts— decoder streaming CSI/SGR/X10: wheel menjadi key scroll, klik mouse tidak masuk teks.test/errors-usage.test.ts— format error UI:formatError(kanonik untuk TUI/wizard/one-shot) meredaksi credential (bearer, kunci provider, query, token di batas potong 150 kolom), membuang escape ANSI, memetakan kategori/kind provider, dan menjaga pesan biasa tetap informatif.test/checkpoint-lifecycle.test.ts— checkpoint = milik lifecycle sesi: manifest tersedia saatclose()kembali (barrier), tak ada operasi yang tertinggal, kegagalan checkpoint tercatat + dilaporkan tanpa unhandled rejection, dan semua operasi dari beberapa turn selesai saat drain.test/tui-transcript.test.ts— projection source-mapped: wrap, CJK/ANSI, user prefix, tabel TSV, selection text, cap buffer live, dan render markdown inline jawaban model (heading/bold/code; delimiter fence disembunyikan, isi fence dan gema user tetap literal).test/screen-buffer.test.ts— parser frame harness (unit).test/tui-transcript.test.ts— I7/I12/I17/I22 (ledger satu baris per tool, cap 5000 + marker evict,total()monotonik kebal evict, viewport, thinking minimized/expanded).test/presentation-projections.test.ts— I12/I17/I32 (projection snapshot, running/elapsed, status terminal, retry/child grouping, summary, marker evict +total()monotonik, dan parity flag OFF).test/presentation-linear-acp.test.ts— I33 (linear projection status/duration/receipt dan lifecycle ACP terstruktur; parity policy-vs-legacy).test/presentation-policy.test.ts— I32/I33 (seleksi node per mode, envelope machine, digest divergensi; purity tanpa clock/IO).test/writer-inventory.test.ts— OAP-008 (setiap writer langsung terdaftar dengan owner; jumlah tak boleh naik diam-diam).test/exec-json-envelope.test.ts+test/cli-session.test.ts(envelope machine) — I28/I33/I34 (summary berversi, tanpa ANSI/secret, tanpa raw kernel; P11.2 scoping prompt: flag-mendahului vsexec-dahulu identik, kataexecmilik user tetap utuh).
test/tui-popup.test.ts— I16 (komposit di atas transkrip, anti-bocor, anti-hantu).test/form.test.ts— I21 (field, validasi, secret, select, confirm, batal; Esc-1 kembalikan prefill, Esc-2 batal).test/approval-tui.test.ts— I23 (blok + keputusan tercatat, deny; stdout-pipe tanpa sink = deny).test/footer-render.test.ts— I13 (footer minimal tanpa label status).test/work-mode-runtime.test.ts— I35 (WorkMode runtime: ceiling permission dievaluasi per pemanggilan tool; transisi EXECUTE↔DISCUSS↔PLAN pada sesi yang sama; daftar tool yang dilihat kernel ikut berubah; hooks mengikuti WorkMode aktif; delegasi mewarisi WorkMode parent terbaru; argumen delegasi tak bisa menaikkan WorkMode;/work-modeend-to-end lewat TUI termasuk nilai invalid;/mode+ Shift+Tab tak menyentuh WorkMode; label footerPLAN·auto/DISCUSS·auto).test/work-mode-contract.test.ts— I35 (ceiling handler WorkMode di atas handler PM: 18 sel matriks, gerbang approvalPLAN × auto/ask,describeDenialper pemilik keputusan,always/K-1 tetap utuh,__setModetak mencabut ceiling).test/work-mode-tool-visibility.test.ts— I35 (visibilitas tool = irisan permission mode ∧ WorkMode;PLAN × readonlytak memperluas readonly; tool MCP ikut dibatasi).test/work-mode-hooks.test.ts— I35 (hooks:DISCUSSmenahan,PLAN/EXECUTEmengikuti permission mode; pemanggil lama tanpa WorkMode kompatibel).test/work-mode-subagent.test.ts— I35 (child mewarisi WorkMode parent; tak ada eskalasi lewat mode child; default tanpa WorkMode = perilaku lama).test/work-mode-cli.test.ts— I35 (--work-mode/env: flag > env > default EXECUTE; nilai invalid → EXECUTE + peringatan; ACPworkModeterpisah darimode).test/tui-app.test.ts,test/tui-popup.test.ts— I13/I17 (composer editing/busy, dots clock, timer, cursor, resize, popup). *Klausul "satu baris kosong memisahkan composer dari footer" TIDAK punya assertion — lihat Gap proteksi.*test/markdown-table.test.ts— I8/I11 (pipe-table parser, streaming parity, ANSI/CJK width, TTY/non-TTY, TUI resize, machine-output policy).test/terminal-contract.test.ts— I2/I3/I7/I8/I9 (split stdout/stderr, kepemilikan layar tunggal, ledger satu baris per tool, streaming tanpa duplikasi, foreign writer boleh mentah).test/transient-arbitration.test.ts— I3/I9/I27 (hold layar interaktif: picker/askLine, nesting, delayMs).test/statusline-bun-guard.test.ts— I4 (painter Bun/Windows: painter dinonaktifkan sendiri, write asli dipulihkan).test/turn-status.test.ts— garis status turn lifecycle deterministik (grace 250 ms, latch anti-strobe, pembersihan saatendTurn).test/tui-format.test.ts— I2/I7/I8 (preview arg sanitasi satu-baris; stdout = output program / stderr = ledger; 20 tool berurutan = 1 baris masing-masing tanpa overlap) + I18 (envelope OSC52/copybenar saat TTY, ditolak tanpa TTY, dan 1–10 turn terakhir oldest-to-newest).test/theme.test.ts— I6 (strip DCS/APC/PM/SOS + final CSI non-huruf).test/ansi-fragmentation.test.ts— I6/I8 (setiap titik split ≡ string utuh).test/non-tty-output.test.ts— I6.test/thinking-truncation.test.ts— I12 (buffer thinking >200 KB →/expandmembawa marker truncasi).test/collapse.test.ts,test/presentation-expand.test.ts,test/presentation-store.test.ts— I24 (semantik/expandpada Content Store: buka-ulang identik, dengan id vs tanpa id, miss → durable sqlite + penanda retensi).test/ui-combined.test.ts— DIHAPUS bersama chrome.test/repl-linear.test.ts,test/footer-chrome.test.ts,test/info-window.test.ts— DIHAPUS bersama REPL/chrome/jendela.test/cli-commands.test.ts— /status cetak, /sessions popup.test/screen.test.ts— I16 (pairing + paintRegion).test/dialog.test.ts— I11/I16/I20 (geometri, backdrop, box).test/tui-overlay.test.ts— I11/I16 (popup region + gate layar).test/ui-boundary.test.ts— I1/I3 (src/ui tak impor keluar).test/i18n.test.ts— I26 (resolusi, fallback, interpolasi, kelengkapan; LC_ALL > LANG, string kosong diabaikan).test/i18n-hardcode.test.ts— I26 (lexer sadar-state: tanpa literal Indonesia di luar kamus; kontrol positif id.ts kena, negatif en.ts bersih).test/tui-lang.test.ts— I26 (locale sesi/langend-to-end + persisten; nilai tak dikenal ditolak) + I24 (/helpsatu sumber;/copy 2menyalin dua turn terakhir).test/cli-help-language.test.ts— I26/I25 (label en, kelengkapan /help).test/tui-geometry.test.ts— I20/I25 (lebar tetap per permukaan, step wizard, form error).test/sanitize.test.ts— I2 (C1/bidi/tag dibuang, ZWJ dipertahankan; NO_COLOR menang di cleanUntrusted).test/width.test.ts— I2/I7 (tab 8 kolom; truncate/chunk hanya-SGR + auto-reset; chunk(0) kosong).test/exit-codes.test.ts— I28 (0/1/2 di semua handler; lookup tetap 1; hermetic subprocess).test/exec-json-envelope.test.ts— I28 (summary ok:false di stdout saat setup gagal; jalur manusia tak berubah).test/cli-subcommands.test.ts— I34 (daemon scoped argv: `--json daemon statusmencapai aksistatus, bukan exit 2 palsu; bentukdaemon`-dahulu tidak berubah; aksi asing tetap exit 2; HOME hermetik, exit 3 not-alive mengikuti kontrak daemon yang sudah ada — bukan klaim baru I34).test/acp.test.ts— I29 (parser/framing/params + smoke spawn stdio: initialize→shutdown exit 0, tanpa provider).test/input-resize.test.ts— I30 (gagal di kode lama: CUP\x1b[1Ake jangkar basi sesudah resize; multiline, dropdown terbuka, resize ganda, resize cepat ×10 → nilai submit utuh).test/tui-diff.test.ts— I2/I7 (path & isi disanitasi).test/highlight.test.ts— I2/I8 (CR dibuang; escape dibuang di hulu).test/tui-theme-highlight.test.ts— I2 (section satu-baris; markdown buang escape di hulu).test/provider-manager-flows.test.ts— I21 (Ctrl+C dua-tahap revert prefill; penanda aktif anti-truncasi).test/tui-signal.test.ts— I31 (SIGTERM→143, SIGHUP→129: alt-screen exit + raw mode pulih; handler dilepas saat quit — pasangan ketat; tanparun()sinyal tak di-intercept).test/tui-audit-fixes.test.ts— I14 (Home/End jump top/tail saat prompt kosong + baris berisi tetap editing; Shift+PgUp/PgDn half-page + dekode ESC[5;2~; hint abort pertama), I13 (MINICODE_MOTION=0: spark statis, env dibaca saat dipakai), I25 (kursor CUP parkir di dalam blok ?2026 yang sama), I21 (konstanta idle form 80+10 dtk + teks peringatan).test/pty.test.ts— I13/I17/I31/I28 via BYTE STREAM NYATA (harness PTYtest/helpers/pty-harness.ts, CLI di pseudo-terminal sesungguhan + provider fake hermetic): boot → ?1049h + timer idle, turn penuh end-to-end, drag-selection mouse SGR → Ctrl+C → OSC52 (koordinat mouse dihitung dari LAYAR NYATA, bukan hardcoded), resize → repaint, SIGTERM → restore + exit 143 (POSIX). *Klausa "exec --json bersih tanpa ANSI" di test ini berjalan sebagai proses biasa tanpa PTY (lihat komentar di berkasnya) — jadi ia bukan bukti byte stream; bukti envelope ada ditest/exec-json-envelope.test.ts.* Platform tak mampu (ConPTY rusak) = SKIP beralasan, bukan hijau palsu. Jalur mouse SGR punya capability gate (ptySgrMouseDelivered()): transport yang TAK mengantarkan byte SGR → SKIP beralasan; probe tak konklusif → SKIP dengan alasan "tak konklusif" (BUKAN bukti SGR tak didukung); di CI Linux verdict WAJIB "supported" — selain itu test SGR GAGAL dengan diagnosa, bukan skip senyap. Parser SGR dan selection SGR tetap terverifikasi terpisah ditest/prompt-engine.test.tsdantest/tui-app.test.ts(fake TTY).
#Gap proteksi (terbukti, belum ditutup)
Bagian ini menahan klaim yang benar secara implementasi tetapi belum punya assertion yang menjaganya. Menutup gap = menambah test; mengubah kata-kata di sini TIDAK menutup gap.
| Klaim | Status implementasi | Yang hilang |
|---|---|---|
| I14 — "berlaku juga saat layar MENCIUT: abort+scroll selalu lolos" | Terimplementasi (src/ui/tui/app.ts: scroll/abort tetap lolos saat tooSmall()) | Tidak ada assertion. test/tui-app.test.ts ("terminal menciut tengah sesi") hanya asserting bahwa ketikan diabaikan + Ctrl+D keluar. |
| I13/I17 — "satu baris kosong memisahkan composer dari footer" | Terimplementasi (src/ui/tui/app.ts merender satu spacer per frame) | Kata "spacer" tidak muncul di berkas test mana pun — tak ada assertion tinggi-geometry maupun rendah. |
| I20 — lebar kotak popup tetap per permukaan | Terimplementasi | test/tui-app.test.ts menandai salah satu test-nya sebagai (I20), padahal test itu milik I13 (status bar). Lebar popup diverifikasi di test/tui-geometry.test.ts + test/dialog.test.ts; label di tui-app menyesatkan. |
| I5 — tak ada popup yatim / input bocor setelah success/failure/cancel/SIGINT/session end | Tak terverifikasi | test/turn-status.test.ts dan test/statusline-bun-guard.test.ts tidak meng-assert apa pun soal popup yatim atau kebocoran input. |
| I26 pada jalur presentasi | i18n ada dan dijaga terpisah | test/presentation-projections.test.ts mengunci locale en di setiap test dan tak pernah menguji locale id; klaim i18n dijaga test/i18n.test.ts + test/i18n-hardcode.test.ts. |
| I2/I6/I28 pada proyeksi linear/ACP | Alur linear/ACP ada | test/presentation-linear-acp.test.ts tak meng-assert split stdout/stderr, absence cursor-control, maupun exit code secara langsung. |