Land staged session/MCP/chrome work + sidebar clear-J fix

This batches the in-flight [Unreleased] block from CHANGELOG.md into a
single commit. Highlights:

- Real MCP protocol layer (initialize / tools/list / tools/call) so
  vendor MCP clients can complete the handshake against the per-PID
  socket. Legacy direct-dispatch preserved for the harness.
- New mcp_injection kinds — cli_override for codex, config_env for
  opencode — joining the existing env-var and config_file paths so
  patterm can slot into more agents without touching their real
  config or auth.
- Ctrl+A/D and Ctrl+W/S focus navigation across tabs and intra-tab
  process lists, recognised in legacy / kitty CSI u / xterm
  modifyOtherKeys encodings.
- Palette macros (sw / k / sp ) and reordering so open sessions
  surface above spawn-new entries.
- Two-row tab bar, sidebar/tabbar/status chrome cache, viewport-wipe
  on agent spawn, CR-terminated orchestrator injections, and split-
  Enter PTY writes so paste-detecting TUIs see Enter as a key event.

Also fixes the bug logged in TODO: claude's Ctrl+O tool-call expansion
emits CSI 0 J, which the viewport renderer was forwarding verbatim —
wiping the sidebar to the right of the cursor and leaving the chrome
cache convinced nothing had changed. CSI 0 J and CSI 1 J are now
translated into per-row ECH sequences clamped to the viewport, same
as CSI 2 J and CSI K already were.

Agent guides (CLAUDE.md / AGENTS.md) now spell out the
TODO->CHANGELOG workflow so completed items land in the changelog
rather than as ticked entries left behind in TODO.
This commit is contained in:
2026-05-14 19:09:35 +01:00
parent 7649587f9a
commit 3622c41fd0
25 changed files with 1951 additions and 163 deletions

View File

@@ -11,13 +11,13 @@ func TestTerminalLayoutWideUsesMainViewport(t *testing.T) {
if !l.sidebarVisible {
t.Fatal("wide layout should show sidebar")
}
if l.childCols() != 92 {
t.Fatalf("child cols: got %d want 92", l.childCols())
if l.childCols() != 91 {
t.Fatalf("child cols: got %d want 91", l.childCols())
}
if l.childRows() != 36 {
t.Fatalf("child rows: got %d want 36", l.childRows())
if l.childRows() != 37 {
t.Fatalf("child rows: got %d want 37", l.childRows())
}
if l.mainTop != 4 || l.statusRow != 40 {
if l.mainTop != 3 || l.statusRow != 40 {
t.Fatalf("unexpected vertical chrome: mainTop=%d statusRow=%d", l.mainTop, l.statusRow)
}
}
@@ -30,8 +30,8 @@ func TestTerminalLayoutNarrowHidesSidebar(t *testing.T) {
if l.childCols() != 38 {
t.Fatalf("child cols: got %d want 38", l.childCols())
}
if l.childRows() != 8 {
t.Fatalf("child rows: got %d want 8", l.childRows())
if l.childRows() != 9 {
t.Fatalf("child rows: got %d want 9", l.childRows())
}
}
@@ -46,13 +46,13 @@ func TestSpawnSizingUsesViewportDimensions(t *testing.T) {
l := newTerminalLayout(120, 40)
launcher := NewLauncher(nil, "", l.childCols(), l.childRows())
cols, rows := launcher.size()
if cols != 92 || rows != 36 {
t.Fatalf("launcher size: got %dx%d want 92x36", cols, rows)
if cols != 91 || rows != 37 {
t.Fatalf("launcher size: got %dx%d want 91x37", cols, rows)
}
host := newToolHost(nil, nil, nil, preset.Set{}, nil, l.childCols(), l.childRows())
cols, rows = host.size()
if cols != 92 || rows != 36 {
t.Fatalf("tool host size: got %dx%d want 92x36", cols, rows)
if cols != 91 || rows != 37 {
t.Fatalf("tool host size: got %dx%d want 91x37", cols, rows)
}
}