The earlier guard only suppressed throttled `requestRender(false)`; forced repaints (e.g. `#finishSixelProbe`, image-budget eviction, `resetDisplay`) bypassed it through `requestRender(true)` or the direct `#doRender` path and could still paint into a still-reflowing pane inside the 50 ms settle window — recreating the flash race.
Extracted `#armMultiplexerResizeTimer(clearScrollback)` as the single arm-or-extend point used by the SIGWINCH callback, the deferral branch in `requestRender(true)`, and `resetDisplay()`. Each call cancels any queued throttled render, ORs the callers `clearScrollback` intent into `#deferredForcedClearScrollback`, and re-arms the timer. When the timer fires it consumes the flag and re-enters `requestRender(true, { clearScrollback })`; clearing `#multiplexerResizeTimer` before re-entry lets the deferred call pass straight to `#prepareForcedRender`. `stop()` clears the flag.
Added regression tests for forced `requestRender(true)` and `resetDisplay()` landing inside the debounce window.
A streamed-token tick that landed `requestRender(false)` in the same 30fps frame as the SIGWINCH left `#renderTimer` armed, so the throttled render could fire inside the 50 ms settle window and paint at the new geometry while tmux was still reflowing — the same race the debounce is meant to avoid. The immediate-force path used to clear that timer through `#prepareForcedRender`; the debounce path now does the same when arming, and `#scheduleRender` short-circuits while the multiplexer timer is active so any follow-on `requestRender(false)` is held off until the eventual forced render. Added a regression test covering a queued render + SIGWINCH in the same frame.
- Handled SGR mouse motion reports in `PlanReviewOverlay` to set hovered options from hit-test rows and clear highlights outside option rows.
- Updated option rendering to paint a background band on hovered, non-disabled options while preserving keyboard selection behavior.
- Enabled any-motion mouse tracking for overlays and expanded regression coverage to verify hover tracking setup and teardown.
Tmux/screen/zellij send SIGWINCH while the pane is still mid-reflow and fire several events during drag-resize or pane-close animations. Forcing an immediate render on each event raced those mid-reflow paints — the multiplexer overwrote the TUI output and the user saw the viewport flash blank before the next throttled frame.
The SIGWINCH callback now debounces inside multiplexer sessions through a 50 ms render-scheduler timer; subsequent events cancel and re-arm it, so a single forced render fires at the final geometry once the pane is quiet. `#resizeEventPending` is still set on every event so the eventual render classifies as a resize, and stop() cancels the pending timer.
Fixes#2088
- Reframed the plan artifact as a self-contained execution spec a fresh agent runs after context is cleared.
- Folded high-consensus requirements (sequencing, contracts, cutover, verification) into existing sections as inline rules.
- Banned decision-free sections and conversation back-references; kept the decision-complete self-check and completeness-wins tiebreak.
- Added a reassembly buffer for DEC 2048 in-band resize reports split across stdin reads.
- Dropped invalid or stale partial resize sequences to prevent leaked trailing bytes from entering terminal input.
- Added tests that verify split resize reports are handled and split kitty key sequences are forwarded correctly.
- Tracked focus transitions in TUI and flagged renders after focus changes to allow unknown viewport mutations.
- Used the new focus-change flag when deciding explicit viewport mutation so subsequent frames repaint even when the terminal lacks a viewport-oracle position.
- Added a regression test covering menu teardown focus changes on unknown-viewport terminals with eager scrollback risk.
- Added reconstructed SSE event emission for OpenAI, Azure, and Anthropic streams.
- Added raw SSE text to debug report bundles, including raw-sse.txt output.
- Included dropped-record metadata in raw SSE text when events were trimmed.
- Updated raw SSE and sse-debug tests for observer-based capture and safety checks.
- Built canonical and alias key maps for action/custom handlers and rebuilt them on changes.
- Preserved the first handler when custom key alias collisions occurred during rebuilds.
- Exported canonicalKeyId and addKeyAliases from keybindings for external use.
- Added hardware cursor state caching to reuse last known position and visibility.
- Updated cursor flow to preserve target row/column/visibility across rerenders.
- Reworked cursor output emission to skip unchanged cursor moves and hide/show writes.
- Replaced the TUI row truncation path with per-character and ANSI-sequence iteration that tracks visible cells before clipping output.
- Added ANSI helpers to parse sequence boundaries and preserve OSC66 visible payloads while enforcing max source length during truncation.
- Added natives regression coverage for Ghostty super+alt backspace key matching and parsing.
- Switched long-row truncation to visible-cell width, preserving suffixes after zero-width prefixes.
- Added issue-2045 regression test in issue-2045-repro.test.ts for combining-prefix truncation.
Ghostty on macOS reports Option+Backspace as kitty modifier 11
(wire) = 10 (mask) = super(8) | alt(2). The native key matcher only
recognised shift/ctrl/alt, so:
- format_kitty_key in crates/pi-natives/src/keys.rs rejected any
sequence whose effective modifier had bits beyond shift/ctrl/alt
set; parseKey returned undefined for \x1b[127;11u.
- parse_key_id recognised only those three modifiers, so even an
explicit 'super+alt+backspace' degraded to 'alt+backspace' and
could not match an actual super+alt encoded press.
- The editor's matchesKey(data, 'alt+backspace') branch was never
reached and Option+Backspace either no-op'd or fell through to
single-character delete.
Added a MOD_SUPER constant, taught parse_key_id, format_with_mods,
and format_kitty_key about the super bit, added super+alt+backspace
(and super+alt+delete / super+alt+d) to tui.editor.deleteWord*
defaults, and broadened the editor's direct matchesKey calls to
accept the super+alt variants. Existing alt+backspace / alt+d /
alt+delete paths are untouched, so the legacy ESC+DEL workaround
keeps working too.
Added Rust unit tests for the super modifier (positive and
negative for hyper/meta sequences we still drop) and TS
regressions in keys.test.ts and editor.test.ts that exercise
\x1b[127;11u end-to-end.
Fixes#2064
Detected OSC 66 text-sizing sequences in the bounded line fitter and included them whole so payloads (e.g. Markdown headings) are not skipped as zero-width.
Added a regression assertion that OSC 66 visible cells survive the bound when the row begins with the sized span.
Scanned oversized render rows by ANSI sequence so bounded line fitting skips leading zero-width control spans without dropping subsequent visible cells.
Extended issue #2045 regression coverage to assert visible text survives long SGR and OSC hyperlink prefixes.
Skipped repainting tmux panes when an offscreen shrink leaves the visible tail unchanged, and added regression coverage for the no-content-bytes contract.
Fixes#2046
Bounded the raw row span passed into TUI line fitting so ANSI-heavy subagent output cannot grow terminal write buffers independently of viewport width.
Added issue #2045 regression coverage for pathological zero-width ANSI rows.
Fixes#2045
The kitty keyboard progressive-enhancement probe sends `CSI ? u CSI c`
and used the DA1 reply as a fallback sentinel: if no kitty reply arrived
the modifyOtherKeys fallback was engaged. The kitty handler then refused
to enable kitty whenever modifyOtherKeysActive was already true, which
silently dropped the reply on terminals that answer DA1 before CSI ? u
(Superset / xterm-on-Electron). The protocol never enabled, so
Shift+Enter was delivered as a bare \r and the input box submitted.
Make the kitty reply authoritative regardless of ordering: drop the
`!modifyOtherKeysActive` guard, and if the premature DA1 already engaged
the modifyOtherKeys fallback, send `CSI > 4 ; 0 m` to disable it before
pushing the kitty stack frame. Kitty is strictly preferred (per the
existing #3259-fallback comment), so this is a clean cutover.
Add a regression test that drives a real ProcessTerminal through the
existing render harness across three orderings: kitty-then-DA1 (control),
DA1-then-kitty (#2042 regression), and DA1-only (modifyOtherKeys
fallback stays).
Fixes#2042
WSL processes report process.platform === 'linux', but their stdout still
funnels through wslhost into the same Windows ConPTY pipe, so the >32 KB
viewport-tracking regression reproduces there as well. The gate now fires
whenever process.platform === 'win32' OR isWindowsSubsystemForLinux()
detects WSL_DISTRO_NAME/WSL_INTEROP. Genuine POSIX terminals keep the
single-write fast path.
Added a WSL-specific test alongside the existing win32 / clean-linux
cases to lock the broader gate.
Fixes#2034
Windows ConPTY ties viewport tracking to per-WriteFile boundaries: when a
single process.stdout.write exceeds ~32-64 KB, the pseudo-console stops
following the cursor and the host UI's scroll position stays parked at
wherever the write began. Full paints (resume, history rebuild, large
permission dialogs) showed only the first screenful until Alt+Tab forced
the host to re-query the cursor; the bytes always landed in scrollback.
ProcessTerminal#safeWrite now routes win32 writes through a new
chunkForConPTY helper that splits oversized buffers into <=8 KiB pieces
at \n boundaries, keeping every WriteFile well below the threshold and
escape sequences intact (TUI paints separate rows with \r\n, so a line
break almost always exists in range). Non-win32 platforms keep the
single-write fast path.
Fixes#2034
- Increased `Bun.sleep` delays in TUI tests from short frame intervals to 40ms.
- Adjusted mocked `performance.now` tick advances from 20ms to 40ms so render throttling sees full frame gaps.
- Modified the streaming scrollback settle helper to yield a `nextTick` before sleeping, then flush.
- Constrained the no-append repaint optimization to execute only when the logical viewport position was unchanged.
- Documented the live-region pinned repaint cursor re-anchoring fix in the TUI changelog entry.
- Updated key canonicalization in keybinding parsing to parse modifiers case-insensitively and infer `shift` from printable uppercase keys.
- Added regression tests for `shift+a` matching uppercase `A` and for cursor anchoring during no-append live repaint scenarios.
- Recorded the uppercase Shift keybinding fix in the package changelog entry.
- Updated settle helpers in repro tests to wait 40ms before terminal flush.
- Raised process-terminal-render-harness SETTLE_MS to 67ms for 30fps timing.
- Updated render-throttle mocks to advance mock time by 40ms per frame.
- Added Loader component cadence test for 30fps animated repaint timing.
- Added render-regressions check that non-force renders are scheduled at ~33ms.
- Added animated message-color gating so loader timers run at 30fps only when needed.
- Added no-op checks in loader and text updates to skip work for unchanged values.
- Optimized key matching by canonicalizing key IDs and precomputing alias sets.
- Added prepared-line caching and bounded terminal/width scans to reduce repaint overhead.
- Updated the initial render intent to carry a `clearScrollback` flag.
- Adjusted first-frame rendering logic to preserve existing scrollback by default and clear only when requested.
- Added `resolver` stubs to coding-agent and web-search test registries for interface compatibility.
- Added a clear-initial-terminal-history flag to interactive mode initialization and passed it from cold-launch startup.
- Extended TUI.start to accept clearScrollback and clear native scrollback on initial full paint for non-multiplexer sessions.
- Preserved ED3 streaming scrollback accounting during capped streaming frames and added tests to prevent committed prefix duplication across terminal history rewrites.
- Added tests confirming `Markdown.render` returns caller-owned lines so per-instance mutations do not change the next render output.
- Added coverage that mutating one instance's render output cannot leak injected lines into another instance via shared cache reuse.
- Added a repeated mutate-then-render test to ensure no line accumulation across multiple render cycles.
- Replaced the repaint viewport clamp with strict bottom anchoring at lines.length - height.
- Removed the anti-duplication clamping path that could persist off-tail #viewportTopRow values between frames.
- Kept native scrollback cleanup to the existing dirty checkpoint mechanism for stale committed prefixes.
- Added `EventLoopKeepalive` in `instrumentedCompleteSimple` to keep the event loop alive during oneshot completion calls.
- This prevented long-lived completion promises from freezing host spinners in Bun by keeping timer servicing active.
- Marked Markdown cached lines as readonly snapshots and returned copied arrays from `render()`.
- Stored fresh snapshot arrays in both L1 and L2 caches so callers appending rows cannot mutate shared cached Markdown output.
- Added an AskTool test that renders calls and results twice to verify repeated renders remain stable and do not duplicate option labels.
- Updated InputController streaming submit handling to interrupt and resume when queued steering exists, refreshing the pending-message UI and render.
- Added AgentSession.interruptAndFlushQueuedMessages to abort active work and continue processing queued messages immediately.
- Added tests for queued steering interruption in both agent-session concurrency and input-controller keybinding flows.
- Added OSC5522 parsing and a payload controller that prefers image MIME and reassembles chunks.
- Added bracketed image-path detection and `onPasteImagePath` callback wiring in the editor.
- Added local image-path paste insertion with fallback to text paste on image-size errors.
- Added `Editor.pasteText()` and OSC5522 disable sequencing on terminal cleanup.
- Removed the per-frame `#fitLineCache` in `TUI` rendering to avoid rope-string hashing during repaints.
- Deleted `packages/tui/bench/width.ts`, retiring an obsolete visibleWidth benchmark script.
- Fixed `visibleWidth` parity with native width using `Bun.stringWidth` ANSI stripping.
- Fixed tab and OSC66 width parsing with `s=`/`w=` metadata handling.
- Added ASCII and long-string fast paths in `visibleWidth` for faster width checks.
- Consolidated visible-width regression coverage by replacing removed IME/Arabic/Jamo tests with `visible-width.test.ts`.
- Clamped repaint anchor to min(firstChanged, scrollbackHighWater) so byte-identical committed rows are not re-painted into the active grid.
- Added submitPinsViewportToTail capability to reconcile native scrollback at submit on unprobeable local terminals.
- Renamed allowUnknownViewportMutation to allowUnknownViewportTransientRepaint to clarify it never authorizes a settled transcript commit.
- Made `Component.invalidate` optional in `packages/tui/src/tui.ts` and callsites.
- Added `iconOverride` support to status-line rendering in `status-line.ts`.
- Added `borderColor` and `framedBlock()` primitives in `output-block.ts` for framed output styling.
- Adjusted `renderOutputBlock()` to draw a continuous border when no header text is present.
- Added terminal checkpoint-reconciliation tests in `submit-checkpoint-reconcile.test.ts` for pinned-tail behavior.
- Added terminal detection for submit-key actions that pin the viewport to tail, and added a test override for that capability.
- Converted TERMINAL into a clone-based RuntimeTerminal so mutable capability fields can be updated directly by runtime setters.
- Updated checkpoint replay gating to allow ED3 rebuild when viewport position is unprobeable but submit-pin behavior is reported true.
- Added SGR mouse input handling for wheel and click actions, mapped to option, ToC, and body targets.
- Added deleted-heading state to undo snapshots so removed titles persist and restore correctly.
- Updated the plan-mode active prompt to require decision-complete, implementation-ready plans.
- Enabled mouse tracking for fullscreen overlays and asserted mouse-on/off sequences in tests.