fix: stop terminal tearing on Claude no-flicker repaints (v0.4.6) - #180
Merged
Conversation
Claude Code's no-flicker renderer wraps its fullscreen repaints in \x1b[?2026h/l synchronized-update markers. tmux strips them unless the attached client advertises the sync terminal feature, so repaints reached xterm.js as unmarked fragments (PTY reads cap at 2-4KB) and tore mid-frame while streaming or scrolling (#158). Attach with tmux -T sync (per-client, no server option mutation) from both the local PTY proxy and the SSH proxy. Version-gate on tmux >= 3.2 where -T exists; older tmux keeps today's behavior. AGENTBOARD_TMUX_SYNC=0 opts out.
xterm.js 6.0.0 implements DEC mode 2026 (xtermjs/xterm.js#5453): the renderer holds painting between \x1b[?2026h/l, so tmux frames that arrive split across PTY reads and WebSocket messages paint atomically. Together with the -T sync attach flag this stops Claude no-flicker repaints from tearing while streaming or scrolling (#158). Also bumps addon-search 0.16, addon-webgl 0.19, addon-progress 0.2 to the 6.0-era releases. No API changes were needed; typecheck/lint/tests/build all pass unchanged.
- SSH: fetch the remote tmux version via new-session -P -F '#{version}'
in the existing create round-trip instead of a dedicated tmux -V probe;
only the duplicate-session recovery path still probes. Removes the extra
per-attach SSH handshake (ControlMaster=no makes each command a fresh
connection).
- Pty: log terminal_sync_probe_failed when the tmux -V probe fails, matching
the observability convention of the sibling doStart() tmux calls.
- Fail closed on an empty version string: pre-2.4 tmux expands #{version}
to nothing and must not get -T sync (which would abort its attach).
- AGENTBOARD_TMUX_SYNC is now read per call (syncFeatureEnabled()) so the
opt-out is testable.
- Tests: SSH attach gains explicit coverage (sync flag present for 3.4,
omitted for 3.1c, duplicate-session fallback probe, probe-failure
degradation, env opt-out) plus a Pty opt-out test.
gbasin
added a commit
that referenced
this pull request
Jul 30, 2026
Fixes the black border around the terminal view introduced by the xterm.js 6.0 upgrade in #180, reported on mobile Safari immediately after v0.4.6. ## Root cause Agentboard gives `.xterm` an 8px padding ring. Through xterm.js 5.5, the library painted `.xterm-viewport` (which spans the full box, padding included) with the theme background via an inline style on every theme change, so the ring matched the terminal (`#2d2d2d`). xterm.js 6.0's viewport rework applies the theme background to its new scrollable-element node instead — leaving the viewport with `xterm.css`'s default `background-color: #000`. Result: an 8px pure-black frame around the terminal content, most visible on mobile where the terminal fills the screen. Verified by DOM measurement under iPhone emulation: `.xterm-viewport` computed `rgb(0,0,0)` spanning 390×713 while `.xterm-screen` sat 8px inset painting the theme color. ## Fix - `styles/index.css`: `.xterm .xterm-viewport { background-color: transparent !important }` — let the container show through. - `Terminal.tsx`: the terminal container div now carries `terminalTheme.background` (reactive to theme switches), so the padding ring always matches the terminal content. After the fix, the same measurement shows the viewport transparent over a `rgb(45,45,45)` container, and the mobile screenshot renders edge-to-edge theme background with no border. ## Notes - `bun run lint && bun run typecheck && bun run test` green. - Includes the version bump to 0.4.7 for release-on-merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #158.
Root cause
Claude Code's no-flicker renderer wraps its fullscreen repaints in DEC 2026 synchronized-update markers (
ESC[?2026h … ESC[?2026l). tmux honors them internally (MODE_SYNC) but strips them from client output unless the attached client advertises thesyncterminal feature — which agentboard'sTERM=xterm-256colorpty client did not. Repaints then reach the browser as unmarked fragments (PTY reads cap at 2–4 KB, so a realistic multi-KB frame always splits), and xterm.js 5.5 — which has no DEC 2026 support — freely paints between fragments: transient garbage rows, worst at the left edge, self-healing once the stream idles. Copy-mode scrolling is the same mechanism via tmux's full-redraw path, which is why the reporter saw it both while scrolling and while streaming, and why/tui defaultorAGENTBOARD_CLAUDE_NO_FLICKER=0made it disappear.Every link was verified empirically:
claude2.1.220 emits the markers (captured viapipe-pane); tmux drops them for a plain client and emits them with the feature enabled (0 vs 18 wrapped frames in a controlled capture); 25 KB frames split across ~24 PTY reads in 18/18 samples.Fix
tmux -T syncat attach (local pty proxy + SSH proxy): per-client flag, no server-option mutation, so users' own tmux clients are untouched. Version-gated on tmux ≥ 3.2 (where-Texists — it aborts the attach on older versions); older tmux keeps today's behavior. The SSH proxy probestmux -Vover the existing command channel, so it works under any remote login shell.AGENTBOARD_TMUX_SYNC=0opts out (documented in README).windowsMode,fastScrollModifier, canvas addon,overviewRulerWidth) were in use.Verification
-T syncon tmux ≥ 3.2 and omit it on 3.1c;tmuxSupportsClientFeaturescovers letter suffixes, two-digit minors, and versionless dev builds.?2026h/lpairs observed arriving in the browser's WebSocket frames end-to-end; a 22-row color-cycling frame emitter rendered with a uniform frame number on every row (a torn paint shows interleaved frame numbers); wheel-scroll entered tmux copy-mode and agentboard's "Jump to bottom" flow worked under xterm 6.0's redesigned viewport; zero console/page errors, WebGL renderer active.bun run lint && bun run typecheck && bun run testgreen (723 tests).Notes for review