PROP-042 — AIUI observation: the render plane & the vibe aiui surface
01Status: ACTIVE (v0.1, 2026-07-16). Module: vibe-cli.
Related: PROP-037 (the vibe tree TUI it observes), PROP-039 §11.3 (the model
plane / vibe-actions::aiui), PROP-036 (the tree model). The terminal products
(vibeterm, vibeframe) and their contracts now live in the vibevm-term products
repo — this PROP cites them as cross-repo contracts
(spec://vibeterm/*, spec://term-common/*); the vibe aiui / vibe term
CLI surface itself stays on the host.
- 02This contract governs the render plane — a terminal-free way to render the
vibe treeTUI to a symbolic snapshot so an agent (or a golden test) can see the interface without a real terminal — and thevibe aiuiCLI surface that exposes it. - The terminal plane (vibeterm) and the model plane are governed
elsewhere (a vibeterm PROP in
vibevm-term/ PROP-039).
1. The render plane
- 03REQ. The TUI renders headlessly: given a built
PackageTree, a terminal sizecols×rows, and an optional key script (§3), the surface drives the real input + render path —input::handlefor each scripted key, thenrender::drawinto an off-screenratatui::Buffer— and returns that Buffer. - No terminal, no alternate screen, no raw mode, no
rat-salsaloop; the entrypoint is a pure function of(tree, size, script).
- 04REQ. The headless render is deterministic: it uses the built-in theme
defaults (the canonical Rosé Pine palette, Tier 3 — §PROP-037 §2.2) and never
loads user settings from disk, so the same
(tree, size, script)always yields the same Buffer. - Snapshot callers pin
tree(a fixture),size, andscript.
- 05REQ. A scripted key that would escape the process or mutate the world is
refused, not executed:
F4(spawns the settings subprocess) andF6/Shift+F6(write the clipboard) are rejected by the key-script parser (§3). - The render plane observes; it does not act outside the model.
2. The snapshot contract
06REQ. A rendered Buffer projects to one of two snapshot formats, the same schema every observation plane emits:
- 07
text— the glyph grid: one line per row, each row the concatenation of the cells' symbols with trailing whitespace trimmed. The golden-file form (committed.snap.txt, re-rendered and diffed). cells— JSON:{cols, rows, rows:[[run,…],…]}where each run is{n, ch, fg?, bg?, mods?}—ncells of glyphchsharing a style, run-length encoded per row;fg/bgare#rrggbb(or an ANSI role name),modsthe set ofbold/dim/italic/underlined/reversedpresent. Enables style/colour assertions (e.g. "the active group's border run is the accent colour").
- 08REQ.
textis lossless for layout (every cell's glyph, in grid order) andcellsis lossless for style; neither invents content. - A blank cell is a space; the trim is per-row and right-only, so column alignment within a row is preserved.
3. The key script
- 09REQ. A key script is a space-separated list of key names driving the TUI
before the snapshot. The grammar: function keys
F1–F12; navigationUp,Down,Left,Right;Enter,Esc,Tab,BackTab,Space,Backspace; aShift+prefix on any of them (e.g.Shift+Left,Shift+Tab≡BackTab). Names are case-insensitive. - An unknown name, or a refused side-effecting key
(
F4,F6; §1), is a hard error naming the offending token — never a silent skip.
- 10REQ. The render plane (§1) turns each key name straight into a
crossterm::event::Event— terminal-free, no escape bytes. - The terminal
plane (§4,
vibe aiui send) must instead encode each name to the bytes the hosted program's platform expects, and the encoding is platform-specific: on Unix, the standard xterm VT sequences (SS3ESC O P–Sfor F1–F4, CSI for the rest); on Windows, win32-input-mode (ESC [ Vk;Sc;Uc;Kd;Cs;Rc _— a key-down record then a key-up), the form a ConPTY translates into the consoleINPUT_RECORDs a raw reader expects. - The raw VT form is not reliable on
Windows: conhost synthesises a key record from it for a cooked reader (a shell)
but not for a raw reader (a crossterm TUI such as
vibe tree), so the keys are silently dropped. - A caller therefore drives the same key script identically on either plane; the encoding difference is the implementation's to hide.
4. The vibe aiui surface
11REQ. vibe aiui is the agent-facing command family. Its render-plane verb:
12vibe aiui render [--path <dir>] [--size <COLSxROWS>] [--send "<script>"] [--format text|cells]
- 13builds the
vibe treemodel at--path(the same resolvervibe treeuses), drives--send(§3) at--size(default80x24), and prints the--formatsnapshot (§2, defaulttext) to stdout. - It is read-only and non-interactive: it never enters the TUI, spawns a terminal, or touches user state.
14REQ. The terminal-plane verbs drive a live vibeterm control session:
15vibe aiui open [--exec <cmd>] [--size <COLSxROWS>] [--timeout-ms <n>]
vibe aiui send <key>... [--text <literal>] [--session <pid>]
vibe aiui snapshot [--session <pid>]
vibe aiui wait [--idle-ms <n>] [--timeout-ms <n>] [--session <pid>]
vibe aiui close [--session <pid>]
vibe aiui inspect <expr> [--session <pid>]
vibe aiui pty-stop [--session <pid>]
vibe aiui pty-start [--session <pid>]
vibe aiui scrollbar <auto|on|off> [--session <pid>]
- 16
openlaunches a windowless vibeterm running--exec(default: the consolevibe treeover the current directory) with a control server, waits for its discovery file, and prints the session id (the vibeterm pid). senddrives a key script (§3) and/or literal--text.snapshotprints the live grid (§2).waitblocks until the hosted program has answered the last input and the grid has settled (deterministic snapshots — never the pre-key screen).closetears the session down.inspectevaluates a JavaScript expression in the live renderer page over CDP and prints its return value as JSON — the agent reads the renderer's real runtime state (the xterm grid's cols and cell metrics, the scrollbar box) instead of inferring it from a snapshot. Requires a--controlsession.pty-stopstops the hosted program — the PTY child — without restarting Electron: the renderer, the CDP endpoint and the discovery file all stay live, so the program's binary is freed for a rebuild while the session survives.pty-start(re)spawns the hosted program at the current grid. Paired withpty-stoparound a rebuild it is the fast TUI-preview loop: the agent sees the change without reconnecting CDP or relaunching Electron.scrollbarsets the scrollbar policy live —auto(hidden for a full-screen TUI, shown for a shell),on(always),off(never). The renderer refits the grid; no Electron restart. Requires a--controlsession.- A verb defaults to the most recent session;
--session <pid>targets a specific one.
17REQ. The model-plane verb projects the TUI state — no rendering at all:
18vibe aiui state [--path <dir>] [--send "<script>"]
- 19builds the
vibe treemodel at--path, drives--send(§3), and prints a serialisableModelView(PROP-039 §11.2/§11.3) — display mode, ordering, the active tab, the selection, the visible rows, and which modals are open. - It is read-only and non-interactive; it observes structured state an agent asserts on (flow, focus, open menus), never pixels.
- The projection is a pure function of
the built
Appand carries no rendering types.
- 20REQ. The control transport is loopback-only and token-guarded. A
--controlvibeterm serves JSON overhttp://127.0.0.1:<ephemeral>. - It writes a discovery
file
~/.vibe/aiui/<pid>.jsonplus alatest.jsonpointer, each{ port, token, pid, startedAt }at mode0600. - Every request carries the
bearer token; the socket binds
127.0.0.1only. openaccepts a discovered session only when itsstartedAtis at or after the spawn instant, so a stalelatest.jsonis never mistaken for the freshly-spawned one.- The model-plane
stateverb is governed by PROP-039 §11.2/§11.3; itsvibe treeprojection is prototyped here per PROP-039 §13 (the TUI is the reference surface).
5. The vibe term launcher
- 21REQ.
vibe termlaunches the vibeterm terminal app hosting an interactive shell, so the terminal can be used and eyeball-debugged standalone. - The shell is
detected: on Windows, modern PowerShell 7+ (
pwsh) is preferred over the built-in Windows PowerShell 5.1 — resolved via the standard install locations (%ProgramFiles%\PowerShell\7\pwsh.exe,%LOCALAPPDATA%\…\WindowsApps\pwsh.exe) thenPATH, falling back to…\WindowsPowerShell\v1.0\powershell.exe; on other platforms$SHELL, falling back to/bin/sh. - An explicit
--exec <cmd>overrides the detected shell.
22REQ. The terminal app (vibeterm for vibe term, vibeframe for vibe tree
-t) is located in three tiers in order:
- 23an explicit
$VIBEVM_<APP>directory wins (<APP>is the uppercased app name —VIBETERM/VIBEFRAME, an override a developer or a launcher sets); - else an installed
vibechecks the packaged<app>/shipped inside its own instance dir, next to its binary (the legacy, pre-extraction layout — kept for back-compat with instances that still carry it); - else a
PATHlookup for the app-named packaged binary (vibeterm/vibeframe) — the extracted-product path, how the vibevm-term repo's<app> self installpublishes the product. The directory the binary sits in is treated as the packaged root.
- 24The resolver distinguishes a
packaged dir (electron binary at its root,
resources/app/inside — invoked directly, no app-path arg) from a dev dir (Electron resolved vianode_modules/electron/path.txt, the app dir passed as a positional arg — only reachable through$VIBEVM_<APP>now that the in-treeapps/source has moved to vibevm-term). - Resolution failure returns a typed error;
vibe term/vibe framesurface it to the user, whilevibe treefalls back to running the console TUI in place (§5.1).
5.1 In-place upgrade & the icon protocol
- 25REQ. vibeterm sets
VIBETERM=1in its PTY environment. - A
vibe treelaunched inside vibeterm (this env present) does not spawn a second window — it upgrades the current terminal in place: the-t/ vibeterm launch resolves to the in-terminal console TUI here, so a plain shell becomes a "VibeTree terminal" for the session (PROP-036 §2.13). Outside vibeterm,-tstill opens the desktop app.
- 26REQ. While the tree is open in that upgrade, vibeterm's window + taskbar icon
is swapped to
vibetree, reverting to the window's launch icon on exit. The swap is in-band:vibe treeemitsOSC 7773 ; <icon-name> ST(an empty name reverts); the vibeterm renderer forwards the name to the main process, which callswin.setIcon. - Windows + Linux only —
setIconis a no-op on macOS (the app owns its Dock icon there), the documented platform gap. - In any non-vibeterm terminal the OSC is an unknown sequence, harmlessly discarded.
6. Never
- 27Never load user settings into a snapshot render — determinism dies and goldens churn. Defaults only.
- Never execute a side-effecting key (
F4/F6) in the render plane. - Never let a snapshot format invent or drop content —
textis every glyph in grid order;cellsis every run with its true style. - Never enter the interactive TUI from
vibe aiui— it is headless by contract.