From 9223eb9874ac24bd8e5d0e503c8895e1c0fbbf69 Mon Sep 17 00:00:00 2001 From: Allan Thraen Date: Sat, 5 Sep 2026 15:52:33 +0200 Subject: [PATCH] fix(terminal): re-fit once the font has loaded, not on a timing guess MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01NDsEfog5kVkT5NmX1Ya5be --- src/CodeShellManager/Assets/terminal-init.js | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/CodeShellManager/Assets/terminal-init.js b/src/CodeShellManager/Assets/terminal-init.js index efe5f3f..858d989 100644 --- a/src/CodeShellManager/Assets/terminal-init.js +++ b/src/CodeShellManager/Assets/terminal-init.js @@ -108,6 +108,16 @@ if (opts.padding !== undefined) document.getElementById('terminal').style.padding = opts.padding; if (opts.retro !== undefined) document.body.classList.toggle('retro', !!opts.retro); fitAddon.fit(); + // A profile override can switch fontFamily/fontSize to a face that isn't loaded + // yet, so the fit above measures the wrong metrics for the same reason the + // initial one can. Re-fit once the new face is ready. + if (opts.fontFamily !== undefined || opts.fontSize !== undefined) { + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(function () { + try { fitAddon.fit(); } catch (e) {} + }); + } + } } else if (msg.type === 'dropOverlayClear') overlay.classList.remove('active'); else if (msg.type === 'setBootState') { @@ -261,4 +271,25 @@ setTimeout(() => { try { fitAddon.fit(); term.focus(); } catch {} }, 50); setTimeout(() => { try { fitAddon.fit(); } catch {} }, 250); + // Re-fit once the font has actually loaded. + // + // 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 — text then wraps and + // overlaps mid-line. + // + // The ResizeObserver above cannot correct this: the ELEMENT size never changed, + // only the glyph metrics, so no resize fires. It stays wrong until something else + // forces a fit, which is why switching layouts appeared to "fix" it. + // + // The two timeouts above are guesses at "fonts are probably ready by now" and are + // easily too early during a heavy restore with many WebView2s initialising. They + // stay as a fallback for the 0x0 case; this is the real signal. + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(function () { + try { fitAddon.fit(); } catch (e) {} + }); + } + term.focus();