feat(theme): 主题三态偏好模型,默认跟随系统自动切换暗黑模式

- 新增 src/lib/theme.ts:三态偏好单一真源
  (system/light/dark,无存储值默认跟随系统)
- ThemeToggle 改三态循环(Monitor→Sun→Moon):
  实时监听 prefers-color-scheme(仅 system 档生效)
  + storage 跨标签页同步;尺寸位置不变
- layout.tsx FOUC 内联脚本支持 system 档,
  首帧前解析避免闪烁
- e2e 视觉回归 L2 改用 test.use({ colorScheme }) 驱动
  (事后 setAttribute 存在被挂载效果覆盖的竞态)
- header.test.tsx lucide 白名单补 Monitor
- CLAUDE.md 同步三态语义与 variant 踩坑
- 新增 scripts/audit/theme-system-verify.mjs
  (55 断言:首帧无闪烁/循环/实时跟随/旧值兼容/
  污染回退/6 路由冒烟)

验证:单测 131 套件 1662 通过 0 失败;
tsc 0 错;eslint 0 错;next build 通过;
运行时 55/55 PASS(dogfood-output/theme-system-verify/)
This commit is contained in:
2026-09-20 11:50:58 +08:00
parent 5159862e96
commit 0bd1e9a7e2
9 changed files with 962 additions and 71 deletions
+6 -3
View File
@@ -170,11 +170,14 @@ export default async function RootLayout({
suppressHydrationWarning
>
<head>
{/* 主题防 FOUC(暗黑模式 Phase 1 基础设施):首帧前同步读取 localStorage 设置 data-theme,
无存储值时保持浅色(默认)。当前无切换 UI,仅作挂载点,未来切换器写入 novalon-theme 即可生效。 */}
{/* 主题防 FOUC:首帧前同步解析偏好并写入 data-theme,避免刷新闪白/闪黑。
三档语义(与 src/lib/theme.ts 严格一致,改动时两处必须同步):
'light' | 'dark' → 用户显式选择,直接生效
'system' | 无值 → 跟随操作系统 prefers-color-scheme
显式选择优先于系统偏好;localStorage 不可用(隐私模式)时兜底浅色。 */}
<script
dangerouslySetInnerHTML={{
__html: `(function(){try{var t=localStorage.getItem('novalon-theme');if(t==='dark'||t==='light'){document.documentElement.setAttribute('data-theme',t);}}catch(e){}})();`,
__html: `(function(){try{var p=localStorage.getItem('novalon-theme');if(p!=='light'&&p!=='dark'&&p!=='system'){p='system';}var t=p;if(p==='system'){t=(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}document.documentElement.setAttribute('data-theme',t);}catch(e){try{document.documentElement.setAttribute('data-theme','light');}catch(e2){}}})();`,
}}
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />