Files
patterm/CHANGELOG.md
Harry Bayliss 53f06b604f Normalize whitespace in grid get_process_output to save tokens
Grid snapshots pad every row to the full terminal width and leave the
bottom of the screen blank, so MCP grid reads carried a lot of dead
whitespace. Add normalizeGridText (CRLF/lone-CR to LF, right-trim each
line, collapse blank runs to a single blank, drop leading/trailing
blanks) and apply it to the grid branch of GetProcessOutput only.
Stream output, raw output, and WaitForPattern matching are untouched.

Resolves the terminal-read newline/token-waste TODO item.
2026-05-25 12:33:59 +01:00

43 KiB
Raw Blame History

Changelog

All notable changes to patterm are tracked in this file. Format follows Keep a Changelog and the project loosely follows Semantic Versioning.

[Unreleased]

Added

  • MCP clients can now call scratchpad_delete with a scratchpad name to remove a shared project scratchpad.

Changed

  • Grid-mode get_process_output now returns whitespace-normalized text to avoid sending padded terminal rows and repeated blank lines over MCP.

Fixed

  • Closing an agent now escalates from SIGTERM to SIGKILL when needed, so agents that ignore SIGTERM disappear from the running tab bar after one Close action while keeping their exited pane readable.
  • Sidebar timer indicators now repaint as their visible countdown value changes, so labels progress from minutes to seconds without waiting for unrelated terminal output or focus changes.
  • Raw terminal focused actions now show a single Close row instead of separate stop/delete-style lifecycle choices that did the same thing for ephemeral terminal panes.
  • Restarting a process from the palette now restores the focused pane and host chrome before waiting for the old process to exit, so the tab bar and sidebar do not disappear during slow restarts.
  • Deleting the focused scratchpad now moves focus to another scratchpad when one exists, or back to a running terminal/agent instead of dropping into the empty state.
  • Multiline paste into raw terminal and command panes no longer pays the agent-specific per-Enter delay, making large pasted input arrive as one PTY write outside Claude/Codex/OpenCode panes.

[0.0.7] - 2026-05-18

Added

  • The top tab bar now prefixes each agent tab's label with its idle-state glyph (✕ error, ? permission, ◐ thinking, ○ idle, ● working), matching the sidebar's vocabulary so the state of every open agent is visible without opening or focusing each tab.

Changed

  • Built-in agent presets (claude, codex, opencode) now live in memory and user preset files merge over them by name instead of patterm writing default preset files into $XDG_CONFIG_HOME. Add "disabled": true in a matching user preset to hide a built-in.
  • Generated MCP config files for agent launches now live under the runtime agent directory instead of $XDG_CONFIG_HOME/patterm/mcp.
  • Auto-summarization settings now save as soon as a changed row is applied, including cadence/provider/toggle changes and model edits, without requiring a separate save step.
  • The Agents / Auto-summarization settings screen no longer shows explicit Save, Cancel, or Back rows, and its footer copy no longer describes a separate save/cancel flow.
  • Auto-summarization setting rows now visually separate grey labels from regular-colour values.
  • The active-thread summary in the tab bar is now constrained to the active tab's width instead of spanning the whole top row.
  • Sidebar summary text now wraps from the full summary text instead of using an ellipsized single-line value.

Fixed

  • Claude permission prompts are now detected from the rendered pane as well as the recent output tail, so the sidebar marks the pane as waiting for permission even while Calling patterm... continues to repaint.
  • Removed the redundant "Back to Settings" row from the Agents / Auto-summarization settings screen.
  • Pending timer_* entries are now cancelled when their owning or watched child is closed via close_process, preventing stale timer bodies from being re-delivered to the orchestrator pane after the work has already been handled.

[0.0.6] - 2026-05-15

Changed

  • Toast notifications now reserve three content rows and word-wrap the message body inside the box, replacing the previous single-line+ellipsis layout. The Ctrl-N · N more inline hint is gone; instead the host status strip surfaces a Ctrl-N · dismiss hint, shown only while a notification is on screen so the chord doesn't advertise itself when it has nothing to dismiss.

Fixed

  • Auto-summary no longer fails immediately with codex summarizer: error: unexpected argument '--ask-for-approval' found. The codex CLI dropped that flag; we now rely on --sandbox read-only (which already implies no approvals) instead of passing it.
  • Toast box no longer flickers / half-erases while the focused child (claude, codex, opencode, etc.) repaints its TUI. The overlay is now stitched onto the end of the per-chunk PTY write under outMu, and wrapped in DECSET 2026 (synchronized output) brackets so terminals that support it batch the child's redraw + the box paint into a single frame instead of racing cell-by-cell.

[0.0.5] - 2026-05-15

Changed

  • Replaced the single-slot status-line "flash" with a stackable toast surface anchored at the top-right of the focused pane. flashError, flashTransient, and MCP request_human_attention now push onto the toast stack (cap 5, oldest drops). Toasts persist until dismissed with Ctrl-N, or cleared via the new "Clear notifications" palette command. The status line no longer shows the [!] prefix.
  • Ctrl-N is consumed by the host only when there is a toast to dismiss; an empty stack lets Ctrl-N pass through to the focused child so readline / nano / emacs / opencode keep their bindings.
  • Command palette is calmer when something is focused. Focused-section rows now read as bare verbs (Rename, Close, Stop, Restart, Delete, Edit) instead of repeating the focused name (Close agent: codex); the title bar's on: codex / pad: notes.md carries the subject. Fuzzy queries still match the dropped context through the row hint (e.g. typing close codex still finds the Close row).
  • Dashed ── Focused ── / ── Open ── / ── Spawn ── section banners are gone. Sections are separated by a single blank spacer row, so the action labels themselves carry the visual weight.
  • The Open section no longer lists a Switch to <current> row for the pane you're already focused on.

[0.0.4] - 2026-05-15

Changed

  • Release workflow (.gitea/workflows/release.yml) now provisions Zig and Go through jdx/mise-action@v2, reading the versions from .mise.toml (zig 0.15.2, go 1.26.3). Both toolchains were previously installed via mlugg/setup-zig and actions/setup-go, whose mirror chase / GitHub fetch combined for ~8 minutes per run before any patterm code compiled. mise pulls each tool once and caches the install dir, so subsequent runs hit the cache instead of re-downloading. make deps still resolves zig via mise which zig with a PATH fallback; go.mod already pinned go 1.26.3, so the new go entry in .mise.toml just keeps CI and local builds on the same toolchain.
  • A Go module/build cache step (actions/cache@v4, keyed on go.sum) was added so go build doesn't re-download dependencies on every tag push.

[0.0.3] - 2026-05-15

Added

  • Auto-summarization for top-level agent tabs. patterm now loads $XDG_CONFIG_HOME/patterm/settings.json, enables Codex-based summaries by default (gpt-5.4-mini; OpenCode defaults to opencode-go/minimax-m2.7), and can run Codex, OpenCode, or opt-in Claude summarizers with configurable model names. Summary attempts are armed by meaningful human input, wait for recent output to go quiet, and respect a minimum cadence so unchanged tabs are not summarized on a timer. The active thread summary appears under the top tab title and in the sidebar below the Agent Tree section.
  • Settings overlay reachable from the command palette via Open Settings. The searchable Settings picker opens Agents / Auto-summarization, where users can enable/disable summaries, choose provider, edit provider model names, cycle cadence, test the selected summarizer (patterm okay), summarize the current top-level agent immediately, and explicitly save or cancel draft settings changes. Cadence choices match Solo: 15s, 30s, and 1m; the value is a minimum quiet/activity gap before another summary attempt for the same top-level agent, not a background periodic timer.

Changed

  • Command palette UX overhaul. The single flat list grew section bands (── Focused ──, ── Open ──, ── Spawn ──, ── Quit ──) so the rows are scannable at a glance; cursor navigation skips the dim header rows transparently. A chip strip — [All] Open Spawn Close — sits below the query line and tracks the active macro filter; Tab / Shift-Tab cycle through the chips, and the typed-prefix macros (sw , sp , k ) still work and now collapse the whole prefix on a single backspace instead of leaving a stray sw behind. The title bar surfaces the current focus subject (on: <child> / pad: <name>) so the user knows which Focused row is targeting what. The duplicate global Close list is gone — close is reachable via the Focused-section action, the k macro / [Close] chip, or the new Ctrl-X inline close on a Switch row. The "(current)" marker on the focused Switch row became a leading . The empty-state hint now reads no matches · ⌫ to widen instead of bare no matches. The middle divider shows a ▼ N more / ▲ N above scroll indicator when the list overflows, and the footer carries a cursor/total counter.
  • Spawn verbs are unified on Spawn: Run process: …Spawn process: …, New TerminalSpawn terminal, and the freeform-form row is now Spawn process… (custom) so the trailing ellipsis still signals it opens a form.
  • Filtering switched from binary fuzzy-include to scored ranking. Prefix matches beat word-boundary matches beat substring matches beat scattered-fuzzy matches; ties fall back to section order so a Focused-section hit always outranks an equally tight Spawn hit. The matched characters in the rendered label render in accent+bold so the user can see why a row matched.
  • Rename forms split the long subject (scratchpad: some-really-long-name.md) onto its own dim row above the input so the title bar no longer truncates with an ellipsis when the subject name is wide.
  • New palette accelerators: Alt-1Alt-9 quick-pick the Nth visible row, Home / End jump to first / last selectable row, ? (with empty query) opens an inline keybinding cheat-sheet which any further keystroke dismisses, and Ctrl-R inside the Spawn-process form toggles "Relaunch on exit" without leaving the command field.

Fixed

  • Error/status flashes now restore the currently focused pane instead of drawing the empty-state hint over a running agent or process.
  • Release workflow (.gitea/workflows/release.yml) now uses mlugg/setup-zig@v2 instead of the deprecated @v1. v1 hard-coded the pre-0.14 tarball name (zig-linux-x86_64-<ver>.tar.xz), so every mirror and the official ziglang.org/builds returned 404 for Zig 0.15.2 and the v0.0.1 / v0.0.2 tag pushes never produced a release asset. v2 uses the post-0.14 zig-x86_64-linux-<ver>.tar.xz layout, so the runner can fetch Zig and build patterm.
  • Typing into a focused child while its emulator viewport is scrolled up into scrollback history now auto-snaps the viewport back to the live area. Previously the keystroke reached the child PTY but the input box was off-screen below the visible region, so it looked like typing did nothing. Wheel scrolling and Ctrl-B are unchanged; only forwarded keystrokes snap.
  • Top tab bar now keeps the top-level agent's tab highlighted when focus is on one of its sub-agents (or on a Processes pane entry, matching the existing agent-tree behavior). Previously the tab would lose its highlight as soon as you stepped into a child agent, even though you were still within that thread.

Changed

  • MCP tool descriptions and help('coordination') / help('readiness') now spell out that a sub-agent's reply to send_message lands in the caller's own pane (tagged [sub-agent:<name>]), not in the sub-agent's output. The canonical wait-for-reply pattern — send_messagetimer_fire_when_idle_any on the sub-agent → read your own pane — is now called out on send_message, wait_for_pattern, both timer_fire_when_idle_*, the help topics, and the server-instructions preamble every agent reads at startup. Previously wait_for_pattern was the obvious blocking primitive in the catalog, and agents routinely called it against the sub-agent for a reply that had already arrived in their own pane, deadlocking until the wait timed out. No behaviour changes; descriptions only.
  • Agent-initiated spawn_agent and spawn_process MCP calls no longer steal viewport focus from the currently active tab. The new child still appears in the sidebar and tab bar; switch to it explicitly via the palette or select_process. Palette-initiated spawns and persistence restores are unchanged — they still auto- focus the new pane.
  • Sidebar rows (Processes, Agent Tree, Scratchpads) now truncate overflowing names with a trailing instead of spilling into the main viewport. The focused row marquees its name when it overflows — 1 s hold on the head, ~150 ms per cell scroll until the tail is visible, 1 s hold on the tail, snap back. Row position never moves while the marquee animates. When budget is tight, the trailing timer indicator drops before the name ellipses, since the name is the only identifier the row carries.

[0.0.2] - 2026-05-15

Added

  • .mise.toml pinning zig = "0.15.2" (the minimum version the vendored Ghostty commit requires). Contributors run mise install once; the Makefile picks up the resulting zig binary automatically via mise which zig and falls back to PATH when mise isn't available, so the existing build flow still works.
  • ASCII-video stress benchmarks (internal/app/bench_test.go): per-frame and per-stream variants at 30 / 60 / 120 fps targets, three workload fixtures (8-colour cells, 24-bit truecolor cells, and a Bad-Apple-style 1-bit pattern). Each stream benchmark reports µs/frame, an achievable fps_ceiling, and budget_pct so you can read off "do we hit N fps?" directly. A matching Pipeline_ASCIIVideo_* set includes libghostty-vt's em.Write CGO and an io.Discard stdout write so the FPS claim reflects the whole pipeline, not just the renderer.
  • MCP initialize.instructions, the spawn_agent tool description (visible to LLMs via tools/list), and the help('spawning') topic now spell out — in the three places vendor TUIs actually consult — that the connected patterm MCP server is the only correct way to drive the host. Anti-patterns called out by name: (a) trying to launch patterm / patterm mcp-stdio themselves, (b) piping JSON-RPC into the per-PID Unix socket via perl / nc / socat / curl, and (c) shelling out to claude / codex / opencode to start a peer. Each of those bypasses caller identity, so a sub-agent spawned that way reads back as a stray top-level tab instead of a child under the spawning agent. Codex was hitting (b) and (c) in practice — this is the fix.
  • --debug[=DIR] flag captures detailed run artefacts for offline analysis: a verbose patterm.log (the existing PATTERM_DEBUG_LOG stream), an events.jsonl lifecycle log (spawn / exit / idle-state changes with timestamps), and per-child <id>.raw files containing the raw PTY byte stream. With no argument, the dated subdir $XDG_STATE_HOME/patterm/debug/YYYYMMDD-HHMMSS is used; pass an explicit path to override. All output goes to files — stdout/stderr are untouched.
  • --profile[=DIR] flag captures pprof data plus concrete performance counters for performance work: cpu.pprof (running for the lifetime of the session), plus heap.pprof and goroutine.pprof snapshots written on shutdown; alongside them, a per-hot-path metrics tracker writes metrics.jsonl (one row per second with chunk/byte rates, per-stage mean and max latencies, and cache hit rates) plus a final metrics.json aggregate and a human-readable summary.txt on exit. Instrumented hot paths: OnPTYOut, viewport renderer.Render, host stdout writes, libghostty-vt emulator.Write / Title, sidebar / tab bar / status line draws (with cache-hit accounting), snapshot replays, and the chrome ticker (so you can see how often it fires with nothing to do). Defaults to $XDG_STATE_HOME/patterm/profile/YYYYMMDD-HHMMSS. All diagnostics (startup, errors) are written to profile.log inside the dir, never to stdout/stderr.
  • Renderer benchmark suite (internal/app/bench_test.go). Three workload fixtures — plain ASCII, SGR-styled lines, and a ratatui-style cursor-shuffling burst — plus an OSC-gate micro-benchmark. Run via go test -bench=. -benchmem ./internal/app/. Gives a stable reference for the per-chunk cost of the viewport renderer so future changes can be compared apples-to-apples.
  • "New Terminal" entry in the command palette spawns a bare interactive $SHELL pane (kind terminal). Unlike "Run process: …" presets, which are session-persistent and reachable via restart_process, terminals are ephemeral — once they exit they vanish from the Processes sidebar instead of lingering as a dead row. The default shell process preset that previously seeded on first run has been removed; this entry replaces it.
  • Per-child idle-state classifier with five states (idle, working, thinking, permission, error) and three pluggable strategies: output_activity (claude / opencode defaults), osc_title_stability (codex), and osc_title_status (gemini-style status-in-title agents). Optional permission_patterns / thinking_patterns / error_patterns regexes promote a base state when matched against the tail of recent output. State and last-match reason are exposed via MCP on ProcessInfo and get_process_status (idle_state, idle_reason).
  • New idle_detection block on preset.Preset for setting the strategy threshold, title-to-state map, and promoter regex lists. Bundled defaults are shipped for the first-party claude / codex / opencode presets.
  • Sidebar now renders a state glyph per process row ( idle, working, thinking, ? permission, error) and, when a process has a pending or paused timer, appends a nearest-timer indicator (⏱ 12s or ⏸ paused).
  • MCP timer surface expanded to match Solo's tool set: timer_set, timer_fire_when_idle_any, timer_fire_when_idle_all, timer_cancel, timer_pause, timer_resume, timer_list. Idle-aware timers registered against already-idle children fire synchronously (status: already_satisfied) for idle_all, and report already_idle / waiting_on arrays so callers can introspect the watch set. Timer bodies are delivered to the owner process via the same orchestrator-injection path as send_message.
  • Timer tools accept an explicit owner_process_id so top-level (non-agent) callers — including the harness MCP client — can attribute timers to a specific process. Omitting it treats the caller as the orchestrator with universal cancel / pause / resume / list privileges.
  • libghostty-vt Title() accessor on the emulator surface, polled from the session pump so OSC 0/1/2 title updates feed into the classifier without a callback round-trip.
  • Harness wait_until_mcp step type that re-runs an MCP method until an assertion (Equals / Contains) holds or the timeout elapses. Used by the new idle / timer scenarios.
  • User-created top-level command processes now survive a patterm restart. Each spawn (palette form, command preset, or MCP spawn_process with kind=command) writes a record to $XDG_DATA_HOME/patterm/projects/<key>/processes.json; on next startup patterm replays those entries before the UI accepts input, so things like bun run dev or tail -F log come back without re-typing. close_process (and the palette's close action) drops the entry, and rename / "relaunch on exit" toggles are mirrored as they happen. Agents and terminals stay ephemeral by design.
  • The command palette (Ctrl-K) now surfaces context-aware actions at the top of the list, based on what's currently focused:
    • Scratchpad in focus: Delete, Rename (inline form), and Edit (fire-and-forget launch of zed against the pad file).
    • Agent in focus: Rename agent (inline form) and Close agent.
    • Process in focus: Rename process, Delete process (drops the entry; SIGKILLs if alive), Stop process (SIGTERM, keep entry), and Restart process (same argv).
  • patterm --version prints the build version, git commit, and build date (e.g. patterm v0.0.1 (commit abc1234, built 2026-05-14)). The version string is injected by the build (make patterm derives it from git describe; the release workflow injects the pushed tag). Commit and date come from the Go toolchain's embedded VCS info, so nothing has to be bumped by hand.
  • Ctrl+R restarts the focused command process from the Processes sidebar, including command entries that have already exited.
  • Scratchpads are now first-class navigation targets. Ctrl+W / Ctrl+S step from the Processes section and agent tree onto scratchpad entries; a focused scratchpad renders its content in the main viewport (with the pad name as the header) instead of cramping the bottom of the sidebar. The sidebar's scratchpad section is a names- only list with the focused pad highlighted, and external MCP scratchpad_write / scratchpad_append updates repaint the pad view immediately.
  • Focused scratchpads now render as markdown — headings, bold, inline code, fenced code blocks, bullet/numbered lists, blockquotes, and horizontal rules pick up styling instead of the previous flat word-wrap. Long pads scroll: the mouse wheel is the primary control (patterm enables SGR mouse reporting while a pad is focused), and Up/Down / PageUp/PageDown / Home / End work for keyboard users. The header reports the visible row range and total row count. Esc leaves the pad view and falls back to the first running process (or an empty viewport). The scroll offset is preserved across MCP scratchpad_write / scratchpad_append writes so a live update doesn't snap the view back to the top.
  • Inline wheel scrollback for the focused child, backed by libghostty-vt's native 5000-row scrollback history. On the primary screen, mouse-wheel events scroll the emulator viewport in-place with full SGR styling preserved — no modal view to enter or exit. On the alternate screen wheel events still pass through to the child so vim / less / codex receive them as input. Ctrl+B snaps the viewport back to the live (bottom) area as the escape hatch from a scrolled-up state. Patterm now keeps SGR mouse reporting armed on the host terminal while the alt screen is active and filters mouse-mode toggles from the child stream so wheel events keep arriving even after a child program disables mouse tracking.

Changed

  • The palette's per-child "Kill " action is now labelled "Close ". The underlying signal (SIGTERM) and behaviour are unchanged; the new label matches the existing "Close agent: …" context entry and reads less violent for what is really just a graceful termination.
  • timer_wait is now a thin wrapper over the shared timer manager (timer_set semantics). Existing callers see no behavioural change; the timer is visible in timer_list while it's pending.
  • CLI flag parsing switched from Go's stdlib flag to spf13/pflag. --project (and the internal --socket / --identity / --scenario / --patterm-bin flags) are now the only accepted form — single-hyphen long flags like -project are rejected. Help output renders the canonical --flag form.

Fixed

  • make deps now builds libghostty-vt with -Doptimize=ReleaseFast instead of zig's silent Debug default, and resolves zig through mise when a project .mise.toml pins it. The default-Debug build shipped an unoptimised CSI/SGR parser that ate 16-29 ms per 30-70 KiB full-screen frame in benchmarks, capping the entire PTY-to-host pipeline at 34-63 fps. After the rebuild the same pipeline runs at 930-2030 fps: 27-32× the prior throughput, and 7-16× margin over 120 fps for full-screen truecolor ASCII video. Static library size drops from 33 MiB to 13 MiB. Override with make deps GHOSTTY_VT_OPTIMIZE=Debug only when debugging the upstream library itself. Apply on existing checkouts with mise install && make clean-deps && make deps.
  • Long claude session resume (and codex steady-state rendering) is noticeably faster. Two costs that scaled per-PTY-chunk are now deferred or short-circuited: (1) drawSidebar() used to run synchronously for every chunk that scrolled — on a session resume where every chunk scrolls, this rebuilt the full sidebar string hundreds of times for a frame that was almost always cache-equal. The sidebar now signals dirty and the chrome ticker (60 Hz) handles the repaint. (2) pumpChild polled the emulator's OSC title after every PTY chunk via CGO, even for chunks (the common case under codex/ratatui) that carry no OSC bytes at all. The poll is now gated on a containsOSC scan over the chunk.
  • Click-and-drag text selection from alt-screen TUIs (codex in particular) now works. Patterm used to keep host SGR mouse reporting armed continuously, which forced the host terminal to forward every click as an escape sequence and prevented native selection. The host's mouse mode now follows the focused child's screen side: primary-screen children keep mouse armed (so wheel scrollback works), alt-screen children get host mouse disabled by default. Alt-screen TUIs that need mouse events (vim, less, etc.) re-enable mouse-mode themselves; the viewport renderer forwards those toggles to the host while the child is on alt. Leaving alt re-arms host mouse reporting so wheel scrollback resumes.
  • Exited terminal panes (kind terminal, including those launched via the new "New Terminal" palette entry or MCP spawn_process with kind=terminal) are now removed from the session and the Processes sidebar as soon as they exit. Previously they stuck around as a greyed-out row indistinguishable from an exited command process, even though terminals have no restart path.
  • whoami and help("timers") now advertise the full Solo-parity timer surface (timer_set, timer_fire_when_idle_any, timer_fire_when_idle_all, timer_cancel, timer_pause, timer_resume, timer_list) so agents using either tool for orientation discover them — previously only timer_wait was listed.
  • Resuming a paused idle-aware timer now re-checks the satisfaction condition. Previously, if every watched process became idle (or, for idle_any, any non-baseline watcher went idle) while the timer was paused, the timer stayed pending forever because no further state transitions were observed.
  • Fired and canceled timers are now removed from the timer registry, so long-running patterm sessions no longer accumulate completed timer records and message bodies. timer_list and the sidebar indicator already filtered them out; only the in-memory leak is fixed.
  • Per-preset idle-detection config is now installed through SpawnSpec before the child is published to the session, closing a race in which the classifier goroutine could observe a freshly spawned process before its preset's classifier strategy was attached.
  • Opening the command palette while a scratchpad was focused left the palette wedged — typing did nothing and Esc left the palette's top border drawn over the pad until you closed the pad with Ctrl-W and re-opened the palette. The stdin loop's scratchpad-input branch ran before the palette branch and silently dropped every byte except a handful of app-level chords, so palette filter input and Esc never reached palette.handleInput. The palette branch now takes precedence whenever the palette is open, and closePalette repaints the pad (instead of the empty focused-child slot) on cancel / no-op action. Switching from a pad to a child via the palette now clears the pad focus and wipes the viewport, matching focusProcess.
  • Tab bar and bottom status row no longer get overwritten by long claude / codex sessions. Three holes were letting child output land on the chrome: (1) absolute cursor moves — CUP / HVP / VPA — added the row offset but didn't clamp to the viewport, so a child whose internal row state drifted past its assigned height could walk the host cursor onto the status row (or above the tab bar); (2) relative cursor moves — CUU / CUD / CNL / CPL — were forwarded verbatim, so a CSI 50 A from viewport row 1 walked the host cursor into the tab bar before the next printable wiped it; (3) the host's DECSTBM scroll region was only set during snapshot-replay preludes, so the windows between (startup before first focus, post-SIGWINCH, post-clearScreen) left the region defaulted to the full screen and any LF / IND / NEL / RI / SU / SD at the viewport bottom scrolled the chrome rows along with the pane. The cursor shifter now clamps CUP/HVP/VPA rows to mainTop..mainBottom, the viewport renderer rewrites CUU/CUD/CNL/CPL with a clamped step (and homes the column for CNL/CPL), and patterm installs the host scroll region after enterScreen and after every clearScreen (and resets it cleanly on leaveScreen so the calling shell isn't left with a constrained region).
  • Plain line-feed scrolling at the bottom of a child pane now invalidates and repaints the sidebar, so long agent output can no longer drag the sidebar border and labels out of view while the chrome cache stays warm.
  • Child DEC origin-mode and left/right-margin controls are now handled inside the viewport renderer instead of being forwarded to the host terminal, so later tab bar, sidebar, and status-line repaints keep using physical screen coordinates and do not appear inside the focused pane.
  • Exited command processes in the top Processes section are now reachable with Ctrl+W/S navigation, so a dead shell entry can be focused and restarted instead of becoming a visible but unreachable row.
  • Resizing the host terminal no longer makes codex (and other diff-based TUIs) scroll-jump for several seconds. SIGWINCH is now coalesced into a single resize after an ~80ms idle, the resize path skips the full snapshot replay (the child's own SIGWINCH-driven redraw fills the viewport), and Child.NudgeRedraw no longer toggles the PTY through rows-1 + rows back-to-back during a drag-resize.
  • Steady-state CPU during a long codex session dropped sharply. Tab-bar and status-line repaints moved off the per-PTY-chunk path to a 16ms chrome ticker; the scratchpad listing is cached and only rebuilt when the pads change; the post-spawn / post-repaint styled-snapshot replay budget dropped from 8 chunks to 2; URL/port scanning short-circuits chunks that don't contain "http"; the three writes around the autowrap toggle in OnPTYOut collapsed into one syscall; the per-PTY-read make+copy was removed (the 64 KiB read buffer is reused, with a documented "do not retain" listener contract); session listeners now dispatch through an atomic.Pointer snapshot instead of a mutex copy on every chunk; the per-child output ring is a true wrap-around buffer instead of a slide-and-trim slice; wait_for_pattern wakes on PTY chunk events with a 500ms fallback instead of unconditional 50ms polling; ANSI stripping in MCP get_process_output stream, search_output, and wait_for_pattern scrollback is now an in-place byte walk; and the viewport renderer copies long ASCII runs en bloc instead of feeding the state machine one byte at a time.

[0.0.1] - 2026-05-14

Fixed

  • Tab bar redraw used \x1b[2K to clear rows 1 and 2 before painting labels, which wiped the sidebar columns on those rows too. When the sidebar cache was still warm the rail never repainted, leaving a gap where the sidebar's top border and "Processes" header should be. The clear is now bounded to the viewport width.
  • Long-running TUIs (claude / codex) whose internal column state drifted past the patterm viewport could spray text into the sidebar columns — overwriting the session-tree and scratchpad rail until the user opened/closed the palette to force a full repaint. The viewport renderer now clamps absolute cursor positioning (CUP / HVP / CHA / HPA) to the viewport's right edge and drops printable bytes (ASCII and full UTF-8 glyphs) that would otherwise land past it. Covered by a unit suite and a new sidebar_survives_wide_writes harness scenario.
  • Sub-agent panes spawned while another diff-based TUI (claude/codex/ opencode) held focus could come up corrupted because the new child's first incremental updates targeted cells the host viewport hadn't populated yet. OnChildSpawned now primes the snapshot-replay budget for the new child, so its first PTY chunks render from the full styled emulator grid — the same path that fixed the symptom when the user manually cycled focus with Ctrl+W / Ctrl+S.

Changed

  • Command palette Kill … entries now mark the focused tab with the same "• … (current)" marker the Switch to … entries use, so the user can tell at a glance which tab a kill action targets.
  • Status line now advertises the navigation chords (Ctrl-A/D · tabs, Ctrl-W/S · tree) alongside Ctrl-K · palette. Hints decay shortest-first when the terminal is too narrow to fit all three.

Added

  • "Spawn process…" entry in the command palette opens a two-field form for typing an arbitrary command line and ticking "Relaunch on exit". The command runs through sh -lc so multi-word lines like bun run dev resolve binaries the way an interactive shell would. When the relaunch flag is set, patterm Starts the process again after it exits (1s backoff). Killing the process from the palette clears the flag so it does not come back.
  • Dedicated "Processes" section in the sidebar above the agent tree, listing every top-level command/terminal process. It is global to the patterm session — switching between agent tabs no longer changes which processes are visible. The relaunch-on-exit indicator () shows next to processes the user opted into auto-restart for.
  • Ctrl+W / Ctrl+S now traverse the combined Processes section and the active agent tree as one flat list, so the user can step out of the agent tree into the Processes pane and back without leaving the keyboard.
  • New lifecycle help topic spelling out that the caller owns the processes it spawns and should call close_process when a sub-agent or spawned process is no longer needed. The spawn_agent and spawn_process tool descriptions advertised via tools/list now call out the same cleanup duty (with a pointer to help('lifecycle')), and the spawning help topic and topics index cross-reference it.
  • spawn_agent now prepends a single-line [system: …] orientation block to the sub-agent's first prompt so vendor TUIs (claude, codex, opencode) know they're a sub-agent, are reminded to send_message their parent with a summary when done, and to clean up processes / scratchpads they created. Skipped for top-level launches and empty instructions.
  • Ctrl+A / Ctrl+D step focus between top-level tabs; Ctrl+W / Ctrl+S step through processes (root + sub-agents) inside the current tab. Recognised in legacy, kitty CSI u, and xterm modifyOtherKeys encodings. The chord shadows the corresponding raw byte for the focused pane — pressing Ctrl+D no longer sends EOF to the underlying shell, for instance.
  • MCP protocol layer (internal/mcp/protocol.go) implementing initialize, tools/list, tools/call, ping, and MCP notifications. Tool catalog with input schemas is advertised via tools/list. Real MCP clients (claude, etc.) can now complete the handshake against patterm's per-PID socket. Legacy direct-tool dispatch is preserved so the harness keeps working unchanged.
  • mcp_injection.kind = "cli_override" for agents that accept inline key=value config overrides on the command line. The default codex preset uses it to emit -c mcp_servers.patterm.command=… and -c mcp_servers.patterm.args=[…] — zero files written, no CODEX_HOME override.
  • mcp_injection.kind = "config_env" for agents that read their config from an env var. The default opencode preset uses it to pass a merged opencode.json inline through OPENCODE_CONFIG_CONTENT, so auth/agents/tui.json/skills resolve from the user's real $HOME with no XDG override.
  • Palette macros: typing sw , k , or sp filters the list to switch / kill / spawn entries respectively. Footer shows the available macros.

Changed

  • The sidebar's session-tree section is now labeled "Agent Tree" and shows only agent sessions (and any sub-agents they spawn). Top-level command and terminal processes live in the new "Processes" section above it.
  • Tab bar tabs now correspond to agent sessions only. Command/terminal processes that previously claimed a top-level tab now appear in the Processes sidebar section, so the tab strip is reserved for agent context.
  • Focus, lifecycle, and repaint paths now capture terminal layout before taking UI state locks, reducing resize-time deadlock risk without changing visible behavior.
  • Focused PTY output no longer rebuilds the scratchpad sidebar on every chunk. The sidebar still repaints on focus/lifecycle/resize changes and when child output scrolls over the chrome, but normal output avoids repeated scratchpad disk reads.
  • Harness scenario tests now reuse one built patterm binary per test run and write failure artifacts under a repo-rooted, collision-proof directory.
  • Palette ordering: open agents/processes (Switch to …) now appear above the option to spawn new ones, with kill entries pushed down toward the end of the list.
  • Tab bar trimmed from three rows (label / subtitle / underline) to two (label / underline). Tabs flex to fill the available host width evenly with leftover columns distributed to the leftmost tabs; the + new hint sits in a reserved slot on the right. Layout's mainTop consequently drops from 4 to 3, giving each pane one extra row of viewport.

Fixed

  • Agent MCP injection no longer writes unused config files for inline injection modes (cli_override / config_env). File-backed injection modes track their generated paths and clean them up when the child is closed, exits, or patterm shuts down.
  • MCP tools/list descriptions now match the runtime argument values for process output and pattern waiting, and typed invalid-argument errors map to JSON-RPC invalid params instead of generic internal errors.
  • Scratchpad writes and appends are serialized inside a patterm process so expected_revision checks cannot race another local scratchpad mutation.
  • The sidebar scratchpad list now refreshes after MCP scratchpad_write and scratchpad_append calls.
  • UI chrome now reads renamed child display names through the DisplayName accessor, avoiding races with rename_process.
  • Child processes spawned by an orchestrator are now killed when the orchestrator dies, recursively through the tree. Applies whether the parent was closed via MCP, Ctrl-C'd by the user, or exited on its own — reapChild cascades a SIGTERM (escalating to SIGKILL after 2s) to every direct child, and each descendant's own reap fires the same cascade so the kill flows through arbitrary depth.
  • Killed agents no longer linger in the command palette. Agent entries that aren't running are filtered out of the switch list; session-persistent commands (which can be restarted) stay visible.
  • tools/list now emits a concrete properties object ({}) for parameterless tools instead of null. Claude rejected the null-properties form with "tools fetch failed" even though the initialize handshake had succeeded.
  • Sidebar no longer flickers on every PTY chunk. The tab bar, sidebar, and status line now cache their last rendered byte string and skip the write when the new frame matches; full repaint paths (resize / focus change / palette close / screen clear) invalidate the cache so the next draw fires unconditionally.
  • Spawning a child agent now clears the viewport area before it paints, so the previous focused child's PTY output no longer bleeds through underneath the new pane.
  • Orchestrator-injected input (initial agent prompts, MCP send_input with submit: true, send_message, timer_wait callbacks) now ends with CR (\r) instead of LF (\n). Claude treated \n as "newline in textarea"; with CR the prompt actually submits, matching what the host terminal sends when a user presses Enter directly.
  • Enter is now written to a child PTY as its own write() call, separated from the preceding text by a short delay. Both InjectAsUser (user typing forwarded through patterm) and InjectAsOrchestrator (MCP / send_message / initial-prompt paths) share the split. Without it, claude — and other paste-detecting TUIs — coalesced "hello\r" into one read and inserted the CR as literal text instead of treating it as the Enter keystroke.
  • Sidebar (and tab bar) no longer get wiped when the focused child issues CSI 0 J / CSI 1 J (clear-to-cursor). The viewport renderer already clamped CSI 2 J and CSI K to viewport columns, but the partial-screen variants were forwarded verbatim, so any tool-call expansion in claude (Ctrl+O) would erase every cell to the right of the cursor — including the right rail. Both forms are now translated into per-row ECH sequences that stop at the viewport's right edge.
  • Sidebar left border no longer vanishes when the viewport repaints. The border column was the same column as the viewport's rightmost cell, so any child write to that column (or clearViewport's ECH) would erase it. The viewport is now one column narrower so the border has a dedicated column.
  • Sidebar session-tree entries no longer get pushed downward when an agent emits a scroll burst on startup. Codex (Ratatui) issues 8× RI (\x1bM) right after setting its scroll region, which scrolls the region down across every column — dragging the right-rail entries with it. The chrome cache then hid the clobber because the computed frame still matched. The viewport renderer now flags any chunk that contains a scroll-triggering escape (RI / IND / NEL / SU / SD / IL / DL) and OnPTYOut drops the sidebar cache when the flag is set, so the next drawSidebar repaints over the drift.

Conventions

  • This file is the single record of user-visible changes; the TODO is scratch space, not history.
  • One bullet per change, written in the past tense from the user's point of view. Reference the package or preset name when it helps a reader find the code.