native macOS codings agent orchestrator
6
fork

Configure Feed

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

Merge pull request #170 from supabitapp/plan/pfw-best-practices-execplan

Refactor settings bindings and menus

authored by

khoi and committed by
GitHub
4958611b f01afb56

+100 -267
+3 -5
supacode/App/ContentView.swift
··· 11 11 12 12 struct ContentView: View { 13 13 @Bindable var store: StoreOf<AppFeature> 14 + @Bindable var repositoriesStore: StoreOf<RepositoriesFeature> 14 15 let terminalManager: WorktreeTerminalManager 15 16 @Environment(\.scenePhase) private var scenePhase 16 17 @State private var leftSidebarVisibility: NavigationSplitViewVisibility = .all 17 18 18 19 init(store: StoreOf<AppFeature>, terminalManager: WorktreeTerminalManager) { 19 20 self.store = store 21 + repositoriesStore = store.scope(state: \.repositories, action: \.repositories) 20 22 self.terminalManager = terminalManager 21 23 } 22 24 23 25 var body: some View { 24 - let repositoriesStore = store.scope(state: \.repositories, action: \.repositories) 25 26 Group { 26 27 if store.repositories.isInitialLoadComplete { 27 28 NavigationSplitView(columnVisibility: $leftSidebarVisibility) { ··· 44 45 store.send(.scenePhaseChanged(newValue)) 45 46 } 46 47 .fileImporter( 47 - isPresented: Binding( 48 - get: { store.repositories.isOpenPanelPresented }, 49 - set: { store.send(.repositories(.setOpenPanelPresented($0))) } 50 - ), 48 + isPresented: $repositoriesStore.isOpenPanelPresented.sending(\.setOpenPanelPresented), 51 49 allowedContentTypes: [.folder], 52 50 allowsMultipleSelection: true 53 51 ) { result in
+5 -7
supacode/Commands/WorktreeCommands.swift
··· 3 3 import SwiftUI 4 4 5 5 struct WorktreeCommands: Commands { 6 - let store: StoreOf<AppFeature> 7 - @ObservedObject private var viewStore: ViewStore<AppFeature.State, AppFeature.Action> 6 + @Bindable var store: StoreOf<AppFeature> 8 7 @FocusedValue(\.openSelectedWorktreeAction) private var openSelectedWorktreeAction 9 8 @FocusedValue(\.confirmRemoveWorktreeAction) private var confirmRemoveWorktreeAction 10 9 @FocusedValue(\.removeWorktreeAction) private var removeWorktreeAction ··· 13 12 14 13 init(store: StoreOf<AppFeature>) { 15 14 self.store = store 16 - viewStore = ViewStore(store, observe: { $0 }) 17 15 } 18 16 19 17 var body: some Commands { 20 - let repositories = viewStore.state.repositories 18 + let repositories = store.repositories 21 19 let orderedRows = repositories.orderedWorktreeRows() 22 20 let pullRequestURL = selectedPullRequestURL 23 - let githubIntegrationEnabled = viewStore.state.settings.githubIntegrationEnabled 21 + let githubIntegrationEnabled = store.settings.githubIntegrationEnabled 24 22 CommandMenu("Worktrees") { 25 23 ForEach(worktreeShortcuts.indices, id: \.self) { index in 26 24 let shortcut = worktreeShortcuts[index] ··· 111 109 } 112 110 113 111 private var selectedPullRequestURL: URL? { 114 - let repositories = viewStore.state.repositories 112 + let repositories = store.repositories 115 113 guard let selectedWorktreeID = repositories.selectedWorktreeID else { return nil } 116 114 let pullRequest = repositories.worktreeInfoByID[selectedWorktreeID]?.pullRequest 117 115 return pullRequest.flatMap { URL(string: $0.url) } ··· 135 133 136 134 private func worktreeShortcutTitle(index: Int, row: WorktreeRowModel?) -> String { 137 135 guard let row else { return "Worktree \(index + 1)" } 138 - let repositoryName = viewStore.state.repositories.repositoryName(for: row.repositoryID) ?? "Repository" 136 + let repositoryName = store.repositories.repositoryName(for: row.repositoryID) ?? "Repository" 139 137 return "\(repositoryName) — \(row.name)" 140 138 } 141 139 }
+6 -1
supacode/Domain/WorktreeNameGenerator.swift
··· 90 90 91 91 static func nextName(excluding existing: Set<String>) -> String? { 92 92 let normalized = Set(existing.map { $0.lowercased() }) 93 - let randomSuffix = String(format: "%03d", Int.random(in: 0...999)) 93 + let randomSuffix = Int.random(in: 0...999) 94 + .formatted( 95 + .number 96 + .grouping(.never) 97 + .precision(.integerLength(3)) 98 + ) 94 99 let available = adjectives.flatMap { adjective in 95 100 animals.map { "\(adjective)-\($0)-\(randomSuffix)" } 96 101 }.filter { !normalized.contains($0) }
+3 -2
supacode/Features/App/Reducer/AppFeature.swift
··· 138 138 let ids = Set(repositories.flatMap { $0.worktrees.map(\.id) }) 139 139 let worktrees = repositories.flatMap(\.worktrees) 140 140 state.runScriptStatusByWorktreeID = state.runScriptStatusByWorktreeID.filter { ids.contains($0.key) } 141 - if case .repository(let repositoryID) = state.settings.selection, 141 + if case .repository(let repositoryID)? = state.settings.selection, 142 142 !repositories.contains(where: { $0.id == repositoryID }) 143 143 { 144 144 return .merge( ··· 175 175 ) 176 176 177 177 case .settings(.setSelection(let selection)): 178 - switch selection { 178 + let resolvedSelection = selection ?? .general 179 + switch resolvedSelection { 179 180 case .repository(let repositoryID): 180 181 guard let repository = state.repositories.repositories[id: repositoryID] else { 181 182 state.settings.repositorySettings = nil
+14 -13
supacode/Features/Repositories/Views/RepositorySectionView.swift
··· 32 32 } 33 33 } 34 34 HStack { 35 - RepoHeaderRow( 36 - name: repository.name, 37 - initials: repository.initials, 38 - isExpanded: isExpanded, 39 - isRemoving: isRemovingRepository 40 - ) 41 - Spacer() 35 + Button { 36 + toggleExpanded() 37 + } label: { 38 + RepoHeaderRow( 39 + name: repository.name, 40 + initials: repository.initials, 41 + isExpanded: isExpanded, 42 + isRemoving: isRemovingRepository 43 + ) 44 + .frame(maxWidth: .infinity, alignment: .leading) 45 + } 46 + .buttonStyle(.plain) 47 + .contentShape(.rect) 48 + .disabled(isRemovingRepository) 42 49 if isRemovingRepository { 43 50 ProgressView() 44 51 .controlSize(.small) ··· 75 82 .help("New Worktree (\(AppShortcuts.newWorktree.display))") 76 83 .disabled(isRemovingRepository) 77 84 } 78 - .contentShape(Rectangle()) 79 - .onTapGesture { 80 - toggleExpanded() 81 - } 82 - .accessibilityAddTraits(.isButton) 83 - .disabled(isRemovingRepository) 84 85 .contextMenu { 85 86 Button("Repo Settings") { 86 87 openRepoSettings()
+1 -1
supacode/Features/Repositories/Views/WorktreeDetailView.swift
··· 240 240 } 241 241 } label: { 242 242 Image(systemName: "chevron.down") 243 - .font(.system(size: 8)) 243 + .font(.caption2) 244 244 .monospaced() 245 245 .accessibilityLabel("Open in menu") 246 246 }
+8 -6
supacode/Features/Repositories/Views/XcodeStyleToolbarViews.swift
··· 247 247 HStack(spacing: 8) { 248 248 Image(systemName: style.icon) 249 249 .foregroundStyle(style.color) 250 - .font(.system(size: 14)) 250 + .font(.callout) 251 251 .monospaced() 252 252 .accessibilityHidden(true) 253 253 254 254 Text(Self.message(for: context.date)) 255 - .font(.system(size: 12)) 255 + .font(.footnote) 256 256 .monospaced() 257 257 .foregroundStyle(.secondary) 258 258 } ··· 266 266 HStack(spacing: 5) { 267 267 Image(systemName: "exclamationmark.triangle.fill") 268 268 .foregroundStyle(.yellow) 269 - .font(.system(size: 12)) 269 + .font(.footnote) 270 270 .monospaced() 271 271 .accessibilityHidden(true) 272 272 Text("3") 273 - .font(.system(size: 12, weight: .medium, design: .monospaced)) 273 + .font(.footnote.weight(.medium)) 274 + .monospaced() 274 275 } 275 276 .padding(.horizontal, 8) 276 277 .padding(.vertical, 4) ··· 279 280 HStack(spacing: 5) { 280 281 Image(systemName: "xmark.circle.fill") 281 282 .foregroundStyle(.red) 282 - .font(.system(size: 12)) 283 + .font(.footnote) 283 284 .monospaced() 284 285 .accessibilityHidden(true) 285 286 Text("0") 286 - .font(.system(size: 12, weight: .medium, design: .monospaced)) 287 + .font(.footnote.weight(.medium)) 288 + .monospaced() 287 289 } 288 290 .padding(.horizontal, 8) 289 291 .padding(.vertical, 4)
+7 -49
supacode/Features/RepositorySettings/Reducer/RepositorySettingsFeature.swift
··· 13 13 var isBranchDataLoaded = false 14 14 } 15 15 16 - enum Action { 16 + enum Action: BindableAction { 17 17 case task 18 18 case settingsLoaded(RepositorySettings, isBareRepository: Bool) 19 19 case branchDataLoaded([String], defaultBaseRef: String) 20 - case setSetupScript(String) 21 - case setRunScript(String) 22 - case setWorktreeBaseRef(String) 23 - case setCopyIgnoredOnWorktreeCreate(Bool) 24 - case setCopyUntrackedOnWorktreeCreate(Bool) 25 20 case delegate(Delegate) 21 + case binding(BindingAction<State>) 26 22 } 27 23 28 24 @CasePathable ··· 34 30 @Dependency(\.gitClient) private var gitClient 35 31 36 32 var body: some Reducer<State, Action> { 33 + BindingReducer() 37 34 Reduce { state, action in 38 35 switch action { 39 36 case .task: ··· 89 86 state.isBranchDataLoaded = true 90 87 return .none 91 88 92 - case .setSetupScript(let script): 93 - state.settings.setupScript = script 94 - let settings = state.settings 95 - let rootURL = state.rootURL 96 - let repositorySettingsClient = repositorySettingsClient 97 - return .run { send in 98 - repositorySettingsClient.save(settings, rootURL) 99 - await send(.delegate(.settingsChanged(rootURL))) 100 - } 101 - 102 - case .setRunScript(let script): 103 - state.settings.runScript = script 104 - let settings = state.settings 105 - let rootURL = state.rootURL 106 - let repositorySettingsClient = repositorySettingsClient 107 - return .run { send in 108 - repositorySettingsClient.save(settings, rootURL) 109 - await send(.delegate(.settingsChanged(rootURL))) 110 - } 111 - 112 - case .setWorktreeBaseRef(let ref): 113 - state.settings.worktreeBaseRef = ref 114 - let settings = state.settings 115 - let rootURL = state.rootURL 116 - let repositorySettingsClient = repositorySettingsClient 117 - return .run { send in 118 - repositorySettingsClient.save(settings, rootURL) 119 - await send(.delegate(.settingsChanged(rootURL))) 120 - } 121 - 122 - case .setCopyIgnoredOnWorktreeCreate(let isEnabled): 123 - guard !state.isBareRepository else { return .none } 124 - state.settings.copyIgnoredOnWorktreeCreate = isEnabled 125 - let settings = state.settings 126 - let rootURL = state.rootURL 127 - let repositorySettingsClient = repositorySettingsClient 128 - return .run { send in 129 - repositorySettingsClient.save(settings, rootURL) 130 - await send(.delegate(.settingsChanged(rootURL))) 89 + case .binding: 90 + if state.isBareRepository { 91 + state.settings.copyIgnoredOnWorktreeCreate = false 92 + state.settings.copyUntrackedOnWorktreeCreate = false 131 93 } 132 - 133 - case .setCopyUntrackedOnWorktreeCreate(let isEnabled): 134 - guard !state.isBareRepository else { return .none } 135 - state.settings.copyUntrackedOnWorktreeCreate = isEnabled 136 94 let settings = state.settings 137 95 let rootURL = state.rootURL 138 96 let repositorySettingsClient = repositorySettingsClient
+7 -84
supacode/Features/Settings/Reducer/SettingsFeature.swift
··· 13 13 var notificationSoundEnabled: Bool 14 14 var githubIntegrationEnabled: Bool 15 15 var deleteBranchOnArchive: Bool 16 - var selection: SettingsSection = .general 16 + var selection: SettingsSection? = .general 17 17 var repositorySettings: RepositorySettingsFeature.State? 18 18 19 19 init(settings: GlobalSettings = .default) { ··· 41 41 } 42 42 } 43 43 44 - enum Action { 44 + enum Action: BindableAction { 45 45 case task 46 46 case settingsLoaded(GlobalSettings) 47 - case setAppearanceMode(AppearanceMode) 48 - case setConfirmBeforeQuit(Bool) 49 - case setUpdatesAutomaticallyCheckForUpdates(Bool) 50 - case setUpdatesAutomaticallyDownloadUpdates(Bool) 51 - case setInAppNotificationsEnabled(Bool) 52 - case setNotificationSoundEnabled(Bool) 53 - case setGithubIntegrationEnabled(Bool) 54 - case setDeleteBranchOnArchive(Bool) 55 - case setSelection(SettingsSection) 47 + case setSelection(SettingsSection?) 56 48 case repositorySettings(RepositorySettingsFeature.Action) 57 49 case delegate(Delegate) 50 + case binding(BindingAction<State>) 58 51 } 59 52 60 53 @CasePathable ··· 65 58 @Dependency(\.settingsClient) private var settingsClient 66 59 67 60 var body: some Reducer<State, Action> { 61 + BindingReducer() 68 62 Reduce { state, action in 69 63 switch action { 70 64 case .task: ··· 84 78 state.deleteBranchOnArchive = settings.deleteBranchOnArchive 85 79 return .send(.delegate(.settingsChanged(settings))) 86 80 87 - case .setAppearanceMode(let mode): 88 - state.appearanceMode = mode 89 - let settings = state.globalSettings 90 - return .merge( 91 - .send(.delegate(.settingsChanged(settings))), 92 - .run { _ in 93 - await settingsClient.save(settings) 94 - } 95 - ) 96 - 97 - case .setConfirmBeforeQuit(let value): 98 - state.confirmBeforeQuit = value 99 - let settings = state.globalSettings 100 - return .merge( 101 - .send(.delegate(.settingsChanged(settings))), 102 - .run { _ in 103 - await settingsClient.save(settings) 104 - } 105 - ) 106 - 107 - case .setUpdatesAutomaticallyCheckForUpdates(let value): 108 - state.updatesAutomaticallyCheckForUpdates = value 109 - let settings = state.globalSettings 110 - return .merge( 111 - .send(.delegate(.settingsChanged(settings))), 112 - .run { _ in 113 - await settingsClient.save(settings) 114 - } 115 - ) 116 - 117 - case .setUpdatesAutomaticallyDownloadUpdates(let value): 118 - state.updatesAutomaticallyDownloadUpdates = value 119 - let settings = state.globalSettings 120 - return .merge( 121 - .send(.delegate(.settingsChanged(settings))), 122 - .run { _ in 123 - await settingsClient.save(settings) 124 - } 125 - ) 126 - 127 - case .setInAppNotificationsEnabled(let value): 128 - state.inAppNotificationsEnabled = value 129 - let settings = state.globalSettings 130 - return .merge( 131 - .send(.delegate(.settingsChanged(settings))), 132 - .run { _ in 133 - await settingsClient.save(settings) 134 - } 135 - ) 136 - 137 - case .setNotificationSoundEnabled(let value): 138 - state.notificationSoundEnabled = value 139 - let settings = state.globalSettings 140 - return .merge( 141 - .send(.delegate(.settingsChanged(settings))), 142 - .run { _ in 143 - await settingsClient.save(settings) 144 - } 145 - ) 146 - 147 - case .setDeleteBranchOnArchive(let value): 148 - state.deleteBranchOnArchive = value 149 - let settings = state.globalSettings 150 - return .merge( 151 - .send(.delegate(.settingsChanged(settings))), 152 - .run { _ in 153 - await settingsClient.save(settings) 154 - } 155 - ) 156 - 157 - case .setGithubIntegrationEnabled(let value): 158 - state.githubIntegrationEnabled = value 81 + case .binding: 159 82 let settings = state.globalSettings 160 83 return .merge( 161 84 .send(.delegate(.settingsChanged(settings))), ··· 165 88 ) 166 89 167 90 case .setSelection(let selection): 168 - state.selection = selection 91 + state.selection = selection ?? .general 169 92 return .none 170 93 171 94 case .repositorySettings:
+4 -6
supacode/Features/Settings/Views/AppearanceSettingsView.swift
··· 9 9 Form { 10 10 Section("Appearance") { 11 11 HStack { 12 + let appearanceMode = $store.appearanceMode 12 13 ForEach(AppearanceMode.allCases) { mode in 13 14 AppearanceOptionCardView( 14 15 mode: mode, 15 - isSelected: mode == store.appearanceMode 16 + isSelected: mode == appearanceMode.wrappedValue 16 17 ) { 17 - store.send(.setAppearanceMode(mode)) 18 + appearanceMode.wrappedValue = mode 18 19 } 19 20 } 20 21 } ··· 22 23 Section("Quit") { 23 24 Toggle( 24 25 "Confirm before quitting", 25 - isOn: Binding( 26 - get: { store.confirmBeforeQuit }, 27 - set: { store.send(.setConfirmBeforeQuit($0)) } 28 - ) 26 + isOn: $store.confirmBeforeQuit 29 27 ) 30 28 .help("Ask before quitting Supacode") 31 29 }
+1 -4
supacode/Features/Settings/Views/GithubSettingsView.swift
··· 49 49 Section("GitHub integration") { 50 50 Toggle( 51 51 "Enable GitHub integration", 52 - isOn: Binding( 53 - get: { store.githubIntegrationEnabled }, 54 - set: { store.send(.setGithubIntegrationEnabled($0)) } 55 - ) 52 + isOn: $store.githubIntegrationEnabled 56 53 ) 57 54 .help("Enable GitHub integration") 58 55 }
+2 -8
supacode/Features/Settings/Views/NotificationsSettingsView.swift
··· 10 10 Section("Notifications") { 11 11 Toggle( 12 12 "Show bell icon next to worktree", 13 - isOn: Binding( 14 - get: { store.inAppNotificationsEnabled }, 15 - set: { store.send(.setInAppNotificationsEnabled($0)) } 16 - ) 13 + isOn: $store.inAppNotificationsEnabled 17 14 ) 18 15 .help("Show bell icon next to worktree") 19 16 Toggle( 20 17 "Play notification sound", 21 - isOn: Binding( 22 - get: { store.notificationSoundEnabled }, 23 - set: { store.send(.setNotificationSoundEnabled($0)) } 24 - ) 18 + isOn: $store.notificationSoundEnabled 25 19 ) 26 20 .help("Play a sound when a notification is received") 27 21 }
+9 -25
supacode/Features/Settings/Views/RepositorySettingsView.swift
··· 7 7 var body: some View { 8 8 let baseRefOptions = 9 9 store.branchOptions.isEmpty ? [store.defaultWorktreeBaseRef] : store.branchOptions 10 + let settings = $store.settings 10 11 Form { 11 12 Section { 12 13 if store.isBranchDataLoaded { 13 14 Picker( 14 15 "Branch new workspaces from", 15 - selection: Binding( 16 - get: { 17 - (store.settings.worktreeBaseRef ?? "").isEmpty 18 - ? store.defaultWorktreeBaseRef 19 - : store.settings.worktreeBaseRef ?? store.defaultWorktreeBaseRef 20 - }, 21 - set: { store.send(.setWorktreeBaseRef($0)) } 22 - ) 16 + selection: $store.settings.worktreeBaseRef 23 17 ) { 18 + Text("Automatic (\(store.defaultWorktreeBaseRef))") 19 + .tag(String?.none) 24 20 ForEach(baseRefOptions, id: \.self) { ref in 25 21 Text(ref) 26 - .tag(ref) 22 + .tag(Optional(ref)) 27 23 } 28 24 } 29 25 .labelsHidden() ··· 41 37 Section { 42 38 Toggle( 43 39 "Copy ignored files to new worktrees", 44 - isOn: Binding( 45 - get: { store.settings.copyIgnoredOnWorktreeCreate }, 46 - set: { store.send(.setCopyIgnoredOnWorktreeCreate($0)) } 47 - ) 40 + isOn: settings.copyIgnoredOnWorktreeCreate 48 41 ) 49 42 .disabled(store.isBareRepository) 50 43 Toggle( 51 44 "Copy untracked files to new worktrees", 52 - isOn: Binding( 53 - get: { store.settings.copyUntrackedOnWorktreeCreate }, 54 - set: { store.send(.setCopyUntrackedOnWorktreeCreate($0)) } 55 - ) 45 + isOn: settings.copyUntrackedOnWorktreeCreate 56 46 ) 57 47 .disabled(store.isBareRepository) 58 48 if store.isBareRepository { ··· 69 59 Section { 70 60 ZStack(alignment: .topLeading) { 71 61 TextEditor( 72 - text: Binding( 73 - get: { store.settings.setupScript }, 74 - set: { store.send(.setSetupScript($0)) } 75 - ) 62 + text: settings.setupScript 76 63 ) 77 64 .font(.body) 78 65 .monospaced() ··· 96 83 Section { 97 84 ZStack(alignment: .topLeading) { 98 85 TextEditor( 99 - text: Binding( 100 - get: { store.settings.runScript }, 101 - set: { store.send(.setRunScript($0)) } 102 - ) 86 + text: settings.runScript 103 87 ) 104 88 .font(.body) 105 89 .monospaced()
+8 -9
supacode/Features/Settings/Views/SettingsView.swift
··· 14 14 15 15 struct SettingsView: View { 16 16 @Bindable var store: StoreOf<AppFeature> 17 + @Bindable var settingsStore: StoreOf<SettingsFeature> 18 + 19 + init(store: StoreOf<AppFeature>) { 20 + self.store = store 21 + settingsStore = store.scope(state: \.settings, action: \.settings) 22 + } 17 23 18 24 var body: some View { 19 - let settingsStore = store.scope(state: \.settings, action: \.settings) 20 25 let updatesStore = store.scope(state: \.updates, action: \.updates) 21 26 let repositories = store.repositories.repositories 22 - let selection = store.settings.selection 23 - let selectionBinding = Binding<SettingsSection?>( 24 - get: { store.settings.selection }, 25 - set: { selection in 26 - store.send(.settings(.setSelection(selection ?? .general))) 27 - } 28 - ) 27 + let selection = settingsStore.selection ?? .general 29 28 30 29 NavigationSplitView(columnVisibility: .constant(.all)) { 31 30 VStack(spacing: 0) { 32 - List(selection: selectionBinding) { 31 + List(selection: $settingsStore.selection.sending(\.setSelection)) { 33 32 Label("General", systemImage: "gearshape") 34 33 .tag(SettingsSection.general) 35 34 Label("Notifications", systemImage: "bell")
+2 -8
supacode/Features/Settings/Views/UpdatesSettingsView.swift
··· 11 11 Section("Automatic Updates") { 12 12 Toggle( 13 13 "Check for updates automatically", 14 - isOn: Binding( 15 - get: { settingsStore.updatesAutomaticallyCheckForUpdates }, 16 - set: { settingsStore.send(.setUpdatesAutomaticallyCheckForUpdates($0)) } 17 - ) 14 + isOn: $settingsStore.updatesAutomaticallyCheckForUpdates 18 15 ) 19 16 Toggle( 20 17 "Download and install updates automatically", 21 - isOn: Binding( 22 - get: { settingsStore.updatesAutomaticallyDownloadUpdates }, 23 - set: { settingsStore.send(.setUpdatesAutomaticallyDownloadUpdates($0)) } 24 - ) 18 + isOn: $settingsStore.updatesAutomaticallyDownloadUpdates 25 19 ) 26 20 .disabled(!settingsStore.updatesAutomaticallyCheckForUpdates) 27 21 }
+1 -4
supacode/Features/Settings/Views/WorktreeSettingsView.swift
··· 11 11 VStack(alignment: .leading) { 12 12 Toggle( 13 13 "Delete branch on archive", 14 - isOn: Binding( 15 - get: { store.deleteBranchOnArchive }, 16 - set: { store.send(.setDeleteBranchOnArchive($0)) } 17 - ) 14 + isOn: $store.deleteBranchOnArchive 18 15 ) 19 16 .help("Delete the local branch when archiving a worktree") 20 17 Text("Delete the local branch when archiving a worktree.")
-1
supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift
··· 15 15 static let activeTabOffset: CGFloat = 0.5 16 16 static let activeTabBottomPadding: CGFloat = 1 17 17 static let closeButtonSize: CGFloat = 16 18 - static let closeIconSize: CGFloat = 9 19 18 static let dirtyIndicatorSize: CGFloat = 8 20 19 static let overflowShadowWidth: CGFloat = 24 21 20 static let dropIndicatorWidth: CGFloat = 2
+1 -1
supacode/Features/Terminal/TabBar/Views/TerminalTabCloseButton.swift
··· 20 20 } 21 21 .labelStyle(.iconOnly) 22 22 .buttonStyle(TerminalPressTrackingButtonStyle(isPressed: $isPressing)) 23 - .font(.system(size: TerminalTabBarMetrics.closeIconSize)) 23 + .font(.caption2) 24 24 .monospaced() 25 25 .bold() 26 26 .foregroundStyle(
+1 -1
supacode/Features/Terminal/Views/TerminalSplitTreeView.swift
··· 133 133 .overlay { 134 134 if isHovering { 135 135 Image(systemName: "ellipsis") 136 - .font(.system(size: 14, weight: .semibold)) 136 + .font(.callout.weight(.semibold)) 137 137 .monospaced() 138 138 .foregroundStyle(.primary.opacity(0.5)) 139 139 .accessibilityHidden(true)
+14 -30
supacode/Support/DebugCaseOutput.swift
··· 73 73 genericsAbbreviated: Bool = true 74 74 ) -> String { 75 75 var name = _typeName(type, qualified: qualified) 76 - .replacingOccurrences( 77 - of: #"\(unknown context at \$[[:xdigit:]]+\)\."#, 78 - with: "", 79 - options: .regularExpression 80 - ) 76 + .replacing(#/\(unknown context at \$[0-9A-Fa-f]+\)\./#, with: "") 81 77 for _ in 1...10 { 82 78 let abbreviated = 83 79 name 84 - .replacingOccurrences( 85 - of: #"\bSwift.Optional<([^><]+)>"#, 86 - with: "$1?", 87 - options: .regularExpression 88 - ) 89 - .replacingOccurrences( 90 - of: #"\bSwift.Array<([^><]+)>"#, 91 - with: "[$1]", 92 - options: .regularExpression 93 - ) 94 - .replacingOccurrences( 95 - of: #"\bSwift.Dictionary<([^,<]+), ([^><]+)>"#, 96 - with: "[$1: $2]", 97 - options: .regularExpression 98 - ) 80 + .replacing(#/\bSwift\.Optional<([^><]+)>/#) { match in 81 + "\(match.1)?" 82 + } 83 + .replacing(#/\bSwift\.Array<([^><]+)>/#) { match in 84 + "[\(match.1)]" 85 + } 86 + .replacing(#/\bSwift\.Dictionary<([^,<]+), ([^><]+)>/#) { match in 87 + "[\(match.1): \(match.2)]" 88 + } 99 89 if abbreviated == name { break } 100 90 name = abbreviated 101 91 } 102 - name = name.replacingOccurrences( 103 - of: #"\w+\.([\w.]+)"#, 104 - with: "$1", 105 - options: .regularExpression 106 - ) 92 + name = name.replacing(#/\w+\.([\w.]+)/#) { match in 93 + "\(match.1)" 94 + } 107 95 if genericsAbbreviated { 108 - name = name.replacingOccurrences( 109 - of: #"<.+>"#, 110 - with: "", 111 - options: .regularExpression 112 - ) 96 + name = name.replacing(#/<.+>/#, with: "") 113 97 } 114 98 return name 115 99 }
+3 -2
supacodeTests/SettingsFeatureTests.swift
··· 1 1 import ComposableArchitecture 2 + import CustomDump 2 3 import DependenciesTestSupport 3 4 import Foundation 4 5 import Testing ··· 61 62 ) 62 63 } 63 64 64 - await store.send(.setAppearanceMode(.light)) { 65 + await store.send(.binding(.set(\.appearanceMode, .light))) { 65 66 $0.appearanceMode = .light 66 67 } 67 68 let expectedSettings = GlobalSettings( ··· 77 78 await store.receive(\.delegate.settingsChanged) 78 79 79 80 await store.finish() 80 - #expect(saved.value == expectedSettings) 81 + expectNoDifference(saved.value, expectedSettings) 81 82 } 82 83 83 84 @Test(.dependencies) func selectionDoesNotMutateRepositorySettings() async {