feat(gui): consolidate Settings and add theme switching#331
Conversation
Fold the standalone About window into Settings and reorganise the window around a gpui-component-driven theme system. - About becomes an About page; update controls move to a dedicated Updates page (status hero, check / download&install / restart, the auto-check and auto-install toggles, update source). - New Appearance page: a macOS-style light / dark / follow-system picker (three preview thumbnails), a colour-theme grid (the OpenLogi brand theme plus the upstream gpui-component themes, filterable by name), corner radius, and the interface language. The sidebar shows Language and Theme as sub-items. - theme::palette now derives from cx.theme() tokens, so every bespoke surface re-skins with the selected theme. Ship an OpenLogi light/dark theme that reproduces the previous look. - Persist appearance / theme / radius in AppSettings and sync the macOS titlebar (NSApp appearance) to the chosen mode. - auto_install_updates: when opted in, a found update downloads and stages in the background, applied on next restart. Themes: only the OpenLogi brand theme is committed (themes/openlogi.json). The upstream gpui-component themes are not vendored into the repo — build.rs locates gpui-component's source via `cargo metadata` (robust across the local git cache, CI, and Nix's vendored tree) and embeds its theme files into OUT_DIR. Set OPENLOGI_THEMES_DIR to override the lookup. Cards use the Outline group-box variant for definition. The Settings page builders are split into per-page submodules under windows/settings/ (general, updates, permissions, appearance, language, assets, about), leaving the view + window plumbing in settings.rs. Update-status chips use Tag and the corner-radius picker uses ButtonGroup; the About page links are a uniform row of small ghost buttons with Lucide icons (changelog and bug vendored under action-icons/, since gpui-component's IconName set lacks them).
Greptile SummaryThis PR consolidates the standalone About window into the Settings panel and introduces a gpui-component-driven theme system. Settings is refactored into per-page submodules, and the Appearance page exposes a light/dark mode picker, theme grid, corner-radius selector, and language chooser. Persistence (appearance, theme slots, radius) lands in
Confidence Score: 5/5Safe to merge — all changes are additive UI/UX improvements with no mutations to persisted device config, no schema-breaking changes (all new fields carry serde(default)), and the two prior blocking issues from review round 1 are correctly resolved. The core logic — theme application, persistence, OS appearance sync, auto-install observer — is well-guarded: radius override is applied after Theme::change so it isn't clobbered, the auto-install observer guards on Available to avoid re-entry, and the Follow-System check in theme_card prevents wiping the mode preference. The remaining observations are display cosmetics and a maintenance brittleness in the deep-link page index, none of which affect correctness at runtime. crates/openlogi-gui/src/windows/settings/updates.rs (download progress display and hardcoded URL) and crates/openlogi-gui/src/windows/settings.rs (hardcoded deep-link page indices). Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant SettingsView
participant AppState
participant theme_apply as theme::apply_from_settings
participant ThemeRegistry
participant Theme
participant OS as platform::os
User->>SettingsView: click mode card / theme card / radius btn
SettingsView->>AppState: set_appearance / set_theme / set_ui_radius
AppState->>AppState: save_atomic (persist to disk)
SettingsView->>theme_apply: apply_from_settings(None, cx)
theme_apply->>AppState: read appearance, theme_light, theme_dark, ui_radius
theme_apply->>OS: set_app_appearance(appearance)
theme_apply->>theme_apply: cx.window_appearance() → os_appearance
theme_apply->>ThemeRegistry: pick light and dark theme configs
theme_apply->>Theme: set light_theme / dark_theme
theme_apply->>Theme: Theme::change(mode, None, cx)
Note over Theme: resets radius to theme default
theme_apply->>Theme: "global_mut(cx).radius = user override (if any)"
theme_apply->>SettingsView: cx.refresh_windows()
Note over User,OS: OS appearance change (System mode)
OS-->>SettingsView: observe_window_appearance fires
SettingsView->>theme_apply: apply_from_settings(Some(window), cx)
theme_apply->>Theme: Theme::change(ThemeMode::from(os), Some(window), cx)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant SettingsView
participant AppState
participant theme_apply as theme::apply_from_settings
participant ThemeRegistry
participant Theme
participant OS as platform::os
User->>SettingsView: click mode card / theme card / radius btn
SettingsView->>AppState: set_appearance / set_theme / set_ui_radius
AppState->>AppState: save_atomic (persist to disk)
SettingsView->>theme_apply: apply_from_settings(None, cx)
theme_apply->>AppState: read appearance, theme_light, theme_dark, ui_radius
theme_apply->>OS: set_app_appearance(appearance)
theme_apply->>theme_apply: cx.window_appearance() → os_appearance
theme_apply->>ThemeRegistry: pick light and dark theme configs
theme_apply->>Theme: set light_theme / dark_theme
theme_apply->>Theme: Theme::change(mode, None, cx)
Note over Theme: resets radius to theme default
theme_apply->>Theme: "global_mut(cx).radius = user override (if any)"
theme_apply->>SettingsView: cx.refresh_windows()
Note over User,OS: OS appearance change (System mode)
OS-->>SettingsView: observe_window_appearance fires
SettingsView->>theme_apply: apply_from_settings(Some(window), cx)
theme_apply->>Theme: Theme::change(ThemeMode::from(os), Some(window), cx)
Reviews (2): Last reviewed commit: "fix(gui): gate per-platform Settings imp..." | Re-trigger Greptile |
- permissions.rs: the page split left `Permission` / `PermissionStatus` and the macOS-only helpers imported unconditionally, breaking the Linux/Windows builds (E0432 unresolved import). Split the imports into always / macOS+Linux / macOS-only groups matching their actual use. - Appearance: clicking a theme card no longer clobbers a "Follow System" mode preference — the mode is only pinned when the user already chose an explicit Light/Dark mode (configuring the dark slot must not force the whole app dark). - Appearance: the corner-radius "Default" entry now stores `None` (defer to the theme's own radius) instead of a fixed 6px, fixing the mis-highlight under themes with a different radius and the one-way trap away from the theme value.
Summary
Folds the standalone About window into Settings and rebuilds the window around a gpui-component-driven theme system.
theme::palettederives fromcx.theme()tokens, so every bespoke surface re-skins with the selected theme. A committed OpenLogi light/dark theme reproduces the previous look; the upstream gpui-component themes are selectable too.AppSettings; the macOS titlebar (NSApp appearance) follows the chosen mode.Themes via build.rs (not vendored)
Only the OpenLogi brand theme is committed (
themes/openlogi.json). The 21 upstream gpui-component themes are not vendored into the repo:build.rslocates gpui-component's source viacargo metadata(robust across the local git cache, CI, and Nix's vendored tree) and embeds its theme files intoOUT_DIR.OPENLOGI_THEMES_DIRoverrides the lookup.Refactor / components
windows/settings/.Tagfor update-status chips,ButtonGroupfor the corner-radius picker. The About link row is a uniform row of small ghost buttons with Lucide icons (changelog/bug vendored underaction-icons/, since gpui-component'sIconNameset lacks them).Verification
cargo clippy --all-targets -- -D warnings(incl. the build script) clean;cargo test -p openlogi-guigreen.cargo metadatais expected to resolve the themes dir under Nix, withOPENLOGI_THEMES_DIRas the escape hatch.