Make every shell color token theme-aware

Dark mode was persistent in `AppearanceSettings` but never reached any
paint code: every call site in the shell read `colors::INK` etc. as a
`pub const u32`, so toggling `ThemeMode::Dark` mutated state nothing
sampled. Root-cause fix is to invert the contract — the design-system
exports functions that resolve through a thread-local `Mode`, and the
GPUI render impl sets that mode each frame.

What lands:
- `ely_design_system::colors::Mode` + thread-local + `set_mode` /
  `mode` accessors. Every ink shade, glass surface, stroke, divider,
  hairline, canvas, success / error chip now picks the warm-dark
  counterpart when the active mode is `Mode::Dark`.
- `ElyShell::render` resolves `ThemeMode::System` against
  `Window::appearance()` and pushes the mode before traversing the
  tree, so widgets lower down read the right shade without owning a
  `Mode` parameter.
- `render_wallpaper` + `panel_bg` now branch on `colors::mode()` so
  the gradient base, panel tint, and overlay highlights flip to
  warm-graphite when dark mode is active.
- Mechanical conversion across 687 call sites in 55 files from
  `colors::FOO` constants to `colors::foo()` accessors. The
  `Theme` / `ELY_THEME` const surface (unused outside the design
  system) is removed; the function surface is the new contract.
This commit is contained in:
2026-05-15 19:50:38 -04:00
parent 5548427df3
commit 6b2578c3a8
58 changed files with 1010 additions and 745 deletions
+34 -3
View File
@@ -18,6 +18,19 @@ use super::{ElyShell, ShellState};
impl Render for ElyShell {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// Resolve the active palette before anything else so every
// `colors::ink()` etc. call lower in the tree reads the right
// shade. `ThemeMode::System` defers to the OS preference via
// GPUI's `Window::appearance`; explicit Light / Dark wins.
let appearance = window.appearance();
let theme_mode = match &self.state {
ShellState::Ready(core) => {
core.snapshot().map(|s| s.appearance.theme_mode()).unwrap_or_default()
}
ShellState::StartupError(_) => ely_domain::ThemeMode::default(),
};
colors::set_mode(resolve_color_mode(theme_mode, appearance));
match &self.state {
ShellState::Ready(core) => match (core.snapshot(), core.active_tab().cloned()) {
(Ok(snapshot), Ok(active_tab)) => {
@@ -30,6 +43,24 @@ impl Render for ElyShell {
}
}
fn resolve_color_mode(
theme_mode: ely_domain::ThemeMode,
window_appearance: gpui::WindowAppearance,
) -> colors::Mode {
match theme_mode {
ely_domain::ThemeMode::Light => colors::Mode::Light,
ely_domain::ThemeMode::Dark => colors::Mode::Dark,
ely_domain::ThemeMode::System => match window_appearance {
gpui::WindowAppearance::Dark | gpui::WindowAppearance::VibrantDark => {
colors::Mode::Dark
}
gpui::WindowAppearance::Light | gpui::WindowAppearance::VibrantLight => {
colors::Mode::Light
}
},
}
}
impl ElyShell {
fn render_browser(
&mut self,
@@ -75,7 +106,7 @@ impl ElyShell {
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_window_mouse_up))
.capture_key_down(cx.listener(Self::on_command_overlay_key_down))
.font_family(SANS_FAMILY)
.text_color(rgb(colors::INK))
.text_color(rgb(colors::ink()))
.child(render_wallpaper(snapshot.appearance.wallpaper()))
.child(
div()
@@ -333,8 +364,8 @@ const COLLAPSE_THRESHOLD_PX: f32 = spacing::SHELL_INSET + DEFAULT_SIDEBAR_WIDTH_
fn render_error(message: String) -> AnyElement {
div()
.size_full()
.bg(rgb(colors::CANVAS))
.text_color(rgb(colors::ERROR))
.bg(rgb(colors::canvas()))
.text_color(rgb(colors::error()))
.flex()
.items_center()
.justify_center()