amux 0.2: tiled panes (split tree + vterm compositor) - #1
Merged
Conversation
Add the pure engine for tiled panes, on the new vterm-rs org crate:
- Cargo: depend on nativelite/vterm-rs (org crate, zero third-party held),
bump to 0.2.0.
- src/layout.rs: the split tree. A binary tree of panes and H/V splits
resolving to a rect per leaf, reserving a 1-cell divider gutter between
siblings. Equal splits (MVP); geometric focus movement so a 2x2 grid does
the intuitive thing regardless of nesting; close-collapses-to-sibling.
- src/tile.rs: the compositor. Blit each pane's emulated ansi::Screen into a
master Screen at its offset, draw the divider gutters, highlight the focused
pane's edges, and park the cursor at the focused pane in master coords.
- src/input.rs: extend Action additively (0.1 keys unchanged): SplitH ("),
SplitV (%), MoveFocus (h/j/k/l and prefixed arrows via a small ESC[ state
machine), Zoom (z).
All pure, unit-tested (layout rects/focus/close, compositor cells/cursor).
Wire the split tree and compositor into a second rendering mode beside the 0.1 passthrough loop: - Windows now hold a split tree of panes (Ctrl+A c/n/p/1-9 still switch windows; each window can itself be tiled). A single or zoomed pane keeps the 0.1 passthrough path (raw bytes -> terminal, perfect fidelity); two+ visible panes render tiled: feed each pane's vterm::Term, compose to a master ansi::Screen, and master.diff(&prev) writes only changed bytes. - Ctrl+A "/% split the focused pane, h/j/k/l or arrows move focus, z zooms (full-screen passthrough escape hatch), x kills the focused pane and re-tiles to the survivor synchronously so input never routes to a corpse. - Per-pane resize: on split/close/zoom/terminal-resize, recompute rects and push each to its pty and Term. - bar: keys-help line notes the split/focus/zoom keys. Tests: unit-test the new Actions (incl. prefixed-arrow state machine, chunk splits); e2e (pty-driven) — split makes a second live tile with both shells round-tripping, a 2x2 has four live panes (all markers appear), focus movement re-routes input, zoom toggles, kill re-tiles to a live survivor. python dev.py check green (13 lib + 26 integration + doctests).
JSBtechnologies
added a commit
that referenced
this pull request
Aug 30, 2026
…(0.8.1) A ctl spawn (new window) and --here split sized the worker's pty to a rough estimate but never resized the window, so the agent painted short (content at top, blank below) until a manual terminal resize. Both paths now call resize_window right after spawning, matching the interactive split handlers. Fixes founder repaint reports #1 (new-window spawn) and #3 (--here didn't push to bottom). Verified: python dev.py check green (119 lib + 46 integ).
JSBtechnologies
added a commit
that referenced
this pull request
Sep 4, 2026
…eview #1, #6] Three changes, plus the seam the Windows work lands in. #1a — `caller_privileged` returned `true` for a caller with no token. The gate was inverted: holding NO credential granted strictly more than holding a worker's. `caller` is derived from the capability token and never self-reported, so `None` means exactly "unauthenticated" and must be the least trusted state. A token resolving to no live pane is stale or forged, and gets the same treatment. #1c — the bad-request audit entry logged `truncate(line, 80)` of the raw request. `build_request` emits `{"caller":N,"token":"` — a 21-character prefix — so 59 of a 64-character token went into a log that `ctl audit` will hand to a reader holding no credential. It now records the shape and the parse error, which is what debugging needs and is all it needs. #6 — the background pane drain was an unbounded `while let`, unlike the focused path's `for i in 0..DRAIN_READS_PER_TICK` right above it. One noisy background agent could hold the event loop for as long as it kept producing, starving keystrokes, signal handling and ctl. A fleet makes that likely, not theoretical. Now bounded identically. Windows prep — `spawn_watchdog` is gated to unix. On Windows the watchdog is worse than redundant: it would spawn a second amux that cannot signal a process group and would block on its pipe forever. The comment marks where the Job Object attaches, since the pane set is already known to have changed there. NOT VERIFIED BY TEST, and worth stating rather than implying otherwise: I wrote an integration test for the privilege inversion and it passed with the fix REVERTED, so it was asserting nothing — the elevated spawn is refused earlier, by policy or the sanitizer, and never reaches the privilege gate. Removed rather than kept as false coverage. Review finding #16 already noted this function has zero tests; it still does. A real one has to drive a genuinely privilege-gated path (scope, or the mode cap in a session whose policy would otherwise allow it). macOS 26.6.2: ./dev.py check green — 306 tests, 0 failures, clippy clean. Type-checks for x86_64-pc-windows-msvc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KoBz1RcGsUbYdMSCR7LaK
JSBtechnologies
added a commit
that referenced
this pull request
Sep 4, 2026
…eview #1] The last of the privilege chain, and it is a behaviour change — flagged as such. `--mode` was bypassed outright for a "privileged" caller: `Some(req) if privileged => req`. The reading was that the root pane is the human, and the human may direct their own session. Two things break it. `-n N` and `--grid` give every pane no parent, so EVERY pane in a mass-spawned session was classified as the operator. Any agent in one could ask for `skip` — a full permission bypass, --dangerously-skip-permissions — in a session the human had deliberately launched as `plan`. Since the human issues ctl from inside a pane too, there is no way to tell "you" from "an agent running where you launched it": tree position was never a proxy for authority. And amux's own documentation calls --trust "the mode spawned agents run in, and the ceiling they are capped at". A ceiling a pane can exceed is not a ceiling. The README simultaneously promised that the root pane could elevate above it. Both cannot hold; the ceiling is the one worth keeping. So the bypass is gone and the policy caps every caller. `--mode` still DE-ESCALATES freely — asking for less is always safe, and that is the useful half — it simply cannot escalate. Raising the posture is done at launch with --trust, where it is one visible deliberate choice rather than something a pane can request mid-session. The cap logic is now a pure `effective_mode(requested, policy)`, tested both ways: skip is refused down to plan with a note, and de-escalation passes untouched. README and the CLI help are corrected rather than left contradicting the code, with a short note on why the root-pane carve-out went — a future reader will otherwise re-add it for the same plausible reason it existed. Trade-off, plainly: you can no longer spawn a teammate above the session policy mid-run. If that turns out to matter, the right shape is an explicit operator credential the amux process holds and never injects into any pane env — not an inference from where a pane sits in the tree. macOS 26.6.2: ./dev.py check green — 324 tests, 0 failures, clippy clean. Type-checks for x86_64-pc-windows-msvc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KoBz1RcGsUbYdMSCR7LaK
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
Adds tiled panes to amux — four agents in a 2×2 square, the deferred 0.1 feature. Implements the amux 0.2 tiling design doc.
Dual-mode run loop:
vterm::Termsized to its rect; every pane'sansi::Screenis composited into one master andScreen::diffwrites only the changed bytes.Windows (
Ctrl+A c/1-9/n/p) and splits coexist: each window is its own split tree.New crate dependency
vterm = { git = ".../vterm-rs", branch = "main" }— the emulator core (nativelite/vterm-rs0.1.0), also new. Zero third-party deps holds (org crates only; dep guard passes). Version bumped0.1.1 → 0.2.0.Keys (all additive; every 0.1 key unchanged)
Ctrl+A "Ctrl+A %Ctrl+A+ arrows orh/j/k/lCtrl+A zCtrl+A xNew modules (pure, unit-testable)
src/layout.rs— the split tree:split/close(collapse to sibling) /rects(reserves a 1-cell divider gutter) / geometricmove_focus.src/tile.rs— the compositor: blit each pane at its offset, draw dividers, highlight the focused edge, park the cursor in master coords.How verified
python dev.py checkgreen: dep guard OK, 13 lib unit tests (layout rects/focus/close/2×2, compositor cells/cursor/dividers) + 26 integration tests (all 0.1 tests + splits round-trip,two_by_two_grid_has_four_live_panes, focus routing, zoom, kill-retile, prefixed-arrow parsing incl. chunk-split) + doctests.cargo clippy --all-targetsandcargo fmt --checkclean. Reviewed both pure modules and the dual-mode loop by hand.Fidelity boundaries (honest)
Tiling is the emulated path. Wide/CJK glyphs, sixel, and mouse reporting inside a tile are out of scope for 0.2 — zoom (
Ctrl+A z) is the escape hatch back to raw passthrough. Stated in code comments and the vterm README.Not in this PR
Proportional drag-resize (equal splits only), saved layouts, the deferred settings surface.
🤖 Generated with Claude Code