diff --git a/Cargo.lock b/Cargo.lock index ad6b1e68..4b96668f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,6 +853,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.13.1" @@ -960,7 +969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ "heck 0.4.1", - "indexmap", + "indexmap 2.14.2", "log", "proc-macro2", "quote", @@ -1234,6 +1243,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "configparser" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031ad3d167622974ed636505ae4556267ed4aa2aea0464122fa6bc9698a32825" + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1309,7 +1324,7 @@ dependencies = [ "cookie", "document-features", "idna", - "indexmap", + "indexmap 2.14.2", "log", "serde", "serde_derive", @@ -1325,6 +1340,7 @@ dependencies = [ "assets", "auto_update", "chat", + "flume 0.11.1", "gpui-kit", "gpui-pre", "gpui-pre-linux", @@ -1333,6 +1349,8 @@ dependencies = [ "gpui-pre-reqwest-client", "gpui-pre-windows", "log", + "native-theme", + "native-theme-gpui", "nip29", "nip4e", "nostr-sdk", @@ -1625,6 +1643,40 @@ dependencies = [ "cmov", ] +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.9", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.9", +] + [[package]] name = "data-encoding" version = "2.11.1" @@ -1682,6 +1734,9 @@ name = "deranged" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" +dependencies = [ + "serde_core", +] [[package]] name = "derive_more" @@ -2632,7 +2687,7 @@ dependencies = [ "raw-window-handle", "regex", "ropey", - "schemars", + "schemars 1.2.3", "serde", "serde_json", "smallvec", @@ -2677,7 +2732,7 @@ dependencies = [ "resvg 0.45.1", "ropey", "rust-i18n", - "schemars", + "schemars 1.2.3", "serde", "serde_json", "serde_repr", @@ -2773,13 +2828,13 @@ dependencies = [ "parking", "parking_lot", "pin-project", - "pollster", + "pollster 0.4.0", "profiling", "rand 0.9.5", "raw-window-handle", "regex", "resvg 0.46.0", - "schemars", + "schemars 1.2.3", "seahash", "serde", "serde_json", @@ -2847,7 +2902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "920c2f5b2f1a7813114d89f4841f26f6060d9c35d7c8a852a4205d1a767a7ce0" dependencies = [ "gpui-pre-util", - "indexmap", + "indexmap 2.14.2", "rustc-hash 2.1.3", ] @@ -3106,7 +3161,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71f488bf4780735f6b2dad24f8db1e3d9e2593de0ec298e3574c788f571b81c4" dependencies = [ - "schemars", + "schemars 1.2.3", "serde", "smol_str", ] @@ -3299,7 +3354,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -3340,6 +3395,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -3732,6 +3793,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -3820,6 +3887,17 @@ version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.14.2" @@ -3961,6 +4039,60 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" +dependencies = [ + "defmt 1.2.0", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt 1.2.0", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jni" version = "0.22.4" @@ -4519,7 +4651,7 @@ dependencies = [ "half", "hashbrown 0.16.1", "hexf-parse", - "indexmap", + "indexmap 2.14.2", "libm", "log", "num-traits", @@ -4530,6 +4662,59 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "native-theme" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea89548b5c74493eeda312d5b1dad2c3c5f3bb35076dfef7c9a5b85c5cf140bf" +dependencies = [ + "arc-swap", + "ashpd", + "async-trait", + "block2 0.6.2", + "configparser", + "fontdb", + "inventory", + "native-theme-derive", + "notify", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-text", + "objc2-foundation 0.3.2", + "pollster 1.0.1", + "serde", + "serde_with", + "toml 1.1.8+spec-1.1.0", + "unicase", + "windows 0.62.2", + "zbus", +] + +[[package]] +name = "native-theme-derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cddcfe1cc7b75a3ef362b242343aabf8fd9053ed9b4c72bcf4af4a45989b4d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "native-theme-gpui" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5002c99b80209a2da63224a8ffa6a6b95feb836a1098c1189de47f69f3a40053" +dependencies = [ + "gpui-base", + "gpui-component", + "gpui-pre", + "image", + "native-theme", +] + [[package]] name = "ndk-context" version = "0.1.1" @@ -4728,7 +4913,7 @@ name = "nostr-gossip-memory" version = "0.45.1" source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" dependencies = [ - "indexmap", + "indexmap 2.14.2", "lru", "nostr", "nostr-gossip", @@ -5084,7 +5269,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.2", + "block2 0.6.2", "dispatch2", + "libc", "objc2 0.6.5", ] @@ -5130,6 +5317,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ "bitflags 2.13.2", + "block2 0.6.2", + "libc", "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", @@ -5690,6 +5879,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" +[[package]] +name = "pollster" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336" + [[package]] name = "poly1305" version = "0.8.0" @@ -6626,6 +6821,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.2.3" @@ -6633,7 +6840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" dependencies = [ "dyn-clone", - "indexmap", + "indexmap 2.14.2", "ref-cast", "schemars_derive", "serde", @@ -6839,7 +7046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "foldhash 0.2.0", - "indexmap", + "indexmap 2.14.2", "itoa", "memchr", "serde", @@ -6888,6 +7095,39 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9adc193c780ef8f159aee8b61e2d5801aaa555e6eb0947fe45530ec506296f" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.3", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17bbc68e28663bbbb90df47e058aa7eda4fb445b89fe70457bb94fbccf6e49" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "settings" version = "2.0.0-alpha" @@ -7219,6 +7459,12 @@ dependencies = [ "quote", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.28.0" @@ -7707,7 +7953,7 @@ version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ - "indexmap", + "indexmap 2.14.2", "serde_core", "serde_spanned 1.1.2", "toml_datetime 1.1.2+spec-1.1.0", @@ -7740,7 +7986,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -7754,7 +8000,7 @@ version = "0.25.17+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ - "indexmap", + "indexmap 2.14.2", "toml_datetime 1.1.2+spec-1.1.0", "toml_parser", "winnow 1.0.4", @@ -8584,7 +8830,7 @@ dependencies = [ "cfg_aliases", "document-features", "hashbrown 0.16.1", - "indexmap", + "indexmap 2.14.2", "log", "naga", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 368fbcbf..e391257f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,11 @@ gpui_tokio = { path = "crates/gpui_tokio" } gpui-base = "0.7.1" gpui-kit = "0.7.3" +# Native OS theme reading; the connector is written against gpui-component 0.7.x +# and gpui-pre 0.3.8, both satisfied by the versions above (0.7.3 / 0.3.9). +native-theme = { version = "0.6.1", features = ["watch"] } +native-theme-gpui = { version = "0.6.1", default-features = false, features = ["system-fonts"] } + # Nostr nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" } nostr-memory = { git = "https://github.com/rust-nostr/nostr" } diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 1bd3af98..b5e3e30f 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -35,6 +35,24 @@ pub fn init(cx: &mut App) { AppSettings::set_global(cx.new(AppSettings::new), cx) } +/// Read the persisted appearance mode synchronously. +/// +/// The async load fills the global settings later; the native theme must be +/// applied before the first window opens, so boot reads the mode straight +/// from the settings file instead. +pub fn persisted_appearance_mode() -> AppearanceMode { + #[cfg(not(target_arch = "wasm32"))] + { + let path = config_dir().join(".settings"); + if let Ok(content) = std::fs::read_to_string(path) + && let Ok(settings) = serde_json::from_str::(&content) + { + return settings.appearance_mode; + } + } + AppearanceMode::default() +} + const DEFAULT_FILE_SERVER: &str = "https://nostr.download/"; const LEGACY_FILE_SERVER: &str = "blossom.band"; diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 8426df64..8da77cb7 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -88,12 +88,15 @@ impl Workspace { let workspace = cx.entity().downgrade(); let mut subscriptions = smallvec![]; - subscriptions.push( - // Observe system appearance and update theme - cx.observe_window_appearance(window, |_this, window, cx| { + // Follow OS appearance changes only when the user has not pinned a + // mode; the native-theme watcher drives runtime changes either way. + // The `::settings` path is the external crate; the crate-local + // `settings` module is the preferences dialog. + if ::settings::AppSettings::get_appearance_mode(cx) == ::settings::AppearanceMode::System { + subscriptions.push(cx.observe_window_appearance(window, |_this, window, cx| { gpui_kit::component::Theme::sync_system_appearance(Some(window), cx); - }), - ); + })); + } subscriptions.push( // Subscribe to the nostr events diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 5c8497e6..a858c2a2 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -44,6 +44,9 @@ gpui-kit.workspace = true gpui_linux.workspace = true gpui_windows.workspace = true gpui_macos.workspace = true +native-theme.workspace = true +native-theme-gpui.workspace = true +flume.workspace = true reqwest_client.workspace = true log.workspace = true diff --git a/desktop/src/main.rs b/desktop/src/main.rs index c747ba31..51dd851f 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,17 +1,23 @@ use std::sync::{Arc, Mutex}; use gpui::{ - App, Bounds, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, + App, Bounds, Global, KeyBinding, Menu, MenuItem, SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, actions, point, px, size, }; +use gpui_kit::component::{Theme, ThemeMode}; use gpui_platform::application; +use native_theme::watch::ThemeSubscription; +use native_theme_gpui::apply_system_theme; use nostr_sdk::prelude::SecretKey; +use settings::{AppSettings, AppearanceMode}; use state::{APP_ID, CLIENT_NAME}; use workspace::TAB_BAR_HEIGHT; actions!(coop, [Quit]); +const BASE_FONT_SIZE: f32 = 16.0; + fn main() { // Initialize logging tracing_subscriber::fmt::init(); @@ -59,6 +65,15 @@ fn main() { // Initialize settings settings::init(cx); + let appearance = settings::persisted_appearance_mode(); + match apply_native_theme(appearance, cx) { + Ok(()) => start_theme_watcher(appearance, cx), + Err(error) => { + log::warn!("native theme unavailable ({error})"); + apply_appearance(appearance, cx); + } + } + // Initialize the nostr client state::init(cx, cli_key); @@ -155,3 +170,69 @@ fn quit(_ev: &Quit, cx: &mut App) { log::info!("Gracefully quitting the application . . ."); cx.quit(); } + +/// Apply the native theme to the app, and then apply the persisted appearance mode +fn apply_native_theme(appearance: AppearanceMode, cx: &mut App) -> native_theme::Result<()> { + let mut system = native_theme_gpui::SystemTheme::from_system()?; + let native = system.light.defaults.font.size; + + if native > 0.0 { + system.accessibility.text_scaling_factor *= BASE_FONT_SIZE / native; + } + + apply_system_theme(&system, cx); + apply_appearance(appearance, cx); + + Ok(()) +} + +/// Honor the appearance mode against the installed (native) palettes. +fn apply_appearance(appearance: AppearanceMode, cx: &mut App) { + match appearance { + AppearanceMode::System => Theme::sync_system_appearance(None, cx), + AppearanceMode::Light => Theme::change(ThemeMode::Light, None, cx), + AppearanceMode::Dark => Theme::change(ThemeMode::Dark, None, cx), + } +} + +/// Holds the watcher subscription so it lives as long as the app. +struct ThemeWatcher { + _subscription: ThemeSubscription, + /// The appearance mode the observer in `workspace` was registered with. + appearance: std::cell::Cell, +} + +impl Global for ThemeWatcher {} + +/// Start a background task to watch for OS theme changes and re-apply the native theme. +fn start_theme_watcher(initial_appearance: AppearanceMode, cx: &mut App) { + let (sender, receiver) = flume::unbounded(); + match native_theme::watch::on_theme_change(move |event| { + if let Err(error) = sender.send(event) { + log::warn!("theme change dropped ({error})"); + } + }) { + Ok(subscription) => { + cx.set_global(ThemeWatcher { + _subscription: subscription, + appearance: std::cell::Cell::new(initial_appearance), + }); + cx.spawn(async move |cx| { + while receiver.recv_async().await.is_ok() { + log::info!("OS theme changed; re-applying"); + cx.update(|cx| { + let appearance = AppSettings::get_appearance_mode(cx); + if let Some(watcher) = cx.try_global::() { + watcher.appearance.set(appearance); + } + if let Err(error) = apply_native_theme(appearance, cx) { + log::warn!("failed to apply updated OS theme ({error})"); + } + }); + } + }) + .detach(); + } + Err(error) => log::warn!("OS theme watching unavailable ({error})"), + } +} diff --git a/docs/native-theme-migration.md b/docs/native-theme-migration.md index 1c54935a..1240bfc2 100644 --- a/docs/native-theme-migration.md +++ b/docs/native-theme-migration.md @@ -1,10 +1,23 @@ # Migrate the UI to GPUI-Kit, then native-theme -> **Status: Phase A is implemented** (on the working tree, uncommitted). The -> app builds against `gpui-kit` (resolved 0.7.3 / gpui-pre 0.3.9), every UI -> element comes from the kit, and `crates/theme` is deleted. +> **Status: Phase A and Phase B are implemented** (on the working tree, +> uncommitted). The app builds against `gpui-kit` (resolved 0.7.3 / +> gpui-pre 0.3.9), every UI element comes from the kit, and `crates/theme` is +> deleted. > -> **Follow-up (this revision, also uncommitted):** the last custom modules in +> **Phase B** adopted the OS theme through `native-theme-gpui` 0.6.1 (see +> below). `desktop` reads `SystemTheme::from_system()` at boot — before the +> window opens — and calls `apply_system_theme`, then honors the persisted +> appearance mode on top of it; a `watch`-feature subscription re-applies the +> OS theme at runtime via `native_theme::watch::on_theme_change`. Where the +> OS cannot be read, the boot logs a warning and the kit palette remains. +> The optional preset picker was skipped, matching the signed reference +> (its adoption removed its theme pickers — the connector owns fonts, +> radius and geometry). Web is untouched: `native-theme` is a desktop-only +> dependency and the wasm32 failure set is byte-for-byte the pre-existing +> baseline. +> +> **Follow-up (also on the working tree):** the last custom modules in > `crates/ui` are gone — `motion`, `typography`, `layout` and `styled` were > deleted and every call site now reads gpui-kit tokens directly (`text_xs` / > `text_sm`, `cx.theme().radius` / `radius_lg`, `gap_2`, `Separator`). The @@ -15,10 +28,9 @@ > (`ui::Avatar`): user image when set, deterministic pixel art from the > profile/group seed otherwise, also as the image-load fallback. Desktop > traffic lights recenter for the 44px bar (`workspace::TAB_BAR_HEIGHT`). -> Validation: `cargo check`, `cargo clippy` and `cargo fmt` are clean -> workspace-wide and the desktop binary builds; the wasm32 check fails -> identically before and after (pre-existing `state` → `gpui_tokio` and -> `rustls` blockers, verified at HEAD). +> Icons are vendored in `assets/icons/` (signed's set, filled from the kit +> bundle where coop needs more) and regression-tested in +> `crates/assets/tests`. Plan to move coop's custom UI elements onto [GPUI-Kit](https://gpui-kit.com) (`gpui-kit` 0.7.1), delete the custom theme implementation (`crates/theme`), @@ -257,6 +269,20 @@ site is mapped. ## 5. Phase B — native-theme-gpui +> **Implemented.** `native-theme` 0.6.1 (`watch`) + `native-theme-gpui` 0.6.1 +> (default-features off, `system-fonts`) are workspace deps; only `desktop` +> depends on them, so nothing enters the wasm graph. `desktop/src/main.rs` +> applies the OS theme after `settings::init` and before the window opens, +> then honors the persisted appearance mode; on read failure it logs a +> warning and keeps the kit palette (which the appearance dropdown still +> drives). A `ThemeWatcher` global holds the `watch` subscription and +> re-applies on OS theme changes through a flume channel. The optional +> preset picker (step 3) was skipped, matching the signed reference where +> adoption *removed* the theme pickers. Settings, workspace and all UI +> crates are untouched — Phase A's appearance controls work on the native +> palettes as planned. The web fallback-preset idea was dropped with it: +> web keeps the kit palette and its appearance dropdown, exactly as before. + 1. Add `native-theme-gpui = "0.6"` (pulls `native-theme`; keep default features or trim icon features coop doesn't use). 2. Boot, after `gpui_kit::init` and before opening the window: