fix(terminal): re-fit once the font has loaded, not on a timing guess - #113
Merged
Conversation
Reported as initial rendering looking wrong — text wrapping and running together mid-line — which then corrected itself as soon as the view changed. xterm derives its column count from the MEASURED advance width of the font. The first fit() runs immediately after term.open(); if Cascadia Code hasn't loaded yet, xterm measures the fallback's metrics, computes the wrong cols, and reports a width to the PTY that doesn't match what is drawn. The ResizeObserver cannot correct this: the ELEMENT size never changed, only the glyph metrics, so no resize event fires. It stays wrong until something else forces a fit — which is exactly why switching layouts appeared to fix it. The two existing setTimeout fits (50ms, 250ms) are guesses at "fonts are probably ready by now", and are easily too early during a restore with many WebView2s initialising at once. They stay, since they also cover the separate 0x0-container case, but document.fonts.ready is the actual signal. Same treatment where a profile override changes fontFamily/fontSize — that can switch to a face that isn't loaded either, and fit() there had the same problem. Note this got worse recently rather than appearing from nowhere: #105 stopped rebuilding the grid on activation, and that rebuild used to force an incidental re-fit that masked the mismeasurement. 312/312 pass, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be
AThraen
added a commit
that referenced
this pull request
Sep 5, 2026
Release review of the 20 commits since v0.6.0. Three documentation defects, one of them actively wrong. WRONG: the active-terminal highlight section claimed the ring's accent "is stashed on Border.Tag at build time so the highlight method doesn't need to look up the VM". The code does the opposite and its own comment says so — RepoRoot is populated asynchronously by GitService and AccentColor changes when it lands, so a cached Tag goes stale and stops matching the sidebar. The Tag is only a fallback. MISSING: three services added this cycle were undocumented — DbGate (#102), PwshLocator (#104), ClaudeConfigGate (now shutdown-only after #111). MISSING: nothing described what actually makes a session active, which is now the most subtle thing in the codebase and was got wrong twice. Added a section covering all three routes and, more importantly, why two of them must come from the page: - WebView2 is an HwndHost, so mouse input on hosted native content raises no WPF routed events at all — a Preview handler on the host Border only ever sees the ring around the terminal (#108). - xterm's onData is not "the user typed": it also carries device-attribute replies, cursor-position reports, OSC colour replies and focus in/out, none of which are distinguishable from typing by inspecting the bytes (#106). Also recorded why the font-load re-fit exists (#113) and why the ClaudeLaunch stagger must stay a flat delay — including that a plain Task.Delay(2000) still logged gate=36339ms after the revert, which is what finally showed the gate was measuring an app-wide stall rather than causing one. Written as a "do not try this again" note with the measurements, since the adaptive version looks obviously better on paper and was attempted three times. Verified while here: every service on disk is now documented, and all nine keybindings in MainWindow match the documented table. Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported as initial rendering looking wrong — text wrapping and running together mid-line — which corrected itself as soon as the view changed.
Cause
xterm derives its column count from the measured advance width of the font. The first
fit()runs immediately afterterm.open(). If Cascadia Code hasn't loaded yet, xterm measures the fallback's metrics, computes the wrongcols, and reports a width to the PTY that doesn't match what's drawn.The
ResizeObservercannot correct this: the element size never changed, only the glyph metrics, so no resize event fires. It stays wrong until something else forces a fit — which is exactly why switching layouts appeared to fix it.The two existing
setTimeoutfits (50ms, 250ms) are guesses at "fonts are probably ready by now", and are easily too early during a restore with many WebView2s initialising at once.Fix
document.fonts.readyis the actual signal. The timeouts stay, since they also cover the separate 0×0-container case, but they're no longer load-bearing for font metrics.Same treatment where a profile override changes
fontFamily/fontSize— that can switch to a face that isn't loaded either, and thefit()there had the identical problem.Worth noting for the #105 discussion
This got worse recently rather than appearing from nowhere. #105 stopped rebuilding the grid on activation, and that rebuild used to force an incidental re-fit that masked the mismeasurement.
That's now three things #105 turned out to be propping up — the lost re-fit on click (#108), this one, and the signature/version-counter machinery it needed to avoid breaking pane paging and restarted-session rendering. Its own benefit was never measured, and the restore cost turned out to be elsewhere. I still think reverting it is the cleaner call, and I'd rather raise that a third time than keep patching around it.
How to verify
Restore a full session set and look at the panes before touching anything. Text should be correctly wrapped from the first paint, with no mid-line overlap and no need to switch layouts to correct it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be