fix: custom connections sidebar

This commit is contained in:
Peter Steinberger
2026-01-05 07:25:13 +01:00
parent 5431a9c692
commit 30038f7d37
4 changed files with 49 additions and 49 deletions

View File

@@ -9,7 +9,7 @@
- TUI: migrate key handling to the updated pi-tui Key matcher API. - TUI: migrate key handling to the updated pi-tui Key matcher API.
- macOS: prefer gateway config reads/writes in local mode (fall back to disk if the gateway is unavailable). - macOS: prefer gateway config reads/writes in local mode (fall back to disk if the gateway is unavailable).
- macOS: local gateway now connects via tailnet IP when bind mode is `tailnet`/`auto`. - macOS: local gateway now connects via tailnet IP when bind mode is `tailnet`/`auto`.
- macOS: Connections removes the sidebar toggle from the Settings toolbar to avoid overflow. - macOS: Connections settings now use a custom sidebar to avoid toolbar toggle issues, with rounded styling and full-width row hit targets.
- macOS: drop deprecated `afterMs` from agent wait params to match gateway schema. - macOS: drop deprecated `afterMs` from agent wait params to match gateway schema.
- Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json. - Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json.
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding. - Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.

View File

@@ -2,13 +2,11 @@ import SwiftUI
extension ConnectionsSettings { extension ConnectionsSettings {
var body: some View { var body: some View {
NavigationSplitView { HStack(spacing: 0) {
self.sidebar self.sidebar
} detail: {
self.detail self.detail
} }
.navigationSplitViewStyle(.balanced) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.toolbar(removing: .sidebarToggle)
.onAppear { .onAppear {
self.store.start() self.store.start()
self.ensureSelection() self.ensureSelection()
@@ -20,27 +18,30 @@ extension ConnectionsSettings {
} }
private var sidebar: some View { private var sidebar: some View {
List(selection: self.$selectedProvider) { ScrollView {
if !self.enabledProviders.isEmpty { LazyVStack(alignment: .leading, spacing: 8) {
Section("Configured") { if !self.enabledProviders.isEmpty {
self.sidebarSectionHeader("Configured")
ForEach(self.enabledProviders) { provider in ForEach(self.enabledProviders) { provider in
self.sidebarRow(provider) self.sidebarRow(provider)
.tag(provider)
} }
} }
}
if !self.availableProviders.isEmpty { if !self.availableProviders.isEmpty {
Section("Available") { self.sidebarSectionHeader("Available")
ForEach(self.availableProviders) { provider in ForEach(self.availableProviders) { provider in
self.sidebarRow(provider) self.sidebarRow(provider)
.tag(provider)
} }
} }
} }
.padding(.vertical, 10)
.padding(.horizontal, 10)
} }
.listStyle(.sidebar) .frame(minWidth: 220, idealWidth: 240, maxWidth: 280, maxHeight: .infinity, alignment: .topLeading)
.frame(minWidth: 220, idealWidth: 240, maxWidth: 280) .background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(nsColor: .windowBackgroundColor)))
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
} }
private var detail: some View { private var detail: some View {
@@ -81,18 +82,41 @@ extension ConnectionsSettings {
} }
private func sidebarRow(_ provider: ConnectionProvider) -> some View { private func sidebarRow(_ provider: ConnectionProvider) -> some View {
HStack(spacing: 8) { let isSelected = self.selectedProvider == provider
Circle() return Button {
.fill(self.providerTint(provider)) self.selectedProvider = provider
.frame(width: 8, height: 8) } label: {
VStack(alignment: .leading, spacing: 2) { HStack(spacing: 8) {
Text(provider.title) Circle()
Text(self.providerSummary(provider)) .fill(self.providerTint(provider))
.font(.caption) .frame(width: 8, height: 8)
.foregroundStyle(.secondary) VStack(alignment: .leading, spacing: 2) {
Text(provider.title)
Text(self.providerSummary(provider))
.font(.caption)
.foregroundStyle(.secondary)
}
} }
.padding(.vertical, 4)
.padding(.horizontal, 6)
.frame(maxWidth: .infinity, alignment: .leading)
.background(isSelected ? Color.accentColor.opacity(0.18) : Color.clear)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.background(Color.clear) // ensure full-width hit test area
.contentShape(Rectangle())
} }
.padding(.vertical, 4) .frame(maxWidth: .infinity, alignment: .leading)
.buttonStyle(.plain)
.contentShape(Rectangle())
}
private func sidebarSectionHeader(_ title: String) -> some View {
Text(title)
.font(.caption.weight(.semibold))
.foregroundStyle(.secondary)
.textCase(.uppercase)
.padding(.horizontal, 4)
.padding(.top, 2)
} }
private func detailHeader(for provider: ConnectionProvider) -> some View { private func detailHeader(for provider: ConnectionProvider) -> some View {

View File

@@ -75,7 +75,6 @@ struct SettingsRootView: View {
} }
.padding(.horizontal, 28) .padding(.horizontal, 28)
.padding(.vertical, 22) .padding(.vertical, 22)
.background(SettingsToolbarCleaner())
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading) .frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.onReceive(NotificationCenter.default.publisher(for: .clawdbotSelectSettingsTab)) { note in .onReceive(NotificationCenter.default.publisher(for: .clawdbotSelectSettingsTab)) { note in

View File

@@ -1,23 +0,0 @@
import AppKit
import SwiftUI
struct SettingsToolbarCleaner: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
NSView()
}
func updateNSView(_ nsView: NSView, context: Context) {
DispatchQueue.main.async {
guard let toolbar = nsView.window?.toolbar else { return }
let items = toolbar.items
for (index, item) in items.enumerated().reversed() {
let isSidebarToggle =
item.itemIdentifier == .toggleSidebar
|| item.itemIdentifier.rawValue == "com.apple.NSToolbarShowSidebarItem"
if isSidebarToggle {
toolbar.removeItem(at: index)
}
}
}
}
}