diff --git a/src/web/public/constants.js b/src/web/public/constants.js
index 20df330a..4a126e81 100644
--- a/src/web/public/constants.js
+++ b/src/web/public/constants.js
@@ -527,6 +527,41 @@ function sortSessionsByActivity(rows) {
return (Array.isArray(rows) ? rows.slice() : []).sort(compareSessionActivity);
}
+// Terminal font stack — the single source for every xterm surface (the main
+// terminal in terminal-ui.js, the log-viewer terminal in panels-ui.js).
+// "Symbols Nerd Font Mono" is a bundled icons-only webfont (fonts/ +
+// @font-face in styles.css): browsers fall back PER GLYPH, so Nerd Font
+// prompt icons (powerline segments, folder/git glyphs from p10k, starship,
+// oh-my-posh) render even though the text fonts carry no private-use-area
+// symbols — while all readable text keeps coming from the text fonts.
+const TERMINAL_FONT_DEFAULT_STACK =
+ '"Fira Code", "Cascadia Code", "JetBrains Mono", "SF Mono", Monaco, "Symbols Nerd Font Mono", monospace';
+
+/**
+ * Resolve the xterm fontFamily from the per-device `terminalFontFamily`
+ * setting. A user-set family (or comma-separated list) is PREPENDED to the
+ * built-in stack, never a replacement — the symbols fallback and a final
+ * `monospace` must survive whatever the user types. Blank input yields the
+ * default. Unquoted names that need quoting for CSS (spaces, digits leading,
+ * etc.) are quoted; embedded quotes are stripped rather than escaped, since
+ * a font name cannot contain them anyway.
+ */
+function resolveTerminalFontFamily(custom) {
+ const raw = typeof custom === 'string' ? custom.trim() : '';
+ if (!raw) return TERMINAL_FONT_DEFAULT_STACK;
+ const families = raw
+ .split(',')
+ .map((f) => f.trim().replace(/^["']|["']$/g, '').replace(/["']/g, '').trim())
+ .filter(Boolean)
+ // Drop generic families the user may append — the default stack already
+ // ends in `monospace`, and a duplicate earlier entry would shadow the
+ // symbols fallback behind it.
+ .filter((f) => !/^(monospace|serif|sans-serif|system-ui)$/i.test(f))
+ .map((f) => (/^[A-Za-z][A-Za-z0-9-]*$/.test(f) ? f : `"${f}"`));
+ if (!families.length) return TERMINAL_FONT_DEFAULT_STACK;
+ return `${families.join(', ')}, ${TERMINAL_FONT_DEFAULT_STACK}`;
+}
+
if (typeof window !== 'undefined') {
window.WEBGL_FALLBACK = WEBGL_FALLBACK;
window.evaluateWebGLLongTaskTrip = evaluateWebGLLongTaskTrip;
@@ -560,6 +595,10 @@ if (typeof window !== 'undefined') {
compare: compareSessionActivity,
sort: sortSessionsByActivity,
};
+ window.CodemanTerminalFont = {
+ DEFAULT_STACK: TERMINAL_FONT_DEFAULT_STACK,
+ resolve: resolveTerminalFontFamily,
+ };
}
// Scheduler API — prioritize terminal writes over background UI updates.
diff --git a/src/web/public/fonts/LICENSE-nerd-fonts.txt b/src/web/public/fonts/LICENSE-nerd-fonts.txt
new file mode 100644
index 00000000..06eb073d
--- /dev/null
+++ b/src/web/public/fonts/LICENSE-nerd-fonts.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ryan L McIntyre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/web/public/fonts/symbols-nerd-font-mono.woff2 b/src/web/public/fonts/symbols-nerd-font-mono.woff2
new file mode 100644
index 00000000..4762129c
Binary files /dev/null and b/src/web/public/fonts/symbols-nerd-font-mono.woff2 differ
diff --git a/src/web/public/i18n.js b/src/web/public/i18n.js
index 4c2b391d..a321b1a9 100644
--- a/src/web/public/i18n.js
+++ b/src/web/public/i18n.js
@@ -326,6 +326,10 @@
// Input settings
Input: '输入',
+ Font: '字体',
+ 'Terminal font': '终端字体',
+ 'Prepended to the built-in stack, so fallbacks (including bundled Nerd Font symbols) keep working. Must be installed on this device. Leave empty for the default.':
+ '置于内置字体栈之前,回退字体(包括内置的 Nerd Font 图标)仍然生效。需已安装在本设备上。留空使用默认值。',
'Local Echo': '本地回显',
'CJK Input': '中日韩输入',
'Extended Keyboard Bar': '扩展键盘栏',
diff --git a/src/web/public/index.html b/src/web/public/index.html
index cdb3110f..480d287f 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -24,6 +24,9 @@
+
+