Commit f62211d
authored
Cut the interactive TUI over to OpenTUI (#333)
* Document TUI layout and scroll platform rebuild plan
Capture the OpenTUI foundation plan and product brief with locked
interaction contract, branch hard cutover, and constitution scope so
dispatch and Linear can share one source of truth.
* Write TUI layout constitution and chrome zone registry
Lock geometry ownership, residual transcript floor, zone budgets, and
the kill list so Platform work has one implementer contract before any
OpenTUI shell lands.
* Write TUI interaction contract for queue steer and focus
Document locked Enter queue, Alt+Enter tool-boundary steer, Ctrl+C
interrupt, palette chord, focus tree, and scroll lease so migration
implements one input model.
* Write Ink freeze policy and overflow ticket triage
Restrict Ink to true P0 daily-use patches and route layout or scroll
classes into the OpenTUI platform instead of new chrome geometry.
* Document OpenTUI migration branch hard cutover
Require a single migration branch, full acceptance before merge, and
scrap-on-fail so Ink and OpenTUI never ship as dual paint paths.
* Record OpenTUI Bun spike go decision and evidence
Prove isolated core and Solid install, sticky ScrollBox, focus, and
Enter Alt+Enter Ctrl+C key shapes so binding and packaging can proceed.
* Record OpenTUI Solid plus core binding decision
Choose Solid with core class APIs and keymap for the migration branch,
grounded in the Bun spike go evidence and OpenCode peer alignment.
* Document OpenTUI install CI and packaging plan
Describe macOS and Linux contributor install, optional native packages,
and CI matrix gates so packaging is clear before root dependency cutover.
* Link product and architecture docs to TUI constitution pack
Point operators and implementers from PRODUCT and ARCHITECTURE into the
layout constitution, interaction contract, freeze policy, and cutover docs.
* Point spike report at the OpenTUI binding ADR
Link the go report to the Solid plus core decision so binding evidence
and the ADR stay one hop apart for implementers.
* Add OpenTUI deps and tui-opentui scaffold
Platform kit root for migration/opentui-tui: core, solid, keymap 0.5.1.
Ink CLI entry unchanged.
* Add zone registry and geometry resolver
Pure resolveGeometry for OpenTUI shell: zone budgets, collapse order,
transcript floors (idle 12 / inset 8), prompt and overlay caps.
* Add focus tree and scroll lease state machine
Immutable focus stack with overlay > observe > shell priority and a
single scroll lease owner for wheel/page routing.
* Add shared list viewport kit
Windowing helpers for keep-active-visible lists (permissions, models,
settings consumers).
* Add OpenTUI headless test harness
createHarness / withTestRenderer wrap createTestRenderer with cleanup and
named chords for Enter, Alt+Enter, and Ctrl+C.
* Add OpenTUI app shell frame
Header, sticky transcript ScrollBox, prompt Input, and status bar owned by
createAppShell. Geometry resolver and focus lease drive layout and Tab focus.
* Prove OpenTUI shell sticky scroll and key chords
Integration tests cover follow/pin, focus lease, Tab, Enter/Alt+Enter/Ctrl+C.
Wave 2 platform status note and dispatch tree recorded.
* Add session queue and stream row models
Pure queue/steer/interrupt state machine and role-styled transcript
formatters for the Wave 3 product skin.
* Skin OpenTUI shell as a fake Corbits session
Wire transcript roles, bordered prompt with binding hint, local
queue/steer/interrupt keys, and an inset permission overlay on the
Wave 2 platform. Demo and headless product-skin tests included.
* Wire OpenTUI shell to a fixture-driven session bridge
Map reactor-like events into stream rows, route queue/steer/interrupt
through a SessionPort, and cover the path with headless fixture tests.
Ink entry stays production.
* Add primary overlays on the shared OpenTUI list kit
Permissions, operator question, and model/provider picker open on the
focus stack + list viewport + geometry host, with headless open/navigate/Esc
and resize-floor coverage. Ink entry stays production.
* Add Wave 6 palette, long-log windowing, chrome zones, and copy
Command palette on Ctrl+O (reclaimed from tool-expand), windowed
transcript paint past collapse threshold, measured goal/task/agents
chrome via the zone registry, and Alt+C keyboard copy for stream rows.
* Add Wave 7 residual surfaces, observe, and cutover readiness
Settings/help/plugins/resume/mentions share the list kit; subagent observe
swaps stream + lease with Esc restore. Acceptance §10 scored and blockers
listed without authorizing Ink delete or main merge.
* Add Level 1 live session bridges for OpenTUI cutover
Wire injectable SessionPort, production stream event map, overlay
onAccept callbacks, registry-backed palette catalog, and chrome state
helpers so the product host can bind real runner/session APIs without
fixtures.
* Wire residual overlays for host-injected catalogs and accept actions
Settings/help/plugins/resume/mentions openers take optional items, itemIds,
and onAccept; fixtures remain defaults when the host omits data. Shell hooks
gain residual kind callbacks so product-host can bind toggles and resume.
* Add live subagent observe: host-pushed child rows + parent restore
appendObserveStreamRow for live child stream while parent appends
route to the parent snapshot; leave/Esc restores parent lease.
* Let Alt+C open a copy-mode picker instead of a one-shot stub
OpenTUI Alt+C now freezes selectable stream targets in the inset
overlay (Ink parity), defaults to the last non-system row, and
reports via status flash so copy never mutates the transcript.
Fixes CL-5415.
* Add OpenTUI product host and pure cutover bridges
Land mountProductHost plus gate, catalog, history, observe, and
chrome helpers with unit tests so production can leave Ink. Runner
still mounts Ink; wire next.
* Derive the OpenTUI help catalog from the keymap table
* Extract gate emitter wiring into a disposable module
* Drive subagent observe from host-supplied sessions
* Source the help catalog from an OpenTUI-owned binding table
* Cover the product host mount lifecycle with headless tests
* Add prompt kill ring and yank chords to the OpenTUI shell
* Mount the OpenTUI host as the interactive entry path
* Port onboarding, session picker, and mode prompt to OpenTUI
* Mount slash-command surfaces on the OpenTUI overlay kit
* Delete the Ink renderer tree
* Remove Ink dependencies and dead renderer modules
* Restore command registration, slash dispatch, and telemetry disclosure
* Re-score the acceptance corpus against the shipped OpenTUI path
* Wire orphaned TUI features into the OpenTUI host
Four strands of work that were left unwired by the renderer cutover:
Prompt input: Ctrl+P attaches a clipboard image, with an attachment
channel threaded shell -> bridge -> port -> runner -> agent so images
reach inference as content blocks. Pasted image paths are detected and
attached inline. Sent-message recall on Up/Down persists across resumes.
Typing @ opens path suggestions and resolves mentions on the send path.
Model picker: recents, favorites, provider grouping and the Go-on-Zen
billing warning, sourced from real settings and the billing detector.
Approval display: chained commands render as numbered segments and long
payloads collapse to inspectable placeholders, expandable in the overlay
and mirrored untruncated to the transcript. Code-consuming segments are
never collapsed.
Deletes seven modules the OpenTUI renderer supersedes or makes obsolete:
observe-chrome, chrome-zones, copy, stdin-filter, sync-output, osc8 and
styled-segment-props.
* Render assistant markdown and restore turn progress, quota retry, and stall recovery
* Restore MCP result tables, exit command, and the prompt model bar
* Render edit tool diffs and install OpenTUI native platform packages
* Fix live transcript streaming, overlay row overflow, and slash command entry
* Rebuild the TUI as a prompt-first surface on the Corbits palette
* Lay out the transcript as bubbles with tool glyphs and collapsed skills
* Carry session metadata in the prompt border and rebuild the landing hero
* Grow the prompt into a wrapping multi-line composer and make pickers toggle
* Collapse tool arguments and reasoning behind one expand key
Tool calls show a human summary of their arguments instead of raw JSON —
a view tree reads as its shape, a shell call as its command, an edit as
its file. Expanding renders the real structure rather than pretty-printed
JSON, reusing the view renderer with a palette resolver so it paints in
the product's own tones.
Reasoning streams as a single windowed line while a turn thinks, then
settles to an elapsed-time phrase chosen from a band. Phrases report only
how long reasoning took, never what it concluded.
Skills, summarized calls and settled reasoning all collapse behind the
same expand key, and every collapsed row says it can be opened.
Fixes wrapped tool bodies escaping to column 0: continuation lines now
wrap to the body column and stay inside the shell gutter.
Also drops the mountain glyph from the prompt border. One row has no
vertical range for a silhouette — the wide ridge read as a lump and the
three-cell token as an anonymous peak — so the slot is the word alone and
the mark keeps its full expression on the landing. Adds a bottom margin
row, and makes the brand yield rather than starve the workspace path.
* Describe what a selection does before it is chosen
* Anchor the transcript to the prompt and put row actions behind Alt
* Move the running indicator into the border and reduce context to a percent
* Summarize tool results and put the cost figure behind a setting
* Fold a tool result into its call row and quiet the reasoning line
* Keep plugin warnings out of the frame after the rebase
* Fold a turn's reasoning into one row and give the exit notice a lifetime
* Stop negotiating the kitty keyboard protocol
* Negotiate the kitty keyboard protocol the way OpenCode does
* Re-enable CI on push and pull request
* Humanize MCP tool names and stop the server-name doubling
Exa ships tool names like web_search_exa that already carry the server as a
suffix, so namespacing them produced 'Exa: web search exa'. Strip a leading or
trailing word matching the server before title-casing both halves.
* Remove the unused alt-screen helper
No callers since the OpenTUI cutover, and it would fight the renderer's own
alternate-screen handling if it were ever wired up.
* Add Ctrl+Enter as a newline fallback and drop any-motion mouse tracking
Shift+Enter is unreportable unless the terminal negotiates the kitty keyboard
protocol, so a terminal that does not send a bare CR and the newline is lost.
Ctrl+Enter arrives everywhere. Alt+Enter is deliberately not bound: the shell
owns it for steering.
Nothing reads hover, so any-motion tracking was one report per cell traversed
for no consumer. Press and drag reporting stay, which click-to-expand and the
transcript drag-scroll both need.
* Size the permission overlay to its content and stop headings flashing raw
The overlay asked for a share of the terminal rather than a row per option, so
a three-choice gate filled the screen and a nine-choice gate was no taller. It
now asks for its item count and lets the resolver cap and scroll it. Choices
lost their blank spacer row so the list reads as one list.
The gate wrote its transcript row at open time, directly above an overlay
saying the same thing. That row is now written once the operator decides, and
carries the choice.
A row ending in a bare '####' is not yet a heading, so the marker painted as
literal text until the title arrived and the line re-laid out underneath text
already on screen. Streaming rows now withhold a trailing heading-marker-only
line.
Observe mode focused the prompt while the focus tree said observe owned keys,
so keystrokes landed silently in the parent prompt where a stray Enter would
have sent them.
* Pin CI to the bun version the tests are written against
1.2.19 has no test.serial, so whole integration files failed to load and took
their tests with them.
* Free every renderable in a discarded subtree instead of only its root
OpenTUI releases a renderable's native TextBuffer in its own destroy(),
which detaches children without destroying them. Clearing the landing,
repainting the transcript window or disposing a shell therefore stranded
every descendant's buffer, and a full test run exhausted the native
allocator: 32 'Failed to create TextBuffer' errors on a dev machine and
76 renderer-dependent failures on a 2-core CI runner.
* Fix the defects the review panel found
Ctrl+D quit unconditionally, so deleting a character mid-edit ended the
session. It now needs a focused, empty prompt. Esc from transcript browse did
nothing; it returns to the prompt.
Batched identical tool calls dropped every result after the first, because the
coalesced run row cleared its pending flag on the first answer. A run row now
tracks what it is still owed. The existing test passed because it drove a
different path than the app.
The control-character stripper let bidirectional overrides through, so text in
the approval overlay could read as one thing and run as another. Model output
reached the screen unsanitized, including sequences split across deltas.
Width math used UTF-16 code units where columns were required, most visibly in
the wrap that lays out what the operator is approving. The Ambiguous-width
contract with the renderer is now stated and checked at startup.
Onboarding truncated a pasted API key at a thousand characters without saying
so. Provider failures rendered as raw SDK strings; they now name the recovery.
The stall watchdog said nothing for fifteen minutes.
Four emitter channels had no listeners, so lifecycle hooks, subagent progress,
MCP status and grant confirmations were silently absent.
The standalone binary excluded its own native module and could not start away
from a node_modules tree.
Paint entry points now stand down once the shell is disposed. A poll or a
resolved continuation that outlives the renderer would otherwise write into
freed buffers.
* Bound ripgrep output, isolate perf spans, and stop resume dropping blocks
The grep byte cap never bounded anything: on a breach the handler settled with
everything it had accumulated, 153 KB against a 200 byte limit in one measured
run. The notice fired, so it read as enforced. Collection now owns the bytes
behind three explicit settle points where exactly one wins, and truncates on a
line boundary.
The perf span test was not order-dependent. Every perf file cleared the store
after itself and assumed it began empty, but the store is process-global and
one process runs them all.
Resumed sessions dropped view, plan and tasks blocks with no marker. The loose
history-block shape discarded their payloads before anything could paint them.
Replaying all 49 sessions on this machine recovers 6 blocks that previously
vanished.
* Answer operator questions, and cap search output on hosts without ripgrep
A gate arriving while another overlay was open was dropped: the host refuses a
second non-palette open, so the handler returned without resolving and the run
blocked forever with nothing on screen. Gates now queue and take the host when
it frees. An approval and a question in the same turn is enough to hit it.
The overlay had no way to type an answer, though the tool has always promised
one and OperatorResult already carried it. There is now an answer field, and a
question with no options opens straight into it rather than offering a chooser
with nothing to choose.
The GitHub runner has no ripgrep, so CI exercised the fallback walker for every
search test — and the fallback never capped its output. An unbounded grep could
reach the model on any host without rg. The cap now belongs to the plugin, and
CI installs ripgrep so the real path is covered.
* Make the suite state its own preconditions
Tests were reading the developer's machine and passing there while failing on a
runner. The launch tests resolved configuration from the real global settings —
the provider env vars they set are read by nothing — and the theme tests assert
hex, which only appears when COLORTERM says truecolor.
One test wrote into the real home directory: session state lands under the home
root, not the sandboxed cwd, so goal-state left project directories behind on
every run. Its home is now an override, matching its sibling in session/.
A preload clears the terminal and CORBITS variables so a test that needs one
must set it, forces telemetry off so no path can write an installation id into
a real settings file, and fails loudly when ripgrep is missing — without it the
search tools quietly run their fallback and the suite stays green.
Bun snapshots os.homedir() at process start, so assigning process.env.HOME in a
test does nothing. Sandboxing goes through the config and cwd flags instead.1 parent 2228578 commit f62211d
366 files changed
Lines changed: 42546 additions & 30459 deletions
File tree
- .github/workflows
- briefs
- docs
- adr
- plans
- evals/capability
- src
- agent
- config
- cost
- mcp
- perf
- permission
- plugins
- session
- subagent
- telemetry
- tools
- tui-opentui
- focus
- geometry
- tui
- commands
- components
- at-mention
- hooks
- util
- view
- util
- tests
- setup
- unit
- tui
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
4 | | - | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
12 | 7 | | |
13 | 8 | | |
14 | 9 | | |
| |||
26 | 21 | | |
27 | 22 | | |
28 | 23 | | |
29 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| |||
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
22 | 26 | | |
23 | 27 | | |
24 | 28 | | |
| |||
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | 36 | | |
38 | 37 | | |
39 | 38 | | |
| |||
117 | 116 | | |
118 | 117 | | |
119 | 118 | | |
120 | | - | |
121 | | - | |
122 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
123 | 124 | | |
124 | 125 | | |
125 | | - | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
130 | 130 | | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
143 | 139 | | |
144 | 140 | | |
145 | 141 | | |
146 | 142 | | |
147 | 143 | | |
148 | 144 | | |
149 | | - | |
| 145 | + | |
150 | 146 | | |
151 | 147 | | |
152 | 148 | | |
| |||
167 | 163 | | |
168 | 164 | | |
169 | 165 | | |
170 | | - | |
| 166 | + | |
171 | 167 | | |
172 | 168 | | |
173 | 169 | | |
174 | | - | |
| 170 | + | |
175 | 171 | | |
176 | 172 | | |
177 | 173 | | |
| |||
289 | 285 | | |
290 | 286 | | |
291 | 287 | | |
292 | | - | |
| 288 | + | |
293 | 289 | | |
294 | 290 | | |
295 | 291 | | |
| |||
345 | 341 | | |
346 | 342 | | |
347 | 343 | | |
348 | | - | |
| 344 | + | |
349 | 345 | | |
350 | 346 | | |
351 | 347 | | |
| |||
367 | 363 | | |
368 | 364 | | |
369 | 365 | | |
370 | | - | |
| 366 | + | |
371 | 367 | | |
372 | 368 | | |
373 | 369 | | |
| |||
391 | 387 | | |
392 | 388 | | |
393 | 389 | | |
394 | | - | |
| 390 | + | |
395 | 391 | | |
396 | 392 | | |
397 | 393 | | |
| |||
414 | 410 | | |
415 | 411 | | |
416 | 412 | | |
417 | | - | |
| 413 | + | |
418 | 414 | | |
419 | 415 | | |
420 | 416 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| |||
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
105 | | - | |
| 105 | + | |
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
| |||
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
| 236 | + | |
237 | 237 | | |
238 | 238 | | |
239 | 239 | | |
| |||
0 commit comments