feat: 添加 3 套主题切换系统提升学习效率#6
Conversation
将单一黑金主题升级为 4 选项主题系统: - Obsidian(默认·黑金奢华·夜间) - Parchment(米黄纸·长时间阅读) - Daylight(清亮白·强光环境) - System(跟随 prefers-color-scheme) 技术方案:CSS 变量 + data-theme 属性切换,偏好持久化到 settingsStore,main.tsx 同步初始化防 FOUC,App.tsx 监听 系统主题变化实时跟随。全站(含着陆页)内联硬编码颜色 统一替换为 var(--token)。 SDD 产物: - docs/superpowers/specs/2026-06-23-theme-system-design.md - docs/superpowers/plans/2026-06-23-theme-system-implementation.md Closes #4 Co-authored-by: traeagent <traeagent@users.noreply.github.com>
There was a problem hiding this comment.
代码评审报告: feat: 添加 3 套主题切换系统提升学习效率
风险等级: 高
处理建议: 请求修改
决策摘要: ** 现在不建议合并;system 主题路径在 matchMedia 不可用时会出错,而且首帧初始化与运行时解析逻辑分裂,先把这条共享契约收敛再合入更稳妥。
级联分析
- 变更符号: 原始模型未提供结构化级联字段。
- 受影响流程: 原始模型未提供结构化级联字段。
- 变更集外调用方: unknown
- 置信度: degraded
问题发现
- [高] system 主题路径缺少与 bootstrap 一致的 API 防护
- 证据:
src/App.tsx:27在theme === 'system'时直接调用window.matchMedia(...).addEventListener(...);而src/main.tsx:17也直接调用window.matchMedia(...),只是被try/catch吞掉,导致初始化和运行时的主题解析行为不一致。 - 受影响调用方/流程: 默认主题为
system的首次访问用户、切到system的现有用户,以及任何matchMedia不可用的运行环境。 - 最小可行修复: 统一通过
resolveSystemTheme()计算实际主题,并在注册监听器前先判空matchMedia;bootstrap 和 React effect 共享同一段解析逻辑。
- 证据:
行级发现
- [src/App.tsx:27] theme === 'system'
分支没有检查window.matchMedia是否存在;这里会在不支持该 API 的环境里抛错,建议先复用resolveSystemTheme()` 并在监听前加存在性判断。
Karpathy 评审
- 假设: 模型输出需要归一化为固定 Markdown 契约。
- 简洁性: 已提取 summary、finding、evidence 与 fix;原始 prose 不再附在评论中,避免占用下游解析与代理上下文。
- 变更范围: 原始模型未提供结构化范围字段。
- 验证: 需要查看 CI、测试或人工 CR 证据补强合并信心。
缺失覆盖
- 输出未命中 Repo Guard Markdown 契约;建议补充真实模型质量评估覆盖。
| } | ||
| apply() | ||
|
|
||
| if (theme !== 'system') return |
There was a problem hiding this comment.
theme === 'system'分支没有检查window.matchMedia是否存在;这里会在不支持该 API 的环境里抛错,建议先复用resolveSystemTheme()` 并在监听前加存在性判断。
repo-guard PR #6 CR 指出 system 主题路径的 matchMedia 防护不一致: - App.tsx 直接调用 window.matchMedia(...).addEventListener 无存在性检查 - main.tsx 直接调用 window.matchMedia(...) 靠 try/catch 兜底 - 两者都绕过了 settingsStore.resolveSystemTheme() 的统一防护 修复: - App.tsx: 在 addEventListener 前增加 matchMedia 存在性判断, 与 resolveSystemTheme() 的防护条件一致 - main.tsx: 改用 resolveSystemTheme() 替代内联 matchMedia 逻辑, bootstrap 与运行时共享同一段解析契约 Co-authored-by: traeagent <traeagent@users.noreply.github.com>
There was a problem hiding this comment.
代码评审报告: feat: 添加 3 套主题切换系统提升学习效率
风险等级: 中
处理建议: 请求修改
决策摘要: ** 暂不建议合并;防 FOUC 实现位置不能保证首帧前生效,且 Obsidian 默认视觉已有可确认的像素级回归,需先做最小修复。
级联分析
- 变更符号: 原始模型未提供结构化级联字段。
- 受影响流程: 原始模型未提供结构化级联字段。
- 变更集外调用方: unknown
- 置信度: degraded
问题发现
- [中] 防 FOUC 放在
main.tsx不能满足“首帧无闪烁”的验收- 证据: 当前同步逻辑在应用入口模块执行前才读取
localStorage并写data-theme;浏览器仍可能在模块脚本执行前先绘制默认 CSS/空白页面,尤其生产构建 CSS 被提前加载时,浅色系统首次访问可能先看到 Obsidian/默认背景。 - 受影响调用方/流程: 首次访问、刷新恢复、
theme=system且系统为浅色的首屏体验;这是 issue #4 的明确验收项“刷新后主题保持,无首帧闪烁 / 首次访问浅色系统→Daylight”。 - 最小可行修复: 将同一段主题解析逻辑内联到
index.html的 head 中,并在 stylesheet/应用脚本前设置<html data-theme>;main.tsx可保留运行时兜底但不应作为唯一 FOUC 防线。
- 证据: 当前同步逻辑在应用入口模块执行前才读取
- [中] 噪点 overlay 被双重 opacity 衰减,Obsidian 不再像素级一致
- 证据: SVG data URL 内部仍保留
opacity='0.04',新增 CSSopacity: var(--grain-opacity)后默认 Obsidian 实际强度约为0.04 * 0.04 = 0.0016,远低于原视觉。 - 受影响调用方/流程: 全站背景质感,尤其着陆页品牌视觉;违反 issue #4 验收“现有黑金视觉与切换前像素级一致”。
- 最小可行修复: 选择单一 opacity 控制点;例如移除 SVG 内部
opacity='0.04'并用 CSS 变量控制,或保留 SVG opacity 并不要再给 pseudo element 设置额外 opacity。
- 证据: SVG data URL 内部仍保留
- [中] Hero ring 的 Obsidian 透明度被替换成更强 token,默认视觉发生可见变化
- 证据: 原第二/第三圈分别是
rgba(45, 212, 191, 0.1)与rgba(45, 212, 191, 0.08),现在都使用--teal-border,默认值为rgba(45, 212, 191, 0.25),透明度提升 2.5x/3.125x 且两个层级丢失差异。 - 受影响调用方/流程: Landing hero 视觉层级与默认 Obsidian 像素级一致性。
- 最小可行修复: 增加专用 token(如
--teal-ring-soft/--teal-ring-faint)并让 Obsidian 默认值保持原透明度,或在该局部保留等价变量值。
- 证据: 原第二/第三圈分别是
行级发现
- [src/main.tsx:8] 这段 FOUC 防护仍在入口模块执行阶段才运行,不能保证发生在浏览器首帧绘制前;应把主题解析脚本内联到
index.htmlhead 并在样式/应用脚本前写入data-theme。 - [src/index.css:147] SVG 噪点内部已经有
opacity='0.04',这里再套var(--grain-opacity)会让 Obsidian 噪点强度从 0.04 变成约 0.0016;应只保留一个 opacity 控制点。 - [src/components/Hero.tsx:34] 原值是
rgba(45, 212, 191, 0.1),替换为默认--teal-border后变成 0.25 透明度,破坏 Obsidian 默认视觉;应使用保持原强度的 ring 专用 token。 - [src/components/Hero.tsx:48] 原值是
rgba(45, 212, 191, 0.08),现在与上一层共用--teal-border,既变亮又丢失层级差异;应拆出 faint ring token 或保持等价变量。
Karpathy 评审
- 假设: 模型输出需要归一化为固定 Markdown 契约。
- 简洁性: 已提取 summary、finding、evidence 与 fix;原始 prose 不再附在评论中,避免占用下游解析与代理上下文。
- 变更范围: 原始模型未提供结构化范围字段。
- 验证: 需要查看 CI、测试或人工 CR 证据补强合并信心。
缺失覆盖
- 输出未命中 Repo Guard Markdown 契约;建议补充真实模型质量评估覆盖。
| import './index.css' | ||
| import './styles/app.css' | ||
|
|
||
| /** |
There was a problem hiding this comment.
这段 FOUC 防护仍在入口模块执行阶段才运行,不能保证发生在浏览器首帧绘制前;应把主题解析脚本内联到 index.html head 并在样式/应用脚本前写入 data-theme。
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"); | ||
| pointer-events: none; | ||
| z-index: 9999; | ||
| opacity: var(--grain-opacity); |
There was a problem hiding this comment.
SVG 噪点内部已经有 opacity='0.04',这里再套 var(--grain-opacity) 会让 Obsidian 噪点强度从 0.04 变成约 0.0016;应只保留一个 opacity 控制点。
| inset: '40px', | ||
| animation: 'ringRotate 25s linear infinite reverse', | ||
| borderColor: 'rgba(45, 212, 191, 0.1)', | ||
| borderColor: 'var(--teal-border)', |
There was a problem hiding this comment.
原值是 rgba(45, 212, 191, 0.1),替换为默认 --teal-border 后变成 0.25 透明度,破坏 Obsidian 默认视觉;应使用保持原强度的 ring 专用 token。
| inset: '120px', | ||
| animation: 'ringRotate 35s linear infinite reverse', | ||
| borderColor: 'rgba(45, 212, 191, 0.08)', | ||
| borderColor: 'var(--teal-border)', |
There was a problem hiding this comment.
原值是 rgba(45, 212, 191, 0.08),现在与上一层共用 --teal-border,既变亮又丢失层级差异;应拆出 faint ring token 或保持等价变量。
repo-guard PR #6 CR 给出 CHANGES_REQUESTED 后,agent 未等待 评审完成就宣布任务完成。新增硬禁令与完成标准: - 新增 NEVER:未等 Repo Guard CR 完成 + 处理 CHANGES_REQUESTED 前不得宣布工作完成 - step 7 强化:必须主动轮询 gh pr view 检查评审状态,不能假设 CI 通过即代表 Repo Guard CR 通过 - step 8 强化:CHANGES_REQUESTED 必须用 follow-up commit 处理并等重新评审 - 新增 Completion criteria 清单:5 项全部为真才算 done Co-authored-by: traeagent <traeagent@users.noreply.github.com>
repo-guard 第二轮 CR(commit af948f)指出 3 个问题:
1. [中] 防 FOUC 放在 main.tsx 不能保证首帧前生效
- 浏览器可能在模块脚本执行前先绘制默认 CSS。
- 修复:将主题解析逻辑内联到 index.html head,在 stylesheet
和应用脚本之前同步设置 html data-theme。main.tsx 保留运行时
兜底但不再作为唯一 FOUC 防线。
2. [中] 噪点 overlay 双重 opacity 衰减
- SVG data URL 内部 opacity=0.04 乘以 CSS var(--grain-opacity)=0.04
= 实际 0.0016,远低于原视觉。
- 修复:移除 SVG 内部 opacity,单一用 CSS var(--grain-opacity) 控制。
3. [中] Hero ring 透明度被替换成更强 token
- 原第二/第三圈分别是 0.1/0.08,改用 --teal-border(0.25) 后
透明度提升 2.5x/3.125x 且丢失层级差异。
- 修复:新增 --teal-ring-soft(0.1) / --teal-ring-faint(0.08) 专用
token,三套主题均保持原透明度;Hero.tsx 改用新 token。
Co-authored-by: traeagent <traeagent@users.noreply.github.com>
代码评审报告: feat: 添加 3 套主题切换系统提升学习效率风险等级: 中 级联分析
问题发现
行级发现
Karpathy 评审
缺失覆盖
|
* chore: 强化 Harness Loop 执行力与硬禁令 将 Harness Loop 从建议性语言升级为 MANDATORY,新增 4 条硬禁令 (不直推 main、不无 Issue 推送、不跳 Repo Guard CR、不把 spec/plan 当终点),新增 Pre-flight checklist,明确 SDD ⟶ Harness 联动, 明确 Agent Authority 全局适用。 Closes #3 Co-authored-by: traeagent <traeagent@users.noreply.github.com> * docs: trigger contract recheck with updated PR body Co-authored-by: traeagent <traeagent@users.noreply.github.com> * chore: 强化 Repo Guard CR 等待规则与完成标准 repo-guard PR #6 CR 给出 CHANGES_REQUESTED 后,agent 未等待 评审完成就宣布任务完成。新增硬禁令与完成标准: - 新增 NEVER:未等 Repo Guard CR 完成 + 处理 CHANGES_REQUESTED 前不得宣布工作完成 - step 7 强化:必须主动轮询 gh pr view 检查评审状态,不能假设 CI 通过即代表 Repo Guard CR 通过 - step 8 强化:CHANGES_REQUESTED 必须用 follow-up commit 处理并等重新评审 - 新增 Completion criteria 清单:5 项全部为真才算 done Co-authored-by: traeagent <traeagent@users.noreply.github.com> * fix: 修复 repo-guard CR 指出的循环依赖与 reviewDecision 误判 repo-guard PR #5 CR 指出两个问题: 1. SDD 入口条件写成循环依赖:Step 1 要求 Issue 引用 spec/plan, 但 spec/plan 尚未创建。改为 spec/plan 创建后引用 Issue。 2. gh pr view --json reviews 输出历史事件而非当前结论,会因 旧 CHANGES_REQUESTED 记录误判。改用 reviewDecision 字段 获取折叠后的最新状态。 Co-authored-by: traeagent <traeagent@users.noreply.github.com> * fix: 收紧完成标准为 APPROVED 并移除 when feasible 逃生口 repo-guard 第二轮 CR 指出两处问题: 1. Work 第 2 条保留 `when feasible` 措辞,与同文件硬禁令目标冲突, 会让 agent 在关键路径上跳过 predev。 2. Completion criteria 只排除 `CHANGES_REQUESTED`,但 `REVIEW_REQUIRED` 或 `null`/pending 也会被误判为完成;与步骤 8 "需 approval" 不一致。 修复: - Work 第 2 条改为 MANDATORY,明确 trivial 变更才可跳过且需在 commit message 中声明原因;同步 CLAUDE.md 与 docs/workflow.md。 - Completion criteria 改为要求 `reviewDecision` 为 `APPROVED`,显式排除 `REVIEW_REQUIRED`/`PENDING`/`null`,仅允许 maintainer 显式 override。 - 新增 2 个测试断言:APPROVED 要求与 when-feasible 移除。 Co-authored-by: traeagent <traeagent@users.noreply.github.com> * fix: 让 CLAUDE.md 与 docs/workflow.md 显式委托 AGENTS.md 为 canonical source repo-guard 第三轮 CR(commit ae17ff3)指出: CLAUDE.md 和 docs/workflow.md 只同步了 pre-flight gates 一行, 没有承接本 PR 新增的 Harness Loop 硬禁令、completion criteria 和 all-modes Agent Authority,不同 agent 入口会执行出不一致规则。 修复(采用 repo-guard 建议的 canonical delegation 方案): - CLAUDE.md 顶部新增 Canonical source 声明,明确 AGENTS.md 为 Harness Loop/硬禁令/完成标准/Agent Authority 的权威来源, 本文件不得弱化,冲突时以 AGENTS.md 为准;并重述全部硬禁令。 - docs/workflow.md Agent Rules 段新增同样的 Canonical source 声明, 并新增第 7 条要求非平凡变更必须遵守 AGENTS.md 完整 Harness Loop。 - 新增 2 个契约测试:CLAUDE.md 和 docs/workflow.md 必须声明 AGENTS.md 为 canonical、必须重述关键硬禁令,防止未来漂移。 Co-authored-by: traeagent <traeagent@users.noreply.github.com> * fix: 修正完成标准以适配 GitHub Actions 无法发布 approval review 的限制 发现 repo-guard 实际已批准两个 PR(处理建议: 批准),但 GitHub 不允许 Actions 发布正式 APPROVED review 事件(HTTP 422),repo-guard 降级为普通评论。因此 reviewDecision 会永远停留在上一轮的 CHANGES_REQUESTED,之前要求 reviewDecision=APPROVED 的完成标准 技术上不可能满足。 修复: - 步骤 7/8 和 Completion criteria 改为要求检查 repo-guard 在当前 head commit 上的最新输出(review 或 comment),若为 "处理建议: 批准"/"APPROVE" 即视为通过;明确说明不可仅依赖 reviewDecision 字段。 - 更新测试断言:从要求 APPROVED reviewDecision 改为要求文档说明 GitHub Actions 限制与评论形式批准。 Co-authored-by: traeagent <traeagent@users.noreply.github.com> * fix: 修正 docs/workflow.md 完成条件与 AGENTS.md comment-based approval 一致 repo-guard 第四轮 CR(commit 3e658cc)指出: docs/workflow.md 第 7 条仍要求 "reviewDecision is APPROVED", 与 AGENTS.md 新增的 "Do NOT rely on reviewDecision alone" 规则冲突。 GitHub Actions 无法发布正式 APPROVED review,approval 以评论形式 表达,mirror doc 不能重新引入旧的 reviewDecision 单点判断。 修复: - docs/workflow.md 第 7 条改为检查 current head commit 上最新 Repo Guard 输出的 "处理建议: 批准"/"APPROVE"(评论形式)或正式 APPROVED review,明确不单独依赖 reviewDecision。 - 新增负向契约测试:mirror docs 不得包含 "reviewDecision...APPROVED" 模式,防止未来漂移。 Co-authored-by: traeagent <traeagent@users.noreply.github.com> * fix: 修正 Pre-flight checklist 时序矛盾,不依赖 step 6 才创建的 PR repo-guard 第五轮 CR(commit b296536)指出: Pre-flight checklist(before step 4)要求 "Issue referenced in branch name or PR",但 PR 在 step 6 才创建。若分支名不含 issue 号, agent 在 step 4 前无法满足 checklist,只能违反规则或提前开空 PR。 修复: - Pre-flight checklist 第一条改为 "referenced in the branch name or planned PR body",并明确说明 PR 本身在 step 6 才创建, 接受计划中的 Closes #N 或分支名包含 issue 号。 - 新增契约测试验证 pre-flight checklist 不依赖 step 6 才存在的 PR。 Co-authored-by: traeagent <traeagent@users.noreply.github.com> --------- Co-authored-by: traeagent <traeagent@users.noreply.github.com>
Linked Issue Or Context
Closes #4
Summary
将单一黑金主题升级为 4 选项主题系统(Obsidian / Parchment / Daylight / System),通过 CSS 变量 +
data-theme属性实现全站主题切换,提升学习者在不同环境光下的阅读效率。主题定义
#f5f0e6+ 深棕字 + 暗金prefers-color-scheme自动选择技术方案
:root+[data-theme="parchment"]+[data-theme="daylight"]三套 tokensettingsStore新增theme/setTheme/resolveSystemTheme,持久化到kf-settingsmain.tsx同步初始化脚本在 React 渲染前写data-themeApp.tsx的useThemeEffect监听prefers-color-scheme变化ThemeSwitcher组件集成到AppNav和Nav#xxx/rgba()替换为var(--token)Impact Scope
src/index.css/src/styles/app.css— CSS 变量重构tailwind.config.js— 颜色令牌引用 CSS 变量src/store/settingsStore.ts— 新增 theme 状态src/App.tsx/src/main.tsx— 主题应用与防 FOUCsrc/components/ThemeSwitcher.tsx(新建)src/components/AppNav.tsx/src/components/Nav.tsx— 集成切换器docs/superpowers/specs/+docs/superpowers/plans/— SDD 产物Impact Summary
npm run quality:precommit通过(lint 0 错误 / build 成功 / test 10/10 / test:workflows 22/22)Verification
npm run lint— 0 errors, 2 pre-existing warnings (StudyPage, 与本次无关)npm run build— ✓ 74 modules, CSS 27.93 kBnpm run test— ✓ 10/10 passednpm run test:workflows— ✓ 22/22 passedChecklist
npm run quality:precommit, or explained why it could not run.npm run quality:localfor critical skeleton changes, or explained why it could not run.