包含
- Settings 中的 Appearance 与 Font
- 首帧、React 水合后及系统明暗变化
- 主窗口、桌面浮层和新建窗口
- 表面、文字、边框、状态、focus、composer 和 DAG
回答三个问题:主题由哪里选择和持久化;一次设置会影响哪些界面;怎样保证新增或修改主题时不会只改变部分组件、部分窗口或部分状态。
dark、light 等具体主题名写分支。Appearance 是四层:Mode、命名主题包、强调色、CSS overlay。字体选项来自 font-pref.ts。
schema 3 写入 agentic_theme_style + agentic_theme_mode;强调色与 overlay 是同一 prefs 族。字体同时维护 cookie 以保证首帧。
首帧脚本和 hook 设置 data-theme、data-custom-css、强调色 token 与 --font-sans。
每个主题包定义同一 token 契约;强调色只覆盖三个 accent token;CSS overlay 叠在当前包之上。
Tailwind bridge、CSS modules、桌面浮层全部读取 token,不判断具体主题名。
桌面启动和系统配色更新优先读取主界面保存的有效 theme-prefs.json 整份偏好。仅当文件缺失或明暗模式、风格无效时,才使用旧版浏览器存储迁移。浏览器数据库的原始字节扫描无法识别最新记录,不覆盖有效桌面偏好,也不恢复已清除的强调色。网页加载后仍以自身 localStorage 为准并同步桌面缓存。
桌面主页面记录消息成功发送的时间点、首屏初始化、设置修改、系统配色事件、跨窗口偏好同步和 HTML 主题属性变化。记录只含时间、固定事件名称、主题枚举和系统明暗状态,不含聊天内容、页面地址、CSS 或用户路径。桌面 userData 下的 theme-events.json 最多保留 64 条,写入失败不影响主题显示。日志用于确认偶发切换来源,不代表已修复未知触发条件。
主题文件负责“值”;组件负责“用途”。同一用途在所有主题中使用同一个 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"] |
| Accent | --accent-orange / fill / hover | 主操作色、选中边界、桌面错误页与目录列表的链接色 | 新增第四个 accent token 名 |
| Font | --font-sans | body、button、input、select、textarea、optgroup;显式 mono 区域除外 | 普通组件重新声明系统字体栈 |
| CSS overlay | #user-custom-css + data-custom-css | 叠在当前主题包上的可选 snippet;未覆写项仍来自该包 | 把 overlay 再做成第四套皮肤卡片 |
| 主题族 | 主题 | 主色 | 作用范围 |
|---|---|---|---|
| Beige | beige-dark / beige-light | 橙色 / 珊瑚色 | 同一套组件角色,只替换主题值 |
| Neutral | dark / light | 蓝色 | Default Button、主操作填充、选中态、链接与强调边界 |
| Aurora | aurora / aurora-light | 青绿色 | 同一套组件角色,并使用深色 foreground 保证填充对比 |
| 方案 | 采用 | 原因与边界 | 当前缺失能力 |
|---|---|---|---|
CSS 自定义属性 + data-theme | 采用 | 原生级联覆盖 Web、CSS modules、Tailwind arbitrary values 和桌面同源浮层。 | CSS 本身不能验证每个主题是否漏 token,因此增加静态契约检查。 |
Tailwind @theme | 作为 bridge | 只把 CSS token 暴露给 utility 名,不保存另一份色值。 | 不能动态生成 CSS import,主题文件与 TS id 由检查脚本对齐。 |
| JavaScript 主题对象 / Context | 拒绝 | 会复制色值且无法自然覆盖原生 CSS、首帧和 Electron overlay。 | 不提供运行时逐组件主题 props。 |
组件级 [data-theme] 分支 | 拒绝 | 新增主题时必然漏改;改为主题文件提供组件角色 token。 | 组件不得知道内置主题 id。 |
light-dark() 仅按 color-scheme | 拒绝 | 只能表达明暗两类,不能区分 Beige、Neutral、Aurora 三套包。 | 仍使用各主题显式值。 |
规范依据:CSS 自定义属性第 1 级规范、CSS Color Adjustment 的 color-scheme。现有实现入口见 apps/web/lib/prefs/theme-pref.ts、apps/web/app/globals.css 与 apps/web/app/styles/themes/。
custom-light.css 仅作为旧 snippet 回落,取值与 beige-light 一致。--accent-orange / --accent-fill / --accent-orange-hover;空值使用当前包默认。桌面错误页与目录列表的链接色跟随覆盖值,窗口底色仍是包的 --bg-primary。[data-theme=custom] 选择器改写到 data-custom-css。--bg-primary。--font-sans;mono 区域显式例外。--tab-active-bg 使用内容区底色混合 12% 白色,普通标签与分屏组合共用。选中背景在悬停时保持稳定;浅色模式与浏览器标签保持内容区底色。文本输入框和原生下拉框在聚焦时只改变现有边框的主题强调色,不添加 outline、偏移外圈或 box-shadow 边框,不改变控件尺寸。搜索框和聊天输入区由容器绘制唯一边界,内部编辑区保持无边框。导航、复选框、单选框、滑块和嵌入的第三方文档保留各自的键盘聚焦提示。
当前实现采用 Obsidian 式 Appearance:Mode × 命名主题包 × 强调色 × CSS overlay。
schema 3 的 THEME_STYLE_PAIRS 仍是 Beige / Neutral / Aurora 的深浅矩阵。Custom 不再是第四张风格卡;存量 custom / custom-light 迁到 beige,已保存的 CSS 打开 overlay,旧选择器改写到 data-custom-css。强调色独立持久化,只覆盖三个 accent token。composer、provider icon 与 DAG 只消费角色 token。桌面 BrowserWindow 底色仍用包的 --bg-primary;错误页与目录列表链接色在有覆盖时使用强调色。主题契约检查已纳入 Web 总检查。
Windows 测试使用 Node.js 的 fileURLToPath 将模块 URL 转为本地文件路径,避免驱动器路径前出现额外斜杠;主题行为和断言保持不变。