native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add Archived Worktrees sidebar row with enum-based selection

Refactor sidebar selection to use SidebarSelection enum directly in
state instead of separate selectedWorktreeID and boolean. This
eliminates invalid states and follows swift-navigation best practices.

- Add SidebarSelection.archivedWorktrees case
- Replace selectedWorktreeID stored property with selection enum
- Add computed selectedWorktreeID and isShowingArchivedWorktrees
- Add ArchivedWorktreesDetailView placeholder
- Update all selection write operations in reducer

khoi 38615813 0103f124

+78 -32
+32 -18
supacode/Features/Repositories/Reducer/RepositoriesFeature.swift
··· 16 16 var repositoryRoots: [URL] = [] 17 17 var repositoryOrderIDs: [Repository.ID] = [] 18 18 var loadFailuresByID: [Repository.ID: String] = [:] 19 - var selectedWorktreeID: Worktree.ID? 19 + var selection: SidebarSelection? 20 20 var worktreeInfoByID: [Worktree.ID: WorktreeInfoEntry] = [:] 21 21 var worktreeOrderByRepository: [Repository.ID: [Worktree.ID]] = [:] 22 22 var isOpenPanelPresented = false ··· 45 45 case refreshWorktrees 46 46 case reloadRepositories(animated: Bool) 47 47 case repositoriesLoaded([Repository], failures: [LoadFailure], roots: [URL], animated: Bool) 48 + case selectArchivedWorktrees 48 49 case openRepositories([URL]) 49 50 case openRepositoriesFinished( 50 51 [Repository], ··· 341 342 } 342 343 return .merge(allEffects) 343 344 345 + case .selectArchivedWorktrees: 346 + state.selection = .archivedWorktrees 347 + return .send(.delegate(.selectedWorktreeChanged(nil))) 348 + 344 349 case .selectWorktree(let worktreeID): 345 - state.selectedWorktreeID = worktreeID 350 + state.selection = worktreeID.map(SidebarSelection.worktree) 346 351 let selectedWorktree = state.worktree(for: worktreeID) 347 352 return .send(.delegate(.selectedWorktreeChanged(selectedWorktree))) 348 353 ··· 425 430 detail: "" 426 431 ) 427 432 ) 428 - state.selectedWorktreeID = pendingID 433 + state.selection = .worktree(pendingID) 429 434 let existingNames = Set(repository.worktrees.map { $0.name.lowercased() }) 430 435 return .run { send in 431 436 var newWorktreeName: String? ··· 498 503 state.pendingSetupScriptWorktreeIDs.insert(worktree.id) 499 504 state.pendingTerminalFocusWorktreeIDs.insert(worktree.id) 500 505 removePendingWorktree(pendingID, state: &state) 501 - if state.selectedWorktreeID == pendingID { 502 - state.selectedWorktreeID = worktree.id 506 + if state.selection == .worktree(pendingID) { 507 + state.selection = .worktree(worktree.id) 503 508 } 504 509 insertWorktree(worktree, repositoryID: repositoryID, state: &state) 505 510 return .merge( ··· 678 683 didUpdateWorktreeOrder = true 679 684 } 680 685 _ = removeWorktree(worktreeID, repositoryID: repositoryID, state: &state) 681 - let selectionNeedsUpdate = state.selectedWorktreeID == worktreeID 686 + let selectionNeedsUpdate = state.selection == .worktree(worktreeID) 682 687 if selectionNeedsUpdate { 683 - state.selectedWorktreeID = 684 - nextSelection ?? firstAvailableWorktreeID(in: repositoryID, state: state) 688 + let nextWorktreeID = nextSelection ?? firstAvailableWorktreeID(in: repositoryID, state: state) 689 + state.selection = nextWorktreeID.map(SidebarSelection.worktree) 685 690 } 686 691 let roots = state.repositories.map(\.rootURL) 687 692 let repositories = state.repositories ··· 812 817 analyticsClient.capture("repository_removed", nil) 813 818 state.removingRepositoryIDs.remove(repositoryID) 814 819 if selectionWasRemoved { 815 - state.selectedWorktreeID = nil 820 + state.selection = nil 816 821 state.shouldSelectFirstAfterReload = true 817 822 } 818 823 let selectedWorktree = state.worktree(for: state.selectedWorktreeID) ··· 1204 1209 let didPrunePinned = prunePinnedWorktreeIDs(state: &state) 1205 1210 let didPruneRepositoryOrder = pruneRepositoryOrderIDs(roots: roots, state: &state) 1206 1211 let didPruneWorktreeOrder = pruneWorktreeOrderByRepository(roots: roots, state: &state) 1207 - if !isSelectionValid(state.selectedWorktreeID, state: state) { 1208 - state.selectedWorktreeID = nil 1212 + if !state.isShowingArchivedWorktrees, !isSelectionValid(state.selectedWorktreeID, state: state) { 1213 + state.selection = nil 1209 1214 } 1210 1215 if state.shouldRestoreLastFocusedWorktree { 1211 1216 state.shouldRestoreLastFocusedWorktree = false 1212 - if state.selectedWorktreeID == nil, 1217 + if state.selection == nil, 1213 1218 isSelectionValid(state.lastFocusedWorktreeID, state: state) 1214 1219 { 1215 - state.selectedWorktreeID = state.lastFocusedWorktreeID 1220 + state.selection = state.lastFocusedWorktreeID.map(SidebarSelection.worktree) 1216 1221 } 1217 1222 } 1218 - if state.selectedWorktreeID == nil, state.shouldSelectFirstAfterReload { 1219 - state.selectedWorktreeID = firstAvailableWorktreeID(from: repositories, state: state) 1223 + if state.selection == nil, state.shouldSelectFirstAfterReload { 1224 + state.selection = firstAvailableWorktreeID(from: repositories, state: state) 1225 + .map(SidebarSelection.worktree) 1220 1226 state.shouldSelectFirstAfterReload = false 1221 1227 } 1222 1228 return ApplyRepositoriesResult( ··· 1282 1288 } 1283 1289 1284 1290 extension RepositoriesFeature.State { 1291 + var selectedWorktreeID: Worktree.ID? { 1292 + selection?.worktreeID 1293 + } 1294 + 1295 + var isShowingArchivedWorktrees: Bool { 1296 + selection == .archivedWorktrees 1297 + } 1298 + 1285 1299 func worktreeInfo(for worktreeID: Worktree.ID) -> WorktreeInfoEntry? { 1286 1300 worktreeInfoByID[worktreeID] 1287 1301 } ··· 1823 1837 pendingID: Worktree.ID, 1824 1838 state: inout RepositoriesFeature.State 1825 1839 ) { 1826 - guard state.selectedWorktreeID == pendingID else { return } 1840 + guard state.selection == .worktree(pendingID) else { return } 1827 1841 if isSelectionValid(id, state: state) { 1828 - state.selectedWorktreeID = id 1842 + state.selection = id.map(SidebarSelection.worktree) 1829 1843 } else { 1830 - state.selectedWorktreeID = nil 1844 + state.selection = nil 1831 1845 } 1832 1846 } 1833 1847
+11
supacode/Features/Repositories/Views/ArchivedWorktreesDetailView.swift
··· 1 + import SwiftUI 2 + 3 + struct ArchivedWorktreesDetailView: View { 4 + var body: some View { 5 + ContentUnavailableView( 6 + "Archived Worktrees", 7 + systemImage: "archivebox", 8 + description: Text("Archived worktrees will appear here") 9 + ) 10 + } 11 + }
+18 -2
supacode/Features/Repositories/Views/SidebarListView.swift
··· 8 8 9 9 var body: some View { 10 10 let selection = Binding<SidebarSelection?>( 11 - get: { store.selectedWorktreeID.map(SidebarSelection.worktree) }, 12 - set: { store.send(.selectWorktree($0?.worktreeID)) } 11 + get: { 12 + if store.isShowingArchivedWorktrees { 13 + return .archivedWorktrees 14 + } 15 + return store.selectedWorktreeID.map(SidebarSelection.worktree) 16 + }, 17 + set: { newValue in 18 + switch newValue { 19 + case .archivedWorktrees: 20 + store.send(.selectArchivedWorktrees) 21 + case .worktree(let id): 22 + store.send(.selectWorktree(id)) 23 + case nil: 24 + store.send(.selectWorktree(nil)) 25 + } 26 + } 13 27 ) 14 28 let state = store.state 15 29 let orderedRoots = state.orderedRepositoryRoots() 16 30 let repositoriesByID = Dictionary(uniqueKeysWithValues: store.repositories.map { ($0.id, $0) }) 17 31 List(selection: selection) { 32 + Label("Archived Worktrees", systemImage: "archivebox") 33 + .tag(SidebarSelection.archivedWorktrees) 18 34 if orderedRoots.isEmpty { 19 35 ForEach(store.repositories) { repository in 20 36 RepositorySectionView(
+3
supacode/Features/Repositories/Views/SidebarSelection.swift
··· 1 1 enum SidebarSelection: Hashable { 2 2 case worktree(Worktree.ID) 3 + case archivedWorktrees 3 4 4 5 var worktreeID: Worktree.ID? { 5 6 switch self { 6 7 case .worktree(let id): 7 8 return id 9 + case .archivedWorktrees: 10 + return nil 8 11 } 9 12 } 10 13 }
+3 -1
supacode/Features/Repositories/Views/WorktreeDetailView.swift
··· 24 24 let runScriptEnabled = hasActiveWorktree && runScriptConfigured 25 25 let runScriptIsRunning = selectedWorktree.flatMap { state.runScriptStatusByWorktreeID[$0.id] } == true 26 26 let content = Group { 27 - if let loadingInfo { 27 + if repositories.isShowingArchivedWorktrees { 28 + ArchivedWorktreesDetailView() 29 + } else if let loadingInfo { 28 30 WorktreeLoadingView(info: loadingInfo) 29 31 } else if let selectedWorktree { 30 32 let shouldRunSetupScript = repositories.pendingSetupScriptWorktreeIDs.contains(selectedWorktree.id)
+1 -1
supacode/Support/CustomDump+Extensions.swift
··· 24 24 var customDumpValue: Any { 25 25 ( 26 26 repositories: repositories.count, 27 - selectedWorktreeID: selectedWorktreeID, 27 + selection: selection, 28 28 pending: pendingWorktrees.count, 29 29 deleting: deletingWorktreeIDs.count, 30 30 hasAlert: alert != nil
+1 -1
supacodeTests/AppFeatureTerminalSetupScriptTests.swift
··· 192 192 var repositoriesState = RepositoriesFeature.State() 193 193 repositoriesState.repositories = [repository] 194 194 if selected { 195 - repositoriesState.selectedWorktreeID = worktree.id 195 + repositoriesState.selection = .worktree(worktree.id) 196 196 } 197 197 if pendingSetupScript { 198 198 repositoriesState.pendingSetupScriptWorktreeIDs = [worktree.id]
+9 -9
supacodeTests/RepositoriesFeatureTests.swift
··· 16 16 } 17 17 18 18 await store.send(.selectWorktree(worktree.id)) { 19 - $0.selectedWorktreeID = worktree.id 19 + $0.selection = .worktree(worktree.id) 20 20 } 21 21 await store.receive(\.delegate.selectedWorktreeChanged) 22 22 } ··· 277 277 let updatedWorktree = makeWorktree(id: "/tmp/repo/main", name: "main-updated", repoRoot: repoRoot) 278 278 let updatedRepository = makeRepository(id: repoRoot, worktrees: [updatedWorktree]) 279 279 var initialState = makeState(repositories: [repository]) 280 - initialState.selectedWorktreeID = worktree.id 280 + initialState.selection = .worktree(worktree.id) 281 281 let store = TestStore(initialState: initialState) { 282 282 RepositoriesFeature() 283 283 } ··· 304 304 let repository = makeRepository(id: repoRoot, worktrees: [selectedWorktree, remainingWorktree]) 305 305 let updatedRepository = makeRepository(id: repoRoot, worktrees: [remainingWorktree]) 306 306 var initialState = makeState(repositories: [repository]) 307 - initialState.selectedWorktreeID = selectedWorktree.id 307 + initialState.selection = .worktree(selectedWorktree.id) 308 308 let store = TestStore(initialState: initialState) { 309 309 RepositoriesFeature() 310 310 } ··· 318 318 ) 319 319 ) { 320 320 $0.repositories = [updatedRepository] 321 - $0.selectedWorktreeID = nil 321 + $0.selection = nil 322 322 $0.isInitialLoadComplete = true 323 323 } 324 324 await store.receive(\.delegate.repositoriesChanged) ··· 332 332 let repository = makeRepository(id: repoRoot, worktrees: [mainWorktree, removedWorktree]) 333 333 let updatedRepository = makeRepository(id: repoRoot, worktrees: [mainWorktree]) 334 334 var initialState = makeState(repositories: [repository]) 335 - initialState.selectedWorktreeID = mainWorktree.id 335 + initialState.selection = .worktree(mainWorktree.id) 336 336 initialState.deletingWorktreeIDs = [removedWorktree.id] 337 337 initialState.pendingSetupScriptWorktreeIDs = [removedWorktree.id] 338 338 initialState.pendingTerminalFocusWorktreeIDs = [removedWorktree.id] ··· 384 384 let repository = makeRepository(id: repoRoot, worktrees: [mainWorktree, removedWorktree]) 385 385 let updatedRepository = makeRepository(id: repoRoot, worktrees: [mainWorktree]) 386 386 var initialState = makeState(repositories: [repository]) 387 - initialState.selectedWorktreeID = removedWorktree.id 387 + initialState.selection = .worktree(removedWorktree.id) 388 388 initialState.deletingWorktreeIDs = [removedWorktree.id] 389 389 let store = TestStore(initialState: initialState) { 390 390 RepositoriesFeature() ··· 402 402 ) { 403 403 $0.deletingWorktreeIDs = [] 404 404 $0.repositories = [updatedRepository] 405 - $0.selectedWorktreeID = mainWorktree.id 405 + $0.selection = .worktree(mainWorktree.id) 406 406 } 407 407 await store.receive(\.delegate.repositoriesChanged) 408 408 await store.receive(\.delegate.selectedWorktreeChanged) ··· 428 428 detail: "" 429 429 ), 430 430 ] 431 - initialState.selectedWorktreeID = pendingID 431 + initialState.selection = .worktree(pendingID) 432 432 let store = TestStore(initialState: initialState) { 433 433 RepositoriesFeature() 434 434 } withDependencies: { ··· 445 445 $0.pendingSetupScriptWorktreeIDs.insert(newWorktree.id) 446 446 $0.pendingTerminalFocusWorktreeIDs.insert(newWorktree.id) 447 447 $0.pendingWorktrees = [] 448 - $0.selectedWorktreeID = newWorktree.id 448 + $0.selection = .worktree(newWorktree.id) 449 449 $0.repositories = [updatedRepository] 450 450 } 451 451