feat: adopt the native OS theme #66

Merged
reya merged 14 commits from feat/native-theme into master 2026-10-23 04:07:12 +00:00
7 changed files with 413 additions and 31 deletions
Showing only changes of commit cab58f6332 - Show all commits
Generated
+263 -17
View File
@@ -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",
+5
View File
@@ -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" }
+18
View File
@@ -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::<Settings>(&content)
{
return settings.appearance_mode;
}
}
AppearanceMode::default()
}
const DEFAULT_FILE_SERVER: &str = "https://nostr.download/";
const LEGACY_FILE_SERVER: &str = "blossom.band";
+8 -5
View File
@@ -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
+3
View File
@@ -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
+82 -1
View File
@@ -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<AppearanceMode>,
}
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::<ThemeWatcher>() {
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})"),
}
}
+34 -8
View File
@@ -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: