Skip to content

Terminal: 1-frame glyph-size flicker on tab switch with CJK cell-width experiment #178

Description

@im-ian

Summary

The opt-in CJK terminal cell-width correction experiment (Settings → Experiments) currently exhibits a 1-frame glyph-size shift on tab switch when the visible pane size changes. Tracks #164 follow-up work.

Repro

  1. Settings → Terminal → set Font family to a CJK monospaced font where W and measure the same advance (e.g. D2Coding, Sarasa Mono K, IA Writer Mono V)
  2. Settings → Experiments → enable CJK terminal cell-width correction
  3. Open at least two sessions and switch between tabs while their panes differ in size

Expected: tab swap is a clean cut between rendered buffers.
Actual: 1 frame at the natural W advance is visible mid-swap — characters look briefly larger ("커졌다가") before snapping back to the halved cell width.

Root cause

Frame-by-frame inspection of a screen capture confirmed the transient. The portal target div hosting xterm moves limbo → pane body on tab activation. ResizeObserver fires → fitAddon.fit()core._renderService.handleResizeDomRenderer._updateDimensions:

this.dimensions.device.cell.width =
  this.dimensions.device.char.width + Math.round(letterSpacing);

resets cell.width to the raw W advance (× dpr). The addon's post-fit patchTerminalCellMeasurements reapplies the halved value, but a paint can occur in between, producing the visible jump.

What we tried (all kept the transient)

  • Module-level measurement memo + fast-path early return on cached + already-at-target. Avoided redundant widthCache.clear + recalibrateDefaultSpacing rounds but did not close the resize-time gap.
  • _charSizeService.width getter intercept via Object.defineProperty so xterm's _updateDimensions would read the halved value from the start. Behavior unchanged in app.
  • renderer.handleResize wrap (call original, then applyCellWidth(target) in the same sync tick before next paint). Behavior unchanged in app.
  • term.options.letterSpacing public option (with dpr correction since xterm folds it into device.cell.width not the CSS value). Behavior unchanged in app.

Possible next steps

  • Replace the xterm.js DOM renderer with a forked variant whose _updateDimensions reads the half-cell target as the canonical char width.
  • Replace fitAddon with a custom fit that does not call core._renderService.clear() when only the surrounding pane size changes.
  • Investigate whether a requestAnimationFrame deferral of the patch reapply could collapse into the same paint as xterm's resize repaint (instead of one extra paint after).
  • Render the terminal at the actual pane dimensions while in limbo (currently fixed 800×600) so tab activation does not trigger a resize.

Workaround

The toggle ships off by default. Users hitting the original D2Coding-style misalignment can opt in and accept the flicker; the Experiments row description spells out the limitation.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions