From 4af76ef548be585c91b200053dff393368a5a6eb Mon Sep 17 00:00:00 2001 From: Anton Arnaudov Date: Fri, 24 Jul 2026 11:00:09 +0200 Subject: [PATCH] settings: guard that the theme editor stays reachable from the sidebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reported "colour-wheel editor is unreachable" repro was against an old installed build (v0.1.0), where "Create your own custom theme" was a README link that opened a browser and the editor had no sidebar entry. That's already fixed on main / v0.2.9: the Appearance page points at the sidebar, and the nav lists CustomizeUi, ThemeGallery ("Explore themes"), and ThemeCreator ("Create your own custom theme") → the ThemeCreatorPageView with the colour wheel. No functional change is needed. To keep it that way, extract the sidebar nav list into a pure `base_nav_items()` and add a regression test asserting the theme editor, theme gallery, and Customize UI each keep a sidebar entry — so the in-app entry point can't be dropped again in a refactor. Co-Authored-By: Claude Opus 4.8 --- app/src/settings_view/mod.rs | 51 ++++++++++++++++++------------ app/src/settings_view/mod_tests.rs | 31 ++++++++++++++++++ 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/app/src/settings_view/mod.rs b/app/src/settings_view/mod.rs index 0fc03f2a..eadc39c4 100644 --- a/app/src/settings_view/mod.rs +++ b/app/src/settings_view/mod.rs @@ -988,6 +988,36 @@ enum NavStop { }, } +/// The Uncaged settings sidebar, in order — the single source of truth for +/// which sections are reachable from the nav. AI/Code subpages are grouped under +/// umbrellas; everything else is a direct page. Kept as a standalone builder so +/// the reachable set — notably the theme editor (`ThemeCreator`) and the theme +/// gallery (`ThemeGallery`) — is trivial to unit-test and hard to drop by +/// accident. The cloud/account-era sections (Account, Billing, Teams, …) are +/// omitted on purpose: Uncaged is account-free and fully local. +fn base_nav_items() -> Vec { + vec![ + SettingsNavItem::Umbrella(SettingsUmbrella::new( + "Agents", + SettingsSection::ai_subpages().to_vec(), + )), + SettingsNavItem::Umbrella(SettingsUmbrella::new( + "Code", + vec![ + SettingsSection::CodeIndexing, + SettingsSection::EditorAndCodeReview, + ], + )), + SettingsNavItem::Page(SettingsSection::Appearance), + SettingsNavItem::Page(SettingsSection::CustomizeUi), + SettingsNavItem::Page(SettingsSection::ThemeGallery), + SettingsNavItem::Page(SettingsSection::ThemeCreator), + SettingsNavItem::Page(SettingsSection::Features), + SettingsNavItem::Page(SettingsSection::Keybindings), + SettingsNavItem::Page(SettingsSection::About), + ] +} + /// Builds the ordered list of arrow-key nav stops from `nav_items`. /// /// `is_visible` decides which sections are currently shown in the sidebar; @@ -1342,26 +1372,7 @@ impl SettingsView { // Referrals, Shared blocks, Warp Drive, Warpify, and Privacy — are dropped from // the nav. Their page handles remain constructed (so nothing else breaks); they // are simply not navigable. - let mut nav_items = vec![ - SettingsNavItem::Umbrella(SettingsUmbrella::new( - "Agents", - SettingsSection::ai_subpages().to_vec(), - )), - SettingsNavItem::Umbrella(SettingsUmbrella::new( - "Code", - vec![ - SettingsSection::CodeIndexing, - SettingsSection::EditorAndCodeReview, - ], - )), - SettingsNavItem::Page(SettingsSection::Appearance), - SettingsNavItem::Page(SettingsSection::CustomizeUi), - SettingsNavItem::Page(SettingsSection::ThemeGallery), - SettingsNavItem::Page(SettingsSection::ThemeCreator), - SettingsNavItem::Page(SettingsSection::Features), - SettingsNavItem::Page(SettingsSection::Keybindings), - SettingsNavItem::Page(SettingsSection::About), - ]; + let mut nav_items = base_nav_items(); if FeatureFlag::WarpControlCli.is_enabled() { let shared_blocks_index = nav_items diff --git a/app/src/settings_view/mod_tests.rs b/app/src/settings_view/mod_tests.rs index 7b69de71..ea137483 100644 --- a/app/src/settings_view/mod_tests.rs +++ b/app/src/settings_view/mod_tests.rs @@ -17,6 +17,37 @@ fn ai_subpages_are_identified() { assert!(!SettingsSection::CodeIndexing.is_ai_subpage()); } +/// Guards the theme-editor regression: in v0.1.0 the colour-wheel editor had no +/// sidebar entry and the Appearance "link" opened a web page instead, so the +/// editor was unreachable in-app. It now lives at `SettingsSection::ThemeCreator` +/// in the sidebar — this asserts that entry (and the theme gallery / Customize UI) +/// stays in the nav so it can't silently disappear again. +#[test] +fn theme_editor_and_gallery_are_reachable_from_the_sidebar() { + use super::{base_nav_items, SettingsNavItem, SettingsSection}; + + let pages: Vec = base_nav_items() + .into_iter() + .filter_map(|item| match item { + SettingsNavItem::Page(section) => Some(section), + SettingsNavItem::Umbrella(_) => None, + }) + .collect(); + + assert!( + pages.contains(&SettingsSection::ThemeCreator), + "the colour-wheel theme editor must have a sidebar entry" + ); + assert!( + pages.contains(&SettingsSection::ThemeGallery), + "the theme gallery must have a sidebar entry" + ); + assert!( + pages.contains(&SettingsSection::CustomizeUi), + "the Customize UI page must have a sidebar entry" + ); +} + #[test] fn code_subpages_are_identified() { assert!(SettingsSection::CodeIndexing.is_code_subpage());