diff --git a/THEMES.md b/THEMES.md
index 1fa8c9c8..e3191f71 100644
--- a/THEMES.md
+++ b/THEMES.md
@@ -144,6 +144,36 @@ font size. Persists alongside theme and skin.
---
+## Typography Tokens
+
+The three font tokens are declared in `static/style.css`:
+
+- `--font-ui`: user interface chrome, controls, composer chrome, labels, and
+ ordinary non-code UI text
+- `--font-conversation`: user/assistant message prose
+- `--font-mono`: code, tool details, logs, identifiers, and terminal text
+
+By default:
+
+```css
+--font-conversation: var(--font-ui);
+--font-mono: ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
+```
+
+For CSS-based custom skins and extension stylesheets/scripts, override the three
+font tokens directly (`--font-ui`, `--font-conversation`, `--font-mono`) on the
+skin root (`:root[data-skin="..."]`), including any mode-specific variant.
+`window.registerHermesSkin()` currently accepts only the documented design-token
+allowlist (no `--font-*` tokens), so custom font families should be supplied via
+CSS rather than skin registration.
+
+Leaving `--font-conversation` untouched keeps prose coupled to `--font-ui`;
+override it only when a different reading face is explicitly required. Avoid
+hard-coded selector-level font declarations when one of these tokens already
+expresses the intent.
+
+---
+
## How It Works Internally
1. **Theme:** `document.documentElement.classList.toggle('dark', isDark)` —
diff --git a/docs/UIUX-GUIDE.md b/docs/UIUX-GUIDE.md
index 5791fe3b..0fe44958 100644
--- a/docs/UIUX-GUIDE.md
+++ b/docs/UIUX-GUIDE.md
@@ -96,16 +96,25 @@ unless the code and tests prove it is implemented.
## Typography and content
-Use split typography intentionally:
+Use three explicit font tokens:
-- assistant prose uses the same system sans stack as the rest of the UI by
- default (`--font-ui` in `static/style.css`),
-- editorial serif assistant prose is historical/proposal or skin-scoped only;
- do not reintroduce a global assistant serif without explicit design approval
- plus code and test evidence,
-- user bubbles and functional UI also use the system sans stack unless a scoped
- skin intentionally overrides typography,
-- monospace is for code, file paths, commands, tool names, and compact metadata.
+- `--font-ui`: shell chrome, controls, composer, labels, and ordinary UI text
+- `--font-conversation`: user/assistant message prose; by default this is
+ `var(--font-ui)` in `static/style.css`
+- `--font-mono`: code, file paths, command lines, tool payloads, technical logs,
+ and terminal output
+
+Use semantic tokens for typography. Keep prose on `--font-conversation` by
+default so it tracks `--font-ui` whenever a skin intentionally retunes UI type.
+Override `--font-conversation` only for a skin that intentionally wants a
+distinct prose face.
+Conversation prose must remain the system sans by default. Do not introduce a
+global conversation serif through the `--font-conversation` token or
+selector-level overrides without explicit design approval plus code and test
+evidence; keep distinct editorial prose typography explicitly opt-in or
+skin-scoped.
+Avoid hard-coding selector-level font stacks when a token already carries the
+intent.
Keep scale tight. Avoid introducing near-duplicate one-off font sizes, colors,
radius values, or spacing values when an existing token works.
diff --git a/static/boot.js b/static/boot.js
index e13d35de..c0d50011 100644
--- a/static/boot.js
+++ b/static/boot.js
@@ -3871,5 +3871,5 @@ async function shutdownServer() {
function _showServerStopped() {
var stoppedMsg = (typeof t === 'function' ? t('settings_shutdown_stopped_message') : 'Server stopped. You can close this tab.');
- document.body.innerHTML = '
';
+ document.body.innerHTML = '';
}
diff --git a/static/index.html b/static/index.html
index 8e9568de..10e5dc9c 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1718,7 +1718,7 @@
-
+
diff --git a/static/panels.js b/static/panels.js
index f625103c..3024b725 100644
--- a/static/panels.js
+++ b/static/panels.js
@@ -12312,7 +12312,7 @@ function _openAuxAdvancedOptions(taskCfg,cfg){
_auxAdvancedInputHtml('auxAdvancedBaseUrl',t('settings_aux_advanced_base_url')||'Base URL',_auxAdvancedValue(cfg,'base_url'),t('settings_aux_advanced_base_url_desc')||'Optional provider endpoint override.','text','inputmode="url"')+
serviceTierField+
timingFields+
- `${esc(t('settings_aux_advanced_extra_body')||'Extra body JSON')} ${esc(t('settings_aux_advanced_extra_body_desc')||'Optional JSON object merged into the model request body.')} `+
+ `${esc(t('settings_aux_advanced_extra_body')||'Extra body JSON')} ${esc(t('settings_aux_advanced_extra_body_desc')||'Optional JSON object merged into the model request body.')} `+
_auxAdvancedInputHtml('auxAdvancedApiKey',t('settings_aux_advanced_api_key')||'API key override','',apiKeyHint,'text','autocomplete="one-time-code" inputmode="text" readonly onfocus="this.removeAttribute("readonly")"',';-webkit-text-security:disc')+
`${esc(t('settings_aux_advanced_api_key_clear')||'Clear existing API key override')} `;
}
diff --git a/static/style.css b/static/style.css
index eb2e228f..dd3c22fd 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1,4 +1,9 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+ /* ── Typography contract ──
+ --font-ui: shell chrome, controls, composer, labels, and ordinary UI.
+ --font-conversation: user/assistant message prose (defaults to --font-ui).
+ --font-mono: inline code, fenced code blocks, logs, tool/metadata strings, and terminal.
+ */
/* ── Light mode (default) — warm gold-tinted palette from Hermes brand ── */
:root {
--bg:#FEFCF7;--sidebar:#FAF7F0;--border:#E0D8C8;--border2:rgba(0,0,0,0.15);
@@ -15,6 +20,8 @@
--message-body-font-size:14px;--message-body-line-height:1.75;--message-code-font-size:12.5px;--message-pre-code-font-size:13px;--message-table-font-size:12px;
--file-tree-toggle-width:10px;
--font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
+ --font-conversation:var(--font-ui);
+ --font-mono:ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
--surface-subtle:rgba(0,0,0,.025);--surface-subtle-hover:rgba(0,0,0,.045);
--border-subtle:rgba(0,0,0,.08);--border-muted:rgba(0,0,0,.12);
font-family:var(--font-ui);font-size:14px;line-height:1.6;
@@ -141,7 +148,6 @@
--user-bubble-bg:#EFEFEF;--user-bubble-border:#DEDEDE;--user-bubble-text:#242424;--user-bubble-placeholder:#707070;
--user-selection-bg:rgba(0,0,0,0.16);--user-selection-text:#111111;
--font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
- --font-mono:ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
font-family:var(--font-ui);
}
:root.dark[data-skin="graphite"]{
@@ -203,7 +209,7 @@
:root.dark[data-skin="graphite"] .composer-box{box-shadow:0 1px 2px rgba(0,0,0,0.28);}
:root[data-skin="graphite"] .composer-box:focus-within{border-color:var(--accent)!important;box-shadow:0 0 0 2px var(--focus-ring)!important;}
:root[data-skin="graphite"] textarea#msg{font-family:var(--font-ui)!important;font-size:14px;font-weight:430;letter-spacing:0;line-height:1.45;}
- :root[data-skin="graphite"] .msg-body{font-family:var(--font-ui);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
+ :root[data-skin="graphite"] .msg-body{font-family:var(--font-conversation);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
:root[data-skin="graphite"][data-font-size="small"] .msg-body{font-size:11.5px;line-height:1.6;}
:root[data-skin="graphite"][data-font-size="large"] .msg-body{font-size:15px;line-height:1.62;}
:root[data-skin="graphite"][data-font-size="xlarge"] .msg-body{font-size:17px;line-height:1.62;}
@@ -251,7 +257,7 @@
:root[data-skin="graphite"] .btn.primary:hover,
:root[data-skin="graphite"] .update-primary:hover,
:root[data-skin="graphite"] .clarify-submit:hover{background:var(--accent-hover)!important;border-color:var(--accent-hover)!important;}
- /* ── Skin: Codex (OpenAI terminal IDE) ──
+ /* ── Skin: Codex (OpenAI terminal IDE) ──
Full palette rewrite with minimal editor surfaces, crisp contrast,
and restrained OpenAI green action chrome. Uses native system fonts so it stays local,
fast, and license-safe while feeling closer to modern code workbenches. */
@@ -268,7 +274,6 @@
--user-bubble-bg:#EBEBEB;--user-bubble-border:#D9D9D9;--user-bubble-text:#252523;--user-bubble-placeholder:#6A6A68;
--user-selection-bg:rgba(46,122,96,0.20);--user-selection-text:#0D0D0D;
--font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
- --font-mono:ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
font-family:var(--font-ui);
}
:root.dark[data-skin="codex"]{
@@ -330,7 +335,7 @@
:root.dark[data-skin="codex"] .composer-box{box-shadow:0 1px 2px rgba(0,0,0,0.28);}
:root[data-skin="codex"] .composer-box:focus-within{border-color:var(--accent)!important;box-shadow:0 0 0 2px var(--focus-ring)!important;}
:root[data-skin="codex"] textarea#msg{font-family:var(--font-ui)!important;font-size:14px;font-weight:430;letter-spacing:0;line-height:1.45;}
- :root[data-skin="codex"] .msg-body{font-family:var(--font-ui);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
+ :root[data-skin="codex"] .msg-body{font-family:var(--font-conversation);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
:root[data-skin="codex"][data-font-size="small"] .msg-body{font-size:11.5px;line-height:1.6;}
:root[data-skin="codex"][data-font-size="large"] .msg-body{font-size:15px;line-height:1.62;}
:root[data-skin="codex"][data-font-size="xlarge"] .msg-body{font-size:17px;line-height:1.62;}
@@ -395,7 +400,6 @@
--user-bubble-bg:#F0EEE6;--user-bubble-border:#E2DED2;--user-bubble-text:#30302E;--user-bubble-placeholder:#87867F;
--user-selection-bg:rgba(217,119,87,0.24);--user-selection-text:#141413;
--font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
- --font-mono:ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
font-family:var(--font-ui);
}
:root.dark[data-skin="terracotta"]{
@@ -457,7 +461,7 @@
:root.dark[data-skin="terracotta"] .composer-box{box-shadow:0 1px 2px rgba(0,0,0,0.28);}
:root[data-skin="terracotta"] .composer-box:focus-within{border-color:var(--accent)!important;box-shadow:0 0 0 2px var(--focus-ring)!important;}
:root[data-skin="terracotta"] textarea#msg{font-family:var(--font-ui)!important;font-size:14px;font-weight:430;letter-spacing:0;line-height:1.45;}
- :root[data-skin="terracotta"] .msg-body{font-family:var(--font-ui);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
+ :root[data-skin="terracotta"] .msg-body{font-family:var(--font-conversation);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
:root[data-skin="terracotta"][data-font-size="small"] .msg-body{font-size:11.5px;line-height:1.6;}
:root[data-skin="terracotta"][data-font-size="large"] .msg-body{font-size:15px;line-height:1.62;}
:root[data-skin="terracotta"][data-font-size="xlarge"] .msg-body{font-size:17px;line-height:1.62;}
@@ -522,7 +526,6 @@
--user-bubble-bg:#EFEFEF;--user-bubble-border:#DEDEDE;--user-bubble-text:#242424;--user-bubble-placeholder:#707070;
--user-selection-bg:rgba(0,0,0,0.16);--user-selection-text:#111111;
--font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
- --font-mono:ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
font-family:var(--font-ui);
}
:root.dark[data-skin="github"]{
@@ -584,7 +587,7 @@
:root.dark[data-skin="github"] .composer-box{box-shadow:0 1px 2px rgba(0,0,0,0.28);}
:root[data-skin="github"] .composer-box:focus-within{border-color:var(--accent)!important;box-shadow:0 0 0 2px var(--focus-ring)!important;}
:root[data-skin="github"] textarea#msg{font-family:var(--font-ui)!important;font-size:14px;font-weight:430;letter-spacing:0;line-height:1.45;}
- :root[data-skin="github"] .msg-body{font-family:var(--font-ui);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
+ :root[data-skin="github"] .msg-body{font-family:var(--font-conversation);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}
:root[data-skin="github"][data-font-size="small"] .msg-body{font-size:11.5px;line-height:1.6;}
:root[data-skin="github"][data-font-size="large"] .msg-body{font-size:15px;line-height:1.62;}
:root[data-skin="github"][data-font-size="xlarge"] .msg-body{font-size:17px;line-height:1.62;}
@@ -1551,7 +1554,7 @@
.system-health-foot{font-size:11px;color:var(--muted);line-height:1.45;opacity:.82;}
.app-titlebar-icon{display:inline-flex;align-items:center;color:var(--accent);}
.app-titlebar-title{font-size:12px;font-weight:600;color:var(--text);letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:60vw;}
- .app-titlebar-sub{font-size:10px;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;font-family:'SF Mono',ui-monospace,monospace;white-space:nowrap;flex-shrink:0;}
+ .app-titlebar-sub{font-size:10px;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;font-family:var(--font-mono);white-space:nowrap;flex-shrink:0;}
.app-titlebar-sub[hidden]{display:none;}
.app-titlebar-title-popover{position:fixed;z-index:300;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-size:13px;color:var(--text);box-shadow:0 4px 16px rgba(0,0,0,.18);word-break:break-word;line-height:1.4;max-width:calc(100vw - 16px);pointer-events:auto;}
.app-titlebar-hamburger,.app-titlebar-spacer{display:none;width:44px;height:44px;flex-shrink:0;}
@@ -1991,7 +1994,7 @@
.approval-card.collapsed .approval-header{margin-bottom:0;}
.approval-card.collapsed .approval-desc,.approval-card.collapsed .approval-cmd,.approval-card.collapsed .approval-counter,.approval-card.collapsed .approval-btns{display:none;}
.approval-desc{font-size:12px;color:var(--muted);margin-bottom:8px;line-height:1.5;}
- .approval-cmd{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:"SF Mono",ui-monospace,monospace;font-size:12px;color:var(--pre-text);white-space:pre-wrap;word-break:break-all;margin-bottom:14px;max-height:120px;overflow-y:auto;}
+ .approval-cmd{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:8px 12px;font-family:var(--font-mono);font-size:12px;color:var(--pre-text);white-space:pre-wrap;word-break:break-all;margin-bottom:14px;max-height:120px;overflow-y:auto;}
.approval-counter{font-size:11px;color:var(--muted);margin:0 0 12px;font-variant-numeric:tabular-nums;letter-spacing:0.01em;}
.approval-btns{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
.approval-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:8px;font-size:12px;font-weight:600;border:1px solid var(--border2);background:var(--hover-bg);color:var(--text);cursor:pointer;transition:all .15s;white-space:nowrap;}
@@ -2301,7 +2304,7 @@
.role-icon{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0;}
.role-icon.user{background:var(--accent-bg);color:var(--accent-text);border:1px solid var(--accent-bg-strong);}
.role-icon.assistant{background:var(--accent-bg-strong);color:var(--accent-text);border:1px solid var(--accent-bg-strong);}
- .msg-body{font-size:var(--message-body-font-size);line-height:var(--message-body-line-height);color:var(--text);padding-left:30px;max-width:680px;overflow-wrap:anywhere;}
+ .msg-body{font-family:var(--font-conversation);font-size:var(--message-body-font-size);line-height:var(--message-body-line-height);color:var(--text);padding-left:30px;max-width:680px;overflow-wrap:anywhere;}
.msg-body p{margin-bottom:10px;}.msg-body p:last-child{margin-bottom:0;}
.msg-body td p,.msg-body th p{margin:0;}
.msg-body ul,.msg-body ol{margin:6px 0 10px 20px;}.msg-body li{margin-bottom:3px;}
@@ -2317,7 +2320,7 @@
.msg-body > h1:first-child,.msg-body > h2:first-child,.msg-body > h3:first-child,
.msg-body > h4:first-child,.msg-body > h5:first-child,.msg-body > h6:first-child{margin-top:0;}
.msg-body strong{color:var(--strong);font-weight:600;}.msg-body em{color:var(--em);font-style:italic;}
- .msg-body code{font-family:"SF Mono","Fira Code",ui-monospace,monospace;font-size:var(--message-code-font-size);background:var(--code-inline-bg);padding:1px 5px;border-radius:4px;color:var(--code-text);}
+ .msg-body code{font-family:var(--font-mono);font-size:var(--message-code-font-size);background:var(--code-inline-bg);padding:1px 5px;border-radius:4px;color:var(--code-text);}
.msg-body pre{background:var(--code-bg);border:1px solid var(--border);border-radius:10px;padding:14px 16px;overflow-x:auto;contain:content;margin:10px 0;}
.msg-body pre code{background:none;padding:0;border-radius:0;color:var(--pre-text);font-size:var(--message-pre-code-font-size);line-height:1.6;}
.msg-body pre.md-source-block,.preview-md pre.md-source-block{white-space:pre-wrap;overflow-x:hidden;overflow-wrap:anywhere;}
@@ -2385,7 +2388,7 @@
.diff-inline-error{color:#ef4444;font-size:13px;padding:8px 12px;border:1px solid rgba(239,68,68,.2);border-radius:8px;margin:6px 0;}
/* JSON/YAML tree viewer */
.code-tree-wrap{position:relative;}
- .tree-view{padding:4px 0;font-family:'JetBrains Mono',monospace;font-size:13px;}
+ .tree-view{padding:4px 0;font-family:var(--font-mono);font-size:13px;}
.tree-hidden{display:none;}
.tree-toggle-btn{background:none;border:1px solid var(--border);border-radius:4px;padding:1px 8px;font-size:10px;color:var(--muted);cursor:pointer;font-weight:600;}
.tree-toggle-btn:hover{color:var(--text);border-color:var(--muted);}
@@ -2818,7 +2821,7 @@
.workspace-panel-heading.workspace-panel-heading--enabled{cursor:pointer;}
.workspace-panel-heading.workspace-panel-heading--enabled:hover,
.workspace-panel-heading.workspace-panel-heading--enabled:focus-visible{color:var(--text);background:var(--hover-bg);outline:none;}
- .git-badge{grid-column:1 / -1;grid-row:2;justify-self:start;font-size:9px;font-weight:600;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;letter-spacing:.02em;white-space:nowrap;font-family:'SF Mono',ui-monospace,monospace;overflow:hidden;min-width:0;max-width:100%;}
+ .git-badge{grid-column:1 / -1;grid-row:2;justify-self:start;font-size:9px;font-weight:600;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;letter-spacing:.02em;white-space:nowrap;font-family:var(--font-mono);overflow:hidden;min-width:0;max-width:100%;}
.topbar-source-badge{display:inline-flex;align-items:center;margin-left:6px;padding:2px 7px;border-radius:999px;background:var(--accent-bg);color:var(--accent-text);font-size:10px;font-weight:700;letter-spacing:.02em;vertical-align:middle;}
.git-badge.dirty{color:var(--accent-text);background:var(--accent-bg);}
.panel-actions{grid-column:2;grid-row:1;display:flex;gap:4px;flex-shrink:0;margin-left:auto;}
@@ -2897,7 +2900,7 @@
.preview-area{flex:1;overflow:auto;padding:14px;flex-direction:column;gap:8px;display:none;opacity:0;transition:opacity .15s;}
.preview-area.visible{display:flex;opacity:1;}
.preview-path{font-size:11px;color:var(--muted);padding-bottom:8px;border-bottom:1px solid var(--border);flex-shrink:0;}
- .preview-code{font-family:"SF Mono","Fira Code",ui-monospace,monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;color:var(--pre-text);}
+ .preview-code{font-family:var(--font-mono);font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;color:var(--pre-text);}
/* Workspace preview syntax highlighting (#3337): Prism's prism-tomorrow theme
styles BOTH pre[class*="language-"] and code[class*="language-"] with its own
gray background + padding. Prism propagates the language-* class onto the
@@ -2925,7 +2928,7 @@
.preview-md h5{font-size:14px;margin:16px 0 6px;text-transform:uppercase;letter-spacing:0.04em;}
.preview-md h6{font-size:13px;margin:14px 0 6px;color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;}
.preview-md ul,.preview-md ol{margin:4px 0 10px 18px;}.preview-md li{margin-bottom:3px;}
- .preview-md code{font-family:"SF Mono",ui-monospace,monospace;font-size:11.5px;background:var(--code-inline-bg);padding:1px 5px;border-radius:4px;color:var(--code-text);}
+ .preview-md code{font-family:var(--font-mono);font-size:11.5px;background:var(--code-inline-bg);padding:1px 5px;border-radius:4px;color:var(--code-text);}
.preview-md pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:10px 12px;overflow-x:auto;margin:8px 0;}
.preview-md pre code{background:none;padding:0;color:var(--pre-text);font-size:11.5px;line-height:1.55;}
/* Keep original theme background — prevent prism-tomorrow from overriding --code-bg */
@@ -3398,7 +3401,7 @@
.cmd-item-name{font-size:13px;color:var(--text);font-weight:500;}
.cmd-item-parent{color:var(--muted);font-weight:400;}
.cmd-item-subarg{font-weight:600;}
-.cmd-item-path-value{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;font-size:12px;}
+.cmd-item-path-value{font-family:var(--font-mono);font-size:12px;}
.cmd-item-arg{color:var(--muted);font-weight:400;font-style:italic;}
.cmd-item-desc{font-size:11px;color:var(--muted);margin-top:1px;}
.cmd-item-badge{flex-shrink:0;font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;padding:2px 6px;border-radius:999px;border:1px solid var(--border2);color:var(--muted);background:var(--hover-bg);}
@@ -3437,7 +3440,7 @@ body.tts-enabled .msg-tts-btn{display:inline-flex;align-items:center;}
@keyframes tts-pulse{0%,100%{opacity:1}50%{opacity:.5}}
/* ── Edit message inline ── */
-.msg-edit-area{width:100%;background:rgba(255,255,255,.05);border:1px solid var(--accent-bg);border-radius:8px;color:var(--text);padding:10px 12px;font-size:14px;font-family:inherit;line-height:1.6;resize:none;outline:none;min-height:60px;box-sizing:border-box;box-shadow:0 0 0 3px var(--accent-bg);margin-top:4px;}
+.msg-edit-area{width:100%;background:rgba(255,255,255,.05);border:1px solid var(--accent-bg);border-radius:8px;color:var(--text);padding:10px 12px;font-size:14px;font-family:var(--font-conversation);line-height:1.6;resize:none;outline:none;min-height:60px;box-sizing:border-box;box-shadow:0 0 0 3px var(--accent-bg);margin-top:4px;}
.msg-edit-bar{display:flex;gap:8px;margin-top:8px;margin-bottom:4px;}
.msg-edit-send{background:var(--accent);color:#fff;border:none;border-radius:8px;padding:6px 16px;font-size:13px;font-weight:600;cursor:pointer;transition:opacity .15s;}
.msg-edit-send:hover{opacity:.85;}
@@ -3742,7 +3745,7 @@ body.resizing .sidebar{transition:none!important;}
.tool-worklog-group[data-tool-worklog-group="1"]:not([data-run-activity-group="1"]) .as-dot{
display:none;
}
-.tool-worklog-running-label{display:none;color:var(--accent-text);font-family:'SF Mono',ui-monospace,monospace;font-size:12px;font-weight:600;line-height:1.4;}
+.tool-worklog-running-label{display:none;color:var(--accent-text);font-family:var(--font-mono);font-size:12px;font-weight:600;line-height:1.4;}
.tool-worklog-group[data-tool-worklog-running="1"] .tool-worklog-running-label{display:inline-flex;}
.tool-worklog-body{
padding-left:0;
@@ -3971,7 +3974,7 @@ body.resizing .sidebar{transition:none!important;}
.tool-worklog-tool-group-label,
.tg-sum{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.tool-worklog-tool-group-running,
-.tg-run{display:none;color:var(--accent-text);font-family:'SF Mono',ui-monospace,monospace;font-size:12px;font-weight:600;line-height:1.4;white-space:nowrap;}
+.tg-run{display:none;color:var(--accent-text);font-family:var(--font-mono);font-size:12px;font-weight:600;line-height:1.4;white-space:nowrap;}
.tool-worklog-tool-group-body,
.tool-group-body{overflow:hidden;max-height:0;transition:max-height .3s ease;}
.tool-worklog-tool-group.open .tool-worklog-tool-group-body,
@@ -4055,9 +4058,9 @@ body.resizing .sidebar{transition:none!important;}
.tool-card-detail-lead pre{margin:0;color:var(--text);font-family:var(--font-mono);font-size:var(--message-code-font-size);line-height:1.55;white-space:pre-wrap;word-break:break-word;}
.tool-card-args{margin-bottom:6px;}
.tool-card-args div{font-size:var(--message-code-font-size);line-height:1.6;}
-.tool-arg-key{color:var(--blue);font-family:'SF Mono',ui-monospace,monospace;font-size:var(--message-code-font-size);display:block;margin-bottom:2px;}
-.tool-arg-val{color:var(--muted);font-family:'SF Mono',ui-monospace,monospace;font-size:var(--message-code-font-size);white-space:pre-wrap;word-break:break-word;display:block;overflow-x:auto;}
-.tool-card-result pre{font-size:var(--message-code-font-size);color:var(--muted);font-family:'SF Mono',ui-monospace,monospace;white-space:pre-wrap;word-break:break-word;max-height:240px;overflow-y:auto;margin:0;line-height:1.55;width:100%;max-width:100%;box-sizing:border-box;min-width:0;}
+.tool-arg-key{color:var(--blue);font-family:var(--font-mono);font-size:var(--message-code-font-size);display:block;margin-bottom:2px;}
+.tool-arg-val{color:var(--muted);font-family:var(--font-mono);font-size:var(--message-code-font-size);white-space:pre-wrap;word-break:break-word;display:block;overflow-x:auto;}
+.tool-card-result pre{font-size:var(--message-code-font-size);color:var(--muted);font-family:var(--font-mono);white-space:pre-wrap;word-break:break-word;max-height:240px;overflow-y:auto;margin:0;line-height:1.55;width:100%;max-width:100%;box-sizing:border-box;min-width:0;}
/* ── Transparent Stream event rows ── */
/* Inline rhythm: transparent stream events are transcript metadata inside an
@@ -4623,7 +4626,7 @@ body.resizing .sidebar{transition:none!important;}
color:var(--strong);
}
.handoff-summary-body code{
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
font-size:.92em;
background:var(--code-inline-bg);
color:var(--code-text);
@@ -4678,7 +4681,7 @@ body.resizing .sidebar{transition:none!important;}
.compression-card-label{
font-size:11px;
font-weight:700;
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
letter-spacing:.03em;
color:var(--muted);
flex-shrink:0;
@@ -4686,7 +4689,7 @@ body.resizing .sidebar{transition:none!important;}
.compression-card-command{
font-size:11px;
color:var(--text);
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
@@ -4724,7 +4727,7 @@ body.resizing .sidebar{transition:none!important;}
}
.compression-card-focus .compression-card-focus-label{
color:var(--muted);
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
}
.compression-card-error-body{
color:#fecaca;
@@ -4760,7 +4763,7 @@ body.resizing .sidebar{transition:none!important;}
.compression-reference-kind{
font-size:11px;
font-weight:700;
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
letter-spacing:.03em;
flex-shrink:0;
white-space:nowrap;
@@ -4812,7 +4815,7 @@ body.resizing .sidebar{transition:none!important;}
}
.compression-reference-body pre{
margin:0;
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
font-size:11px;
line-height:1.55;
white-space:pre-wrap;
@@ -4850,7 +4853,7 @@ body.resizing .sidebar{transition:none!important;}
.compression-complete-label{
font-size:11px;
font-weight:700;
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
letter-spacing:.03em;
flex-shrink:0;
}
@@ -4861,7 +4864,7 @@ body.resizing .sidebar{transition:none!important;}
text-overflow:ellipsis;
white-space:nowrap;
color:var(--text);
- font-family:'SF Mono',ui-monospace,monospace;
+ font-family:var(--font-mono);
font-size:11px;
}
.compression-complete-toggle{
@@ -4969,7 +4972,7 @@ main.main > #mainPlugin{display:none;}
.settings-section-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:20px;padding-bottom:14px;border-bottom:1px solid var(--border);}
.settings-section-title{font-size:18px;font-weight:600;letter-spacing:-.01em;color:var(--text);line-height:1.3;margin-bottom:4px;}
.settings-section-meta{font-size:13px;color:var(--muted);line-height:1.55;}
-.settings-version-badge{display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:var(--surface);color:var(--muted);font-size:11px;font-weight:600;font-family:'SF Mono',ui-monospace,SFMono-Regular,Menlo,monospace;flex-shrink:0;align-self:flex-start;letter-spacing:.02em;}
+.settings-version-badge{display:inline-flex;align-items:center;padding:3px 8px;border-radius:999px;background:var(--surface);color:var(--muted);font-size:11px;font-weight:600;font-family:var(--font-mono);flex-shrink:0;align-self:flex-start;letter-spacing:.02em;}
#checkUpdatesBlock{display:inline-flex;align-items:center;gap:0.5em;font-size:12px;flex-shrink:0;align-self:flex-start;flex-wrap:wrap;}
#checkUpdatesBlock .btn-tiny{padding:4px 10px;border-radius:8px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:11px;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:5px;transition:border-color .15s,color .15s;}
#checkUpdatesBlock .btn-tiny:hover{border-color:var(--accent);color:var(--accent);}
@@ -5382,7 +5385,7 @@ main.main > #mainPlugin{display:none;}
border:1px solid var(--border);
border-radius:8px;
font-size:12px;
- font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
+ font-family:var(--font-mono);
outline:none;
transition:border-color .15s ease, box-shadow .15s ease;
}
@@ -5569,7 +5572,7 @@ main.main > #mainPlugin{display:none;}
.provider-card-model-tag{
display:inline-block;
padding:2px 8px;
- font-size:10.5px;font-family:ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,monospace;
+ font-size:10.5px;font-family:var(--font-mono);
color:var(--muted);
background:var(--surface);
border:1px solid var(--border);
@@ -5640,7 +5643,7 @@ main.main > #mainPlugin{display:none;}
.cron-script-badge{flex-shrink:0;font-size:12px;line-height:1;opacity:.92;}
.cron-script-job-banner{margin-bottom:12px;}
.cron-script-job-banner .detail-alert-title{font-weight:600;color:rgba(245,158,11,.98);}
-.detail-script{background:var(--sidebar);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-size:12px;white-space:pre-wrap;line-height:1.55;color:var(--text);font-family:'SF Mono',ui-monospace,monospace;word-break:break-word;}
+.detail-script{background:var(--sidebar);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-size:12px;white-space:pre-wrap;line-height:1.55;color:var(--text);font-family:var(--font-mono);word-break:break-word;}
.detail-hint{font-size:11px;color:var(--muted);line-height:1.5;}
.cron-script-card .detail-hint,.cron-script-card-hint{margin-top:8px;}
.detail-badge.cron-mode-badge.script{background:rgba(148,163,184,.14);color:var(--text-secondary);}
@@ -5949,7 +5952,8 @@ main.main > #mainPlugin{display:none;}
.msg-body { padding-left: var(--msg-rail); padding-top: 8px; max-width: var(--msg-max); }
.msg-body code,
.msg-body pre,
-.msg-body table { font-family: 'SF Mono', ui-monospace, monospace; }
+.msg-body table { font-family:var(--font-mono); }
+body code[class*="language-"],body pre[class*="language-"],body pre code[class*="language-"]{font-family:var(--font-mono);}
.msg-body:empty { display: none; }
.assistant-turn { width: 100%; }
.assistant-turn-blocks { display: flex; flex-direction: column; }
@@ -6279,7 +6283,7 @@ main.main > #mainPlugin{display:none;}
transition: max-height .22s ease, opacity .18s ease, padding .22s ease, border-top-color .22s ease;
}
.thinking-card.open .thinking-card-body { max-height: 260px; overflow-y: auto; opacity: 1; padding: 8px 12px; border-top-color: var(--accent-bg-strong); }
-.thinking-card-body pre { font-family: 'SF Mono', ui-monospace, monospace; font-size: 11px; line-height: 1.6; color: var(--muted); white-space: pre-wrap; word-break: break-word; margin: 0; }
+.thinking-card-body pre { font-family:var(--font-mono); font-size: 11px; line-height: 1.6; color: var(--muted); white-space: pre-wrap; word-break: break-word; margin: 0; }
/* ── Tool cards: quiet disclosure rows ── */
.tool-card { background:transparent;border:0;border-left:0;border-radius:0;margin-top:0;margin-bottom:0; }
@@ -6417,7 +6421,7 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
.skill-file-breadcrumb{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding-bottom:10px;border-bottom:1px solid var(--border-subtle,var(--border));}
.skill-file-back{font-size:12px;color:var(--accent-text);text-decoration:none;font-weight:500;}
.skill-file-back:hover{text-decoration:underline;}
-.skill-file-path{font-size:12px;color:var(--muted);font-family:'SF Mono',ui-monospace,monospace;}
+.skill-file-path{font-size:12px;color:var(--muted);font-family:var(--font-mono);}
.skill-file-code{background:var(--sidebar);border:1px solid var(--border);border-radius:8px;padding:12px 14px;font-size:12px;overflow:auto;max-width:960px;margin:0 auto;}
@media(max-width:700px){
@@ -6432,7 +6436,7 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
.detail-row:last-child{border-bottom:none;}
.detail-row-label{flex:0 0 140px;color:var(--muted);font-size:12px;}
.detail-row-value{flex:1;min-width:0;color:var(--text);word-break:break-word;}
-.detail-row-value code{background:var(--sidebar);border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:12px;font-family:'SF Mono',ui-monospace,monospace;}
+.detail-row-value code{background:var(--sidebar);border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:12px;font-family:var(--font-mono);}
.detail-badge{display:inline-block;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600;border:1px solid var(--border);color:var(--muted);}
.detail-badge.active{background:var(--accent-bg);color:var(--accent-text);border-color:var(--accent-bg-strong,var(--accent-bg));}
.detail-badge.ok{background:rgba(100,200,100,.12);color:rgba(100,200,100,.95);border-color:rgba(100,200,100,.3);}
@@ -6457,7 +6461,7 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
.detail-card-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px;}
.detail-expand-toggle{border:1px solid var(--border);background:var(--surface);color:var(--muted);border-radius:6px;padding:3px 8px;font-size:11px;line-height:1.3;cursor:pointer;}
.detail-expand-toggle:hover{color:var(--text);border-color:var(--accent);}
-.detail-prompt{background:var(--sidebar);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-size:12px;white-space:pre-wrap;line-height:1.55;color:var(--text);font-family:'SF Mono',ui-monospace,monospace;max-height:240px;overflow-y:auto;}
+.detail-prompt{background:var(--sidebar);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-size:12px;white-space:pre-wrap;line-height:1.55;color:var(--text);font-family:var(--font-mono);max-height:240px;overflow-y:auto;}
.detail-prompt.expanded{max-height:none;overflow-y:visible;}
.detail-run-item{border-top:1px solid var(--border);padding:8px 0;}
.detail-run-item:first-child{border-top:none;}
@@ -6475,11 +6479,11 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
.workspace-artifact-empty{padding:16px 8px;color:var(--muted);font-size:12px;line-height:1.5;text-align:center;}
.workspace-artifact-item{display:block;width:100%;text-align:left;background:var(--surface-subtle);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 10px;margin-bottom:6px;cursor:pointer;}
.workspace-artifact-item:hover{border-color:var(--accent);}
-.workspace-artifact-filename{font-family:'SF Mono',ui-monospace,monospace;font-size:12px;overflow-wrap:anywhere;}
-.workspace-artifact-directory{display:flex;min-width:0;overflow:hidden;white-space:nowrap;font-family:'SF Mono',ui-monospace,monospace;font-size:11px;color:var(--muted);}
-.workspace-artifact-directory-head{min-width:0;flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;}
-.workspace-artifact-directory-tail{flex:0 0 auto;min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;}
-.workspace-artifact-meta{display:inline-block;margin-top:4px;padding:1px 5px;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11px;line-height:1.3;}
+ .workspace-artifact-filename{font-family:var(--font-mono);font-size:12px;overflow-wrap:anywhere;}
+ .workspace-artifact-directory{display:flex;min-width:0;overflow:hidden;white-space:nowrap;font-family:var(--font-mono);font-size:11px;color:var(--muted);}
+ .workspace-artifact-directory-head{min-width:0;flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;}
+ .workspace-artifact-directory-tail{flex:0 0 auto;min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;}
+ .workspace-artifact-meta{display:inline-block;margin-top:4px;padding:1px 5px;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11px;line-height:1.3;}
.workspace-artifacts-count{opacity:.65;font-size:11px;}
.rightpanel[data-active-tab="artifacts"] .breadcrumb-bar,.rightpanel[data-active-tab="artifacts"] .file-tree,.rightpanel[data-active-tab="artifacts"] #wsEmptyState{display:none!important;}
.rightpanel[data-active-tab="artifacts"] .workspace-artifacts{display:block;}
@@ -6526,8 +6530,8 @@ main.main > .main-view:not([id="mainChat"]):not([id="mainSettings"]) .main-view-
.cron-schedule-preset-field input[type="time"]{min-width:120px;}
.cron-schedule-preset-field input[type="number"]{width:64px;}
.cron-schedule-preset-time-hint{flex-basis:100%;margin-top:2px;}
-.cron-schedule-preview{font-family:'SF Mono',ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text);opacity:.85;}
-.detail-form-row textarea{resize:vertical;font-family:'SF Mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;}
+.cron-schedule-preview{font-family:var(--font-mono);color:var(--text);opacity:.85;}
+.detail-form-row textarea{resize:vertical;font-family:var(--font-mono);font-size:12px;}
.detail-form-row .detail-form-hint{font-size:11px;color:var(--muted);line-height:1.5;}
.detail-form-warning{font-size:11px;line-height:1.5;border:1px solid rgba(245,158,11,.35);background:rgba(245,158,11,.1);color:rgba(245,158,11,.98);border-radius:8px;padding:8px 10px;}
.cron-once-warning{margin-top:2px;}
@@ -6893,7 +6897,7 @@ main.main.showing-insights > #mainInsights{display:flex;overflow-y:auto;}
font-size:11px;
color:var(--muted);
}
-.kanban-modal-hint code{background:var(--input-bg);padding:1px 5px;border-radius:4px;font-family:'SF Mono',ui-monospace,Menlo,monospace;font-size:11px;color:var(--text);}
+.kanban-modal-hint code{background:var(--input-bg);padding:1px 5px;border-radius:4px;font-family:var(--font-mono);font-size:11px;color:var(--text);}
.kanban-modal-hint em{color:var(--text);font-style:normal;font-weight:600;}
.kanban-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:14px;}
.kanban-modal-error{
@@ -7038,7 +7042,7 @@ main.main.showing-insights > #mainInsights{display:flex;overflow-y:auto;}
.kanban-profile-lane-unassigned .kanban-profile-lane-head{border-bottom:1px solid color-mix(in srgb,var(--warning,#f5c542) 45%,var(--border));padding-bottom:6px}
.kanban-board-in-lane{min-height:0;overflow-x:auto}
.kanban-card-topline{display:flex;gap:6px;align-items:center;margin-bottom:4px;font-size:10px;color:var(--muted)}
-.kanban-card-id{font-family:var(--mono);opacity:.8}
+.kanban-card-id{font-family:var(--font-mono);opacity:.8}
.kanban-badge{border:1px solid var(--border);border-radius:999px;padding:1px 6px;font-size:10px;color:var(--muted)}
.kanban-badge.priority{color:var(--accent)}
.kanban-badge.tenant{color:var(--text)}
@@ -7051,7 +7055,7 @@ main.main.showing-insights > #mainInsights{display:flex;overflow-y:auto;}
.kanban-card-stale-amber{border-color:rgba(245,197,66,.55)}
.kanban-card-stale-red{border-color:rgba(255,95,95,.65)}
.kanban-column.drop-target{outline:2px solid var(--accent);outline-offset:-2px}
-.hermes-kanban-md p{margin:0 0 4px}.hermes-kanban-md code{font-family:var(--mono);font-size:.95em}.hermes-kanban-md h1,.hermes-kanban-md h2,.hermes-kanban-md h3,.hermes-kanban-md h4,.hermes-kanban-md h5,.hermes-kanban-md h6{margin:10px 0 4px;font-weight:650;color:var(--text)}.hermes-kanban-md h1{font-size:15px}.hermes-kanban-md h2{font-size:14px}.hermes-kanban-md h3{font-size:13px}.hermes-kanban-md h4,.hermes-kanban-md h5,.hermes-kanban-md h6{font-size:12px}.hermes-kanban-md ul,.hermes-kanban-md ol{margin:4px 0;padding-left:20px}.hermes-kanban-md li{margin:2px 0}.hermes-kanban-md li.checked{opacity:.6}.hermes-kanban-md li input[type=checkbox]{margin:0 4px 0 0;vertical-align:middle}.hermes-kanban-md table{border-collapse:collapse;margin:6px 0;font-size:11px;width:100%}.hermes-kanban-md td{border:1px solid var(--border);padding:4px 6px;vertical-align:top}.hermes-kanban-md blockquote{margin:4px 0;padding:2px 8px;border-left:3px solid var(--accent);color:var(--muted);font-size:12px}.hermes-kanban-md pre{background:var(--input-bg);border:1px solid var(--border);border-radius:6px;padding:8px;margin:6px 0;overflow-x:auto;font-size:11px;line-height:1.4;color:var(--text)}.hermes-kanban-md hr{border:none;border-top:1px solid var(--border);margin:8px 0}
+.hermes-kanban-md p{margin:0 0 4px}.hermes-kanban-md code{font-family:var(--font-mono);font-size:.95em}.hermes-kanban-md h1,.hermes-kanban-md h2,.hermes-kanban-md h3,.hermes-kanban-md h4,.hermes-kanban-md h5,.hermes-kanban-md h6{margin:10px 0 4px;font-weight:650;color:var(--text)}.hermes-kanban-md h1{font-size:15px}.hermes-kanban-md h2{font-size:14px}.hermes-kanban-md h3{font-size:13px}.hermes-kanban-md h4,.hermes-kanban-md h5,.hermes-kanban-md h6{font-size:12px}.hermes-kanban-md ul,.hermes-kanban-md ol{margin:4px 0;padding-left:20px}.hermes-kanban-md li{margin:2px 0}.hermes-kanban-md li.checked{opacity:.6}.hermes-kanban-md li input[type=checkbox]{margin:0 4px 0 0;vertical-align:middle}.hermes-kanban-md table{border-collapse:collapse;margin:6px 0;font-size:11px;width:100%}.hermes-kanban-md td{border:1px solid var(--border);padding:4px 6px;vertical-align:top}.hermes-kanban-md blockquote{margin:4px 0;padding:2px 8px;border-left:3px solid var(--accent);color:var(--muted);font-size:12px}.hermes-kanban-md pre{background:var(--input-bg);border:1px solid var(--border);border-radius:6px;padding:8px;margin:6px 0;overflow-x:auto;font-size:11px;line-height:1.4;color:var(--text)}.hermes-kanban-md hr{border:none;border-top:1px solid var(--border);margin:8px 0}
@media (max-width: 640px){
.kanban-board{scroll-snap-type:x mandatory;}
@@ -7086,10 +7090,10 @@ main.main.showing-logs > #mainLogs{display:flex;}
.logs-copy{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);border-radius:8px;padding:7px 10px;font-size:12px;font-weight:600;cursor:pointer;transition:background .15s,border-color .15s,color .15s;}
.logs-copy:hover{background:var(--hover-bg);border-color:var(--border2);}
.logs-copy.compact{padding:6px 10px;white-space:nowrap;}
-.logs-status{font-size:12px;color:var(--muted);margin-top:3px;font-family:'SF Mono',ui-monospace,monospace;}
+.logs-status{font-size:12px;color:var(--muted);margin-top:3px;font-family:var(--font-mono);}
.logs-main-body{padding:18px 24px;}
.logs-content{max-width:1200px;}
-.logs-output{min-height:320px;max-height:calc(100vh - 170px);overflow:auto;background:var(--code-bg);border:1px solid var(--border);border-radius:12px;padding:12px 0;font-family:'SF Mono','Fira Code',ui-monospace,monospace;font-size:12px;line-height:1.55;color:var(--pre-text);white-space:pre;}
+.logs-output{min-height:320px;max-height:calc(100vh - 170px);overflow:auto;background:var(--code-bg);border:1px solid var(--border);border-radius:12px;padding:12px 0;font-family:var(--font-mono);font-size:12px;line-height:1.55;color:var(--pre-text);white-space:pre;}
.logs-output.wrap{white-space:pre-wrap;overflow-wrap:anywhere;}
.log-line{padding:0 14px;min-height:1.55em;border-left:3px solid transparent;}
.log-line:hover{background:rgba(255,255,255,.04);}
diff --git a/static/terminal.js b/static/terminal.js
index 82def255..45855932 100644
--- a/static/terminal.js
+++ b/static/terminal.js
@@ -15,6 +15,11 @@ const TERMINAL_UI={
resizing:false,
resizeStartY:0,
resizeStartHeight:0,
+ lastAppliedTheme:null,
+ lastAppliedFontFamily:null,
+ fontFitFrame:null,
+ fontLoadGeneration:0,
+ fontLoadRequest:null,
};
const TERMINAL_HEIGHT_DEFAULT=260;
@@ -114,8 +119,78 @@ function _terminalTheme(){
};
}
-function syncComposerTerminalTheme(){
- if(TERMINAL_UI.term)TERMINAL_UI.term.options.theme=_terminalTheme();
+function _terminalMonoFont(){
+ return _terminalCssVar(
+ '--font-mono',
+ 'ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace'
+ );
+}
+
+function _terminalThemesEqual(left,right){
+ if(left===right)return true;
+ if(!left||!right)return false;
+ const leftKeys=Object.keys(left);
+ const rightKeys=Object.keys(right);
+ return leftKeys.length===rightKeys.length&&leftKeys.every(key=>Object.prototype.hasOwnProperty.call(right,key)&&left[key]===right[key]);
+}
+
+function _scheduleTerminalFontFit(term){
+ if(TERMINAL_UI.fontFitFrame!==null)return;
+ TERMINAL_UI.fontFitFrame=requestAnimationFrame(()=>{
+ TERMINAL_UI.fontFitFrame=null;
+ if(TERMINAL_UI.term!==term||!TERMINAL_UI.open||TERMINAL_UI.collapsed)return;
+ _fitTerminal();
+ });
+}
+
+function _watchTerminalFontLoad(term,fontFamily){
+ const request={
+ generation:TERMINAL_UI.fontLoadGeneration+1,
+ term,
+ fontFamily,
+ };
+ TERMINAL_UI.fontLoadGeneration=request.generation;
+ TERMINAL_UI.fontLoadRequest=request;
+ const fontSet=document.fonts;
+ if(!fontSet||typeof fontSet.load!=='function')return;
+ const fontSize=Number(term.options&&term.options.fontSize);
+ const fontShorthand=(Number.isFinite(fontSize)&&fontSize>0?fontSize:13)+'px '+fontFamily;
+ let load;
+ try{
+ load=document.fonts.load(fontShorthand);
+ }catch(_){
+ return;
+ }
+ Promise.resolve(load).then(fontFaces=>{
+ if(!fontFaces||fontFaces.length===0)return;
+ if(TERMINAL_UI.fontLoadRequest!==request
+ ||TERMINAL_UI.fontLoadGeneration!==request.generation
+ ||TERMINAL_UI.term!==term
+ ||!TERMINAL_UI.open
+ ||TERMINAL_UI.collapsed
+ ||TERMINAL_UI.lastAppliedFontFamily!==fontFamily)return;
+ _scheduleTerminalFontFit(term);
+ },()=>{});
+}
+
+function syncComposerTerminalAppearance(forceFontLoad=false){
+ if(!TERMINAL_UI.term)return;
+ const term=TERMINAL_UI.term;
+ const theme=_terminalTheme();
+ const fontFamily=_terminalMonoFont();
+ if(!_terminalThemesEqual(TERMINAL_UI.lastAppliedTheme,theme)){
+ term.options.theme=theme;
+ TERMINAL_UI.lastAppliedTheme={...theme};
+ }
+ const fontFamilyChanged=TERMINAL_UI.lastAppliedFontFamily!==fontFamily;
+ if(fontFamilyChanged){
+ term.options.fontFamily=fontFamily;
+ TERMINAL_UI.lastAppliedFontFamily=fontFamily;
+ _scheduleTerminalFontFit(term);
+ }
+ if(fontFamilyChanged||forceFontLoad){
+ _watchTerminalFontLoad(term,fontFamily);
+ }
}
function _xtermReady(){
@@ -130,13 +205,15 @@ function _ensureXterm(){
surface.textContent='Terminal library failed to load. Check network access to cdn.jsdelivr.net.';
return null;
}
+ const theme=_terminalTheme();
+ const fontFamily=_terminalMonoFont();
const term=new window.Terminal({
cursorBlink:true,
fontSize:13,
- fontFamily:'Menlo, Monaco, Consolas, "Liberation Mono", monospace',
+ fontFamily,
scrollback:1000,
convertEol:false,
- theme:_terminalTheme(),
+ theme,
});
let fitAddon=null;
if(window.FitAddon&&typeof window.FitAddon.FitAddon==='function'){
@@ -162,7 +239,10 @@ function _ensureXterm(){
});
TERMINAL_UI.term=term;
TERMINAL_UI.fitAddon=fitAddon;
+ TERMINAL_UI.lastAppliedTheme={...theme};
+ TERMINAL_UI.lastAppliedFontFamily=fontFamily;
_fitTerminal();
+ _watchTerminalFontLoad(term,fontFamily);
return term;
}
@@ -571,12 +651,20 @@ function expandComposerTerminal(opts){
}
function _disposeXterm(){
+ TERMINAL_UI.fontLoadGeneration+=1;
+ TERMINAL_UI.fontLoadRequest=null;
+ if(TERMINAL_UI.fontFitFrame!==null){
+ if(typeof cancelAnimationFrame==='function')cancelAnimationFrame(TERMINAL_UI.fontFitFrame);
+ TERMINAL_UI.fontFitFrame=null;
+ }
if(TERMINAL_UI.term){
try{TERMINAL_UI.term.dispose();}catch(_){}
}
TERMINAL_UI.term=null;
TERMINAL_UI.fitAddon=null;
TERMINAL_UI.typedLine='';
+ TERMINAL_UI.lastAppliedTheme=null;
+ TERMINAL_UI.lastAppliedFontFamily=null;
const {surface}= _terminalEls();
if(surface)surface.textContent='';
}
@@ -696,8 +784,29 @@ window.addEventListener('resize',()=>{
});
if(window.MutationObserver){
- new MutationObserver(syncComposerTerminalTheme).observe(document.documentElement,{
+ new MutationObserver(()=>syncComposerTerminalAppearance()).observe(document.documentElement,{
attributes:true,
- attributeFilter:['class','data-skin'],
+ attributeFilter:['class','data-skin','style'],
});
+
+ const terminalHead=document.head;
+ if(terminalHead){
+ const terminalHeadStylesheetLoadListener=(event)=>{
+ const target=event && event.target;
+ if(!target||typeof target.tagName!=='string'||target.tagName.toLowerCase()!=='link')return;
+ const rel=typeof target.getAttribute==='function'
+ ? target.getAttribute('rel')
+ : target.rel;
+ if(!String(rel||'').toLowerCase().split(/\s+/).includes('stylesheet'))return;
+ syncComposerTerminalAppearance(true);
+ };
+ terminalHead.addEventListener('load',terminalHeadStylesheetLoadListener,true);
+ new MutationObserver(()=>syncComposerTerminalAppearance(true)).observe(terminalHead,{
+ attributes:true,
+ attributeFilter:['href','media','disabled'],
+ childList:true,
+ subtree:true,
+ characterData:true,
+ });
+ }
}
diff --git a/static/ui.js b/static/ui.js
index 7483622e..3639c6c1 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -10140,7 +10140,7 @@ function _renderLockManualInstruction(target, res){
code.style.background='rgba(0,0,0,0.05)';
code.style.padding='6px';
code.style.margin='4px 0';
- code.style.fontFamily='ui-monospace,monospace';
+ code.style.fontFamily='var(--font-mono)';
code.style.borderRadius='4px';
code.style.whiteSpace='pre-wrap';
code.style.wordBreak='break-all';
diff --git a/tests/test_embedded_workspace_terminal.py b/tests/test_embedded_workspace_terminal.py
index 2f4a6fe6..a48b836e 100644
--- a/tests/test_embedded_workspace_terminal.py
+++ b/tests/test_embedded_workspace_terminal.py
@@ -247,8 +247,8 @@ def test_terminal_xterm_theme_follows_appearance_tokens():
assert "function _terminalTheme" in terminal_js
assert "_terminalCssVar('--code-bg'" in terminal_js
assert "_terminalCssVar('--pre-text'" in terminal_js
- assert "syncComposerTerminalTheme" in terminal_js
- assert "attributeFilter:['class','data-skin']" in terminal_js
+ assert "syncComposerTerminalAppearance" in terminal_js
+ assert "attributeFilter:['class','data-skin','style']" in terminal_js
assert "background:var(--code-bg)" in style_css
assert "color:var(--pre-text)" in style_css
diff --git a/tests/test_graphite_skin.py b/tests/test_graphite_skin.py
index 2691b5b3..c4c8c470 100644
--- a/tests/test_graphite_skin.py
+++ b/tests/test_graphite_skin.py
@@ -67,6 +67,6 @@ def test_graphite_skin_uses_native_ui_and_mono_font_stacks():
assert "-webkit-font-smoothing:antialiased" in CSS
assert ':root[data-skin="graphite"] textarea#msg' in CSS
assert ':root[data-skin="graphite"] textarea#msg{font-family:var(--font-ui)!important;font-size:14px;' in CSS
- assert ':root[data-skin="graphite"] .msg-body{font-family:var(--font-ui);font-size:13px;font-weight:430;' in CSS
+ assert ':root[data-skin="graphite"] .msg-body{font-family:var(--font-conversation);font-size:13px;font-weight:430;' in CSS
assert ':root[data-skin="graphite"][data-font-size="large"] .msg-body{font-size:15px;' in CSS
assert ':root[data-skin="graphite"] .tool-card-name' in CSS
diff --git a/tests/test_typography_contract.py b/tests/test_typography_contract.py
new file mode 100644
index 00000000..adc24237
--- /dev/null
+++ b/tests/test_typography_contract.py
@@ -0,0 +1,856 @@
+from pathlib import Path
+import json
+import re
+import shutil
+import subprocess
+
+import pytest
+try:
+ from playwright.sync_api import Error as PlaywrightError, sync_playwright
+except Exception: # pragma: no cover - dependency optional
+ PlaywrightError = None
+ sync_playwright = None
+
+
+REPO = Path(__file__).parent.parent
+CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
+TERMINAL_JS = (REPO / "static" / "terminal.js").read_text(encoding="utf-8")
+PANEL_JS = (REPO / "static" / "panels.js").read_text(encoding="utf-8")
+UI_JS = (REPO / "static" / "ui.js").read_text(encoding="utf-8")
+BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
+INDEX_HTML = (REPO / "static" / "index.html").read_text(encoding="utf-8")
+NODE = shutil.which("node")
+
+
+def _iter_root_skin_blocks(css):
+ selector_re = re.compile(r'(:root(?:\.dark)?\[data-skin="[^"]+"\][^{]*?)\{')
+ for selector_match in selector_re.finditer(css):
+ selector = selector_match.group(1).strip()
+ idx = selector_match.end()
+ depth = 1
+ end = idx
+ while end < len(css) and depth:
+ ch = css[end]
+ if ch == "{":
+ depth += 1
+ elif ch == "}":
+ depth -= 1
+ end += 1
+ if depth:
+ continue
+ yield selector, css[idx : end - 1]
+
+
+def _get_root_skin_block(css, skin):
+ target = f':root[data-skin="{skin}"]'
+ for selector, block in _iter_root_skin_blocks(css):
+ if selector == target:
+ return block
+ return ""
+
+
+def _font_stack_offenders(css):
+ cleaned = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
+ pattern = re.compile(r"(?i)\b(font-family|font)\s*:\s*([^;]+);")
+ offenders = []
+ for _, declaration in pattern.findall(cleaned):
+ value = declaration.strip().lower()
+ if ("monospace" not in value and "ui-monospace" not in value):
+ continue
+ if value == "inherit":
+ continue
+ offenders.append(declaration)
+ return offenders
+
+
+def test_typography_root_tokens_are_defined():
+ assert '--font-ui:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;' in CSS
+ assert '--font-conversation:var(--font-ui);' in CSS
+ assert '--font-mono:ui-monospace,"SFMono-Regular","SF Mono",Menlo,Consolas,"Liberation Mono",monospace;' in CSS
+
+
+def test_msg_body_uses_conversation_font():
+ assert '.msg-body{font-family:var(--font-conversation);font-size:var(--message-body-font-size);line-height:var(--message-body-line-height);' in CSS
+
+
+def test_builtin_skin_msg_body_rules_use_conversation_font():
+ for skin in ("graphite", "codex", "terracotta", "github"):
+ selector = (
+ f':root[data-skin="{skin}"] .msg-body'
+ '{font-family:var(--font-conversation);font-size:13px;font-weight:430;letter-spacing:0;line-height:1.6;}'
+ )
+ assert selector in CSS
+
+
+def test_no_skin_msg_body_rules_use_root_font_ui_token():
+ assert not re.search(
+ r':root(?:\.dark)?\[data-skin="[^"]+"\]\s*\.msg-body\s*\{[^{}]*\bfont-family\s*:\s*var\(--font-ui\)\s*;[^{}]*\}',
+ CSS,
+ re.S,
+ )
+
+
+def test_no_skin_redefines_font_conversation():
+ for selector, block in _iter_root_skin_blocks(CSS):
+ if '--font-conversation:' in block:
+ raise AssertionError(f'Unexpected skin-level --font-conversation: {selector}')
+
+
+def test_nous_skin_keeps_monospace_ui_default():
+ assert _get_root_skin_block(CSS, "nous")
+ assert re.search(
+ r'--font-ui:"SF Mono","Roboto Mono","Courier New",monospace;',
+ _get_root_skin_block(CSS, "nous"),
+ re.S,
+ )
+
+
+def test_geist_and_neon_skins_set_expected_font_ui_tokens():
+ geist_block = _get_root_skin_block(CSS, "geist-contrast")
+ assert geist_block
+ assert "--font-ui:\"Geist\",\"Geist Sans\",-apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,sans-serif;" in geist_block
+ for skin in ("neon", "neon-soft", "neon-paint"):
+ block = _get_root_skin_block(CSS, skin)
+ assert block, f"Missing {skin} root skin block"
+ assert "--font-ui:system-ui,-apple-system,sans-serif;" in block
+
+
+def test_no_stale_var_mono_usage_or_literal_monospace_font_family_stacks():
+ assert 'var(--mono' not in CSS
+ offending_lines = _font_stack_offenders(CSS)
+ assert not offending_lines, f"Unexpected literal monospace font stacks: {offending_lines[:4]}"
+
+
+def test_edit_surfaces_use_font_contract_tokens_structurally():
+ assert re.search(
+ r"\.msg-edit-area\{[^}]*font-family:var\(--font-conversation\)",
+ CSS,
+ re.S,
+ ), ".msg-edit-area must use var(--font-conversation)"
+ assert re.search(
+ r'