OpenProgram / Web UI

主题是一份完整数据,组件只读取语义 token

回答三个问题:主题由哪里选择和持久化;一次设置会影响哪些界面;怎样保证新增或修改主题时不会只改变部分组件、部分窗口或部分状态。

受众:Web / Desktop 实现者状态:当前规范范围:颜色、字体、跨窗口主题

设计边界

唯一运行时仍是 CSS custom properties。 不引入 JS theme object、CSS-in-JS 或第二套组件皮肤。内置主题必须提供相同 token 集;组件不得按 dark、light 等具体主题名写分支。

包含

  • Settings 中的 Appearance 与 Font
  • 首帧、React 水合后及系统明暗变化
  • 主窗口、桌面浮层和新建窗口
  • 表面、文字、边框、状态、focus、composer 和 DAG

不包含

  • 改变组件尺寸、布局或交互逻辑
  • 自动改写用户已保存的 CSS overlay
  • 以主题名为条件的业务逻辑
  • 把语法高亮或网站内容强制套用应用主题

设置如何影响组件

1 · 选择

设置

Appearance 是四层:Mode、命名主题包、强调色、CSS overlay。字体选项来自 font-pref.ts。

2 · 持久化

浏览器偏好

schema 3 写入 agentic_theme_style + agentic_theme_mode;强调色与 overlay 是同一 prefs 族。字体同时维护 cookie 以保证首帧。

3 · 应用

HTML 属性

首帧脚本和 hook 设置 data-theme、data-custom-css、强调色 token 与 --font-sans。

4 · 解析

完整 token 文件

每个主题包定义同一 token 契约;强调色只覆盖三个 accent token;CSS overlay 叠在当前包之上。

5 · 消费

全部组件

Tailwind bridge、CSS modules、桌面浮层全部读取 token,不判断具体主题名。

偶发主题切换诊断

桌面启动和系统配色更新优先读取主界面保存的有效 theme-prefs.json 整份偏好。仅当文件缺失或明暗模式、风格无效时,才使用旧版浏览器存储迁移。浏览器数据库的原始字节扫描无法识别最新记录,不覆盖有效桌面偏好,也不恢复已清除的强调色。网页加载后仍以自身 localStorage 为准并同步桌面缓存。

桌面主页面记录消息成功发送的时间点、首屏初始化、设置修改、系统配色事件、跨窗口偏好同步和 HTML 主题属性变化。记录只含时间、固定事件名称、主题枚举和系统明暗状态,不含聊天内容、页面地址、CSS 或用户路径。桌面 userData 下的 theme-events.json 最多保留 64 条,写入失败不影响主题显示。日志用于确认偶发切换来源,不代表已修复未知触发条件。

完整 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
设置直接状态影响范围禁止
Appearancedata-theme背景、文字、边框、shadow、状态色、tab focus、composer、popover、DAG、desktop overlay组件内写 [data-theme="light"]
Accent--accent-orange / fill / hover主操作色、选中边界、桌面错误页与目录列表的链接色新增第四个 accent token 名
Font--font-sansbody、button、input、select、textarea、optgroup;显式 mono 区域除外普通组件重新声明系统字体栈
CSS overlay#user-custom-css + data-custom-css叠在当前主题包上的可选 snippet;未覆写项仍来自该包把 overlay 再做成第四套皮肤卡片
主题族主题主色作用范围
Beigebeige-dark / beige-light橙色 / 珊瑚色同一套组件角色,只替换主题值
Neutraldark / light蓝色Default Button、主操作填充、选中态、链接与强调边界
Auroraaurora / aurora-light青绿色同一套组件角色,并使用深色 foreground 保证填充对比

内置主题共享同一组件结构

新建聊天浏览器终端
同一 tab、同一文字层级、同一按钮用途主题只替换 token 值,不替换组件结构或行为。

实现方案比较

方案采用原因与边界当前缺失能力
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/。

验收标准

  • 主题列表设置页只展示 Beige / Neutral / Aurora;CSS imports、主题文件、桌面浮层允许值由检查脚本证明一致。
  • Token 完整六套内置主题(三包 × 深浅)提供相同 token 集;custom-light.css 仅作为旧 snippet 回落,取值与 beige-light 一致。
  • 强调色独立 picker 覆盖 --accent-orange / --accent-fill / --accent-orange-hover;空值使用当前包默认。桌面错误页与目录列表的链接色跟随覆盖值,窗口底色仍是包的 --bg-primary。
  • CSS overlay用户 CSS 叠在当前包上,由 Enable 开关门控;旧 [data-theme=custom] 选择器改写到 data-custom-css。
  • 组件隔离theme 文件之外不存在按具体内置主题名分支的组件 CSS。
  • 跨窗口一致六个主题包 id 原样传给 desktop overlay;桌面窗口底色使用该主题的 --bg-primary。
  • 字体一致普通文本和原生表单控件继承同一 --font-sans;mono 区域显式例外。
  • 主色矩阵Neutral 深浅主题为蓝色,Beige 深浅主题为橙色,Aurora 为青绿色;包默认 accent 仍从 CSS 文件读取。
  • 桌面活动标签深色模式下,标签栏的 --tab-active-bg 使用内容区底色混合 12% 白色,普通标签与分屏组合共用。选中背景在悬停时保持稳定;浅色模式与浏览器标签保持内容区底色。
  • 可见验收默认安装版在深色、浅色和 aurora 下检查主窗口、tab、composer、popover 与桌面浮层。

文本输入框和原生下拉框在聚焦时只改变现有边框的主题强调色,不添加 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 转为本地文件路径,避免驱动器路径前出现额外斜杠;主题行为和断言保持不变。