diff --git a/supacode/Features/Repositories/Models/SidebarPresentation.swift b/supacode/Features/Repositories/Models/SidebarPresentation.swift index a211cbbc..44617867 100644 --- a/supacode/Features/Repositories/Models/SidebarPresentation.swift +++ b/supacode/Features/Repositories/Models/SidebarPresentation.swift @@ -4,7 +4,7 @@ struct SidebarPresentation: Equatable { var items: [SidebarItem] static func showsListHeader(repositoryCount: Int) -> Bool { - repositoryCount > 10 + true } var repositoryOrderIDs: [Repository.ID] { diff --git a/supacode/Features/Repositories/Views/EmptyStateView.swift b/supacode/Features/Repositories/Views/EmptyStateView.swift index ef7053cc..35f62e54 100644 --- a/supacode/Features/Repositories/Views/EmptyStateView.swift +++ b/supacode/Features/Repositories/Views/EmptyStateView.swift @@ -38,8 +38,8 @@ struct EmptyStateView: View { private func promptText(shortcutDisplay: String?) -> String { if let shortcutDisplay { - return "Press \(shortcutDisplay) or click Open Repository to choose a folder." + return "Press \(shortcutDisplay) or click Open Repository to add a repository." } - return "Click Open Repository to choose a folder." + return "Click Open Repository to add a repository." } } diff --git a/supacode/Features/Repositories/Views/SidebarFooterView.swift b/supacode/Features/Repositories/Views/SidebarFooterView.swift index 86cbf140..e8eccdd8 100644 --- a/supacode/Features/Repositories/Views/SidebarFooterView.swift +++ b/supacode/Features/Repositories/Views/SidebarFooterView.swift @@ -5,30 +5,10 @@ struct SidebarFooterView: View { let store: StoreOf @Environment(\.surfaceBottomChromeBackgroundOpacity) private var surfaceBottomChromeBackgroundOpacity @Environment(\.openURL) private var openURL - @Environment(CommandKeyObserver.self) private var commandKeyObserver @Environment(\.resolvedKeybindings) private var resolvedKeybindings var body: some View { HStack { - Button { - store.send(.setOpenPanelPresented(true)) - } label: { - HStack(spacing: 6) { - Label("Add Repository", systemImage: "folder.badge.plus") - .font(.callout) - if commandKeyObserver.isPressed, - let shortcut = shortcutDisplay(for: AppShortcuts.CommandID.openRepository) - { - ShortcutHintView(text: shortcut, color: .secondary) - } - } - } - .help( - AppShortcuts.helpText( - title: "Add Repository", - commandID: AppShortcuts.CommandID.openRepository, - in: resolvedKeybindings - )) Spacer() Menu { Button("Homepage", systemImage: "house") { @@ -110,8 +90,4 @@ struct SidebarFooterView: View { Divider() } } - - private func shortcutDisplay(for commandID: String) -> String? { - AppShortcuts.display(for: commandID, in: resolvedKeybindings) - } } diff --git a/supacode/Features/Repositories/Views/SidebarListView.swift b/supacode/Features/Repositories/Views/SidebarListView.swift index cbc21a24..690cf6e4 100644 --- a/supacode/Features/Repositories/Views/SidebarListView.swift +++ b/supacode/Features/Repositories/Views/SidebarListView.swift @@ -67,6 +67,10 @@ struct SidebarListView: View { ) } + if repositoryItems.isEmpty { + emptyRepositoryHint() + } + ForEach(Array(repositoryItems.enumerated()), id: \.element.id) { index, item in repositoryItemView( item, @@ -145,11 +149,22 @@ struct SidebarListView: View { action: RepositoryListHeaderAction, expandableRepositoryIDs: Set ) -> some View { - HStack(spacing: 8) { + HStack(spacing: 4) { Text("Repositories") .font(.caption) .foregroundStyle(.tertiary) .frame(maxWidth: .infinity, alignment: .leading) + Button { + store.send(.setOpenPanelPresented(true)) + } label: { + Label("Add Repository", systemImage: "plus") + .labelStyle(.iconOnly) + .frame(width: 20, height: 20) + .contentShape(.rect) + } + .buttonStyle(.plain) + .foregroundStyle(.secondary) + .help("Add Repository") if !expandableRepositoryIDs.isEmpty { Button { withAnimation(.easeOut(duration: 0.2)) { @@ -179,6 +194,24 @@ struct SidebarListView: View { .padding(.bottom, 4) } + private func emptyRepositoryHint() -> some View { + HStack(spacing: 6) { + Spacer(minLength: 0) + Text("Add your first repository") + .font(.caption) + .foregroundStyle(.secondary) + Image(systemName: "arrow.turn.up.right") + .font(.caption.weight(.semibold)) + .foregroundStyle(.secondary) + .symbolEffect(.pulse, options: .repeating) + .accessibilityHidden(true) + } + .padding(.leading, 12) + .padding(.trailing, 14) + .padding(.top, 2) + .padding(.bottom, 6) + } + @ViewBuilder private func repositoryItemView( _ item: SidebarItem, diff --git a/supacodeTests/RepositorySectionViewTests.swift b/supacodeTests/RepositorySectionViewTests.swift index 885472dd..cbdd7032 100644 --- a/supacodeTests/RepositorySectionViewTests.swift +++ b/supacodeTests/RepositorySectionViewTests.swift @@ -63,9 +63,9 @@ struct RepositorySectionViewTests { ) } - @Test func sidebarHeaderOnlyShowsForLongRepositoryLists() { - #expect(!SidebarListView.showsRepositoryListHeader(repositoryCount: 0)) - #expect(!SidebarListView.showsRepositoryListHeader(repositoryCount: 10)) + @Test func sidebarHeaderAlwaysShows() { + #expect(SidebarListView.showsRepositoryListHeader(repositoryCount: 0)) + #expect(SidebarListView.showsRepositoryListHeader(repositoryCount: 1)) #expect(SidebarListView.showsRepositoryListHeader(repositoryCount: 11)) } diff --git a/supacodeTests/SidebarPresentationTests.swift b/supacodeTests/SidebarPresentationTests.swift index bfc6df66..d61b60f4 100644 --- a/supacodeTests/SidebarPresentationTests.swift +++ b/supacodeTests/SidebarPresentationTests.swift @@ -15,8 +15,9 @@ struct SidebarPresentationTests { let presentation = state.sidebarPresentation(expandedRepositoryIDs: [repository.id]) - #expect(presentation.items.count == 1) - guard case .repository(let model) = presentation.items.first else { + let repositoryItems = presentation.repositoryRowItems + #expect(repositoryItems.count == 1) + guard case .repository(let model) = repositoryItems.first else { Issue.record("Expected repository container") return } @@ -34,7 +35,7 @@ struct SidebarPresentationTests { let presentation = state.sidebarPresentation(expandedRepositoryIDs: []) - guard case .repository(let model) = presentation.items.first else { + guard case .repository(let model) = presentation.repositoryRowItems.first else { Issue.record("Expected repository container") return } @@ -59,7 +60,7 @@ struct SidebarPresentationTests { presentation.repositoryOrderAfterMove(fromOffsets: IndexSet(integer: 0), toOffset: 2) == [ repoA.id, "/tmp/missing", ]) - guard case .failedRepository(let failed) = presentation.items.first else { + guard case .failedRepository(let failed) = presentation.repositoryRowItems.first else { Issue.record("Expected failed repository first") return } @@ -73,7 +74,7 @@ struct SidebarPresentationTests { let presentation = state.sidebarPresentation(expandedRepositoryIDs: [repository.id]) - guard case .repository(let model) = presentation.items.first else { + guard case .repository(let model) = presentation.repositoryRowItems.first else { Issue.record("Expected repository container") return } @@ -100,7 +101,7 @@ struct SidebarPresentationTests { let presentation = state.sidebarPresentation(expandedRepositoryIDs: [repository.id]) - guard case .repository(let model) = presentation.items.first else { + guard case .repository(let model) = presentation.repositoryRowItems.first else { Issue.record("Expected repository container") return } @@ -195,3 +196,9 @@ struct SidebarPresentationTests { return state } } + +extension SidebarPresentation { + fileprivate var repositoryRowItems: [SidebarItem] { + items.filter { $0.repositoryOrderID != nil } + } +} -- 2.51.2 From bf1674d0fb475a14d0296c234663d7f8a035c488 Mon Sep 17 00:00:00 2001 From: onevcat Date: Tue, 5 May 2026 14:31:15 +0900 Subject: [PATCH 2/4] Polish empty-state copy and onboarding arrow direction Tighten EmptyStateView padding, rename "Open Repository" to "Add Repository" so the wording matches the sidebar action, and flip the onboarding hint arrow to arrow.turn.right.up so it points toward the + button in the header. --- .../Repositories/Views/EmptyStateView.swift | 13 ++++++++----- .../Repositories/Views/SidebarListView.swift | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/supacode/Features/Repositories/Views/EmptyStateView.swift b/supacode/Features/Repositories/Views/EmptyStateView.swift index 35f62e54..fc6800e8 100644 --- a/supacode/Features/Repositories/Views/EmptyStateView.swift +++ b/supacode/Features/Repositories/Views/EmptyStateView.swift @@ -7,18 +7,20 @@ struct EmptyStateView: View { var body: some View { let shortcutDisplay = AppShortcuts.display(for: AppShortcuts.CommandID.openRepository, in: resolvedKeybindings) - VStack { + VStack(spacing: 16) { Image(systemName: "tray") .font(.title2) .accessibilityHidden(true) + .padding(.bottom, 4) Text("Open a repository or folder") .font(.headline) Text(promptText(shortcutDisplay: shortcutDisplay)) .font(.subheadline) .foregroundStyle(.secondary) - Button("Open Repository...") { + Button("Add Repository...") { store.send(.setOpenPanelPresented(true)) } + .padding(.top, 4) .modifier( KeyboardShortcutModifier( shortcut: resolvedKeybindings.keyboardShortcut(for: AppShortcuts.CommandID.openRepository) @@ -26,11 +28,12 @@ struct EmptyStateView: View { ) .help( AppShortcuts.helpText( - title: "Open Repository", + title: "Add Repository", commandID: AppShortcuts.CommandID.openRepository, in: resolvedKeybindings )) } + .padding(24) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(nsColor: .windowBackgroundColor)) .multilineTextAlignment(.center) @@ -38,8 +41,8 @@ struct EmptyStateView: View { private func promptText(shortcutDisplay: String?) -> String { if let shortcutDisplay { - return "Press \(shortcutDisplay) or click Open Repository to add a repository." + return "Press \(shortcutDisplay) or click Add Repository to add one." } - return "Click Open Repository to add a repository." + return "Click Add Repository to add one." } } diff --git a/supacode/Features/Repositories/Views/SidebarListView.swift b/supacode/Features/Repositories/Views/SidebarListView.swift index 690cf6e4..d9beccf0 100644 --- a/supacode/Features/Repositories/Views/SidebarListView.swift +++ b/supacode/Features/Repositories/Views/SidebarListView.swift @@ -200,7 +200,7 @@ struct SidebarListView: View { Text("Add your first repository") .font(.caption) .foregroundStyle(.secondary) - Image(systemName: "arrow.turn.up.right") + Image(systemName: "arrow.turn.right.up") .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) .symbolEffect(.pulse, options: .repeating) -- 2.51.2 From cb3e25d4cdf9335eaff2fc4eef37ccbcda644174 Mon Sep 17 00:00:00 2001 From: onevcat Date: Tue, 5 May 2026 15:24:10 +0900 Subject: [PATCH 3/4] Unify empty-state views on ContentUnavailableView Migrate the Repositories and Shelf empty states to ContentUnavailableView so they match the Archived Worktrees and Diff View placeholders. Removes two ad-hoc VStack layouts with mismatched icon sizes and spacing in favor of the platform component, which keeps typography and Dynamic Type consistent across all empty states. --- .../Repositories/Views/EmptyStateView.swift | 16 ++++------------ supacode/Features/Shelf/Views/ShelfView.swift | 16 +++++----------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/supacode/Features/Repositories/Views/EmptyStateView.swift b/supacode/Features/Repositories/Views/EmptyStateView.swift index fc6800e8..8d800bbf 100644 --- a/supacode/Features/Repositories/Views/EmptyStateView.swift +++ b/supacode/Features/Repositories/Views/EmptyStateView.swift @@ -7,20 +7,14 @@ struct EmptyStateView: View { var body: some View { let shortcutDisplay = AppShortcuts.display(for: AppShortcuts.CommandID.openRepository, in: resolvedKeybindings) - VStack(spacing: 16) { - Image(systemName: "tray") - .font(.title2) - .accessibilityHidden(true) - .padding(.bottom, 4) - Text("Open a repository or folder") - .font(.headline) + ContentUnavailableView { + Label("Open a repository or folder", systemImage: "tray") + } description: { Text(promptText(shortcutDisplay: shortcutDisplay)) - .font(.subheadline) - .foregroundStyle(.secondary) + } actions: { Button("Add Repository...") { store.send(.setOpenPanelPresented(true)) } - .padding(.top, 4) .modifier( KeyboardShortcutModifier( shortcut: resolvedKeybindings.keyboardShortcut(for: AppShortcuts.CommandID.openRepository) @@ -33,10 +27,8 @@ struct EmptyStateView: View { in: resolvedKeybindings )) } - .padding(24) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(nsColor: .windowBackgroundColor)) - .multilineTextAlignment(.center) } private func promptText(shortcutDisplay: String?) -> String { diff --git a/supacode/Features/Shelf/Views/ShelfView.swift b/supacode/Features/Shelf/Views/ShelfView.swift index 1aa07110..00d15fe4 100644 --- a/supacode/Features/Shelf/Views/ShelfView.swift +++ b/supacode/Features/Shelf/Views/ShelfView.swift @@ -166,17 +166,11 @@ struct ShelfView: View { @ViewBuilder private func emptyOpenArea() -> some View { - VStack(spacing: 10) { - Image(systemName: "books.vertical") - .font(.system(size: 40)) - .foregroundStyle(.secondary) - .accessibilityHidden(true) - Text("No worktree selected") - .font(.headline) - Text("Click a worktree to open it.") - .font(.callout) - .foregroundStyle(.secondary) - } + ContentUnavailableView( + "No worktree selected", + systemImage: "books.vertical", + description: Text("Click a worktree to open it.") + ) .frame(maxWidth: .infinity, maxHeight: .infinity) } -- 2.51.2 From 86a2070f7bc89d6d098788ecb675e3aa4511a4b2 Mon Sep 17 00:00:00 2001 From: onevcat Date: Tue, 5 May 2026 15:29:46 +0900 Subject: [PATCH 4/4] Move sidebar Help menu to the left edge Place the Help (?) menu at the leading side of the sidebar footer and push refresh / archive / settings to the trailing side via a Spacer, giving the secondary informational menu its own resting spot away from the action cluster. --- supacode/Features/Repositories/Views/SidebarFooterView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supacode/Features/Repositories/Views/SidebarFooterView.swift b/supacode/Features/Repositories/Views/SidebarFooterView.swift index e8eccdd8..38713342 100644 --- a/supacode/Features/Repositories/Views/SidebarFooterView.swift +++ b/supacode/Features/Repositories/Views/SidebarFooterView.swift @@ -9,7 +9,6 @@ struct SidebarFooterView: View { var body: some View { HStack { - Spacer() Menu { Button("Homepage", systemImage: "house") { if let url = URL(string: "https://prowl.onev.cat/") { @@ -36,6 +35,7 @@ struct SidebarFooterView: View { } .menuIndicator(.hidden) .help("Help") + Spacer() Button { store.send(.refreshWorktrees) } label: {