feat(tui): recover the #175 UI round — left rail, centred menu, framed composer - #194
Merged
Conversation
First slice of the #175 UI round, ported onto current main. Every message carries a [copy] button; user messages also carry [try again], which re-runs the text through the same submit path Enter uses rather than a second code path that could drift from it. The clipboard layer is its own module: copy-to-clipboard owns the platform command (pbcopy / clip.exe / wl-copy / xclip), and clipboard-context exposes it through a provider so a test can observe a copy without touching the real clipboard. useClipboard falls back to the shared default writer, so the buttons work whether or not a provider is mounted. Also lands selection-passthrough (the honest answer to mouse text selection) and tasks-list-fit, both self-contained. Deliberately NOT in this slice: the run-mode / fusion files that ship in the same #175 round, since the fusion stack is still under review; and anything touching the contested files (tui-app, menu-popup, sidebar, theme) where main has moved since #175 was cut. Tests: 4828 total, no new failures. The two LlmHealthPoller cases that appear under full-suite load pass in isolation on this branch and on pristine main.
Second slice of the #175 UI round. The rail leads the row container, so it renders on the LEFT, and it now carries the brand lockup, the version and a Menu button alongside Sessions and Tasks. It is drawn in every mode, not just chat: switching to a panel used to take all the chrome off screen. Three things came out of porting it rather than from the branch: - The one-row status bar STAYS. #175 removed it on the grounds that the rail carries the same four things, but its rail never grew the breadcrumb, so removing the bar left no indicator of where you are. The bar keeps the breadcrumb and drops its own brand lockup while the rail is up, since two copies read as a rendering bug. - SIDEBAR_CHROME_ROWS goes 5 -> 13. The rail spends those rows on the mark, the version line and the Menu button. Measured off the rendered component, not estimated: at 24 rows the old constant had the rail drawing 28 rows into a 24-row terminal, and Ink 7 overlaps rather than clips, which garbles the whole frame. - main's layout.ts is kept over #175's. It has SIDEBAR_OUTER_ROWS and SIDEBAR_MIN_ROWS, which #175 predates; only the constant is retuned. Theme gains railBackground / railForeground / railMuted (additive; no palette key is removed) and logo.tsx gains RAIL_MARK, the same drawing as the splash rasterised to a 6x4 cell. Tests: 4830 total, no new failures. The layout and smoke expectations that encoded the old chrome are updated, not deleted: the row-budget test still pins the 2:1 ratio, and the Tab-focus test still branches on whether the rail is up.
Third slice of the #175 UI round. The menu was anchored to the bottom of the pane, hanging off the prompt like a dropdown. It is now a true overlay: position=absolute, centred on both axes, with the app behind it dimmed through setBackdropDimmed. availableColumns subtracts the rail width, so the frame is centred on the content area rather than on the terminal — without that the right border falls off screen whenever the rail is up. The menu keeps this tree's onActivate contract: a node that carries a slash name is run as that command, so the menu still has exactly one dispatch path shared with the slash handler. Tests: 4830 total, no new failures.
Fourth slice of the #175 UI round. The prompt was an opencode-style left tail: one border column capped by a `╹` glyph. It is now a closed rounded frame whose bottom row is an action bar on the rail ground, carrying the model label and a clickable `send →`. Net one row shorter than the tail, so CHROME_ROWS is unchanged. The action bar is unconditional now. It used to appear only when a model or a slot was set; since it carries Send, it cannot come and go without the composer changing height under the operator. Nothing here is coupled to the fusion stack. `formatModel` splits on a local ` ⇄ ` constant so a two-legged label stays readable, but with no fusion present the split yields one element and it behaves exactly as before — kept so the fusion stack can land later without re-touching this file. Also corrects the debug-pane comment that still described the `╹` cap. Tests: 4840 total, no new failures. prompt-shell.test.tsx is replaced rather than merged: it asserted the tail cap the frame removes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Recovers the UI round from #175 onto current
main, as four reviewable commits instead of a 286-file branch.#175 was closed because it re-merged 24 already-landed PRs and would have reverted 15 files that shipped after it was cut. That reasoning still holds — but the UI round inside it was original work that exists nowhere else, and closing the PR took it with it. This is that work, rebuilt on top of everything that has landed since.
What lands
The rail moves left and becomes the app frame. It carries the brand lockup, the version and a Menu button alongside Sessions and Tasks, and it is drawn in every mode rather than chat only — switching to a panel used to take all the chrome off screen.
The menu becomes a centred overlay.
position: absolute, centred on both axes, with the app behind it dimmed.The composer becomes a framed field. A closed rounded frame whose bottom row is an action bar on the rail ground, with the model label and a clickable
send →, replacing the left tail and its╹cap.Every message carries
[copy], and user messages carry[try again]. The clipboard layer is its own module;[try again]re-runs the text through the same submit path Enter uses.Three places where this tree won over the branch
SIDEBAR_CHROME_ROWS5 → 13, measured not estimated. The rail spends those rows on the mark, the version and the Menu button. With the old constant, a 24-row terminal had the rail drawing 28 rows into it — and Ink 7 overlaps rather than clips, so that garbles the whole frame rather than losing a tail.layout.tscomes frommain, not atomic-agent 0.3.0 — every open PR integrated, plus what four rounds of hand testing found #175. main's version hasSIDEBAR_OUTER_ROWSandSIDEBAR_MIN_ROWS, which atomic-agent 0.3.0 — every open PR integrated, plus what four rounds of hand testing found #175 predates; only the constant is retuned.Deliberately excluded
The run-mode / fusion files ship in the same #175 round and are not here — that stack is still under review. The composer's
formatModelkeeps a local⇄split so a two-legged label stays readable; with no fusion present it behaves exactly as before, so the fusion stack can land later without re-touching the file.Verification
npm run lintclean. 4840 tests, 2 failures — the flaky sidecar FIFO case and the$HOME-dependent glob test, both failing on pristinemain.Every surface was checked by rendering the real Ink tree and reading the frame back, not by asserting from source: the rail on the left with its Menu button, the menu centred inside the content area, the framed composer with
send →, and[copy]under a message.Tests that encoded the old chrome are updated rather than deleted — the row-budget test still pins the 2:1 split, and the Tab-focus test still branches on whether the rail is up.