fix(terminal): smooth pixel-level scrolling for TUI apps via patched xterm viewport - #7450
fix(terminal): smooth pixel-level scrolling for TUI apps via patched xterm viewport#7450hmrserver wants to merge 4 commits into
Conversation
…xterm viewport - Patch @xterm/xterm 6.1.0-beta.287 (Viewport.ts + lib bundles) to carry a fractional pixel offset after row scrolls and apply it as a DPR-rounded translateY, instead of snapping to whole rows - Add a catch-up glide when output outpaces the viewport, with prefers-reduced-motion off-switch, mousedown settle for hit-testing, RAF cleanup on dispose, and willChange toggled only while offset is active - Pairs with the existing @xterm/addon-webgl clearModelGeneration patch
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis change updates 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ef33d7e to
59855d0
Compare
|
@Jinwoo-H any updates on this PR? |
…ll-for-tui-default # Conflicts: # package.json # pnpm-lock.yaml
|
Thanks for working on this — smoother terminal scrolling would be a meaningful improvement. I’d like to discuss two limitations I found before we decide what to do with the PR:
Because of that, the current approach may improve ordinary terminal scrollback, but I don’t think it can provide true 1 px scrolling inside Claude as written. Was your intended scope ordinary scrollback as well as application-owned TUI scrolling? And did you find a way around the missing rendered row or the discrete wheel protocol? Possible directions might be narrowing this to scrollback with proper renderer overscan, or exploring TUI-side support/visual interpolation separately. |
|
1. Blank strip — yep, xterm only paints exactly the visible rows, so when we slide the canvas there's literally nothing beyond the edge. Hmm, I was thinking - what if we just render a bit extra on both sides? One overscan row above and below (canvas at 2. Fullscreen TUIs - right, those won't get this, and that's intentional. Alt-screen apps own their rendering (the CLI itself controls scrolling, e.g. Claude Code's fullscreen mode), so there's nothing for the viewport to animate. |
Summary
Scrolling inside TUI apps (Claude Code, vim, htop, etc.) in Orca's terminal currently jumps line by line because xterm.js snaps the viewport to whole rows on every scroll event. Rendering the terminal at fractional pixel offsets keeps those same apps smooth.
This PR brings that behavior to Orca by patching
@xterm/xterm@6.1.0-beta.287through pnpm patches, alongside the existing@xterm/addon-webglpatch:_pixelOffset = newRow * cellHeight - scrollTop) and applies it as a device-pixel-ratio-roundedtranslateYon the screen element instead of snapping to the row grid. The offset resets on buffer switch (normal ↔ alt).@xterm/addon-webglpatch replaces the one-shotbeginFrame()boolean with a monotonicclearModelGenerationcounter so the GPU renderer keeps re-rendering while the offset animates.The same logic is mirrored across
src/browser/Viewport.tsand both shipped bundles (lib/xterm.js,lib/xterm.mjs). Parity greps verified the copies stay in sync.No new user-facing settings. The earlier
smoothScrollDurationsetting experiment was removed from settings UI and all locale files.Screenshots
Visual change: terminal scrolling and streaming TUI output now move at pixel granularity instead of jumping whole rows.
before: whole-row snapping:
before.mov
after: smooth pixel scroll:
after.mov
Testing
pnpm lint— fails with one pre-existing error unrelated to this PR (SourceControlActionRepoOverrideNote.tsx:35switch-exhaustiveness-check; file is untouched by this branch and identical to the base). No new lint errors introduced.pnpm typecheckpnpm test(2359 files, 24509 tests passed)pnpm build.tsparity is enforced by grep checks; runtime behavior was verified manually against Claude Code, vim and streaming output, cross-checked frame-by-frame against reference recordings.AI Review Report
Ran an adversarial multi-pass review (Claude + subagents) over the patch and integration. The review focused on offset math across DPR values, animation lifecycle leaks, interaction correctness during animation, renderer parity (DOM/canvas/WebGL), and drift between patched
.tssource and minified bundles. Findings fixed:prefers-reduced-motion: reduceand snaps instantlymousedownsettles the animation before selection/clicksTerminal.dispose()willChange: transformnow toggles only while an offset is activeResidual: during a glide, a ≤2-row strip at the leading edge can be briefly blank until the next frame. A full fix needs overscan rendering and is out of scope.
Cross-platform: the change is renderer-only inside the xterm patch. It does not touch shortcuts, labels, file paths, shell behavior, or Electron main-process APIs. It runs the same way on macOS, Linux, and Windows, and
prefers-reduced-motionis honored throughmatchMediaon all three. Rendering does not depend on where the PTY lives, so local, remote-server, and SSH-worktree sessions use the same client-side scrolling behavior. SSH latency only affects when output arrives.Security Audit
package.jsononly registers the pnpm patch, and the patch content is integrity-pinned throughpnpm-lock.yamlNotes
config/patches/@xterm__xterm@6.1.0-beta.287.patch; upgrading xterm.js requires re-applying it.Viewport.ts. This follows the same convention as the existing addon-webgl patch because pnpm consumes the builtlib/output. Parity between the three copies was verified.ELI5
Scrolling in full-screen TUI apps jumped whole lines and felt choppy. Pixel-level viewport offsets make Claude, vim, htop, and friends scroll smoothly.