Live metrics (--profile):
- New metricsTracker instruments OnPTYOut, viewport renderer,
stdout writes, libghostty-vt Write/Title CGO calls, sidebar /
tabbar / status draws (with cache-hit accounting), snapshot
replays, and the chrome ticker (so we can see ticker fires that
did nothing).
- Writes metrics.jsonl (one snapshot per second) and metrics.json
+ summary.txt on exit, alongside the existing pprof files.
- All record* methods are nil-safe so disabled paths pay only a
cheap nil check; counters are atomic so the per-PTY-chunk hot
path stays lock-free.
Benchmark suite (go test -bench=.):
- Three workload fixtures — plain ASCII, SGR-styled lines, and a
ratatui-style cursor-shuffling burst — plus a containsOSC
microbenchmark. Reports ns/op, MB/s, allocs/op, B/op.
- Initial baseline numbers added to TODO under the perf-audit
section, alongside two new findings (renderer allocs ~1 per 4
bytes on styled chunks; styled throughput tops out near
90 MB/s) those benchmarks surfaced.
- Add --debug[=DIR] / --profile[=DIR] flags that write run artefacts
(patterm.log, events.jsonl, per-child raw PTY captures, CPU + heap
+ goroutine pprof) to a dir without polluting stdout/stderr.
- Strengthen vendor-TUI orientation in three places (MCP
initialize.instructions, the spawn_agent tool description, and
help('spawning')) to head off codex's habits of poking the Unix
socket via perl and shelling out to launch peers — both bypass
caller identity and produce orphaned top-level tabs.
- Fix click-and-drag text selection from alt-screen TUIs. Host SGR
mouse reporting now follows the focused child's screen side
instead of being permanently armed; alt-screen TUIs that need
mouse re-enable it themselves and the toggle is forwarded.
- Move drawSidebar() off the per-PTY-chunk hot path. Long claude
session resume was paying a full sidebar rebuild for every
scrolled chunk; the chrome ticker now drains a dirty flag at 60 Hz.
- Gate the per-chunk Title() CGO poll on a containsOSC scan so
codex/ratatui's many SGR-only chunks no longer pay a CGO call each.
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.
Module renamed github.com/harrybrwn/patterm → github.com/hjbdev/patterm
across imports.
Chrome:
- Palette redrawn with rounded box-drawing borders, accent left-bar
for the selected item, dim hints, and a separator-aware footer.
- Tab bar grew from 1 row to 3: labels with breathing room, a dim
argv subtitle truncated to each tab's width, and an accent thick
underline for the focused tab with a faint divider extending across
the rest of the host width. Layout, viewport-renderer, and screen-
renderer tests updated for the new mainTop.
- Sidebar reuses the same palette: accent section headers, `▎`
selection marker, `●`/`○` status glyphs, dim previews.
- Shared SGR constants moved into internal/app/style.go.
Palette input:
- Adjacent duplicate arrow events (legacy `\x1b[B` + kitty
`\x1b[57353u` for one keypress, or two of the same form) are now
collapsed via peekArrowEvent + chunk-level dedupe in processStdin.
- On open, push `\x1b[>0u` onto the host's kitty keyboard stack so
palette input is in plain legacy mode regardless of what the child
pushed (codex/ratatui pushes its own flags which had been leaking
to the host). Popped on close.
Tab-switch repaint (repaintFocused):
- Use the emulator's SerializeVT bytes (with SGR / cursor / DECSTBM
/ tabstops) instead of plain text, fed through the per-focused
viewport renderer so the shifter translates row positions.
- Prelude resets host SGR / DECOM / DECSTBM (pinned to viewport) /
cursor visibility before the replay, so leftover modes from the
previously-focused child don't distort the new snapshot.
- Re-emit the saved cursor as a child-space CUP after the
serialized bytes so the host cursor lands at the emulator's
actual position (overriding DECSTBM's home side-effect and the
tabstop-setup CHA sequences) AND the renderer's vr.row/vr.col
get re-synced via trackCSI.
- cursorShifter now carries childRows and rewrites empty
`\x1b[r` to `\x1b[<mainTop>;<mainBottom>r` (host coords) — the
default (1,1) shifted to (4,4) was producing a one-row scrolling
region that scroll-exploded the replay.
- After the snapshot lands, nudge the focused child with a one-row
PTY winsize toggle so the kernel emits SIGWINCH and ratatui-style
TUIs throw away their diff state and emit a fresh frame.
Codex still renders incorrectly after a focus switch; see TODO.md
"Switch-back render divergence" for the deep investigation handoff.