主题
主题系统
本文引用的源码文件
本文引用的文件
目录
简介
本文件系统性梳理 AI Helper 的主题体系,围绕以下目标展开:
- 解释 CSS 变量 Token 体系的设计原理与组织结构(颜色、字体、间距、阴影、品牌色等)。
- 说明亮/暗/跟随系统三态主题的检测算法、状态管理与持久化机制。
- 给出暗黑模式合规审计工具的使用方法,包括对比度检查、白斑检测与自动化测试流程。
- 提供主题开发指南:新增 Token、样式编写规范、兼容性要求与自定义主题实践。
- 说明主题迁移工具与版本升级的处理方式,确保从硬编码色值到 Token 的平滑演进。
项目结构
主题相关代码集中在共享层与脚本层:
- 共享层:主题 Token 定义与运行时切换逻辑。
- 脚本层:静态门禁、动态审计与批量迁移工具。
- 扩展端页面:选项页与侧边栏在初始化时订阅并应用主题。
graph TB subgraph "共享层" TCSS["theme.css<br/>Token 定义"] TJS["theme.js<br/>主题运行时"] end subgraph "扩展端页面" OPT["options/index.js<br/>选项页入口"] SP["side_panel/index.js<br/>侧边栏入口"] end subgraph "脚本层" AUDIT["theme-dark-audit.mjs<br/>动态审计"] CONTRAST["theme-contrast-check.py<br/>静态对比度门禁"] MIGRATE["theme-migrate.py<br/>批量迁移"] MAP["theme-map.json<br/>映射表"] end TCSS --> OPT TCSS --> SP TJS --> OPT TJS --> SP CONTRAST --> TCSS AUDIT --> TJS MIGRATE --> MAP MIGRATE --> TCSS
图表来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
- src/options/index.js#L100-L175
- src/side_panel/index.js#L1730-L1740
章节来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- src/options/index.js#L100-L175
- src/side_panel/index.js#L1730-L1740
核心组件
- 主题 Token 定义(CSS):集中声明亮/暗两套语义化变量,覆盖表面、文本、品牌、边框、状态、浮层、阴影、滚动条、选择高亮等。
- 主题运行时(JS):管理偏好(light/dark/system)、生效值(light/dark)、防闪烁镜像、媒体查询监听、跨标签页存储同步与订阅通知。
- 静态门禁(Python):校验暗色 Token 对比度是否达标,拦截不合规变更。
- 动态审计(Playwright):加载真实扩展产物,遍历已渲染元素,检测对比度违规与“白斑”泄漏。
- 迁移工具(Python + JSON):将硬编码色值替换为 var(--token),并提供白名单、上下文分发族与灰阶/黑白带策略。
章节来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
架构总览
主题系统采用“CSS Token + JS 运行时 + 静态/动态门禁 + 迁移工具”的分层设计:
- 样式层仅引用 Token,禁止硬编码色值。
- 运行时通过 <html data-theme> 驱动 CSS 变量翻转。
- 门禁保障 Token 质量与一致性。
- 迁移工具降低历史遗留成本。
sequenceDiagram participant User as "用户" participant Options as "选项页" participant ThemeJS as "theme.js" participant Storage as "chrome.storage.local" participant CSS as "theme.css" participant Audit as "theme-dark-audit.mjs" User->>Options : 选择主题(light/dark/system) Options->>ThemeJS : setTheme(pref) ThemeJS->>Storage : 写入 appTheme ThemeJS->>ThemeJS : resolve(pref) ThemeJS->>ThemeJS : apply(resolved) ThemeJS->>CSS : 设置 <html data-theme> Note over CSS : 暗色块覆盖 Token 值 Audit->>ThemeJS : 读取 storage.appTheme Audit->>Audit : 遍历已渲染元素计算对比度/白斑 Audit-->>User : 输出审计结果(通过/失败)
图表来源
详细组件分析
主题 Token 体系(CSS)
- 组织方式
- :root 定义亮色 Token;[data-theme="dark"] 定义暗色 Token。
- 分类清晰:表面、文本、品牌、边框分隔、状态(危险/成功/警告/信息)、Toast 底色、实心填充、蒙层/阴影/杂项、固定不翻转(纸面/代码块)。
- 提供 RGB 通道 Token(如 --brand-rgb、--overlay-rgb),便于透明度组合与阴影复用。
- 关键设计点
- 浮层 elevation 由 ring + overlay-rgb + bg-elevated 组合,随主题自适应。
- 原生控件 accent-color 统一使用品牌色,避免 UA 默认色破坏主题。
- Markdown 在暗色下逐类覆盖,保证可读性与一致性。
- 复杂度与可扩展性
- Token 命名遵循语义化前缀,新增类别只需在两个块中成对补充。
- 通过 RGB 通道与 var() 组合减少重复色值,提升一致性。
classDiagram
class ThemeTokens {
"+表面 : --bg-page, --bg-surface, --bg-elevated, --bg-subtle, --bg-sunken, --bg-disabled, --bg-hover, --bg-active, --surface-rgb"
"+文本 : --text-primary, --text-secondary, --text-tertiary, --text-disabled, --text-inverse, --text-link, --text-on-brand"
"+品牌 : --brand-500/600/700, --brand-fill, --brand-accent, --brand-violet-500/600, --brand-soft-bg/border, --brand-rgb/violet-rgb/accent-rgb, --brand-gradient"
"+边框分隔 : --border-subtle/default/strong, --divider"
"+状态 : danger/success/warning/info (500/strong/soft-bg/border/text)"
"+浮层阴影 : --scrim, --overlay-rgb, --shadow-sm/md/lg, --elev-ring/pop/float/surface, --tooltip-bg, --scrollbar-thumb, --selection-bg"
"+固定不翻转 : --mark-bg, --paper, --code-bg, --code-fg"
}
图表来源
章节来源
主题运行时(JS)
- 状态模型
- preference:用户偏好(light | dark | system),持久化于 chrome.storage.local 的 appTheme。
- resolved:生效值(light | dark),写入 <html data-theme> 供 CSS 变量翻转。
- 防闪烁
- 模块顶层同步读取 localStorage 镜像 appThemeResolved,先落一次属性,再异步 initTheme 覆盖。
- 系统主题跟随
- 监听 matchMedia('(prefers-color-scheme: dark)'),当 preference=system 时自动切换 resolved。
- 跨标签同步
- 监听 chrome.storage.onChanged,当其他标签修改 appTheme 时本地更新并触发订阅者。
- 订阅机制
- subscribeTheme(cb) 收集回调,resolved 变化时调用,用于重绘 Mermaid 等依赖主题的元素。
flowchart TD
Start(["模块加载"]) --> Cache["读取 localStorage.appThemeResolved"]
Cache --> ApplyCache{"有缓存?"}
ApplyCache --> |是| SetAttr["documentElement.setAttribute('data-theme', cached)"]
ApplyCache --> |否| WaitInit["等待 initTheme()"]
SetAttr --> WaitInit
WaitInit --> MQ["注册 prefers-color-scheme 监听"]
MQ --> Storage["读取 chrome.storage.local.appTheme"]
Storage --> Resolve["resolve(preference)"]
Resolve --> Apply["apply(resolved)"]
Apply --> Notify["通知订阅者"]
Notify --> End(["就绪"])
图表来源
章节来源
静态对比度门禁(Python)
- 功能
- 解析 theme.css 的 :root 与 [data-theme="dark"] 块,按 WCAG 1.4.3 计算对比度。
- 文本类阈值 4.5:1,图形/强调类 3:1,装饰性分隔仅 INFO 提示。
- 未达标即非零退出,阻止不合规 Token 进入分支。
- 输入/输出
- 输入:theme.css 路径。
- 输出:[AA FAIL] 或 [INFO] 行,最后汇总未达标数量。
flowchart TD
A["读取 theme.css"] --> B["解析暗色块 tokens"]
B --> C["识别文本/图形/装饰性分类"]
C --> D["计算相对亮度与对比度"]
D --> E{"是否低于阈值?"}
E --> |是| F["输出 [AA FAIL]"]
E --> |否| G["跳过或输出 [INFO]"]
F --> H["返回非零退出码"]
G --> I["返回零退出码"]
图表来源
章节来源
动态暗黑审计(Playwright)
- 功能
- 启动 Chromium 加载 dist 扩展,将侧边栏与配置页切到 dark。
- 逐个面板/Tab 展开,遍历所有已渲染元素,实测前景/背景(含 alpha 合成到 --bg-page 底)。
- 检测两类问题:WCAG 对比度不达标、暗色下近纯白大块背景(白斑)。
- 注入 Toast/弹窗表单控件进行瞬态回归防护。
- 运行方式
- 需先 npm run build:silent。
- 可选 --keep 保留浏览器以便人工核对。
- 存在违规或 pageerror 则 exit 1。
sequenceDiagram participant CLI as "命令行" participant PW as "Playwright" participant Ext as "dist 扩展" participant Page as "side_panel.html / options.html" participant Auditor as "页内审计函数" CLI->>PW : 启动并加载扩展 PW->>Ext : 安装 dist PW->>Page : 打开侧边栏/配置页 PW->>Page : 设置 storage.appTheme='dark' PW->>Auditor : evaluate(AUDIT_FN) Auditor-->>PW : violations + blotches PW-->>CLI : 汇总报告 + 退出码
图表来源
- scripts/theme-dark-audit.mjs#L18-L36
- scripts/theme-dark-audit.mjs#L39-L121
- scripts/theme-dark-audit.mjs#L141-L257
章节来源
主题迁移工具(Python + JSON)
- 能力
- 扫描 CSS/JS 中的十六进制色值与 rgba 三元组。
- 根据 theme-map.json 的 direct/contextDispatch/rgbaFamilies/bands/whitelist 规则替换为 var(--token)。
- 支持 --check/--missing/--missing-rgba/--ranges/--mode js 等模式。
- 对 white/black 关键字按上下文智能映射(文字用 on-brand,背景用 surface)。
- 关键策略
- 黑/白带:black 透明叠加走 overlay-rgb/shadow-*,white 半透字保持字面量,白底转 surface-rgb。
- 灰阶:灰色三元组统一走 overlay-rgb。
- 白名单:全局豁免与文件作用域豁免并存,避免误报。
- 工作流
- 先用 --check 发现裸色;再用 apply 批量替换;必要时 review 列表人工复核。
flowchart TD
S["输入文件集"] --> M["加载 theme-map.json"]
M --> Scan["扫描 hex/rgba/关键字"]
Scan --> Map["匹配 direct/contextDispatch/rgbaFamilies/bands"]
Map --> WL{"是否在白名单?"}
WL --> |是| Keep["保持原值"]
WL --> |否| Replace["替换为 var(--token)"]
Replace --> Review{"是否需要人工复核?"}
Review --> |是| List["输出 review 列表"]
Review --> |否| Done["完成"]
Keep --> Done
图表来源
章节来源
页面集成与主题 UI
- 选项页
- 初始化时优先调用 initTheme(),防止闪烁。
- 外观主题三分段单选框,change 事件调用 setTheme 持久化并即时翻转。
- 订阅主题变化以同步 UI 选中状态。
- 侧边栏
- 初始化时同样优先 initTheme(),并订阅主题变化以重绘 Mermaid 等依赖主题的元素。
- 提供主题切换入口,调用 setTheme 并刷新。
sequenceDiagram participant Opt as "options/index.js" participant SP as "side_panel/index.js" participant Theme as "theme.js" Opt->>Theme : initTheme() Opt->>Theme : subscribeTheme(syncUI) SP->>Theme : initTheme() SP->>Theme : subscribeTheme(repaintMermaid) Opt->>Theme : setTheme(radio.value) SP->>Theme : setTheme(value)
图表来源
章节来源
依赖关系分析
- 耦合关系
- 页面入口依赖 theme.js 提供的 initTheme/setTheme/subscribeTheme。
- theme.css 被所有页面样式消费,但不应直接引入色值。
- 脚本层工具相互独立:contrast-check 仅读 theme.css;audit 依赖 Playwright 与 dist;migrate 依赖 theme-map.json。
- 外部依赖
- Chrome API:chrome.storage.local、chrome.storage.onChanged。
- Web API:matchMedia、localStorage。
- Node/Playwright:审计脚本运行环境。
graph LR OPT["options/index.js"] --> THEME["shared/theme.js"] SP["side_panel/index.js"] --> THEME THEME --> STORAGE["chrome.storage.local"] THEME --> MQ["matchMedia"] THEME --> LS["localStorage"] CONTRAST["theme-contrast-check.py"] --> CSS["shared/theme.css"] AUDIT["theme-dark-audit.mjs"] --> THEME MIGRATE["theme-migrate.py"] --> MAP["theme-map.json"]
图表来源
- src/options/index.js#L100-L175
- src/side_panel/index.js#L1730-L1740
- src/shared/theme.js#L1-L90
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
章节来源
- src/shared/theme.js#L1-L90
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-migrate.py#L1-L327
- scripts/theme-map.json#L1-L424
性能与可维护性
- 性能
- 主题切换仅在 resolved 真正变化时触发订阅者,避免无意义重绘。
- 防闪烁镜像减少首屏闪烁,提升用户体验。
- 静态门禁在 CI 阶段执行,不影响运行时。
- 可维护性
- Token 语义化命名与分层,新增/调整成本低。
- 迁移工具与白名单机制降低历史债务清理成本。
- 动态审计覆盖瞬态 UI(Toast/弹窗),提高回归质量。
[本节为通用指导,不涉及具体文件分析]
故障排查指南
- 症状:暗色下文字不可读
- 排查步骤
- 运行静态对比度门禁:python3 scripts/theme-contrast-check.py src/shared/theme.css。
- 运行动态审计:node scripts/theme-dark-audit.mjs,查看 violations 列表。
- 检查是否使用了未 Token 化的色值:python3 scripts/theme-migrate.py --check src/side_panel/styles.css src/options/styles.css。
- 排查步骤
- 症状:暗色下出现大面积白底(白斑)
- 排查步骤
- 使用动态审计 blotches 定位具体选择器与尺寸。
- 检查是否错误地将白色作为背景色,应改为 var(--bg-surface) 或 surface-rgb。
- 排查步骤
- 症状:主题切换后 Mermaid 未更新
- 排查步骤
- 确认 side_panel/index.js 已订阅主题变化并重绘 Mermaid。
- 检查 theme.js 的 subscribeTheme 回调是否被正确调用。
- 排查步骤
章节来源
- scripts/theme-contrast-check.py#L70-L110
- scripts/theme-dark-audit.mjs#L236-L257
- scripts/theme-migrate.py#L296-L323
- src/side_panel/index.js#L1730-L1740
结论
本主题系统通过 Token 化、运行时三态切换、静态/动态门禁与迁移工具,构建了高质量、可演进、可审计的主题基础设施。建议在后续开发中严格遵循 Token 化规范,持续使用门禁与审计保障暗色体验,并通过迁移工具逐步消除历史硬编码色值。
[本节为总结性内容,不涉及具体文件分析]
附录:主题开发规范与迁移流程
新增 Token 的步骤
- 在 theme.css 的 :root 与 [data-theme="dark"] 中成对添加语义化 Token。
- 若涉及透明度组合,新增对应 RGB 通道 Token(如 --xxx-rgb)。
- 运行静态对比度门禁,确保暗色 Token 达标。
- 在 theme-map.json 中补充 direct/contextDispatch/rgbaFamilies 映射(如需)。
- 使用迁移工具扫描并替换旧色值,必要时人工复核 review 列表。
章节来源
- src/shared/theme.css#L8-L228
- scripts/theme-contrast-check.py#L70-L110
- scripts/theme-migrate.py#L45-L154
- scripts/theme-map.json#L1-L424
样式编写规范
- 只允许引用 theme.css 中的 Token,禁止硬编码色值。
- 背景色使用 --bg-surface / --bg-elevated / --bg-subtle 等语义 Token。
- 文字色使用 --text-primary / --text-secondary / --text-tertiary。
- 品牌强调使用 --brand-500/600/700 与 --brand-accent。
- 状态色使用 --danger-* / --success-* / --warning-* / --info-*。
- 浮层与阴影使用 --elev-* / --shadow-* / --scrim / --overlay-rgb。
- 原生控件 accent-color 使用 --brand-500。
章节来源
在组件中使用主题变量
- 示例路径(不展示代码内容)
- 选项页主题 UI:src/options/index.js#L160-L175
- 侧边栏主题初始化与订阅:src/side_panel/index.js#L1730-L1740
- 实践要点
- 页面初始化时调用 initTheme(),并在 change 事件中调用 setTheme()。
- 使用 subscribeTheme 监听主题变化,重绘依赖主题的元素(如 Mermaid)。
章节来源
创建自定义主题
- 方案一:基于现有 Token 调整
- 在 theme.css 中覆盖特定 Token 的值(注意同时维护亮/暗两块)。
- 运行对比度门禁与动态审计验证效果。
- 方案二:扩展新 Token 族
- 新增语义 Token 与 RGB 通道,完善映射表与迁移规则。
- 在页面中按需引用新 Token。
章节来源
- src/shared/theme.css#L8-L228
- scripts/theme-contrast-check.py#L70-L110
- scripts/theme-dark-audit.mjs#L236-L257
主题迁移工具使用指南
- 常用命令
- 检查裸色:python3 scripts/theme-migrate.py --check src/side_panel/styles.css src/options/styles.css
- 缺失 Token 清单:python3 scripts/theme-migrate.py --missing src/side_panel/styles.css
- 缺失 rgba 三元组:python3 scripts/theme-migrate.py --missing-rgba src/side_panel/styles.css
- 批量替换:python3 scripts/theme-migrate.py src/side_panel/styles.css src/options/styles.css
- 限定行段:--ranges "120-340,500-780"
- JS 模式:--mode js
- 白名单策略
- 全局豁免与文件作用域豁免并存,谨慎使用,避免掩盖真实漏网。
章节来源
自动化测试与门禁集成
- package.json 脚本
- theme:check:静态门禁(迁移工具)
- theme:contrast:静态门禁(对比度)
- theme:audit:动态审计(Playwright)
- 建议流程
- 提交前运行 npm run build:silent。
- 依次执行 theme:contrast、theme:check、theme:audit。
- 全部通过后提交。
章节来源