From f827d996854f31fb1f727d347cebc58627b630ea Mon Sep 17 00:00:00 2001 From: Luhaozhu Date: Sun, 16 Aug 2026 23:48:14 -0400 Subject: [PATCH] fix(desktop): restore dark mode in the desktop shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop shell embeds six full HTML pages as Rust string literals in `desktop/src-tauri/src/*.rs`, plus a custom titlebar injected into the SPA's own document. None of it is `.css`/`.tsx`, so when dark mode shipped it was left behind entirely and still renders hardcoded light. What users hit: - On macOS the whole main window is forced back to light. `MAC_OFFSET_SPA` sets `background:…#FFFFFF…,#F5F6F7!important` — with `!important`, so the app's own dark background cannot win. That strip was copied from the sidebar's old blue recipe, and the sidebar has since been tokenised to `color-mix(in srgb, var(--color-bg-gray) 72%, transparent)`, so it had also drifted out of sync in light mode. - On Windows/Linux the titlebar is a solid `#F7F8FA` bar above a dark app, with `#fff` dropdown menus. - The login, first-run, local-setup, close-confirm and server-address pages are all light. Login and close-confirm are hit daily. The titlebar and offset styles are injected into the SPA's own document, so `` already applies to them: they now reference the app's tokens and follow both themes with no extra dark block. The macOS strip mirrors `.jx-sider`'s current recipe again, so future sidebar changes only need editing `sidebar.css`. The five shell pages are separate documents with no access to that attribute. They are served same-origin by the local proxy, so a new `THEME_BOOT_JS` reads the same `hugagent_theme_mode` key using rules identical to the anti-flicker script in `index.html` — meaning "manually chose dark while the OS is light" is honoured too. `prefers-color-scheme` cannot do that; it only follows the OS and fights the manual three-way toggle. The one exception is the updater progress window: it loads from a `data:text/html` URL — an opaque origin that cannot read localStorage — so it keeps the media query, with the reason and trade-off documented in place. Tests: two new cases alongside the existing 41, all green — `injected_shell_styles_carry_no_hardcoded_colors` (styles injected into the SPA must carry no colour literals) and `every_shell_page_boots_and_defines_both_themes` (every shell page needs the theme bootstrap *before* its ` @@ -976,15 +1074,26 @@ const CLOSE_CONFIRM_HTML: &str = r##" 关闭 · HugAgentOS @@ -1025,15 +1135,26 @@ const SERVER_CONFIG_HTML: &str = r##" 服务器地址 · HugAgentOS @@ -1111,12 +1233,69 @@ mod tests { assert!(!block.contains("data-win=\"close\"")); assert!(!block.contains("tb-menuLabel")); assert!(block.contains("--hugagent-desktop-sidebar-width")); - assert!(block.contains("linear-gradient(90deg,rgba(203,223,255,.38)")); - assert!(block.contains("#F5F6F7!important")); + // 左半幅必须逐字复刻 sidebar.css 里 .jx-sider 的配方,否则安全区和侧边栏会脱色; + // 底色引令牌而不是写死,深色档才不会被 !important 摁回浅色。 + assert!(block.contains( + "linear-gradient(90deg,color-mix(in srgb, var(--color-bg-gray) 72%, transparent)" + )); + assert!(block.contains("var(--color-bg-layout)!important")); assert!(block.contains(".jx-brandRow,.jx-miniRail{padding-top:0!important}")); assert!(block.contains("ResizeObserver")); } + /// 每张壳页面都必须成对具备:`` 里的主题引导脚本 + `:root[data-theme="dark"]` 覆盖块。 + /// 少了脚本 → 深色偏好读不到,页面恒亮;少了覆盖块 → 属性打上了也没有对应样式。 + #[test] + fn every_shell_page_boots_and_defines_both_themes() { + for (name, html) in [ + ("login", LOGIN_HTML), + ("init", INIT_HTML), + ("setup", SETUP_HTML), + ("close-confirm", CLOSE_CONFIRM_HTML), + ("server-config", SERVER_CONFIG_HTML), + ] { + assert!( + html.contains(":root[data-theme=\"dark\"]"), + "{name} 页缺少深色覆盖块" + ); + let booted = with_theme_boot(html); + assert!(booted.contains("hugagent_theme_mode"), "{name} 页没注入主题引导"); + // 引导必须早于
正在准备更新…