@polderlabs/bizar 5.5.1 → 5.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/bizar-dash/dist/assets/{EnvVarsSection-BUQnyVQD.js → EnvVarsSection-DoIzqrlb.js} +3 -3
  2. package/bizar-dash/dist/assets/{EnvVarsSection-BUQnyVQD.js.map → EnvVarsSection-DoIzqrlb.js.map} +1 -1
  3. package/bizar-dash/dist/assets/{MobileChat-8FupFgTv.js → MobileChat-CoUCUsbr.js} +1 -1
  4. package/bizar-dash/dist/assets/{MobileChat-8FupFgTv.js.map → MobileChat-CoUCUsbr.js.map} +1 -1
  5. package/bizar-dash/dist/assets/MobileSettings-BeO04g52.js +1 -0
  6. package/bizar-dash/dist/assets/{MobileSettings-BkXtglA6.js.map → MobileSettings-BeO04g52.js.map} +1 -1
  7. package/bizar-dash/dist/assets/{Toast-CUwkLRms.js → Toast-D9nv5N6m.js} +1 -1
  8. package/bizar-dash/dist/assets/{Toast-CUwkLRms.js.map → Toast-D9nv5N6m.js.map} +1 -1
  9. package/bizar-dash/dist/assets/{icons-COpQr35l.js → icons-Btipv2pp.js} +65 -70
  10. package/bizar-dash/dist/assets/icons-Btipv2pp.js.map +1 -0
  11. package/bizar-dash/dist/assets/main-DHXPuddY.js +18 -0
  12. package/bizar-dash/dist/assets/main-DHXPuddY.js.map +1 -0
  13. package/bizar-dash/dist/assets/{main-Bz_zWFCw.css → main-UG4jKK-F.css} +1 -1
  14. package/bizar-dash/dist/assets/mobile-H5KH9cTL.js +1 -0
  15. package/bizar-dash/dist/assets/{mobile-B7dAkmYd.js.map → mobile-H5KH9cTL.js.map} +1 -1
  16. package/bizar-dash/dist/assets/{mobile-layout-ClC6Qa42.js → mobile-layout-BqHxVb6_.js} +2 -2
  17. package/bizar-dash/dist/assets/{mobile-layout-ClC6Qa42.js.map → mobile-layout-BqHxVb6_.js.map} +1 -1
  18. package/bizar-dash/dist/assets/{useSlashCommands-B3MZ1JXa.js → useSlashCommands-C24-O1e7.js} +2 -2
  19. package/bizar-dash/dist/assets/{useSlashCommands-B3MZ1JXa.js.map → useSlashCommands-C24-O1e7.js.map} +1 -1
  20. package/bizar-dash/dist/index.html +7 -7
  21. package/bizar-dash/dist/mobile.html +3 -3
  22. package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  23. package/bizar-dash/src/server/memory-store.mjs +74 -28
  24. package/bizar-dash/src/server/routes/memory.mjs +91 -38
  25. package/bizar-dash/src/web/App.tsx +36 -43
  26. package/bizar-dash/src/web/components/SettingsNav.tsx +92 -0
  27. package/bizar-dash/src/web/components/Sidebar.tsx +26 -7
  28. package/bizar-dash/src/web/components/Topbar.tsx +11 -48
  29. package/bizar-dash/src/web/styles/main.css +18 -3
  30. package/bizar-dash/src/web/styles/settings.css +115 -3
  31. package/bizar-dash/src/web/views/Settings.tsx +184 -205
  32. package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +4 -4
  33. package/bizar-dash/tests/memory-namespace.test.mjs +2 -2
  34. package/bizar-dash/tests/memory-status-root.test.mjs +144 -0
  35. package/bizar-dash/tests/memory-store.test.mjs +5 -4
  36. package/bizar-dash/tests/memory-vault-config-endpoint.test.mjs +173 -0
  37. package/bizar-dash/tests/settings-layout.test.tsx +129 -0
  38. package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
  39. package/bizar-dash/tests/settings-nav.test.tsx +127 -0
  40. package/bizar-dash/tests/views/Memory.test.tsx +40 -1
  41. package/bizar-dash/tests/views/topbar.test.tsx +35 -50
  42. package/cli/memory.mjs +1 -1
  43. package/package.json +1 -1
  44. package/bizar-dash/dist/assets/MobileSettings-BkXtglA6.js +0 -1
  45. package/bizar-dash/dist/assets/icons-COpQr35l.js.map +0 -1
  46. package/bizar-dash/dist/assets/main-gqVwliHz.js +0 -18
  47. package/bizar-dash/dist/assets/main-gqVwliHz.js.map +0 -1
  48. package/bizar-dash/dist/assets/mobile-B7dAkmYd.js +0 -1
  49. package/bizar-dash/tests/views/Settings.test.tsx +0 -160
@@ -43,6 +43,7 @@ import { BackgroundAgents } from './views/BackgroundAgents';
43
43
  import { Doctor } from './views/Doctor';
44
44
  import { Eval } from './views/Eval';
45
45
  import { EvalReport } from './views/EvalReport';
46
+ import { SettingsNav } from './components/SettingsNav';
46
47
  import { Spinner } from './components/Spinner';
47
48
  import { Button } from './components/Button';
48
49
  import { AlertTriangle, Globe, LayoutTemplate, X } from 'lucide-react';
@@ -56,10 +57,16 @@ type ViewProps = {
56
57
  setActiveTab: (id: string) => void;
57
58
  refreshSnapshot: () => Promise<void>;
58
59
  /**
59
- * v6.xOptional settings section id. When set, the SettingsView
60
- * renders only that section (each settings section is its own tab).
60
+ * v4.9.0When true, the sidebar shows the full settings navigation
61
+ * instead of the normal tab rail. Toggled by clicking the Settings tab.
61
62
  */
62
- section?: string;
63
+ settingsMode: boolean;
64
+ /**
65
+ * v4.9.0 — The active settings section highlighted in the sidebar nav.
66
+ * Null means "show all". Managed at App level so Sidebar can read it.
67
+ */
68
+ settingsActiveSection: string | null;
69
+ setSettingsActiveSection: (id: string | null) => void;
63
70
  /**
64
71
  * v3.3.0 — Cross-view scratch state. The "Open in chat" button
65
72
  * on the Agents tab sets `pendingInitialAgent`, which the Chat
@@ -74,25 +81,6 @@ export type CrossViewState = {
74
81
  initialAgent?: string | null;
75
82
  };
76
83
 
77
- // v6.x — Each settings section is its own top-level tab. Registered with
78
- // the `settings-` prefix so they don't collide with runtime tabs (e.g. the
79
- // existing `agents` runtime tab vs. the new `settings-agents` settings tab).
80
- const SETTINGS_SECTION_IDS = [
81
- 'theme',
82
- 'updates',
83
- 'layout',
84
- 'general',
85
- 'env-vars',
86
- 'network',
87
- 'notifications',
88
- 'auth',
89
- 'agents',
90
- 'system-llm',
91
- 'headroom',
92
- 'activity-log',
93
- 'workspaces',
94
- ] as const;
95
-
96
84
  const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
97
85
  overview: Overview,
98
86
  chat: Chat,
@@ -103,6 +91,7 @@ const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
103
91
  activity: Activity,
104
92
  background: BackgroundAgents,
105
93
  config: Config,
94
+ settings: SettingsView,
106
95
  marketplace: Marketplace,
107
96
  mods: Mods,
108
97
  schedules: Schedules,
@@ -121,12 +110,6 @@ const VIEW_MAP: Record<string, (p: ViewProps) => React.ReactNode> = {
121
110
  evalReport: EvalReport,
122
111
  };
123
112
 
124
- // Register each settings section as a dedicated VIEW_MAP entry.
125
- // The SettingsView component renders only the requested section.
126
- for (const sectionId of SETTINGS_SECTION_IDS) {
127
- VIEW_MAP[`settings-${sectionId}`] = (p) => <SettingsView {...p} section={sectionId} />;
128
- }
129
-
130
113
  const VERSION = 'v4.5.0';
131
114
 
132
115
  /**
@@ -242,6 +225,11 @@ function Shell() {
242
225
  const [settings, setSettings] = useState<Settings | null>(null);
243
226
  const [wsStatus, setWsStatus] = useState<WsStatus>('connecting');
244
227
  const [bootError, setBootError] = useState<string | null>(null);
228
+ // v4.9.0 — Settings mode: when true, sidebar shows all settings sections
229
+ const [settingsMode, setSettingsMode] = useState(false);
230
+ // v4.9.0 — The active settings section shown in the sidebar nav.
231
+ // Null means "show all sections". Managed here so Sidebar can read it.
232
+ const [settingsActiveSection, setSettingsActiveSection] = useState<string | null>(null);
245
233
  const [authPromptOpen, setAuthPromptOpen] = useState(false);
246
234
  const [searchOpen, setSearchOpen] = useState(false);
247
235
  const [stuckAgents, setStuckAgents] = useState<{ name: string }[]>([]);
@@ -657,10 +645,12 @@ function Shell() {
657
645
  [toast],
658
646
  );
659
647
 
660
- // v6.xEach settings section is its own tab. No special "settings mode"
661
- // toggle needed; clicking a settings tab just switches to that section.
648
+ // v4.9.0Settings mode: entering settings tab activates the sidebar layout.
649
+ // Navigating to any other tab exits settings mode.
662
650
  const handleTabChange = useCallback((id: string) => {
663
651
  setActiveTab(id);
652
+ if (id === 'settings') setSettingsMode(true);
653
+ else setSettingsMode(false);
664
654
  }, []);
665
655
 
666
656
  // v3.20.3 — Merge built-in tabs with installed mod views. Mod views
@@ -688,11 +678,14 @@ function Shell() {
688
678
  activeTab,
689
679
  setActiveTab: handleTabChange,
690
680
  refreshSnapshot,
681
+ settingsMode,
682
+ settingsActiveSection,
683
+ setSettingsActiveSection,
691
684
  };
692
685
  return renderActiveView(activeTab, viewProps, modViews, modViewsReloadKey);
693
686
  }
694
687
  return null;
695
- }, [activeTab, snapshot, settings, modViews, modViewsReloadKey, refreshSnapshot, handleTabChange]);
688
+ }, [activeTab, snapshot, settings, modViews, modViewsReloadKey, refreshSnapshot, settingsMode, settingsActiveSection, setSettingsActiveSection, handleTabChange]);
696
689
 
697
690
  const refreshProjects = async () => {
698
691
  try {
@@ -735,16 +728,10 @@ function Shell() {
735
728
  toast.info(`/${(r.item as { name: string }).name} — run from the TUI`, 2500);
736
729
  } else if (t === 'setting') {
737
730
  // v3.0.4 — Jump to Settings view and scroll to the matching row.
738
- // v6.x — Each settings section is its own tab; pick the right one
739
- // based on the field key prefix (e.g. `theme.accent` -> `settings-theme`).
740
731
  const settingId = (r.item as { id?: string; path?: string }).id
741
732
  || (r.item as { path?: string }).path
742
733
  || '';
743
- const sectionKey = settingId.split('.')[0] || '';
744
- const sectionId = SETTINGS_SECTION_IDS.includes(sectionKey as typeof SETTINGS_SECTION_IDS[number])
745
- ? `settings-${sectionKey}`
746
- : 'settings-general';
747
- handleTabChange(sectionId);
734
+ handleTabChange('settings');
748
735
  // Defer the scroll to allow the view to mount, then apply a brief
749
736
  // CSS highlight. We poll a few times because the Settings view
750
737
  // mounts lazily after tab switch.
@@ -827,11 +814,17 @@ function Shell() {
827
814
  </div>
828
815
  )}
829
816
  <div className="layout-body">
830
- <Sidebar
831
- tabs={mergedTabs}
832
- activeTab={activeTab}
833
- onTabChange={handleTabChange}
834
- />
817
+ {layout !== 'topnav' && (
818
+ <Sidebar
819
+ tabs={mergedTabs}
820
+ activeTab={activeTab}
821
+ onTabChange={handleTabChange}
822
+ settingsMode={settingsMode}
823
+ settingsActiveSection={settingsActiveSection}
824
+ onSettingsSectionChange={setSettingsActiveSection}
825
+ onExitSettings={() => handleTabChange('overview')}
826
+ />
827
+ )}
835
828
  <main className="content" id="main-content" tabIndex={-1}>
836
829
  {bootError && (
837
830
  <div className="boot-error">
@@ -0,0 +1,92 @@
1
+ // src/components/SettingsNav.tsx — v4.9.0 full-sidebar settings navigation.
2
+ // Shown in the sidebar rail when settingsMode is active. All sections are
3
+ // visible at once; clicking one scrolls the Settings view to that section.
4
+ import { ChevronRight, Palette, Terminal, Cpu, RefreshCw, Gauge, ArrowLeft, LayoutGrid, type LucideIcon } from 'lucide-react';
5
+ import { cn } from '../lib/utils';
6
+
7
+ export type SettingsSection = {
8
+ id: string;
9
+ label: string;
10
+ icon?: LucideIcon;
11
+ };
12
+
13
+ export type SettingsSectionGroup = {
14
+ label: string;
15
+ sections: SettingsSection[];
16
+ };
17
+
18
+ const SECTION_GROUPS: SettingsSectionGroup[] = [
19
+ {
20
+ label: 'General',
21
+ sections: [
22
+ { id: 'theme', label: 'Theme', icon: Palette },
23
+ { id: 'layout', label: 'Layout', icon: LayoutGrid },
24
+ { id: 'general', label: 'General', icon: LayoutGrid },
25
+ ],
26
+ },
27
+ {
28
+ label: 'Core',
29
+ sections: [
30
+ { id: 'env-vars', label: 'Env Vars', icon: Terminal },
31
+ { id: 'system-llm', label: 'System LLM', icon: Cpu },
32
+ ],
33
+ },
34
+ {
35
+ label: 'Experience',
36
+ sections: [
37
+ { id: 'updates', label: 'Updates', icon: RefreshCw },
38
+ { id: 'headroom', label: 'Headroom', icon: Gauge },
39
+ ],
40
+ },
41
+ ];
42
+
43
+ export type SettingsNavProps = {
44
+ activeSection: string | null;
45
+ onSectionChange: (id: string | null) => void;
46
+ onExitSettings: () => void;
47
+ };
48
+
49
+ export function SettingsNav({ activeSection, onSectionChange, onExitSettings }: SettingsNavProps) {
50
+ return (
51
+ <div className="settings-nav-root" aria-label="Settings sections">
52
+ {/* Back button */}
53
+ <button
54
+ type="button"
55
+ className="settings-nav-back"
56
+ onClick={onExitSettings}
57
+ aria-label="Exit settings"
58
+ >
59
+ <ArrowLeft size={14} />
60
+ <span>Back</span>
61
+ </button>
62
+
63
+ <div className="settings-nav-divider" aria-hidden="true" />
64
+
65
+ {/* Section groups */}
66
+ {SECTION_GROUPS.map((group) => (
67
+ <div key={group.label} className="settings-nav-group">
68
+ <div className="settings-nav-group-label">{group.label}</div>
69
+ {group.sections.map((s) => {
70
+ const Icon = s.icon;
71
+ const active = activeSection === s.id;
72
+ return (
73
+ <button
74
+ key={s.id}
75
+ type="button"
76
+ className={cn('settings-nav-item', active && 'settings-nav-item-active')}
77
+ onClick={() => onSectionChange(active ? null : s.id)}
78
+ aria-current={active ? 'page' : undefined}
79
+ >
80
+ {Icon && <Icon size={14} className="settings-nav-item-icon" aria-hidden />}
81
+ <span className="settings-nav-item-label">{s.label}</span>
82
+ {active && (
83
+ <ChevronRight size={12} className="settings-nav-item-chevron" aria-hidden />
84
+ )}
85
+ </button>
86
+ );
87
+ })}
88
+ </div>
89
+ ))}
90
+ </div>
91
+ );
92
+ }
@@ -1,18 +1,37 @@
1
- // src/components/Sidebar.tsx — vertical navigation rail.
2
- // v6.x — Sidebar is always shown. Each settings section is its own tab in
3
- // the rail; there is no longer a "settings mode" that swaps the rail for
4
- // a sub-nav. The rail uses the same `.sidebar-tab` / `.sidebar-tab-active`
5
- // classes as before for a consistent visual language.
1
+ // src/components/Sidebar.tsx — vertical navigation rail for sidebar/both layouts.
6
2
  import type { TabDef } from './Topbar';
3
+ import { SettingsNav } from './SettingsNav';
7
4
  import { cn } from '../lib/utils';
8
5
 
9
6
  export type SidebarProps = {
10
7
  tabs: TabDef[];
11
8
  activeTab: string;
12
9
  onTabChange: (id: string) => void;
10
+ /** v4.9.0 — When true, show the full settings sidebar nav instead of tabs. */
11
+ settingsMode?: boolean;
12
+ /** v4.9.0 — The currently highlighted settings section. */
13
+ settingsActiveSection?: string | null;
14
+ /** v4.9.0 — Called when user clicks a section in the settings sidebar. */
15
+ onSettingsSectionChange?: (id: string | null) => void;
16
+ /** v4.9.0 — Called when user clicks the back button in settings mode. */
17
+ onExitSettings?: () => void;
13
18
  };
14
19
 
15
- export function Sidebar({ tabs, activeTab, onTabChange }: SidebarProps) {
20
+ export function Sidebar({ tabs, activeTab, onTabChange, settingsMode, settingsActiveSection, onSettingsSectionChange, onExitSettings }: SidebarProps) {
21
+ // v4.9.0 — When settingsMode is active, render the full settings nav instead
22
+ // of the normal tab rail. This lets users see all sections at a glance.
23
+ if (settingsMode) {
24
+ return (
25
+ <aside className="sidebar sidebar-settings-mode" aria-label="Settings navigation">
26
+ <SettingsNav
27
+ activeSection={settingsActiveSection ?? null}
28
+ onSectionChange={onSettingsSectionChange ?? (() => {})}
29
+ onExitSettings={onExitSettings ?? (() => {})}
30
+ />
31
+ </aside>
32
+ );
33
+ }
34
+
16
35
  // Split tabs into built-in (no `isMod` flag) and mod-added entries.
17
36
  // Render a separator + small "Mods" label between them so users can
18
37
  // tell what's part of Bizar vs what an installed mod contributed.
@@ -67,4 +86,4 @@ export function Sidebar({ tabs, activeTab, onTabChange }: SidebarProps) {
67
86
  </nav>
68
87
  </aside>
69
88
  );
70
- }
89
+ }
@@ -1,8 +1,4 @@
1
1
  // src/components/Topbar.tsx — header with brand, project selector, search, tabs, ws status.
2
- // v6.x — Each settings section is its own top-level tab in the rail
3
- // (handled by the Sidebar; the Topbar also surfaces them in its tabs row
4
- // when the topbar layout is active). Dropped the layout selector and the
5
- // `settings-mode-indicator` adornment.
6
2
  import { useEffect, useState } from 'react';
7
3
  import type { ReactNode } from 'react';
8
4
  import {
@@ -10,7 +6,9 @@ import {
10
6
  MessageSquare,
11
7
  Bot,
12
8
  Map,
9
+ Folder,
13
10
  CheckSquare,
11
+ Settings2,
14
12
  Sliders,
15
13
  Puzzle,
16
14
  Clock,
@@ -19,6 +17,8 @@ import {
19
17
  ChevronDown,
20
18
  Plus,
21
19
  RefreshCw,
20
+ Power,
21
+ Sparkles,
22
22
  Activity,
23
23
  Radio,
24
24
  Coins,
@@ -26,17 +26,6 @@ import {
26
26
  Stethoscope,
27
27
  ClipboardCheck,
28
28
  Store,
29
- Palette,
30
- LayoutGrid,
31
- Terminal,
32
- Wifi,
33
- Bell,
34
- Lock,
35
- Cpu,
36
- Gauge,
37
- Folder,
38
- Sparkles,
39
- Settings2,
40
29
  type LucideIcon,
41
30
  } from 'lucide-react';
42
31
  import { cn } from '../lib/utils';
@@ -78,25 +67,7 @@ export const TABS: TabDef[] = [
78
67
  // home screen and from the settings surface (where an operator
79
68
  // typically arrives after something looks off).
80
69
  { id: 'doctor', label: 'Doctor', icon: Stethoscope },
81
- // v6.x Settings: each section is its own top-level tab. Grouped
82
- // visually with a divider before "Theme" so the rail still reads
83
- // as "system tabs → settings tabs". Use distinct lucide icons so
84
- // each section is recognisable at a glance instead of a generic
85
- // gear icon for all of them.
86
- { id: '__settings_divider__', label: '', icon: Settings2 },
87
- { id: 'settings-theme', label: 'Theme', icon: Palette },
88
- { id: 'settings-general', label: 'General', icon: Sliders },
89
- { id: 'settings-layout', label: 'Layout', icon: LayoutGrid },
90
- { id: 'settings-network', label: 'Network', icon: Wifi },
91
- { id: 'settings-notifications', label: 'Notifications', icon: Bell },
92
- { id: 'settings-auth', label: 'Auth', icon: Lock },
93
- { id: 'settings-env-vars', label: 'Env Vars', icon: Terminal },
94
- { id: 'settings-agents', label: 'Agents', icon: Bot },
95
- { id: 'settings-system-llm', label: 'System LLM', icon: Cpu },
96
- { id: 'settings-headroom', label: 'Headroom', icon: Gauge },
97
- { id: 'settings-updates', label: 'Updates', icon: RefreshCw },
98
- { id: 'settings-activity-log', label: 'Activity Log', icon: Activity },
99
- { id: 'settings-workspaces', label: 'Workspaces', icon: Folder },
70
+ { id: 'settings', label: 'Settings', icon: Sliders },
100
71
  ];
101
72
 
102
73
  export type TopbarProps = {
@@ -118,7 +89,7 @@ export type TopbarProps = {
118
89
  */
119
90
  notificationsSlot?: ReactNode;
120
91
  /**
121
- * Whether to render the tabs row. In sidebar layout the sidebar
92
+ * Whether to render the tabs row. In sidebar/both layouts the sidebar
122
93
  * carries navigation, so we hide this row to keep the topbar slim.
123
94
  */
124
95
  showTabs?: boolean;
@@ -185,16 +156,6 @@ export function Topbar({
185
156
  {showTabs && (
186
157
  <nav className="tabs-row" role="tablist" aria-label="Primary tabs">
187
158
  {TABS.map((tab) => {
188
- // v6.x — `__settings_divider__` is a layout-only entry; render a
189
- // non-interactive separator that visually splits system tabs from
190
- // settings tabs.
191
- if (tab.id === '__settings_divider__') {
192
- return (
193
- <span key={tab.id} className="tab-separator tab-separator-settings" aria-hidden="true">
194
- <span className="tab-separator-label">Settings</span>
195
- </span>
196
- );
197
- }
198
159
  const Icon = tab.icon;
199
160
  const active = tab.id === activeTab;
200
161
  return (
@@ -209,6 +170,11 @@ export function Topbar({
209
170
  >
210
171
  <Icon size={14} className="tab-icon" />
211
172
  <span className="tab-label">{tab.label}</span>
173
+ {tab.id === 'settings' && active && (
174
+ <span className="settings-mode-indicator" title="Settings mode active">
175
+ <Settings2 size={12} />
176
+ </span>
177
+ )}
212
178
  </button>
213
179
  );
214
180
  })}
@@ -423,6 +389,3 @@ function TopbarAddProjectDialog({
423
389
  </div>
424
390
  );
425
391
  }
426
-
427
- // (No additional exports below this point — keep Topbar's public surface
428
- // to the props, TABS, and the named Topbar component.)
@@ -9085,9 +9085,24 @@ select:focus-visible,
9085
9085
  margin-top: 1rem;
9086
9086
  }
9087
9087
 
9088
- /* v4.9.0 settings-mode-banner / settings-mode-indicator removed in v6.x:
9089
- each settings section is its own tab, so there's no longer a "settings
9090
- mode" to banner or to mark with an indicator dot. */
9088
+ /* v4.9.0 Banner shown in Settings view when settingsMode is active */
9089
+ .settings-mode-banner {
9090
+ display: flex;
9091
+ align-items: center;
9092
+ justify-content: space-between;
9093
+ padding: 12px 20px;
9094
+ background: linear-gradient(to right, rgba(139, 92, 246, 0.1), transparent);
9095
+ border-bottom: 1px solid var(--border);
9096
+ font-size: 13px;
9097
+ color: var(--text-dim);
9098
+ }
9099
+
9100
+ .settings-mode-indicator {
9101
+ display: inline-flex;
9102
+ align-items: center;
9103
+ color: var(--accent, #8b5cf6);
9104
+ margin-left: 6px;
9105
+ }
9091
9106
 
9092
9107
  /* ─── Toggle switch ──────────────────────────────────────────────── */
9093
9108
 
@@ -595,9 +595,121 @@
595
595
  to { transform: rotate(360deg); }
596
596
  }
597
597
 
598
- /* v4.9.0 .sidebar-settings-mode and .settings-nav-* classes removed in v6.x:
599
- each settings section is its own tab in the sidebar, which uses the
600
- shared `.sidebar-tab` / `.sidebar-tab-active` styles. */
598
+ /* ─────────────────────────────────────────────────────────────────────
599
+ v4.9.0 Settings sidebar mode (sidebar layout only)
600
+ When settingsMode is active, the sidebar replaces the normal tab rail
601
+ with a full-width nav showing all settings sections grouped by category.
602
+ ───────────────────────────────────────────────────────────────────── */
603
+
604
+ /* Sidebar when in settings mode — full height, scrollable */
605
+ .sidebar-settings-mode {
606
+ width: var(--sidebar-width, 240px);
607
+ overflow-y: auto;
608
+ border-right: 1px solid var(--border);
609
+ background: var(--bg);
610
+ }
611
+
612
+ /* Settings nav root */
613
+ .settings-nav-root {
614
+ display: flex;
615
+ flex-direction: column;
616
+ padding: var(--space-3);
617
+ gap: 2px;
618
+ }
619
+
620
+ /* Back button */
621
+ .settings-nav-back {
622
+ display: flex;
623
+ align-items: center;
624
+ gap: 6px;
625
+ padding: 6px 8px;
626
+ border-radius: var(--radius);
627
+ border: none;
628
+ background: transparent;
629
+ color: var(--text-dim);
630
+ font-size: 13px;
631
+ font-weight: 500;
632
+ cursor: pointer;
633
+ transition: background var(--motion-fast), color var(--motion-fast);
634
+ margin-bottom: var(--space-2);
635
+ }
636
+
637
+ .settings-nav-back:hover {
638
+ background: var(--bg-elev-2);
639
+ color: var(--text);
640
+ }
641
+
642
+ .settings-nav-divider {
643
+ height: 1px;
644
+ background: var(--border);
645
+ margin: var(--space-2) 0;
646
+ }
647
+
648
+ /* Section groups */
649
+ .settings-nav-group {
650
+ margin-bottom: var(--space-3);
651
+ }
652
+
653
+ .settings-nav-group-label {
654
+ font-size: 10px;
655
+ text-transform: uppercase;
656
+ letter-spacing: 0.08em;
657
+ color: var(--text-dim);
658
+ font-weight: 600;
659
+ padding: 4px 8px 6px;
660
+ }
661
+
662
+ /* Individual nav items */
663
+ .settings-nav-item {
664
+ display: flex;
665
+ align-items: center;
666
+ gap: 8px;
667
+ padding: 6px 8px;
668
+ border-radius: var(--radius);
669
+ border: 1px solid transparent;
670
+ background: transparent;
671
+ color: var(--text-dim);
672
+ font-size: 13px;
673
+ font-weight: 500;
674
+ cursor: pointer;
675
+ text-align: left;
676
+ width: 100%;
677
+ transition: background var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);
678
+ }
679
+
680
+ .settings-nav-item:hover {
681
+ background: var(--bg-elev-2);
682
+ color: var(--text);
683
+ }
684
+
685
+ .settings-nav-item-active {
686
+ background: var(--accent-bg);
687
+ color: var(--accent-2);
688
+ border-color: var(--accent-border);
689
+ }
690
+
691
+ .settings-nav-item-active:hover {
692
+ background: var(--accent-bg);
693
+ color: var(--accent-2);
694
+ }
695
+
696
+ .settings-nav-item-icon {
697
+ flex-shrink: 0;
698
+ opacity: 0.7;
699
+ }
700
+
701
+ .settings-nav-item-active .settings-nav-item-icon {
702
+ opacity: 1;
703
+ }
704
+
705
+ .settings-nav-item-label {
706
+ flex: 1;
707
+ }
708
+
709
+ .settings-nav-item-chevron {
710
+ flex-shrink: 0;
711
+ opacity: 0.5;
712
+ }
601
713
 
602
714
  /* ─────────────────────────────────────────────────────────────────────
603
715
  v5.4 — Mobile settings accordion (mobile-settings)