Files
patterm/CHANGELOG.md

772 lines
44 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to patterm are tracked in this file. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- MCP clients can now call `scratchpad_delete` with a scratchpad name
to remove a shared project scratchpad.
### Changed
- The tab bar now shows each visible agent tab's own summary instead
of only rendering the focused tab's summary.
- `get_process_output` now returns aggressively canonical terminal text
by default, removing ANSI/control noise, decorative borders, duplicate
status churn, and volatile progress/timer fragments; raw PTY bytes are
opt-in with `raw:true`.
- MCP responses now use slimmer defaults: tool-call JSON is no longer
duplicated into text content, large output and scratchpad reads are
capped with truncation metadata, and `whoami` / `get_project_status`
only include full tool lists when `include_tools` is requested.
### Fixed
- Injected agent input now sends the submit Enter as a separated,
settled keystroke so messages reliably submit instead of sometimes
sitting unsent in the composer.
- Codex agents are no longer reported idle while a turn is still
running.
- Slow MCP tool calls such as `wait_for_pattern` no longer block later
tool calls on the same MCP connection.
- 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 Terminal` → `Spawn 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-1` … `Alt-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_message` → `timer_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 <name>" action is now labelled
"Close <name>". 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.