orca/config
Brennan Benson bc1e049b3f
fix(terminal): defer metric option writes to unmeasurable panes (#12944)
* fix(terminal): defer metric option writes to unmeasurable panes

Writing fontSize/fontFamily/fontWeight/lineHeight makes xterm re-measure
cell size against the pane's current box. A hidden or mid-layout pane can
measure a wrong-but-nonzero size, which latches (hasValidSize) and mis-keys
the shared WebGL glyph atlas until a manual resize — the stuck variant of
the P0 bold/blurry-font reports.

Metric writes now land only on measurable panes; otherwise the latest
values park per-pane and flush on the next safe fit or reveal (with a refit
on the light tab-resume path, which otherwise skips fitting). Measurability
helpers move to pane-fit-measurability.ts to stay under the pane-fit.ts
line cap.

* fix(terminal): key metric deferral by terminal, not pane view

getPanes() returns a fresh toPublicPane() wrapper per call, so a
WeakMap keyed on ManagedPane never matched across call sites: deferred
metric options were dropped, not deferred. Key on pane.terminal, which
is carried by reference and dies with the pane.

Also from review:
- flushDeferredPaneMetricOptionsIfMeasurable checks the pending WeakMap
  before the measurability probe, so the common no-deferral case costs
  zero forced style/layout on every reveal.
- applyTerminalAppearance skips the apply (and the probe) when all five
  values are already live and nothing is parked; any settings write
  re-runs the pass over every mounted pane, and arming a no-op deferral
  would trigger a refit on the next reveal.
- fitRevealedPane flushes first: its pixel/grid checks can both no-op
  and return without fitting, stranding parked options.
- Font zoom folds its direct fontSize write into any pending deferral so
  the flush inside safeFit cannot clobber the user's zoom.

Corrects comments that asserted a cell-size re-measure mechanism xterm
does not have: CharSizeService measures via OffscreenCanvas TextMetrics,
independent of the pane box, and only fontSize/fontFamily re-measure.

Test fixtures now allocate a fresh pane view per getPanes() call, which
is what production does and what hid the keying bug.

* fix(terminal): re-check the fit floor after a metric flush

performSafeFit evaluated the min cols/rows gate with the pre-flush cell
size, then flushed and fit unconditionally. A large font jump on a
narrow pane passes the gate at the old size and lands under it at the
new one, so fit() pinned the PTY to the tiny grid the floor exists to
reject. Re-check after a flush that actually landed.

The parked values still apply, so the pane is never stuck on stale
metrics; only the fit is skipped.

* fix(terminal): route a reveal metric flush through the stable fit

fitRevealedPane's new flush branch called safeFit directly, which is
exactly what the function's contract forbids on reveal: resumeRendering
has just re-attached WebGL, whose cell metrics transiently differ from
the DOM renderer's, so a raw fit can propose a one-column-off grid and
reflow — and xterm's wrap/unwrap is not a perfect inverse, leaving a
diff-painting inline TUI corrupted.

A landed flush leaves pixels unchanged with a diverged grid, the same
shape as a snapshot resize, so it takes the same steady-grid repair.
A real resize still fits synchronously, after the flush.

Reachable via window wake, which calls fitAllRevealedPanes with no
pre-flush loop.

* fix(terminal): gate metric writes on the pixel box, not the fit floor

canApplyPaneMetricOptions reused canMeasurePaneForFit, whose >=8 cols /
>=4 rows floor exists to stop a fit pinning the PTY to a sliver. But the
divider clamp is 50px, which clears the 48px pixel floor and proposes
~5 cols — so a pane dragged to the clamp deferred every font change and
never flushed: it never hides, and its box never changes, so no reveal
and no ResizeObserver entry ever arrives. It rendered a stale font until
widened, where pre-PR the write was unconditional.

Gate metric writes on display plus the pixel box only. Hidden panes and
the transient worktree-switch overlay are near-zero, so they still
defer — the deferral's purpose is unchanged. The cols/rows floor stays
on the fit, including the post-flush re-check in performSafeFit.

Apply and flush share the same predicate, so no "applies but never
flushes" state can open up.

* fix(terminal): flush heavy reveal metrics after WebGL resume
2026-08-07 10:14:37 -07:00
..
build-plugins move folders (#12758) 2026-08-05 12:09:24 -07:00
docker/headless-pairing
nsis fix(windows): separate updater from orchestration migration (#11405) 2026-07-29 15:23:06 -07:00
oxlint-plugins perf(lint): consolidate code-quality gates into Oxlint (#11117) 2026-07-28 00:21:13 -07:00
patches Fix terminal corruption after restored snapshot replay (#12363) 2026-08-03 20:00:28 -07:00
relay-assets
scripts fix(terminal): defer metric option writes to unmeasurable panes (#12944) 2026-08-07 10:14:37 -07:00
dev-app-update.yml
electron-builder.config.cjs feat(updater): add an adhoc release channel for branch builds (#12051) 2026-08-02 01:46:51 -07:00
electron-vite-target.config.ts ci: parallelize PR checks and accelerate Vite builds (#10989) 2026-07-27 13:32:29 -07:00
i18n-translation-source.md docs(i18n): adopt gettext PO as the canonical translation source (#11478) 2026-07-31 11:46:25 -07:00
i18next.config.ts chore: declutter top-level repo layout (#11890) 2026-08-01 00:25:35 -07:00
knip.json move folders (#12758) 2026-08-05 12:09:24 -07:00
localization-audit.md docs(i18n): adopt gettext PO as the canonical translation source (#11478) 2026-07-31 11:46:25 -07:00
localization-coverage-allowlist.json fix(i18n): localize the status bar Resource Manager tooltip and remote-host count (#12478) 2026-08-06 19:14:15 -07:00
max-lines-baseline.txt chore(mobile): import the shared marine-creature corpus instead of mirroring it (#12090) 2026-08-02 01:44:03 -07:00
oxlint-code-quality-native-plugins.json perf(lint): consolidate code-quality gates into Oxlint (#11117) 2026-07-28 00:21:13 -07:00
oxlint-code-quality-type-aware.json perf(lint): consolidate code-quality gates into Oxlint (#11117) 2026-07-28 00:21:13 -07:00
oxlint-react-doctor.json chore(quality): ratchet Oxlint, React Doctor, and Zustand performance (#11034) 2026-07-27 18:58:36 -07:00
packaged-runtime-node-modules.cjs fix(packaging): prune non-target native binaries (#12174) 2026-08-03 10:54:00 -07:00
reliability-gates.jsonc fix(terminal): restore preserved remote PTYs after host relaunch (#12990) 2026-08-07 01:25:18 -07:00
tsconfig.cli.json fix(serve): recognize CLI-form serve args on the Electron process (#12818) 2026-08-06 23:56:34 -07:00
tsconfig.node.json move folders (#12758) 2026-08-05 12:09:24 -07:00
tsconfig.relay.json
tsconfig.tc.cli.json
tsconfig.tc.web.json fix(serve): recognize CLI-form serve args on the Electron process (#12818) 2026-08-06 23:56:34 -07:00
tsconfig.web.json
vitest.config.ts test(env): keep happy-dom MutationObserver callbacks alive across GC (#12438) 2026-08-03 23:18:15 -07:00