Skip to content

feat: 深色模式手动开关(三态:跟随系统 / 浅色 / 深色) - #9

Merged
Paulkm2006 merged 1 commit into
refactor/walisfrom
feat/theme-toggle
Aug 4, 2026
Merged

feat: 深色模式手动开关(三态:跟随系统 / 浅色 / 深色)#9
Paulkm2006 merged 1 commit into
refactor/walisfrom
feat/theme-toggle

Conversation

@yujiezhang-ops

Copy link
Copy Markdown
Collaborator

Closes #6

改动

深色配色本来就完整存在,但只由 prefers-color-scheme 驱动,用户无法选择。现在 tokens.css 让同一份暗色变量也能通过 .theme-dark 命中,.theme-light 则用 :not() 退出媒体查询,所以在深色桌面上强制浅色能生效。

三态而不是开关。「跟随系统」是一个真实的选项,它不加任何 class,正是这样才把决定权留给 CSS 媒体查询。二态开关在桌面主题切换的瞬间必须猜"关"是什么意思,而想跟随系统的用户没有办法表达这个意愿。

控件做成 <select>,与紧邻的语言选择器同构;图标反映当前实际生效的配色,所以选「跟随系统」时显示的是太阳或月亮,而不是一个含义模糊的图标。

两个容易出错的地方

暗色调色板写了两遍。 CSS 无法在媒体查询和 class 之间共享声明列表。我没有用变通手段掩盖这一点,而是加了一个测试对比两个块的变量集合,确保它们不会漂移——并做了变异测试验证守卫有效(只改其中一块的 --page-bg 会立刻失败)。

尺寸变量必须留在 :root --radius-*--sidebar-width--footer-height 只在浅色块定义一次。如果跟着复制进暗色块,强制浅色时就会丢失。另有一个测试断言暗色块里不含这些变量。浏览器实测确认三种状态下 --sidebar-width 都是 232px。

防闪烁与窗口色

index.html 用 inline script 在首次绘制前就设好 class。另外 index.html 里那两个 theme-color meta 是 media 驱动的,看不到手动选择,所以 provider 会写一个 resolved 的 meta 覆盖它们——否则窗口边框色会和内容不一致。

一个实测中发现并修掉的布局 bug

我最初把 ThemePicker 放在语言选择器之后,而 margin-top: auto 移到了它身上。结果语言选择器失去下推作用,停留在侧边栏上方(top=225),外观和任务中心却在底部。改成 ThemePicker 排在这组的第一位,三者才作为一组贴底。浏览器实测确认顺序为 外观 722 → 语言 760 → 任务中心 808。

验证

  • 前端 128 个测试通过(新增 11 个),tsc --noEmit 与生产构建通过
  • 浏览器实测三态切换:深色 #151517、浅色 #ececef、跟随系统无 class
  • 强制浅色时尺寸变量不丢失
  • storage 被禁时降级为内存态而非崩溃(hardened webview 场景,与 i18n 的处理一致)

storage key 用 oneagent.theme,与 app 内 oneagent.locale 的点号风格一致(site 用的是连字符 oneagent-theme,但两者不共享 storage,不必迁就)。

🤖 Generated with Claude Code

The dark palette already existed but only ever came from
prefers-color-scheme, so a user could not choose it. tokens.css now reaches
the same palette through .theme-dark, and .theme-light opts out of the media
query so forcing light survives a dark desktop.

Three states rather than a toggle: "system" is a real choice, and it carries
no class at all so the media query stays in charge. A two-state switch would
have to guess what "off" means the moment the desktop flips.

The palette is written twice because CSS cannot share a declaration list
between a media query and a class. A test compares the two blocks so they
cannot drift, and asserts the sizing tokens stay out of them: duplicating
--sidebar-width there would drop it whenever light was forced.

index.html applies the stored class before the first paint, and the provider
writes one resolved theme-color meta tag, since the two media-driven tags
cannot see a forced palette.

ThemePicker sits first in the sidebar's bottom group so its margin-top: auto
pushes appearance, language and the task centre down together.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Paulkm2006
Paulkm2006 merged commit aa7848d into refactor/walis Aug 4, 2026
@Paulkm2006
Paulkm2006 deleted the feat/theme-toggle branch August 5, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 深色模式手动开关(当前只能跟随系统)

2 participants