From 5c297c64834f095e8fd69bac13e9eada8dedcd75 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Fri, 28 Aug 2026 13:18:12 +0700 Subject: [PATCH] fix(run): pin the lane terminal font so Vietnamese glyphs never fall back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xterm left fontFamily unset, so it used the browser/OS default (courier-new/monospace). That face is missing Vietnamese precomposed diacritics on many systems, so the browser silently substitutes those glyphs from another face with a different advance width — corrupting the cell even under the canvas renderer, which only stops a glyph from shoving its neighbours, not from overflowing its own cell. Pin the terminal to the app's own bundled JetBrains Mono and import its vietnamese subset (previously only latin was loaded). --- client/src/components/run/TerminalView.tsx | 16 +++++++++++++++- client/src/main.tsx | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/client/src/components/run/TerminalView.tsx b/client/src/components/run/TerminalView.tsx index b485c6f1..dc876ead 100644 --- a/client/src/components/run/TerminalView.tsx +++ b/client/src/components/run/TerminalView.tsx @@ -26,7 +26,21 @@ export function TerminalView({ runId, wsBaseUrl }: TerminalViewProps) { const containerRef = useRef(null); useEffect(() => { - const term = new Terminal({ convertEol: true, fontSize: 13, cursorBlink: true }); + // An explicit fontFamily pins every glyph to one bundled webfont. Left + // unset, xterm falls back to the browser/OS default (`courier-new`), + // which on Windows lacks Vietnamese precomposed diacritics — the OS then + // substitutes those specific glyphs from a different face with a + // mismatched advance width, corrupting the cell it's drawn into even + // under the canvas renderer (which only stops a glyph from shoving its + // *neighbours*, not from overflowing its own cell). JetBrains Mono is + // already bundled for the rest of the UI; main.tsx additionally imports + // its `vietnamese` subset so this covers the diacritics too. + const term = new Terminal({ + convertEol: true, + fontSize: 13, + cursorBlink: true, + fontFamily: '"JetBrains Mono", monospace', + }); const fit = new FitAddon(); term.loadAddon(fit); if (containerRef.current) term.open(containerRef.current); diff --git a/client/src/main.tsx b/client/src/main.tsx index 38765da0..eedfbf12 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -57,6 +57,12 @@ import "@fontsource/inter/latin-600.css"; import "@fontsource/inter/latin-700.css"; import "@fontsource/jetbrains-mono/latin-400.css"; import "@fontsource/jetbrains-mono/latin-500.css"; +// The lane terminal renders live Vietnamese output in this font (TerminalView), +// which the latin subset above doesn't cover — without it, missing glyphs fall +// back to another installed face with a different advance width and get +// visually mangled inside xterm's fixed-width cells. +import "@fontsource/jetbrains-mono/vietnamese-400.css"; +import "@fontsource/jetbrains-mono/vietnamese-500.css"; import App from "./App"; import "./i18n"; import "./index.css";