Repository navigation
fix(terminal): a revealed paired terminal fits once, at its real size, on Linux - #29668
Conversation
…same grid from any grid The web client positions a retained pane host from a measured tab-group body, but the pane mounted (and fit xterm) a render before that measurement, at the 32px placeholder box, then refit when the measurement landed. Mount the pane only after the first read. FitAddon also divides by xterm's css cell, which is the rounded canvas width over the current cols, so the same box proposed 51 cols from a 75-col replay and 52 from a 52-col pane: a revealed pane resized twice. Fit with the grid-independent device cell instead.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to
|
ELI5
When you open a parked terminal in a paired (web) client, it should show up once, already at the right size. On Linux it showed up at one size, resized itself, then resized again, all within about a quarter of a second. Two separate measuring mistakes caused this. This PR fixes both, so the terminal is measured correctly the first time.
What Changed
Before: revealing a parked paired terminal on a Linux desktop drew the text at 52x50, reflowed it to 51x49 or 52x49, and sometimes reflowed it again after a second hidden replay (the CI samples show 52x50, then 51x49 for about 125 ms, then 52x49). Each extra resize also sends a resize to the remote program, so a TUI redraws.
After: the pane is created at its real box and lands on one grid (51x49 in the test), whatever grid it started from.
Mechanism, two fixes:
RetainedPaneHost). The web client can't use CSS anchor positioning, so the host is placed from a measured tab-group body rect. React runs a child's layout effect before its parent's, so the terminal pane created xterm and fit it while the host still had its placeholder box (top: 32px; height: calc(100% - 32px), 450x804 here, against the real 449x800 at top 36). About 80 ms later the measured rect arrived and the pane refit. The host now renders its children only after the first rect read, which happens in the same commit, before paint. Once the children are mounted, the host never unmounts them again, and a host with no body to measure mounts them right away.StableCellFitAddon). xterm'sFitAddondivides the box byterminal.dimensions.css.cell.width. With the DOM renderer (Linux CI, no GPU) that value isround(charWidth * cols) / cols, so it changes with the current column count. For the same 438 px box it proposes 52 cols from a 52-col pane and 51 cols from the 75-col host-grid replay. So a reveal replay landed at 51, and any later fit from 52 kept 52, which caused a second resize and a second replay. The pane now uses aFitAddonsubclass that divides by the device cell divided by DPR. That value doesn't depend on the grid, and it matches the cell the renderer actually draws. It reuses the existingterminalGridSizeForBoxmirror of FitAddon's math. The new-tab grid predictor (terminal-group-grid-sizes.ts) uses the same cell, so its predictions match real fits.Why
This is (a): a real product issue, not just a test artifact. The test's width-only resize wait wasn't the cause: in every captured sample the window was already 1080x860 and the pane box was a constant 445x796. The jitter came from the two product bugs above.
Alternatives considered:
The reveal, fit and claim machinery from #29269, #29331 and #29349 is unchanged. It just gets the right size the first time now.
Linked Issue
N/A: release-gate triage.
tests/e2e/headless-paired-remote-terminal-reveal-grid.spec.ts(added by #29331) failed every time on Linux CI.Visual Proof
N/A: the e2e samples are the proof. Before (Linux, main): visible frames at 52x50, then 51x49, then 52x49. After: visible frames only at the final 51x49.
Testing
Reproduced on main, on a real Linux host (openclaw, Ubuntu 24.04, xvfb + openbox like CI, isolated /tmp checkout and HOME,
ORCA_BACKGROUND_LAUNCH=1 ORCA_E2E_WEB_CLIENT=1): the spec failed 2/2. A per-sample probe recorded the box, the css cell (8.4231 at 52 cols, 8.4267 at 75, 8.4314 at 51, from one 8.4287 px char) and a stack for everyresize. That's how both causes were pinned down.With this PR, same host: the spec passed 3/3. With only fix 1 it also passed 3/3. Fix 2 removes the 51/52 path dependence that caused the second replay in CI run 38865222465.
Related e2e on the same host:
paired-terminal-switch-desktop-reveal,headless-paired-remote-terminal-viewer-size,terminal-replay-presentation(the WebGL case skips, as on CI), plus the paired remote terminal, split and layout specs. Results are in the report.Unit tests:
terminal-stable-cell-fit-addon.test.tsasserts that stock FitAddon is path-dependent and the stable addon isn't. A newRetainedPaneHosttest checks the child sees the measured geometry at mount (it fails before the fix with32px 100%). Folders run single-worker:lib/pane-manager,components/tab-group,runtime/sync-runtime-graph,components/terminal-pane,components/native-chat.Static analysis: the full
static analysis and typecheckjob steps run locally, including oxlint (root, anti-slop, native and type-aware code quality), changed-code quality, React Doctor,pnpm typecheck, and the max-lines, ts-nocheck, focus-read, layout-writer and Electron-import ratchets.Codex review (gpt-6.1-sol, high): no findings.
Platforms: verified live on Linux only. macOS and Windows are covered by unit tests and CI.
I manually tested these changes locally
Automated tests added/updated, or explained why not below
Agent skill upstream boundary
Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)