Skip to content

feat(client): TUI chrome for take-the-wheel — see who has the wheel, drive it from keys (ADR-0033)#140

Merged
phall1 merged 3 commits into
mainfrom
phux-take-the-wheel-tui
Jun 17, 2026
Merged

feat(client): TUI chrome for take-the-wheel — see who has the wheel, drive it from keys (ADR-0033)#140
phall1 merged 3 commits into
mainfrom
phux-take-the-wheel-tui

Conversation

@phall1

@phall1 phall1 commented Jun 17, 2026

Copy link
Copy Markdown
Owner

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 TerminalControl broadcast) merged in #137; this wires the reference TUI to it. Pure consumer logic per ADR-0017.

See it — the chrome

  • The client sends SUBSCRIBE_EVENTS on attach and handles AgentEvent::TerminalControl, folding lifecycle + lease-holder into each PaneSlot. It captures our own ClientId from ATTACHED (previously discarded) to tell "you hold the wheel" from another client.
  • A right-aligned status-bar badge for the focused pane — [ 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 chrome_dirty outcome 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 a COMMAND frame for the focused pane via a new ActionEffects.command_frames channel. Registered in ACTION_NAMES + REGISTRY (parity test enforced), 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.

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:

  • Fixed a real render bug: live-vs-snapshot parity divergence on an empty configured bar (the badge now rides the bar only when there's a bar to host it).
  • Collapsed 4 duplicated refresh sites into one refresh_window_chrome helper; gated the control-event repaint on the badge actually changing; set_supervisory no-ops under the error line; dropped dead code.
  • The review also confirmed safe-by-design: the palette can only emit the reversible freeze (signals aren't lease-gated server-side), now pinned by a unit test.

Tests

  • Badge formatter unit test (every state), palette-default-is-freeze test, ACTION_NAMES↔REGISTRY parity test.
  • just ci green: full suite, fmt + clippy (-D warnings) + deny + docs-check.

🤖 Generated with Claude Code

phall1 and others added 3 commits June 17, 2026 16:01
…-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>
@phall1 phall1 merged commit b481cbe into main Jun 17, 2026
3 checks passed
@phall1 phall1 deleted the phux-take-the-wheel-tui branch June 17, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant