diff --git a/supacode/App/ContentView.swift b/supacode/App/ContentView.swift index c076c769..579b6906 100644 --- a/supacode/App/ContentView.swift +++ b/supacode/App/ContentView.swift @@ -11,17 +11,18 @@ import UniformTypeIdentifiers struct ContentView: View { @Bindable var store: StoreOf + @Bindable var repositoriesStore: StoreOf let terminalManager: WorktreeTerminalManager @Environment(\.scenePhase) private var scenePhase @State private var leftSidebarVisibility: NavigationSplitViewVisibility = .all init(store: StoreOf, terminalManager: WorktreeTerminalManager) { self.store = store + repositoriesStore = store.scope(state: \.repositories, action: \.repositories) self.terminalManager = terminalManager } var body: some View { - let repositoriesStore = store.scope(state: \.repositories, action: \.repositories) Group { if store.repositories.isInitialLoadComplete { NavigationSplitView(columnVisibility: $leftSidebarVisibility) { @@ -44,10 +45,7 @@ struct ContentView: View { store.send(.scenePhaseChanged(newValue)) } .fileImporter( - isPresented: Binding( - get: { store.repositories.isOpenPanelPresented }, - set: { store.send(.repositories(.setOpenPanelPresented($0))) } - ), + isPresented: $repositoriesStore.isOpenPanelPresented.sending(\.setOpenPanelPresented), allowedContentTypes: [.folder], allowsMultipleSelection: true ) { result in diff --git a/supacode/Commands/WorktreeCommands.swift b/supacode/Commands/WorktreeCommands.swift index 8b75ef40..90f9e858 100644 --- a/supacode/Commands/WorktreeCommands.swift +++ b/supacode/Commands/WorktreeCommands.swift @@ -3,8 +3,7 @@ import ComposableArchitecture import SwiftUI struct WorktreeCommands: Commands { - let store: StoreOf - @ObservedObject private var viewStore: ViewStore + @Bindable var store: StoreOf @FocusedValue(\.openSelectedWorktreeAction) private var openSelectedWorktreeAction @FocusedValue(\.confirmRemoveWorktreeAction) private var confirmRemoveWorktreeAction @FocusedValue(\.removeWorktreeAction) private var removeWorktreeAction @@ -13,14 +12,13 @@ struct WorktreeCommands: Commands { init(store: StoreOf) { self.store = store - viewStore = ViewStore(store, observe: { $0 }) } var body: some Commands { - let repositories = viewStore.state.repositories + let repositories = store.repositories let orderedRows = repositories.orderedWorktreeRows() let pullRequestURL = selectedPullRequestURL - let githubIntegrationEnabled = viewStore.state.settings.githubIntegrationEnabled + let githubIntegrationEnabled = store.settings.githubIntegrationEnabled CommandMenu("Worktrees") { ForEach(worktreeShortcuts.indices, id: \.self) { index in let shortcut = worktreeShortcuts[index] @@ -111,7 +109,7 @@ struct WorktreeCommands: Commands { } private var selectedPullRequestURL: URL? { - let repositories = viewStore.state.repositories + let repositories = store.repositories guard let selectedWorktreeID = repositories.selectedWorktreeID else { return nil } let pullRequest = repositories.worktreeInfoByID[selectedWorktreeID]?.pullRequest return pullRequest.flatMap { URL(string: $0.url) } @@ -135,7 +133,7 @@ struct WorktreeCommands: Commands { private func worktreeShortcutTitle(index: Int, row: WorktreeRowModel?) -> String { guard let row else { return "Worktree \(index + 1)" } - let repositoryName = viewStore.state.repositories.repositoryName(for: row.repositoryID) ?? "Repository" + let repositoryName = store.repositories.repositoryName(for: row.repositoryID) ?? "Repository" return "\(repositoryName) — \(row.name)" } } diff --git a/supacode/Domain/WorktreeNameGenerator.swift b/supacode/Domain/WorktreeNameGenerator.swift index 1d0d89d1..df78f1b3 100644 --- a/supacode/Domain/WorktreeNameGenerator.swift +++ b/supacode/Domain/WorktreeNameGenerator.swift @@ -90,7 +90,12 @@ enum WorktreeNameGenerator { static func nextName(excluding existing: Set) -> String? { let normalized = Set(existing.map { $0.lowercased() }) - let randomSuffix = String(format: "%03d", Int.random(in: 0...999)) + let randomSuffix = Int.random(in: 0...999) + .formatted( + .number + .grouping(.never) + .precision(.integerLength(3)) + ) let available = adjectives.flatMap { adjective in animals.map { "\(adjective)-\($0)-\(randomSuffix)" } }.filter { !normalized.contains($0) } diff --git a/supacode/Features/App/Reducer/AppFeature.swift b/supacode/Features/App/Reducer/AppFeature.swift index 17ea4fb7..902ee96e 100644 --- a/supacode/Features/App/Reducer/AppFeature.swift +++ b/supacode/Features/App/Reducer/AppFeature.swift @@ -138,7 +138,7 @@ struct AppFeature { let ids = Set(repositories.flatMap { $0.worktrees.map(\.id) }) let worktrees = repositories.flatMap(\.worktrees) state.runScriptStatusByWorktreeID = state.runScriptStatusByWorktreeID.filter { ids.contains($0.key) } - if case .repository(let repositoryID) = state.settings.selection, + if case .repository(let repositoryID)? = state.settings.selection, !repositories.contains(where: { $0.id == repositoryID }) { return .merge( @@ -175,7 +175,8 @@ struct AppFeature { ) case .settings(.setSelection(let selection)): - switch selection { + let resolvedSelection = selection ?? .general + switch resolvedSelection { case .repository(let repositoryID): guard let repository = state.repositories.repositories[id: repositoryID] else { state.settings.repositorySettings = nil diff --git a/supacode/Features/Repositories/Views/RepositorySectionView.swift b/supacode/Features/Repositories/Views/RepositorySectionView.swift index 2236bd26..b772cbad 100644 --- a/supacode/Features/Repositories/Views/RepositorySectionView.swift +++ b/supacode/Features/Repositories/Views/RepositorySectionView.swift @@ -32,13 +32,20 @@ struct RepositorySectionView: View { } } HStack { - RepoHeaderRow( - name: repository.name, - initials: repository.initials, - isExpanded: isExpanded, - isRemoving: isRemovingRepository - ) - Spacer() + Button { + toggleExpanded() + } label: { + RepoHeaderRow( + name: repository.name, + initials: repository.initials, + isExpanded: isExpanded, + isRemoving: isRemovingRepository + ) + .frame(maxWidth: .infinity, alignment: .leading) + } + .buttonStyle(.plain) + .contentShape(.rect) + .disabled(isRemovingRepository) if isRemovingRepository { ProgressView() .controlSize(.small) @@ -75,12 +82,6 @@ struct RepositorySectionView: View { .help("New Worktree (\(AppShortcuts.newWorktree.display))") .disabled(isRemovingRepository) } - .contentShape(Rectangle()) - .onTapGesture { - toggleExpanded() - } - .accessibilityAddTraits(.isButton) - .disabled(isRemovingRepository) .contextMenu { Button("Repo Settings") { openRepoSettings() diff --git a/supacode/Features/Repositories/Views/WorktreeDetailView.swift b/supacode/Features/Repositories/Views/WorktreeDetailView.swift index 727d079d..e255815c 100644 --- a/supacode/Features/Repositories/Views/WorktreeDetailView.swift +++ b/supacode/Features/Repositories/Views/WorktreeDetailView.swift @@ -240,7 +240,7 @@ struct WorktreeDetailView: View { } } label: { Image(systemName: "chevron.down") - .font(.system(size: 8)) + .font(.caption2) .monospaced() .accessibilityLabel("Open in menu") } diff --git a/supacode/Features/Repositories/Views/XcodeStyleToolbarViews.swift b/supacode/Features/Repositories/Views/XcodeStyleToolbarViews.swift index 7dfe2604..b62739ed 100644 --- a/supacode/Features/Repositories/Views/XcodeStyleToolbarViews.swift +++ b/supacode/Features/Repositories/Views/XcodeStyleToolbarViews.swift @@ -247,12 +247,12 @@ struct MiddleStatusView: View { HStack(spacing: 8) { Image(systemName: style.icon) .foregroundStyle(style.color) - .font(.system(size: 14)) + .font(.callout) .monospaced() .accessibilityHidden(true) Text(Self.message(for: context.date)) - .font(.system(size: 12)) + .font(.footnote) .monospaced() .foregroundStyle(.secondary) } @@ -266,11 +266,12 @@ struct XcodeStyleDiagnosticsView: View { HStack(spacing: 5) { Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.yellow) - .font(.system(size: 12)) + .font(.footnote) .monospaced() .accessibilityHidden(true) Text("3") - .font(.system(size: 12, weight: .medium, design: .monospaced)) + .font(.footnote.weight(.medium)) + .monospaced() } .padding(.horizontal, 8) .padding(.vertical, 4) @@ -279,11 +280,12 @@ struct XcodeStyleDiagnosticsView: View { HStack(spacing: 5) { Image(systemName: "xmark.circle.fill") .foregroundStyle(.red) - .font(.system(size: 12)) + .font(.footnote) .monospaced() .accessibilityHidden(true) Text("0") - .font(.system(size: 12, weight: .medium, design: .monospaced)) + .font(.footnote.weight(.medium)) + .monospaced() } .padding(.horizontal, 8) .padding(.vertical, 4) diff --git a/supacode/Features/RepositorySettings/Reducer/RepositorySettingsFeature.swift b/supacode/Features/RepositorySettings/Reducer/RepositorySettingsFeature.swift index 9e8a412c..3ddc62f3 100644 --- a/supacode/Features/RepositorySettings/Reducer/RepositorySettingsFeature.swift +++ b/supacode/Features/RepositorySettings/Reducer/RepositorySettingsFeature.swift @@ -13,16 +13,12 @@ struct RepositorySettingsFeature { var isBranchDataLoaded = false } - enum Action { + enum Action: BindableAction { case task case settingsLoaded(RepositorySettings, isBareRepository: Bool) case branchDataLoaded([String], defaultBaseRef: String) - case setSetupScript(String) - case setRunScript(String) - case setWorktreeBaseRef(String) - case setCopyIgnoredOnWorktreeCreate(Bool) - case setCopyUntrackedOnWorktreeCreate(Bool) case delegate(Delegate) + case binding(BindingAction) } @CasePathable @@ -34,6 +30,7 @@ struct RepositorySettingsFeature { @Dependency(\.gitClient) private var gitClient var body: some Reducer { + BindingReducer() Reduce { state, action in switch action { case .task: @@ -89,50 +86,11 @@ struct RepositorySettingsFeature { state.isBranchDataLoaded = true return .none - case .setSetupScript(let script): - state.settings.setupScript = script - let settings = state.settings - let rootURL = state.rootURL - let repositorySettingsClient = repositorySettingsClient - return .run { send in - repositorySettingsClient.save(settings, rootURL) - await send(.delegate(.settingsChanged(rootURL))) - } - - case .setRunScript(let script): - state.settings.runScript = script - let settings = state.settings - let rootURL = state.rootURL - let repositorySettingsClient = repositorySettingsClient - return .run { send in - repositorySettingsClient.save(settings, rootURL) - await send(.delegate(.settingsChanged(rootURL))) + case .binding: + if state.isBareRepository { + state.settings.copyIgnoredOnWorktreeCreate = false + state.settings.copyUntrackedOnWorktreeCreate = false } - - case .setWorktreeBaseRef(let ref): - state.settings.worktreeBaseRef = ref - let settings = state.settings - let rootURL = state.rootURL - let repositorySettingsClient = repositorySettingsClient - return .run { send in - repositorySettingsClient.save(settings, rootURL) - await send(.delegate(.settingsChanged(rootURL))) - } - - case .setCopyIgnoredOnWorktreeCreate(let isEnabled): - guard !state.isBareRepository else { return .none } - state.settings.copyIgnoredOnWorktreeCreate = isEnabled - let settings = state.settings - let rootURL = state.rootURL - let repositorySettingsClient = repositorySettingsClient - return .run { send in - repositorySettingsClient.save(settings, rootURL) - await send(.delegate(.settingsChanged(rootURL))) - } - - case .setCopyUntrackedOnWorktreeCreate(let isEnabled): - guard !state.isBareRepository else { return .none } - state.settings.copyUntrackedOnWorktreeCreate = isEnabled let settings = state.settings let rootURL = state.rootURL let repositorySettingsClient = repositorySettingsClient diff --git a/supacode/Features/Settings/Reducer/SettingsFeature.swift b/supacode/Features/Settings/Reducer/SettingsFeature.swift index 486277e9..05187362 100644 --- a/supacode/Features/Settings/Reducer/SettingsFeature.swift +++ b/supacode/Features/Settings/Reducer/SettingsFeature.swift @@ -13,7 +13,7 @@ struct SettingsFeature { var notificationSoundEnabled: Bool var githubIntegrationEnabled: Bool var deleteBranchOnArchive: Bool - var selection: SettingsSection = .general + var selection: SettingsSection? = .general var repositorySettings: RepositorySettingsFeature.State? init(settings: GlobalSettings = .default) { @@ -41,20 +41,13 @@ struct SettingsFeature { } } - enum Action { + enum Action: BindableAction { case task case settingsLoaded(GlobalSettings) - case setAppearanceMode(AppearanceMode) - case setConfirmBeforeQuit(Bool) - case setUpdatesAutomaticallyCheckForUpdates(Bool) - case setUpdatesAutomaticallyDownloadUpdates(Bool) - case setInAppNotificationsEnabled(Bool) - case setNotificationSoundEnabled(Bool) - case setGithubIntegrationEnabled(Bool) - case setDeleteBranchOnArchive(Bool) - case setSelection(SettingsSection) + case setSelection(SettingsSection?) case repositorySettings(RepositorySettingsFeature.Action) case delegate(Delegate) + case binding(BindingAction) } @CasePathable @@ -65,6 +58,7 @@ struct SettingsFeature { @Dependency(\.settingsClient) private var settingsClient var body: some Reducer { + BindingReducer() Reduce { state, action in switch action { case .task: @@ -84,78 +78,7 @@ struct SettingsFeature { state.deleteBranchOnArchive = settings.deleteBranchOnArchive return .send(.delegate(.settingsChanged(settings))) - case .setAppearanceMode(let mode): - state.appearanceMode = mode - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setConfirmBeforeQuit(let value): - state.confirmBeforeQuit = value - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setUpdatesAutomaticallyCheckForUpdates(let value): - state.updatesAutomaticallyCheckForUpdates = value - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setUpdatesAutomaticallyDownloadUpdates(let value): - state.updatesAutomaticallyDownloadUpdates = value - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setInAppNotificationsEnabled(let value): - state.inAppNotificationsEnabled = value - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setNotificationSoundEnabled(let value): - state.notificationSoundEnabled = value - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setDeleteBranchOnArchive(let value): - state.deleteBranchOnArchive = value - let settings = state.globalSettings - return .merge( - .send(.delegate(.settingsChanged(settings))), - .run { _ in - await settingsClient.save(settings) - } - ) - - case .setGithubIntegrationEnabled(let value): - state.githubIntegrationEnabled = value + case .binding: let settings = state.globalSettings return .merge( .send(.delegate(.settingsChanged(settings))), @@ -165,7 +88,7 @@ struct SettingsFeature { ) case .setSelection(let selection): - state.selection = selection + state.selection = selection ?? .general return .none case .repositorySettings: diff --git a/supacode/Features/Settings/Views/AppearanceSettingsView.swift b/supacode/Features/Settings/Views/AppearanceSettingsView.swift index 163a429c..b760ad76 100644 --- a/supacode/Features/Settings/Views/AppearanceSettingsView.swift +++ b/supacode/Features/Settings/Views/AppearanceSettingsView.swift @@ -9,12 +9,13 @@ struct AppearanceSettingsView: View { Form { Section("Appearance") { HStack { + let appearanceMode = $store.appearanceMode ForEach(AppearanceMode.allCases) { mode in AppearanceOptionCardView( mode: mode, - isSelected: mode == store.appearanceMode + isSelected: mode == appearanceMode.wrappedValue ) { - store.send(.setAppearanceMode(mode)) + appearanceMode.wrappedValue = mode } } } @@ -22,10 +23,7 @@ struct AppearanceSettingsView: View { Section("Quit") { Toggle( "Confirm before quitting", - isOn: Binding( - get: { store.confirmBeforeQuit }, - set: { store.send(.setConfirmBeforeQuit($0)) } - ) + isOn: $store.confirmBeforeQuit ) .help("Ask before quitting Supacode") } diff --git a/supacode/Features/Settings/Views/GithubSettingsView.swift b/supacode/Features/Settings/Views/GithubSettingsView.swift index 9c7f7cdd..2a9f24f9 100644 --- a/supacode/Features/Settings/Views/GithubSettingsView.swift +++ b/supacode/Features/Settings/Views/GithubSettingsView.swift @@ -49,10 +49,7 @@ struct GithubSettingsView: View { Section("GitHub integration") { Toggle( "Enable GitHub integration", - isOn: Binding( - get: { store.githubIntegrationEnabled }, - set: { store.send(.setGithubIntegrationEnabled($0)) } - ) + isOn: $store.githubIntegrationEnabled ) .help("Enable GitHub integration") } diff --git a/supacode/Features/Settings/Views/NotificationsSettingsView.swift b/supacode/Features/Settings/Views/NotificationsSettingsView.swift index a94ea7c2..9c33d855 100644 --- a/supacode/Features/Settings/Views/NotificationsSettingsView.swift +++ b/supacode/Features/Settings/Views/NotificationsSettingsView.swift @@ -10,18 +10,12 @@ struct NotificationsSettingsView: View { Section("Notifications") { Toggle( "Show bell icon next to worktree", - isOn: Binding( - get: { store.inAppNotificationsEnabled }, - set: { store.send(.setInAppNotificationsEnabled($0)) } - ) + isOn: $store.inAppNotificationsEnabled ) .help("Show bell icon next to worktree") Toggle( "Play notification sound", - isOn: Binding( - get: { store.notificationSoundEnabled }, - set: { store.send(.setNotificationSoundEnabled($0)) } - ) + isOn: $store.notificationSoundEnabled ) .help("Play a sound when a notification is received") } diff --git a/supacode/Features/Settings/Views/RepositorySettingsView.swift b/supacode/Features/Settings/Views/RepositorySettingsView.swift index 09027543..fc86653d 100644 --- a/supacode/Features/Settings/Views/RepositorySettingsView.swift +++ b/supacode/Features/Settings/Views/RepositorySettingsView.swift @@ -7,23 +7,19 @@ struct RepositorySettingsView: View { var body: some View { let baseRefOptions = store.branchOptions.isEmpty ? [store.defaultWorktreeBaseRef] : store.branchOptions + let settings = $store.settings Form { Section { if store.isBranchDataLoaded { Picker( "Branch new workspaces from", - selection: Binding( - get: { - (store.settings.worktreeBaseRef ?? "").isEmpty - ? store.defaultWorktreeBaseRef - : store.settings.worktreeBaseRef ?? store.defaultWorktreeBaseRef - }, - set: { store.send(.setWorktreeBaseRef($0)) } - ) + selection: $store.settings.worktreeBaseRef ) { + Text("Automatic (\(store.defaultWorktreeBaseRef))") + .tag(String?.none) ForEach(baseRefOptions, id: \.self) { ref in Text(ref) - .tag(ref) + .tag(Optional(ref)) } } .labelsHidden() @@ -41,18 +37,12 @@ struct RepositorySettingsView: View { Section { Toggle( "Copy ignored files to new worktrees", - isOn: Binding( - get: { store.settings.copyIgnoredOnWorktreeCreate }, - set: { store.send(.setCopyIgnoredOnWorktreeCreate($0)) } - ) + isOn: settings.copyIgnoredOnWorktreeCreate ) .disabled(store.isBareRepository) Toggle( "Copy untracked files to new worktrees", - isOn: Binding( - get: { store.settings.copyUntrackedOnWorktreeCreate }, - set: { store.send(.setCopyUntrackedOnWorktreeCreate($0)) } - ) + isOn: settings.copyUntrackedOnWorktreeCreate ) .disabled(store.isBareRepository) if store.isBareRepository { @@ -69,10 +59,7 @@ struct RepositorySettingsView: View { Section { ZStack(alignment: .topLeading) { TextEditor( - text: Binding( - get: { store.settings.setupScript }, - set: { store.send(.setSetupScript($0)) } - ) + text: settings.setupScript ) .font(.body) .monospaced() @@ -96,10 +83,7 @@ struct RepositorySettingsView: View { Section { ZStack(alignment: .topLeading) { TextEditor( - text: Binding( - get: { store.settings.runScript }, - set: { store.send(.setRunScript($0)) } - ) + text: settings.runScript ) .font(.body) .monospaced() diff --git a/supacode/Features/Settings/Views/SettingsView.swift b/supacode/Features/Settings/Views/SettingsView.swift index 27a349bf..f4c42bb4 100644 --- a/supacode/Features/Settings/Views/SettingsView.swift +++ b/supacode/Features/Settings/Views/SettingsView.swift @@ -14,22 +14,21 @@ extension View { struct SettingsView: View { @Bindable var store: StoreOf + @Bindable var settingsStore: StoreOf + + init(store: StoreOf) { + self.store = store + settingsStore = store.scope(state: \.settings, action: \.settings) + } var body: some View { - let settingsStore = store.scope(state: \.settings, action: \.settings) let updatesStore = store.scope(state: \.updates, action: \.updates) let repositories = store.repositories.repositories - let selection = store.settings.selection - let selectionBinding = Binding( - get: { store.settings.selection }, - set: { selection in - store.send(.settings(.setSelection(selection ?? .general))) - } - ) + let selection = settingsStore.selection ?? .general NavigationSplitView(columnVisibility: .constant(.all)) { VStack(spacing: 0) { - List(selection: selectionBinding) { + List(selection: $settingsStore.selection.sending(\.setSelection)) { Label("General", systemImage: "gearshape") .tag(SettingsSection.general) Label("Notifications", systemImage: "bell") diff --git a/supacode/Features/Settings/Views/UpdatesSettingsView.swift b/supacode/Features/Settings/Views/UpdatesSettingsView.swift index d16f36d7..e9acb8e3 100644 --- a/supacode/Features/Settings/Views/UpdatesSettingsView.swift +++ b/supacode/Features/Settings/Views/UpdatesSettingsView.swift @@ -11,17 +11,11 @@ struct UpdatesSettingsView: View { Section("Automatic Updates") { Toggle( "Check for updates automatically", - isOn: Binding( - get: { settingsStore.updatesAutomaticallyCheckForUpdates }, - set: { settingsStore.send(.setUpdatesAutomaticallyCheckForUpdates($0)) } - ) + isOn: $settingsStore.updatesAutomaticallyCheckForUpdates ) Toggle( "Download and install updates automatically", - isOn: Binding( - get: { settingsStore.updatesAutomaticallyDownloadUpdates }, - set: { settingsStore.send(.setUpdatesAutomaticallyDownloadUpdates($0)) } - ) + isOn: $settingsStore.updatesAutomaticallyDownloadUpdates ) .disabled(!settingsStore.updatesAutomaticallyCheckForUpdates) } diff --git a/supacode/Features/Settings/Views/WorktreeSettingsView.swift b/supacode/Features/Settings/Views/WorktreeSettingsView.swift index 5a5b17b5..5bf7df20 100644 --- a/supacode/Features/Settings/Views/WorktreeSettingsView.swift +++ b/supacode/Features/Settings/Views/WorktreeSettingsView.swift @@ -11,10 +11,7 @@ struct WorktreeSettingsView: View { VStack(alignment: .leading) { Toggle( "Delete branch on archive", - isOn: Binding( - get: { store.deleteBranchOnArchive }, - set: { store.send(.setDeleteBranchOnArchive($0)) } - ) + isOn: $store.deleteBranchOnArchive ) .help("Delete the local branch when archiving a worktree") Text("Delete the local branch when archiving a worktree.") diff --git a/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift b/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift index aa428dac..69841735 100644 --- a/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift +++ b/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift @@ -15,7 +15,6 @@ enum TerminalTabBarMetrics { static let activeTabOffset: CGFloat = 0.5 static let activeTabBottomPadding: CGFloat = 1 static let closeButtonSize: CGFloat = 16 - static let closeIconSize: CGFloat = 9 static let dirtyIndicatorSize: CGFloat = 8 static let overflowShadowWidth: CGFloat = 24 static let dropIndicatorWidth: CGFloat = 2 diff --git a/supacode/Features/Terminal/TabBar/Views/TerminalTabCloseButton.swift b/supacode/Features/Terminal/TabBar/Views/TerminalTabCloseButton.swift index 2b1732af..e3d92ef1 100644 --- a/supacode/Features/Terminal/TabBar/Views/TerminalTabCloseButton.swift +++ b/supacode/Features/Terminal/TabBar/Views/TerminalTabCloseButton.swift @@ -20,7 +20,7 @@ struct TerminalTabCloseButton: View { } .labelStyle(.iconOnly) .buttonStyle(TerminalPressTrackingButtonStyle(isPressed: $isPressing)) - .font(.system(size: TerminalTabBarMetrics.closeIconSize)) + .font(.caption2) .monospaced() .bold() .foregroundStyle( diff --git a/supacode/Features/Terminal/Views/TerminalSplitTreeView.swift b/supacode/Features/Terminal/Views/TerminalSplitTreeView.swift index cb656adb..1f89f315 100644 --- a/supacode/Features/Terminal/Views/TerminalSplitTreeView.swift +++ b/supacode/Features/Terminal/Views/TerminalSplitTreeView.swift @@ -133,7 +133,7 @@ struct TerminalSplitTreeView: View { .overlay { if isHovering { Image(systemName: "ellipsis") - .font(.system(size: 14, weight: .semibold)) + .font(.callout.weight(.semibold)) .monospaced() .foregroundStyle(.primary.opacity(0.5)) .accessibilityHidden(true) diff --git a/supacode/Support/DebugCaseOutput.swift b/supacode/Support/DebugCaseOutput.swift index 26b508d7..572a5bf5 100644 --- a/supacode/Support/DebugCaseOutput.swift +++ b/supacode/Support/DebugCaseOutput.swift @@ -73,43 +73,27 @@ private func typeName( genericsAbbreviated: Bool = true ) -> String { var name = _typeName(type, qualified: qualified) - .replacingOccurrences( - of: #"\(unknown context at \$[[:xdigit:]]+\)\."#, - with: "", - options: .regularExpression - ) + .replacing(#/\(unknown context at \$[0-9A-Fa-f]+\)\./#, with: "") for _ in 1...10 { let abbreviated = name - .replacingOccurrences( - of: #"\bSwift.Optional<([^><]+)>"#, - with: "$1?", - options: .regularExpression - ) - .replacingOccurrences( - of: #"\bSwift.Array<([^><]+)>"#, - with: "[$1]", - options: .regularExpression - ) - .replacingOccurrences( - of: #"\bSwift.Dictionary<([^,<]+), ([^><]+)>"#, - with: "[$1: $2]", - options: .regularExpression - ) + .replacing(#/\bSwift\.Optional<([^><]+)>/#) { match in + "\(match.1)?" + } + .replacing(#/\bSwift\.Array<([^><]+)>/#) { match in + "[\(match.1)]" + } + .replacing(#/\bSwift\.Dictionary<([^,<]+), ([^><]+)>/#) { match in + "[\(match.1): \(match.2)]" + } if abbreviated == name { break } name = abbreviated } - name = name.replacingOccurrences( - of: #"\w+\.([\w.]+)"#, - with: "$1", - options: .regularExpression - ) + name = name.replacing(#/\w+\.([\w.]+)/#) { match in + "\(match.1)" + } if genericsAbbreviated { - name = name.replacingOccurrences( - of: #"<.+>"#, - with: "", - options: .regularExpression - ) + name = name.replacing(#/<.+>/#, with: "") } return name } diff --git a/supacodeTests/SettingsFeatureTests.swift b/supacodeTests/SettingsFeatureTests.swift index 4917405d..268c92ca 100644 --- a/supacodeTests/SettingsFeatureTests.swift +++ b/supacodeTests/SettingsFeatureTests.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import CustomDump import DependenciesTestSupport import Foundation import Testing @@ -61,7 +62,7 @@ struct SettingsFeatureTests { ) } - await store.send(.setAppearanceMode(.light)) { + await store.send(.binding(.set(\.appearanceMode, .light))) { $0.appearanceMode = .light } let expectedSettings = GlobalSettings( @@ -77,7 +78,7 @@ struct SettingsFeatureTests { await store.receive(\.delegate.settingsChanged) await store.finish() - #expect(saved.value == expectedSettings) + expectNoDifference(saved.value, expectedSettings) } @Test(.dependencies) func selectionDoesNotMutateRepositorySettings() async {