diff --git a/app/src/App.tsx b/app/src/App.tsx index 600a539..528fbeb 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -170,7 +170,7 @@ export function App() { /> )} - {settingsOpen && config && setSettingsOpen(false)} />} + {settingsOpen && config && setSettingsOpen(false)} onReload={() => { void loadHealth(); void refresh(); }} />} {wizard && { setWizard(false); setActiveId(id); void refresh(); }} onCancel={() => setWizard(false)} />} {deleteTarget && ( void }) { +export function Settings({ config, health, onClose, onReload }: { config: ConfigView; health: DaemonHealth | null; onClose: () => void; onReload: () => void }) { const ref = useRef(null); useEffect(() => { ref.current?.focus(); }, []); @@ -57,7 +57,7 @@ export function Settings({ config, health, onClose }: { config: ConfigView; heal setShellLocal(e.target.value)} onBlur={() => void setConfig({ default_shell: shellLocal })} style={{ width: "100%", padding: 8, marginBottom: 18, background: COLORS.bgPanel, color: COLORS.textPrimary, border: `1px solid ${COLORS.borderStrong}`, borderRadius: 8 }} /> - + ); @@ -70,8 +70,14 @@ function fmtUptime(ms: number): string { return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`; } -function DaemonSection({ health }: { health: DaemonHealth | null }) { +function DaemonSection({ health, onReload }: { health: DaemonHealth | null; onReload: () => void }) { const [confirm, setConfirm] = useState(null); + // Tick so uptime counts up live while the modal is open. + const [, setTick] = useState(0); + useEffect(() => { + const t = setInterval(() => setTick((n) => n + 1), 1000); + return () => clearInterval(t); + }, []); return (
Daemon
@@ -92,7 +98,7 @@ function DaemonSection({ health }: { health: DaemonHealth | null }) {
- diff --git a/app/src/TerminalView.tsx b/app/src/TerminalView.tsx index cc5817a..4b31dd2 100644 --- a/app/src/TerminalView.tsx +++ b/app/src/TerminalView.tsx @@ -22,6 +22,7 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string; const ref = useRef(null); const termRef = useRef(null); const fitRef = useRef(null); + const webglRef = useRef(null); useEffect(() => { if (!ref.current) return; @@ -39,7 +40,9 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string; termRef.current = term; try { - term.loadAddon(new WebglAddon()); + const webgl = new WebglAddon(); + term.loadAddon(webgl); + webglRef.current = webgl; } catch { // webgl unavailable → fall back to canvas/dom renderer silently } @@ -99,6 +102,7 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string; term.dispose(); termRef.current = null; fitRef.current = null; + webglRef.current = null; }; }, [surfaceId]); // eslint-disable-line react-hooks/exhaustive-deps @@ -110,6 +114,10 @@ export function TerminalView({ surfaceId, font, palette }: { surfaceId: string; if (font) { t.options.fontFamily = `'${font.family}', monospace`; t.options.fontSize = font.size; + // The WebGL renderer caches rasterized glyphs in a texture atlas keyed by + // the old font/size; without clearing it the grid keeps rendering stale + // glyphs after a font change. + webglRef.current?.clearTextureAtlas(); } if (palette) t.options.theme = xtermTheme(palette); requestAnimationFrame(() => { try { fitRef.current?.fit(); } catch { /* ignore */ } });