@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.
- package/bizar-dash/dist/assets/{EnvVarsSection-BUQnyVQD.js → EnvVarsSection-DoIzqrlb.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsSection-BUQnyVQD.js.map → EnvVarsSection-DoIzqrlb.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-8FupFgTv.js → MobileChat-CoUCUsbr.js} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-8FupFgTv.js.map → MobileChat-CoUCUsbr.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-BeO04g52.js +1 -0
- package/bizar-dash/dist/assets/{MobileSettings-BkXtglA6.js.map → MobileSettings-BeO04g52.js.map} +1 -1
- package/bizar-dash/dist/assets/{Toast-CUwkLRms.js → Toast-D9nv5N6m.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-CUwkLRms.js.map → Toast-D9nv5N6m.js.map} +1 -1
- package/bizar-dash/dist/assets/{icons-COpQr35l.js → icons-Btipv2pp.js} +65 -70
- package/bizar-dash/dist/assets/icons-Btipv2pp.js.map +1 -0
- package/bizar-dash/dist/assets/main-DHXPuddY.js +18 -0
- package/bizar-dash/dist/assets/main-DHXPuddY.js.map +1 -0
- package/bizar-dash/dist/assets/{main-Bz_zWFCw.css → main-UG4jKK-F.css} +1 -1
- package/bizar-dash/dist/assets/mobile-H5KH9cTL.js +1 -0
- package/bizar-dash/dist/assets/{mobile-B7dAkmYd.js.map → mobile-H5KH9cTL.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-layout-ClC6Qa42.js → mobile-layout-BqHxVb6_.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-layout-ClC6Qa42.js.map → mobile-layout-BqHxVb6_.js.map} +1 -1
- package/bizar-dash/dist/assets/{useSlashCommands-B3MZ1JXa.js → useSlashCommands-C24-O1e7.js} +2 -2
- package/bizar-dash/dist/assets/{useSlashCommands-B3MZ1JXa.js.map → useSlashCommands-C24-O1e7.js.map} +1 -1
- package/bizar-dash/dist/index.html +7 -7
- package/bizar-dash/dist/mobile.html +3 -3
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/memory-store.mjs +74 -28
- package/bizar-dash/src/server/routes/memory.mjs +91 -38
- package/bizar-dash/src/web/App.tsx +36 -43
- package/bizar-dash/src/web/components/SettingsNav.tsx +92 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +26 -7
- package/bizar-dash/src/web/components/Topbar.tsx +11 -48
- package/bizar-dash/src/web/styles/main.css +18 -3
- package/bizar-dash/src/web/styles/settings.css +115 -3
- package/bizar-dash/src/web/views/Settings.tsx +184 -205
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +4 -4
- package/bizar-dash/tests/memory-namespace.test.mjs +2 -2
- package/bizar-dash/tests/memory-status-root.test.mjs +144 -0
- package/bizar-dash/tests/memory-store.test.mjs +5 -4
- package/bizar-dash/tests/memory-vault-config-endpoint.test.mjs +173 -0
- package/bizar-dash/tests/settings-layout.test.tsx +129 -0
- package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
- package/bizar-dash/tests/settings-nav.test.tsx +127 -0
- package/bizar-dash/tests/views/Memory.test.tsx +40 -1
- package/bizar-dash/tests/views/topbar.test.tsx +35 -50
- package/cli/memory.mjs +1 -1
- package/package.json +1 -1
- package/bizar-dash/dist/assets/MobileSettings-BkXtglA6.js +0 -1
- package/bizar-dash/dist/assets/icons-COpQr35l.js.map +0 -1
- package/bizar-dash/dist/assets/main-gqVwliHz.js +0 -18
- package/bizar-dash/dist/assets/main-gqVwliHz.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-B7dAkmYd.js +0 -1
- 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
|
-
*
|
|
60
|
-
*
|
|
60
|
+
* v4.9.0 — When true, the sidebar shows the full settings navigation
|
|
61
|
+
* instead of the normal tab rail. Toggled by clicking the Settings tab.
|
|
61
62
|
*/
|
|
62
|
-
|
|
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
|
-
//
|
|
661
|
-
//
|
|
648
|
+
// v4.9.0 — Settings 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
|
-
|
|
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
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
9089
|
-
|
|
9090
|
-
|
|
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
|
-
/*
|
|
599
|
-
|
|
600
|
-
|
|
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)
|