- Palette's per-child "Kill <name>" action is now labelled "Close <name>" (action kind unchanged; still SIGTERM). Matches the existing "Close agent: …" context entry and reads less violent for a graceful term. - New "New Terminal" palette entry spawns a bare interactive $SHELL pane via LaunchTerminal (kind=terminal). Replaces the default "shell" process preset that was seeded on first run. - Exited KindTerminal entries are now dropped from the session in reapChild — terminals have no restart path, so leaving them behind as greyed rows in the Processes sidebar was just clutter. processList also filters defensively.
413 lines
24 KiB
Markdown
413 lines
24 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
|
||
- "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
|
||
- 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.
|