Six-phase sweep: section headers (Focused / Open / Spawn / Quit) with
header-skip cursor; chip strip mirroring sw/sp/k macros, driven by
Tab; unified Spawn verbs across agent / process / terminal / custom;
dropped duplicate global Close list in favor of Ctrl-X inline close
on a Switch row plus the [Close] chip; scored matching (prefix >
word-boundary > substring > fuzzy) with matched-char highlighting;
title bar surfaces focus subject; rename forms split long subject
onto its own row; new Alt-1..9 quick-pick, Home/End, ? help overlay,
and Ctrl-R relaunch toggle inside the spawn-process form. Scroll
indicator and cursor/total counter round out the footer.
- Palette's per-child "Kill <name>" action is now labelled "Close <name>"
(action kind unchanged; still SIGTERM). Matches the existing "Close
agent: …" context entry and reads less violent for a graceful term.
- New "New Terminal" palette entry spawns a bare interactive $SHELL pane
via LaunchTerminal (kind=terminal). Replaces the default "shell"
process preset that was seeded on first run.
- Exited KindTerminal entries are now dropped from the session in
reapChild — terminals have no restart path, so leaving them behind as
greyed rows in the Processes sidebar was just clutter. processList
also filters defensively.
When opened with Ctrl-K, the palette now prepends entries for whatever
is currently focused:
- Focused scratchpad: Delete / Rename (inline form) / Edit (fire-and-
forget zed launch with stdio detached so the TUI is not suspended).
- Focused agent: Rename (inline form) / Close.
- Focused process: Rename / Delete (drops the entry; SIGKILL if alive)
/ Stop (SIGTERM, keep entry) / Restart (same argv).
The rename UX is a single-field inline form that mirrors the existing
spawn-process form, so the modal-input contract is unchanged.
scratchpad.Store grows Delete / Rename / Path so the palette can act
on a pad file by name. focusedPad is plumbed onto uiState ahead of the
scratchpad-focus UI work; until that lands it stays empty and the
scratchpad-context entries simply never surface.
Tested with palette_context_test.go and a new rename_process_via_palette
harness scenario.
Bundles the in-flight work into the first tagged release. See
CHANGELOG.md `[0.0.1] - 2026-05-14` for the full per-change list.
Highlights:
- Sidebar / chrome stability: clamp absolute cursor positioning and
printable bytes to the viewport so long-running TUIs (claude, codex)
can't spray into the right rail; bound tab bar's row clear to the
viewport width so the rail isn't wiped on every tab redraw; flag
scroll escapes (RI/IND/NEL/SU/SD/IL/DL) and clamp `CSI 0/1/2 J`/`K`
to viewport columns.
- Palette: "Spawn process…" form, macros (`sw `, `k `, `sp `), kill
entries mark the focused tab, dead agents drop out of the switch
list.
- Sidebar: split into Processes (session-wide) + Agent Tree
(per-active-agent) sections; relaunch indicator; Ctrl+W/S walks the
combined list, Ctrl+A/D steps tabs.
- MCP: protocol handshake (`initialize`, `tools/list`, `tools/call`,
`ping`), `mcp_injection.kind = cli_override / config_env` so codex
and opencode pick up the server with no file writes, `lifecycle`
help topic and tool-description cleanup-duty pointers.
- Lifecycle: orchestrator-spawned children cascade-killed when the
parent dies; orchestrator-injected prompts end with CR + delayed
Enter so claude submits cleanly.
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.
Module renamed github.com/harrybrwn/patterm → github.com/hjbdev/patterm
across imports.
Chrome:
- Palette redrawn with rounded box-drawing borders, accent left-bar
for the selected item, dim hints, and a separator-aware footer.
- Tab bar grew from 1 row to 3: labels with breathing room, a dim
argv subtitle truncated to each tab's width, and an accent thick
underline for the focused tab with a faint divider extending across
the rest of the host width. Layout, viewport-renderer, and screen-
renderer tests updated for the new mainTop.
- Sidebar reuses the same palette: accent section headers, `▎`
selection marker, `●`/`○` status glyphs, dim previews.
- Shared SGR constants moved into internal/app/style.go.
Palette input:
- Adjacent duplicate arrow events (legacy `\x1b[B` + kitty
`\x1b[57353u` for one keypress, or two of the same form) are now
collapsed via peekArrowEvent + chunk-level dedupe in processStdin.
- On open, push `\x1b[>0u` onto the host's kitty keyboard stack so
palette input is in plain legacy mode regardless of what the child
pushed (codex/ratatui pushes its own flags which had been leaking
to the host). Popped on close.
Tab-switch repaint (repaintFocused):
- Use the emulator's SerializeVT bytes (with SGR / cursor / DECSTBM
/ tabstops) instead of plain text, fed through the per-focused
viewport renderer so the shifter translates row positions.
- Prelude resets host SGR / DECOM / DECSTBM (pinned to viewport) /
cursor visibility before the replay, so leftover modes from the
previously-focused child don't distort the new snapshot.
- Re-emit the saved cursor as a child-space CUP after the
serialized bytes so the host cursor lands at the emulator's
actual position (overriding DECSTBM's home side-effect and the
tabstop-setup CHA sequences) AND the renderer's vr.row/vr.col
get re-synced via trackCSI.
- cursorShifter now carries childRows and rewrites empty
`\x1b[r` to `\x1b[<mainTop>;<mainBottom>r` (host coords) — the
default (1,1) shifted to (4,4) was producing a one-row scrolling
region that scroll-exploded the replay.
- After the snapshot lands, nudge the focused child with a one-row
PTY winsize toggle so the kernel emits SIGWINCH and ratatui-style
TUIs throw away their diff state and emit a fresh frame.
Codex still renders incorrectly after a focus switch; see TODO.md
"Switch-back render divergence" for the deep investigation handoff.
Codex (and other ratatui-based children) pushes kitty keyboard flags
onto the host terminal, so Ctrl-K arrives as `\x1b[107;5u` instead of
0x0B and the palette open never fired. With "report event types" also
on, the release event `\x1b[107;5:3u` followed the press and tripped
the palette's "unknown ESC sequence → cancel" branch, making the
palette flash and close.
Add a small CSI scanner / kitty CSI u decoder and use them in two
places: matchCtrlK now accepts the legacy byte, the kitty CSI u form,
and xterm modifyOtherKeys; the palette's input handler consumes whole
CSI sequences, ignores non-press events, and decodes Enter/Esc/
Backspace/arrows/Ctrl-U-N-P in their kitty forms. Ctrl-K Ctrl-K
forwards the raw matched bytes so nested TUIs that asked for kitty
input still receive kitty input.