diff --git a/app/src/settings_view/mod.rs b/app/src/settings_view/mod.rs index 0fc03f2..eadc39c 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 7b69de7..ea13748 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());