Anvil v2.2.16 — The TUI Layout System

Anvil v2.2.16 ships today with the TUI Layout System. Eight live-switchable layout variants built on a per-tab TuiLayoutConfig. The terminal UI is no longer a single hard-coded draw path — it’s a real layout dispatcher with a new default that finally puts your sessions, agents, tools, and memory state in one persistent rail. /layout vertical-split-tabs is what you’ll see when you launch. /layout classic-tabs is the old single-deck view, one keystroke away. 50 commits over v2.2.15.

The TUI you’ve been waiting for

Every Anvil release before today drew the same way. Banner up top, status line, conversation scrollback, input row at the bottom. One renderer. One layout. v2.2.16 breaks that.

Four architectures, each with a tabs and no-tabs variant. Eight real renderers:

  • Vertical Split — persistent left rail (sessions, agents, tools, MEMORY, gate state) next to a swappable right deck. The rail owns all chrome — banner, status, model, cost. The deck has input only. The split anchor is mouse-draggable. This is the new default.
  • Classic — single-deck, minimal, pixel-identical to pre-v2.2.16. The inline 7-layer MEMORY block sits directly above the input. One keystroke from any other layout: /layout classic-tabs.
  • Three-Pane — FOCUS / LOG / CONTEXT bands with an always-on input row. No vim modal. Typing always edits the input, with a framed hint and ghost text making the active band discoverable.
  • Journal — timestamped single-column scroll for when you want a log-only view. Ctrl-K opens a command palette. Stale cells from prior screens never bleed through.

And the /layout command is a full first-class capability — definition, registration, completion, handler, dispatch, rendering, gate, OTel + tests. All eight axes, wired through Anvil’s unified slash dispatcher.

/layout list                       — tabular view of the eight variants with the current selection highlighted
/layout vertical-split             — switch the active tab to vertical split, no tabs
/layout vertical-split-tabs        — switch the active tab to vertical split with workspace tabs
/layout classic                    — restore the pre-v2.2.16 single-deck rendering
/layout three-pane-tabs --global   — switch AND write the choice to ~/.anvil/config.json for new tabs and future runs
/layout reset                      — back to the default (vertical-split-tabs in v2.2.16)

Live preview of every variant: anvilhub.culpur.net/tui-preview.

Migration safety, by design

v2.2.16 changes the default layout. You don’t lose your setting.

The settings parser only falls back to the new default when the tui_layout key is absent from your ~/.anvil/config.json. If you set a layout explicitly any time during the preview cycle — say "tui_layout": "classic-tabs" — that value is preserved verbatim. A unit test (tui_layout_explicit_classic_in_settings_is_preserved) guards this contract at build time.

If you never set the key, first launch after upgrade lands you on Vertical Split + Tabs and emits a one-time toast:

◊ Anvil v2.2.16 introduces TUI layouts. You're on Vertical Split + Tabs
   (the new default — persistent left rail, swappable right deck).
   Try /layout list — 8 variants. /layout classic-tabs restores pre-v2.2.16
   single-deck rendering.

After the toast fires, tui_layout_intro_seen is set to true and it never appears again.

Paste finally works the way you’d expect

Drag-and-drop a PDF onto Anvil’s input. v2.2.16 attaches it as a ContentBlock::Document to your next request — the provider receives the file inline, not a hallucinated transcript. Same for Office docs. The consolidated paste handler routes terminal bracketed paste, OSC52, and drag-and-drop file paths through a single code path, so behavior is consistent across iTerm2, Alacritty, Wezterm, and Terminal.app.

Mouse capture is off by default now — native terminal copy works again. Click-to-switch-tabs is still available via opt-in mouse: true in settings.

Long pastes get a placeholder treatment. Drop 4,000 characters of log into the input and the rendered history shows [Pasted 4,127 chars] while the full content goes to the provider. Drag-and-drop on terminals that don’t emit OSC52 is detected by inter-keystroke timing and converted to a single paste, not 200 individual keystroke events.

Ctrl+C actually cancels now

Across all seven providers. Previously the cancel token was wired in some providers and no-op in others. v2.2.16 plumbs it through every provider implementation in DefaultRuntimeClient, with tokio::select! wrapping the blocking HTTP read so the connection closes when the token fires instead of waiting for EOF. A new wiremock integration test exercises the cancel path end-to-end.

Practical impact: hit Ctrl+C on a slow streaming response and the next prompt comes back immediately. No more 30-second wait while the connection drains.

Three-pane is now genuinely usable

The pre-v2.2.16 three-pane variant had a vim modal — you had to press i to enter Insert mode before typing. People hated it. We hated it. Gone in v2.2.16. The input is always on. Typing always edits the input. The active band gets a framed hint and ghost-text placeholder so it’s discoverable. The CONTEXT band uses Constraint::Fill so it actually fills available height instead of collapsing to one row.

Vertical split rail polish

The new default got the most attention this cycle. The rail owns banner + gate banner + status + model + cost displays. The deck contains only the input row. The split anchor is mouse-draggable at the rail edge. Tool-call boxes close cleanly when the call returns. Markdown is styled inline. Cost rendering is 2-decimal. Section headers are uppercase. Cross-tab status aggregates (e.g. “3 streams active, 2 tabs idle”) render in the rail. QMD memory is folded into the MEMORY block. Agent tabs bind to their parent rail row.

AnvilHub verification gate

The marketplace gets a verification badge contract. HubPackage ships with verified-badge structs, a require_verified config gate refuses to install unverified packages when set, /plugin update REVOKED guards prevent re-installing a revoked package, and /hub status ships all 8 capability-contract axes. The update probe now prefers anvilhub’s /api/version endpoint and falls back to GitHub Releases if the AnvilHub host is down — resilient to either side failing.

TUI correctness long tail

About thirty smaller fixes shipped this cycle, mostly bubbled up from the layout rework:

  • /vault unlock retries up to 3 times and pre-fills the prompt on failure (no more “type your master password three times perfectly or restart”)
  • Welcome banner names the active provider, not hardcoded Anthropic
  • Session-title heuristic skips a bare URL as first message so your sessions list isn’t full of URLs
  • 5xx errors name the configured provider/gateway, never hardcoded Anthropic
  • Spinner color warms green → amber → red as elapsed time climbs
  • Read offset accepts string forms with whitespace and + prefix
  • ANVIL_MCP_TOOL_TIMEOUT env override per request
  • Strict RFC 6749 token-exchange parser + startup OAuth validator
  • Lenient scopes deserializer prevents auth lockout on tokens that drift from the strict shape
  • ProviderLoginModal for in-TUI OAuth/API-key flows — no more bouncing to terminal scrollback to paste a code
  • Slash-completion popup is wired into every render path, so TAB always brings up completions regardless of layout
  • Every render path redraws on every keystroke so input is live across all layouts

The wizard nudges you toward the new default

First-run setup wizard, step 7. v2.2.15 defaulted to Classic [1]. v2.2.16 puts Vertical Split at [1]; Classic moves to [2]. New installs that press Enter through the wizard land on the rail+deck view. The label says (default, recommended) on the new option.

Test coverage

10 new unit tests for TuiLayoutConfig parsing, default, and migration. 10 new integration tests for live-switch state machine. 2379 workspace tests, 0 failures.

Seven platforms, SHA256-verified

macOS ARM64, macOS Intel, Linux x86_64, Linux ARM64, Windows x86_64, FreeBSD x86_64, NetBSD x86_64. Every binary has a paired .sha256 manifest published to anvilhub.culpur.net/sha256 for verification before you trust them. anvil upgrade checks the manifest before swapping the binary.

Upgrade in 5 seconds

brew upgrade anvil      # macOS / Linux Homebrew

# Or one-line installer:
curl -fsSL https://anvilhub.culpur.net/install.sh | bash

Verify with anvil --version — reports 2.2.16.

What you’ll see on first launch:

  • If you’ve never set tui_layout: a one-time toast explaining the new default, then you’re on Vertical Split + Tabs.
  • If you set tui_layout explicitly during the preview cycle: nothing changes. Your choice is preserved. No toast.

Either way, /layout list shows you the eight variants. Switch as often as you want; switching to the same config is a no-op (the equality guard kicks in). Switching to a different layout fires a terminal clear so previous cells don’t bleed through, then redraws.

The full release notes

GitHub release v2.2.16 has the long-form changelog and every binary. culpur.net/anvil has the install CTAs and feature cards. AnvilHub /about has the per-release entry. Everything from v2.2.7 onward at github.com/culpur/anvil/releases.

Eight layouts. Your terminal, your tools, your data, your cost. The only AI coding assistant that doesn’t lock you in.

Scroll to Top