Skip to content

fix(web): stop pty sizing loop on fractional cell widths - #84

Merged
karngyan merged 1 commit into
mainfrom
worktree-fix-resize-oscillation
Aug 17, 2026
Merged

fix(web): stop pty sizing loop on fractional cell widths#84
karngyan merged 1 commit into
mainfrom
worktree-fix-resize-oscillation

Conversation

@karngyan

Copy link
Copy Markdown
Contributor

Problem

An active split pane flickers at certain pane widths — most visibly with a browser sidebar open. The pane redraws its prompt several times a second and never settles.

Root cause

A closed feedback loop between the view's fit reports and the pty, driven by a rounding error:

  1. contentSize() measured .xterm-screen with offsetWidth/offsetHeight, which round to whole pixels. On a Retina display the true width is fractional (a cell is a whole count of device pixels divided by the pixel ratio), so the measurement is off by up to half a pixel.
  2. cellBox divides that by the column count, and cellsThatFit multiplies the error back out across the pane — enough to flip the fitted answer between N and N+1 columns depending on the dimensions the screen currently wears.
  3. The daemon counts a size report as activity (syncSize), so the reporting view's desire always wins: the pty takes N+1, SIGWINCH redraws the prompt, size_changed re-lays the emulator out, the re-measure now rounds the other way and answers N. Forever.

Simulated with real cell metrics: roughly 2–4% of pane widths oscillate permanently, which is why it appears at specific layouts (split panes at fractional flex widths, sidebar open) and follows the active pane — only its reports move the pty.

Fix

Both the DOM renderer and the WebGL addon write the screen's exact laid-out size into the element's style attribute. contentSize() now reads that value (falling back to the offset measurement when absent). The exact cell width makes the fitted answer a function of the pane alone, so the loop has a fixed point and converges after one report.

Testing

  • New regression test: contentSize reports the exact fractional size, not whole pixels (fails on the old implementation).
  • pnpm vitest run: 1464 tests pass; pnpm run lint clean.

🤖 Generated with Claude Code

contentSize read offsetWidth, which rounds the screen's fractional
Retina width to whole pixels. The error, divided into a cell width and
multiplied back across the pane, flips cellsThatFit between N and N+1
depending on the current dimensions — the pty answers each flip with
the other, redrawing the prompt several times a second at unlucky pane
widths (split panes, browser sidebar open). Read the exact size both
renderers write into the screen's style attribute instead.
@karngyan
karngyan merged commit ca73c7f into main Aug 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant