From a89204362e39fce4d58e5d264d8375cd700e54ab Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 5 Jan 2026 05:54:21 +0100 Subject: [PATCH] fix: use sidebar settings layout --- .../Sources/Clawdbot/SettingsRootView.swift | 141 +++++++++++------- 1 file changed, 86 insertions(+), 55 deletions(-) diff --git a/apps/macos/Sources/Clawdbot/SettingsRootView.swift b/apps/macos/Sources/Clawdbot/SettingsRootView.swift index eb3d8aa51..291db410f 100644 --- a/apps/macos/Sources/Clawdbot/SettingsRootView.swift +++ b/apps/macos/Sources/Clawdbot/SettingsRootView.swift @@ -18,64 +18,29 @@ struct SettingsRootView: View { } var body: some View { - VStack(alignment: .leading, spacing: 12) { - if self.isNixMode { - self.nixManagedBanner - } - TabView(selection: self.$selectedTab) { - GeneralSettings(state: self.state) - .tabItem { Label("General", systemImage: "gearshape") } - .tag(SettingsTab.general) - - ConnectionsSettings() - .tabItem { Label("Connections", systemImage: "link") } - .tag(SettingsTab.connections) - - VoiceWakeSettings(state: self.state) - .tabItem { Label("Voice Wake", systemImage: "waveform.circle") } - .tag(SettingsTab.voiceWake) - - ConfigSettings() - .tabItem { Label("Config", systemImage: "slider.horizontal.3") } - .tag(SettingsTab.config) - - InstancesSettings() - .tabItem { Label("Instances", systemImage: "network") } - .tag(SettingsTab.instances) - - SessionsSettings() - .tabItem { Label("Sessions", systemImage: "clock.arrow.circlepath") } - .tag(SettingsTab.sessions) - - CronSettings() - .tabItem { Label("Cron", systemImage: "calendar") } - .tag(SettingsTab.cron) - - SkillsSettings(state: self.state) - .tabItem { Label("Skills", systemImage: "sparkles") } - .tag(SettingsTab.skills) - - PermissionsSettings( - status: self.permissionMonitor.status, - refresh: self.refreshPerms, - showOnboarding: { OnboardingController.shared.show() }) - .tabItem { Label("Permissions", systemImage: "lock.shield") } - .tag(SettingsTab.permissions) - - if self.state.debugPaneEnabled { - DebugSettings(state: self.state) - .tabItem { Label("Debug", systemImage: "ant") } - .tag(SettingsTab.debug) + NavigationSplitView { + List(selection: self.$selectedTab) { + Section("Settings") { + ForEach(self.sidebarTabs, id: \.self) { tab in + Label(tab.title, systemImage: tab.systemImage) + .tag(tab) + } } - - AboutSettings(updater: self.updater) - .tabItem { Label("About", systemImage: "info.circle") } - .tag(SettingsTab.about) } - .tabViewStyle(.sidebar) + .listStyle(.sidebar) + .frame(minWidth: 200, idealWidth: 220, maxWidth: 260) + } detail: { + VStack(alignment: .leading, spacing: 12) { + if self.isNixMode { + self.nixManagedBanner + } + self.detailView(for: self.selectedTab) + } + .padding(.horizontal, 28) + .padding(.vertical, 22) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) } - .padding(.horizontal, 28) - .padding(.vertical, 22) + .navigationSplitViewStyle(.balanced) .frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .onReceive(NotificationCenter.default.publisher(for: .clawdbotSelectSettingsTab)) { note in @@ -145,6 +110,56 @@ struct SettingsRootView: View { return requested } + private var sidebarTabs: [SettingsTab] { + var tabs: [SettingsTab] = [ + .general, + .connections, + .voiceWake, + .config, + .instances, + .sessions, + .cron, + .skills, + .permissions, + ] + if self.state.debugPaneEnabled { + tabs.append(.debug) + } + tabs.append(.about) + return tabs + } + + @ViewBuilder + private func detailView(for tab: SettingsTab) -> some View { + switch tab { + case .general: + GeneralSettings(state: self.state) + case .connections: + ConnectionsSettings() + case .voiceWake: + VoiceWakeSettings(state: self.state) + case .config: + ConfigSettings() + case .instances: + InstancesSettings() + case .sessions: + SessionsSettings() + case .cron: + CronSettings() + case .skills: + SkillsSettings(state: self.state) + case .permissions: + PermissionsSettings( + status: self.permissionMonitor.status, + refresh: self.refreshPerms, + showOnboarding: { OnboardingController.shared.show() }) + case .debug: + DebugSettings(state: self.state) + case .about: + AboutSettings(updater: self.updater) + } + } + @MainActor private func refreshSnapshotPaths() async { let paths = await GatewayConnection.shared.snapshotPaths() @@ -195,6 +210,22 @@ enum SettingsTab: CaseIterable { case .about: "About" } } + + var systemImage: String { + switch self { + case .general: "gearshape" + case .connections: "link" + case .skills: "sparkles" + case .sessions: "clock.arrow.circlepath" + case .cron: "calendar" + case .config: "slider.horizontal.3" + case .instances: "network" + case .voiceWake: "waveform.circle" + case .permissions: "lock.shield" + case .debug: "ant" + case .about: "info.circle" + } + } } @MainActor