feat(client): TUI chrome for take-the-wheel — see who has the wheel, drive it from keys (ADR-0033)#140
Merged
Merged
Conversation
…-0033) The interactive TUI now sees who has the wheel and what is frozen: - Send SUBSCRIBE_EVENTS on attach and handle AgentEvent::TerminalControl, folding lifecycle + input-lease holder into each PaneSlot. Capture our own ClientId from ATTACHED (previously discarded) to distinguish "you hold the wheel" from another client. - A right-aligned status-bar badge — `[ FROZEN ]`, `[ WHEEL:you ]`, `[ WHEEL:c9 ]` — via a painter-owned `supervisory` field mirroring set_windows, with snapshot parity in compose_buffer so `phux snapshot --rendered` shows it too. A new FrameOutcome.chrome_dirty flag triggers an immediate repaint so freeze/lease shows without waiting for a content frame; the badge refreshes on focus/layout change. Unit-tested badge formatter across every state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drive the supervisory surface from inside the TUI, mirroring the existing
selector-resolve-then-command actions:
- take-input (seize the focused pane's input lease), give-input (release),
signal-terminal { interrupt | freeze | resume | terminate | kill } — each
builds a COMMAND frame for the focused pane via a new ActionEffects
command_frames channel. Registered in ACTION_NAMES + REGISTRY (parity test
enforced) and documented in tui.md's action catalog.
- Default bindings under the C-a prefix: W take, g give, f freeze, u resume.
Destructive signals (kill/terminate/interrupt) are deliberately left to
`phux signal` so a stray keystroke can't tear a process down.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Findings from a verified multi-agent review of the diff: - The supervisory badge now rides the status bar only when there is a bar to host it (reverted the empty-bar guard tweaks) — fixes a live-vs-snapshot parity divergence where an empty configured bar painted the badge over un-erased content while `phux snapshot --rendered` showed a clean row. - Collapse the 4 duplicated window+badge refresh sites into one `refresh_window_chrome` helper so the window strip and badge can't drift. - `set_supervisory` now reports whether the badge changed; the TerminalControl repaint is gated on it, so a control event on a *background* pane no longer forces a full-window repaint for state the user can't see. - `set_supervisory` no-ops while an error line is showing (avoids a spurious error-strip re-emit on every wheel/freeze change). - Drop a dead in-loop own_client_id refresh (ATTACHED only lands at bootstrap) and a stale `let _ = focused;` the new arms made redundant. - Pin the palette's signal-terminal default to the reversible freeze with a unit test, since signals are not lease-gated server-side. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
The consumer-side layer of ADR-0033: the interactive TUI now sees supervisory state and lets you drive it from the keyboard. The backend (input leases + process signals + the
TerminalControlbroadcast) merged in #137; this wires the reference TUI to it. Pure consumer logic per ADR-0017.See it — the chrome
SUBSCRIBE_EVENTSon attach and handlesAgentEvent::TerminalControl, folding lifecycle + lease-holder into eachPaneSlot. It captures our ownClientIdfromATTACHED(previously discarded) to tell "you hold the wheel" from another client.[ FROZEN ],[ WHEEL:you ],[ WHEEL:c9 ]— via a painter-ownedsupervisoryfield mirroringset_windows, with snapshot parity incompose_buffersophux snapshot --renderedshows it too. Achrome_dirtyoutcome flag repaints immediately on a control event so freeze/lease shows without waiting for a content frame.Drive it — the keybindings
take-input(seize the focused pane's input lease),give-input(release),signal-terminal {interrupt|freeze|resume|terminate|kill}— each builds aCOMMANDframe for the focused pane via a newActionEffects.command_frameschannel. Registered inACTION_NAMES+REGISTRY(parity test enforced), documented intui.md's action catalog.C-aprefix:Wtake,ggive,ffreeze,uresume. Destructive signals (kill/terminate/interrupt) are deliberately left tophux signalso a stray keystroke can't tear a process down.Reviewed
Ran a multi-agent adversarial review over the diff (4 dimensions, every finding independently verified before acceptance). 7 confirmed findings, all addressed in the final commit:
refresh_window_chromehelper; gated the control-event repaint on the badge actually changing;set_supervisoryno-ops under the error line; dropped dead code.freeze(signals aren't lease-gated server-side), now pinned by a unit test.Tests
just cigreen: full suite, fmt + clippy (-D warnings) + deny + docs-check.🤖 Generated with Claude Code