包含
- Settings 中的 Appearance 与 Font
- 首帧、React 水合后及系统明暗变化
- 主窗口、桌面浮层和新建窗口
- 表面、文字、边框、状态、focus、composer 和 DAG
回答三个问题:主题由哪里选择和持久化;一次设置会影响哪些界面;怎样保证新增或修改主题时不会只改变部分组件、部分窗口或部分状态。
dark、light 等具体主题名写分支。THEME_PREFS 生成主题卡片;字体选项来自 font-pref.ts。
agentic_theme 写入 localStorage;字体同时维护 cookie 以保证首帧。
首帧脚本和 hook 都只设置 <html data-theme> 与 --font-sans。
每个内置主题定义同一 token 契约;:root 只负责未知值和 Custom 的回退。
Tailwind bridge、CSS modules、桌面浮层全部读取 token,不判断具体主题名。
主题文件负责“值”;组件负责“用途”。同一用途在所有主题中使用同一个 token 名。
--bg-primary / secondary / tertiary--bg-input / hover / selected--surface-popover / tooltip--text-bright / primary--text-secondary / muted--nav-color / hover--border / border-light--border-popover--shadow-* / --scrim-*--theme-accent--theme-accent-fill / fill-hover--accent-green / red / yellow--success-soft / warning-soft / danger-soft--selection-bg / --focus-ring--chip-bg / --chip-ring--meter-fill / --meter-track--composer-surface / shadow / filter--provider-icon-bg--dag-ghost| 设置 | 直接状态 | 影响范围 | 禁止 |
|---|---|---|---|
| Appearance | data-theme | 背景、文字、边框、shadow、状态色、tab focus、composer、popover、DAG、desktop overlay | 组件内写 [data-theme="light"] |
| Font | --font-sans | body、button、input、select、textarea、optgroup;显式 mono 区域除外 | 普通组件重新声明系统字体栈 |
| Custom CSS | #user-custom-css | 同一 token 契约,未覆写项从默认主题回退 | 新增只有 Custom 才认识的组件分支 |
| 主题族 | 主题 | 主色 | 作用范围 |
|---|---|---|---|
| Neutral | dark / light | 蓝色 | Default Button、主操作填充、选中态、链接与强调边界 |
| Warm | beige-dark / beige-light | 橙色 / 珊瑚色 | 同一套组件角色,只替换主题值 |
| Aurora | aurora | 青绿色 | 同一套组件角色,并使用深色 foreground 保证填充对比 |
| Custom | custom | 用户定义 | 未定义时使用基础蓝色回退 |
| 方案 | 采用 | 原因与边界 | 当前缺失能力 |
|---|---|---|---|
CSS custom properties + data-theme | 采用 | 原生级联覆盖 Web、CSS modules、Tailwind arbitrary values 和桌面同源浮层。 | CSS 本身不能验证每个主题是否漏 token,因此增加静态契约检查。 |
Tailwind @theme | 作为 bridge | 只把 CSS token 暴露给 utility 名,不保存另一份色值。 | 不能动态生成 CSS import,主题文件与 TS id 由检查脚本对齐。 |
| JavaScript theme object / Context | 拒绝 | 会复制色值且无法自然覆盖原生 CSS、首帧和 Electron overlay。 | 不提供运行时逐组件主题 props。 |
组件级 [data-theme] 分支 | 拒绝 | 新增主题时必然漏改;改为主题文件提供组件角色 token。 | 组件不得知道内置主题 id。 |
light-dark() 仅按 color-scheme | 拒绝 | 只能表达明暗两类,不能区分 beige、neutral、aurora 和 custom。 | 仍使用各主题显式值。 |
规范依据:CSS Custom Properties Level 1、CSS Color Adjustment 的 color-scheme。现有实现入口见 web/lib/prefs/theme-pref.ts、web/app/globals.css 与 web/app/styles/themes/。
--font-sans;mono 区域显式例外。设计与代码实施已完成。
THEME_PREFS 现在是主题入口的唯一清单;五套内置主题各自完整定义 58 个契约 token;composer、provider icon 与 DAG 只消费角色 token,不再判断具体主题 ID;desktop menu overlay 接受并传播全部主题 ID。共享按钮只消费 --primary,由主题层统一映射主色,不在组件内维护主题分支。主题契约检查已纳入 Web 总检查,安装版验收结果记录在本节下方。
npm --prefix web run check:通过,包括 check:theme-contract。web/node_modules/.bin/tsc -p web/tsconfig.json --noEmit:通过。npm --prefix web run build:通过。/Applications/OpenProgram.app:五套内置主题均读取到完整 58 个 token,页面背景均按主题变化,原生按钮继承字体设置;Aurora 下 desktop main-menu overlay 收到 theme=aurora 并使用对应 token。--accent-orange、--accent-fill、--accent-orange-hover 覆盖方式继续有效。