Skip to content

fix(desktop): restore dark mode in the desktop shell - #109

Merged
Luhaozhu merged 1 commit into
mainfrom
fix/desktop-dark-mode-shell
Aug 17, 2026
Merged

fix(desktop): restore dark mode in the desktop shell#109
Luhaozhu merged 1 commit into
mainfrom
fix/desktop-dark-mode-shell

Conversation

@Luhaozhu

Copy link
Copy Markdown
Contributor

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

  • 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.
  • Windows/Linux: the titlebar is a solid #F7F8FA bar above a dark app, with #fff dropdown menus.
  • Login / first-run / local-setup / close-confirm / server-address pages are all light. Login and close-confirm are hit daily.

Approach

The titlebar and offset styles are injected into the SPA's own document, so <html data-theme> 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 (the Windows close-button red is the one documented exemption).
  • every_shell_page_boots_and_defines_both_themes — every shell page needs the theme bootstrap before its <style> (otherwise dark users get a white flash), a dark override block, and no prefers-color-scheme.

Follow-up

This repo has no dark-mode build gate, so nothing stops the next shell page from being written light again. A separate PR adds that feedback loop.

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
`<html data-theme>` 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 `<style>`, a dark override block, and no
`prefers-color-scheme`).
@Luhaozhu
Luhaozhu merged commit eba320a into main Aug 17, 2026
5 checks passed
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.

1 participant