Switches CLI flag parsing from Go's stdlib `flag` to spf13/pflag so `--project` (and the internal `--socket` / `--identity` / `--scenario` flags) are the only accepted form; single-hyphen long flags like `-project` are now rejected. Help output renders the canonical `--` form. Adds `patterm --version`, which prints the build version, short commit, and build date (e.g. `patterm v0.0.1 (commit abc1234, built 2026-05-14)`). The version string is injected at build time — `make patterm` derives it from `git describe --tags --always --dirty`, and the release workflow injects the pushed tag. Commit/date come from the Go toolchain's embedded VCS info via `runtime/debug.ReadBuildInfo`, so no manual bumping is required.
220 lines
12 KiB
Markdown
220 lines
12 KiB
Markdown
# 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
|
||
- `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.
|
||
|
||
### Changed
|
||
- 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.
|
||
|
||
## [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.
|