From 287bae316f87e8f4edee00318e9727bd73d9e250 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 12 Jul 2026 14:16:08 +0900 Subject: [PATCH] settings: remove the no-op update channel picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tip channel is upstream's release strategy; this fork stopped publishing it when releases moved to the local notarized pipeline, and SparkleUpdateDelegate.allowedChannels has returned [] unconditionally since then — the Settings picker and the UpdateChannel plumbing were dead weight. Removes the enum, the GlobalSettings field (old prowl.json keys are ignored on decode), the applySettings/setUpdateChannel path, and the picker UI; folds the updateCheckInterval setup into configure. Sentry environment is now always production. --- docs/README.md | 2 +- docs/components/settings.md | 2 +- docs/components/updates.md | 11 ++-------- docs/reference/settings-fields.md | 1 - supacode/App/supacodeApp.swift | 2 +- supacode/Clients/Updates/UpdaterClient.swift | 16 +------------- .../Features/App/Reducer/AppFeature.swift | 1 - .../Settings/Models/GlobalSettings.swift | 9 -------- .../Settings/Models/UpdateChannel.swift | 4 ---- .../Settings/Reducer/SettingsFeature.swift | 4 ---- .../Settings/Views/UpdatesSettingsView.swift | 6 ------ .../Updates/Reducer/UpdatesFeature.swift | 8 ++----- supacodeTests/SettingsFeatureTests.swift | 6 ------ supacodeTests/UpdatesFeatureTests.swift | 21 +++---------------- 14 files changed, 11 insertions(+), 82 deletions(-) delete mode 100644 supacode/Features/Settings/Models/UpdateChannel.swift diff --git a/docs/README.md b/docs/README.md index 5e79e597..84b0df51 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,7 +56,7 @@ its keyboard shortcuts, detailed behavior, settings, and gotchas. | [`components/github-pull-requests.md`](components/github-pull-requests.md) | GitHub PR integration via `gh`: PR status, CI checks, merge/close/re-run actions from the command palette. | | [`components/custom-actions.md`](components/custom-actions.md) | Run Script (`⌘R`/`⌘.`), Setup & Archive scripts, and per-repo Custom Commands with their own buttons & hotkeys. Injected env vars. | | [`components/settings.md`](components/settings.md) | The Settings window (`⌘,`): every tab and what it controls. | -| [`components/updates.md`](components/updates.md) | Sparkle auto-updates: channels, auto-check, `⌘⇧U`. | +| [`components/updates.md`](components/updates.md) | Sparkle auto-updates: auto-check, `⌘⇧U`. | | [`components/cli.md`](components/cli.md) | The `prowl` CLI — let an agent inspect and drive panes (`list`, `read`, `send`, `key`, `focus`, `tab`, `pane`, `open`). | ## Reference (exact lookups) diff --git a/docs/components/settings.md b/docs/components/settings.md index b6b1b325..7a7bac2a 100644 --- a/docs/components/settings.md +++ b/docs/components/settings.md @@ -20,7 +20,7 @@ window is a sidebar of tabs plus a detail pane. | **Notifications** | In-app alerts, notification sound picker (Never / system sounds / Prowl Classic), macOS system notifications, move-notified-to-top, command-finished notification + threshold, Dock badge & bounce. → [notifications](notifications.md) | | **Shortcuts** | Remap app keyboard shortcuts; view defaults; resolve conflicts. → [keyboard-shortcuts](../reference/keyboard-shortcuts.md) | | **Worktree** | Worktree creation/deletion defaults: prompt on create, fetch before create, base directory, copy ignored/untracked files, delete-branch-on-delete, merged-worktree action, archived auto-delete period. | -| **Updates** | Update channel (Stable/Tip), auto-check toggle, "Check for Updates Now". → [updates](updates.md) | +| **Updates** | Auto-check toggle, "Check for Updates Now". → [updates](updates.md) | | **Advanced** | Analytics, crash reports, restore terminal layout on launch (experimental) + clear saved layout, and the **Install Command Line Tool** (`prowl` CLI) action. | | **GitHub** | Enable GitHub integration (uses the `gh` CLI). → [github-pull-requests](github-pull-requests.md) | | **Repositories / Repo Settings** | Per-repository: setup/archive/run scripts, **Custom Commands**, default base ref & directory, copy-files overrides, open-with app, custom title, icon & color, PR merge strategy, line-diff & PR-state fetching. Reached from the sidebar context menu → "Repo Settings". → [custom-actions](custom-actions.md), [repositories-and-worktrees](repositories-and-worktrees.md) | diff --git a/docs/components/updates.md b/docs/components/updates.md index 5634371b..3aeb012c 100644 --- a/docs/components/updates.md +++ b/docs/components/updates.md @@ -1,8 +1,8 @@ # Updates -> Prowl keeps itself current via Sparkle. Channels, auto-check, and manual checks. +> Prowl keeps itself current via Sparkle. Auto-check and manual checks. -**Keywords:** updates, sparkle, auto-update, check for updates, channel, stable, tip, version, ⌘⇧U +**Keywords:** updates, sparkle, auto-update, check for updates, version, ⌘⇧U **Related:** [settings](settings.md) @@ -26,15 +26,8 @@ Prowl uses the **Sparkle** framework for auto-updates. Releases are notarized. next check. A "Check for Updates" action will therefore never install and relaunch on its own. -## Channels - -`updateChannel` offers **Stable** (default) and **Tip** in Settings → Updates. -Tip is **no longer published separately** and currently resolves to the same feed -as Stable, so the two behave identically today. - ## Settings -- `updateChannel` — `stable` or `tip` (Tip currently resolves to Stable). - `updatesAutomaticallyCheckForUpdates` — background checks (default on). - `updatesAutomaticallyDownloadUpdates` — present in settings but **not currently wired** to Sparkle or exposed in the UI; the background-download preference is diff --git a/docs/reference/settings-fields.md b/docs/reference/settings-fields.md index 71cd7b33..c4221ded 100644 --- a/docs/reference/settings-fields.md +++ b/docs/reference/settings-fields.md @@ -26,7 +26,6 @@ JSON is pretty-printed with sorted keys. Legacy `~/.supacode` is migrated to | `appearanceMode` | enum (`system`/`light`/`dark`) | `dark` | App appearance. | | `defaultEditorID` | String | `auto` | Default app to open worktrees (overridable per repo); `auto` prefers an app matching the detected project type. | | `confirmBeforeQuit` | Bool | `true` | Confirm before quitting Prowl. | -| `updateChannel` | enum (`stable`/`tip`) | `stable` | Sparkle release channel. | | `updatesAutomaticallyCheckForUpdates` | Bool | `true` | Background update checks. | | `updatesAutomaticallyDownloadUpdates` | Bool | `false` | Auto-download updates. | | `inAppNotificationsEnabled` | Bool | `true` | In-app alerts / bell indicators. | diff --git a/supacode/App/supacodeApp.swift b/supacode/App/supacodeApp.swift index 6a421d01..0afcebf2 100644 --- a/supacode/App/supacodeApp.swift +++ b/supacode/App/supacodeApp.swift @@ -132,7 +132,7 @@ struct SupacodeApp: App { #if !DEBUG let infoDictionary = Bundle.main.infoDictionary ?? [:] let releaseName = (infoDictionary["CFBundleShortVersionString"] as? String).map { "prowl@\($0)" } - let environment = initialSettings.updateChannel == .tip ? "tip" : "production" + let environment = "production" if initialSettings.crashReportsEnabled, let dsn = infoPlistSecret(infoDictionary, key: "ProwlSentryDSN") { SentrySDK.start { options in diff --git a/supacode/Clients/Updates/UpdaterClient.swift b/supacode/Clients/Updates/UpdaterClient.swift index dea68d1a..e35c40d3 100644 --- a/supacode/Clients/Updates/UpdaterClient.swift +++ b/supacode/Clients/Updates/UpdaterClient.swift @@ -6,7 +6,6 @@ private let updaterLogger = SupaLogger("Updater") struct UpdaterClient { var configure: @MainActor @Sendable (_ checks: Bool, _ checkInBackground: Bool) -> Void - var setUpdateChannel: @MainActor @Sendable (UpdateChannel) -> Void var checkForUpdates: @MainActor @Sendable () -> Void var installDownloadedUpdate: @MainActor @Sendable () -> Void var events: @MainActor @Sendable () -> AsyncStream @@ -21,15 +20,9 @@ extension UpdaterClient { @MainActor final class SparkleUpdateDelegate: NSObject, SPUUpdaterDelegate { - var updateChannel: UpdateChannel = .stable private var continuation: AsyncStream.Continuation? private var immediateInstallHandler: (() -> Void)? - nonisolated func allowedChannels(for updater: SPUUpdater) -> Set { - // Tip channel is no longer published separately; treat it the same as stable. - [] - } - func setContinuation(_ continuation: AsyncStream.Continuation) { self.continuation?.finish() self.continuation = continuation @@ -232,18 +225,12 @@ extension UpdaterClient: DependencyKey { return UpdaterClient( configure: { checks, checkInBackground in driver.setAutomaticUpdatePreferences(checks: checks) + updater.updateCheckInterval = 3600 updater.automaticallyChecksForUpdates = checks if checkInBackground, checks { updater.checkForUpdatesInBackground() } }, - setUpdateChannel: { channel in - delegate.updateChannel = channel - updater.updateCheckInterval = 3600 - if updater.automaticallyChecksForUpdates { - updater.checkForUpdatesInBackground() - } - }, checkForUpdates: { updater.checkForUpdates() }, @@ -261,7 +248,6 @@ extension UpdaterClient: DependencyKey { static let testValue = UpdaterClient( configure: { _, _ in }, - setUpdateChannel: { _ in }, checkForUpdates: {}, installDownloadedUpdate: {}, events: { AsyncStream { _ in } } diff --git a/supacode/Features/App/Reducer/AppFeature.swift b/supacode/Features/App/Reducer/AppFeature.swift index b58df1d0..95784dd6 100644 --- a/supacode/Features/App/Reducer/AppFeature.swift +++ b/supacode/Features/App/Reducer/AppFeature.swift @@ -445,7 +445,6 @@ struct AppFeature { .send( .updates( .applySettings( - updateChannel: settings.updateChannel, automaticallyChecks: settings.updatesAutomaticallyCheckForUpdates ) ) diff --git a/supacode/Features/Settings/Models/GlobalSettings.swift b/supacode/Features/Settings/Models/GlobalSettings.swift index a07a899e..d0eb264b 100644 --- a/supacode/Features/Settings/Models/GlobalSettings.swift +++ b/supacode/Features/Settings/Models/GlobalSettings.swift @@ -2,7 +2,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { var appearanceMode: AppearanceMode var defaultEditorID: String var confirmBeforeQuit: Bool - var updateChannel: UpdateChannel var updatesAutomaticallyCheckForUpdates: Bool var updatesAutomaticallyDownloadUpdates: Bool var inAppNotificationsEnabled: Bool @@ -48,7 +47,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { appearanceMode: .dark, defaultEditorID: OpenWorktreeAction.automaticSettingsID, confirmBeforeQuit: true, - updateChannel: .stable, updatesAutomaticallyCheckForUpdates: true, updatesAutomaticallyDownloadUpdates: false, inAppNotificationsEnabled: true, @@ -93,7 +91,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { appearanceMode: AppearanceMode, defaultEditorID: String, confirmBeforeQuit: Bool, - updateChannel: UpdateChannel, updatesAutomaticallyCheckForUpdates: Bool, updatesAutomaticallyDownloadUpdates: Bool, inAppNotificationsEnabled: Bool, @@ -136,7 +133,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { self.appearanceMode = appearanceMode self.defaultEditorID = defaultEditorID self.confirmBeforeQuit = confirmBeforeQuit - self.updateChannel = updateChannel self.updatesAutomaticallyCheckForUpdates = updatesAutomaticallyCheckForUpdates self.updatesAutomaticallyDownloadUpdates = updatesAutomaticallyDownloadUpdates self.inAppNotificationsEnabled = inAppNotificationsEnabled @@ -182,7 +178,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { try container.encode(appearanceMode, forKey: .appearanceMode) try container.encode(defaultEditorID, forKey: .defaultEditorID) try container.encode(confirmBeforeQuit, forKey: .confirmBeforeQuit) - try container.encode(updateChannel, forKey: .updateChannel) try container.encode(updatesAutomaticallyCheckForUpdates, forKey: .updatesAutomaticallyCheckForUpdates) try container.encode(updatesAutomaticallyDownloadUpdates, forKey: .updatesAutomaticallyDownloadUpdates) try container.encode(inAppNotificationsEnabled, forKey: .inAppNotificationsEnabled) @@ -229,7 +224,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { case appearanceMode case defaultEditorID case confirmBeforeQuit - case updateChannel case updatesAutomaticallyCheckForUpdates case updatesAutomaticallyDownloadUpdates case inAppNotificationsEnabled @@ -284,9 +278,6 @@ nonisolated struct GlobalSettings: Codable, Equatable, Sendable { confirmBeforeQuit = try container.decodeIfPresent(Bool.self, forKey: .confirmBeforeQuit) ?? Self.default.confirmBeforeQuit - updateChannel = - try container.decodeIfPresent(UpdateChannel.self, forKey: .updateChannel) - ?? Self.default.updateChannel updatesAutomaticallyCheckForUpdates = try container.decode(Bool.self, forKey: .updatesAutomaticallyCheckForUpdates) updatesAutomaticallyDownloadUpdates = try container.decode(Bool.self, forKey: .updatesAutomaticallyDownloadUpdates) inAppNotificationsEnabled = diff --git a/supacode/Features/Settings/Models/UpdateChannel.swift b/supacode/Features/Settings/Models/UpdateChannel.swift deleted file mode 100644 index d7966b70..00000000 --- a/supacode/Features/Settings/Models/UpdateChannel.swift +++ /dev/null @@ -1,4 +0,0 @@ -enum UpdateChannel: String, Codable, CaseIterable, Sendable { - case stable - case tip -} diff --git a/supacode/Features/Settings/Reducer/SettingsFeature.swift b/supacode/Features/Settings/Reducer/SettingsFeature.swift index 73fda7de..5e1be01b 100644 --- a/supacode/Features/Settings/Reducer/SettingsFeature.swift +++ b/supacode/Features/Settings/Reducer/SettingsFeature.swift @@ -9,7 +9,6 @@ struct SettingsFeature { var appearanceMode: AppearanceMode var defaultEditorID: String var confirmBeforeQuit: Bool - var updateChannel: UpdateChannel var updatesAutomaticallyCheckForUpdates: Bool var updatesAutomaticallyDownloadUpdates: Bool var inAppNotificationsEnabled: Bool @@ -68,7 +67,6 @@ struct SettingsFeature { appearanceMode = settings.appearanceMode defaultEditorID = normalizedDefaultEditorID confirmBeforeQuit = settings.confirmBeforeQuit - updateChannel = settings.updateChannel updatesAutomaticallyCheckForUpdates = settings.updatesAutomaticallyCheckForUpdates updatesAutomaticallyDownloadUpdates = settings.updatesAutomaticallyDownloadUpdates inAppNotificationsEnabled = settings.inAppNotificationsEnabled @@ -117,7 +115,6 @@ struct SettingsFeature { appearanceMode: appearanceMode, defaultEditorID: defaultEditorID, confirmBeforeQuit: confirmBeforeQuit, - updateChannel: updateChannel, updatesAutomaticallyCheckForUpdates: updatesAutomaticallyCheckForUpdates, updatesAutomaticallyDownloadUpdates: updatesAutomaticallyDownloadUpdates, inAppNotificationsEnabled: inAppNotificationsEnabled, @@ -239,7 +236,6 @@ struct SettingsFeature { state.appearanceMode = normalizedSettings.appearanceMode state.defaultEditorID = normalizedSettings.defaultEditorID state.confirmBeforeQuit = normalizedSettings.confirmBeforeQuit - state.updateChannel = normalizedSettings.updateChannel state.updatesAutomaticallyCheckForUpdates = normalizedSettings.updatesAutomaticallyCheckForUpdates state.updatesAutomaticallyDownloadUpdates = normalizedSettings.updatesAutomaticallyDownloadUpdates state.inAppNotificationsEnabled = normalizedSettings.inAppNotificationsEnabled diff --git a/supacode/Features/Settings/Views/UpdatesSettingsView.swift b/supacode/Features/Settings/Views/UpdatesSettingsView.swift index 2f2cf096..dd6cc2a1 100644 --- a/supacode/Features/Settings/Views/UpdatesSettingsView.swift +++ b/supacode/Features/Settings/Views/UpdatesSettingsView.swift @@ -8,12 +8,6 @@ struct UpdatesSettingsView: View { var body: some View { VStack(alignment: .leading, spacing: 0) { Form { - Section("Update Channel") { - Picker("Channel", selection: $settingsStore.updateChannel) { - Text("Stable").tag(UpdateChannel.stable) - Text("Tip").tag(UpdateChannel.tip) - } - } Section { Toggle( "Check for updates automatically", diff --git a/supacode/Features/Updates/Reducer/UpdatesFeature.swift b/supacode/Features/Updates/Reducer/UpdatesFeature.swift index 488fbb3d..5275a96e 100644 --- a/supacode/Features/Updates/Reducer/UpdatesFeature.swift +++ b/supacode/Features/Updates/Reducer/UpdatesFeature.swift @@ -13,10 +13,7 @@ struct UpdatesFeature { enum Action { case task - case applySettings( - updateChannel: UpdateChannel, - automaticallyChecks: Bool - ) + case applySettings(automaticallyChecks: Bool) case activateUpdateButton case checkForUpdates case updaterEvent(UpdaterClient.Event) @@ -38,11 +35,10 @@ struct UpdatesFeature { } } - case .applySettings(let channel, let checks): + case .applySettings(let checks): let checkInBackground = !state.didConfigureUpdates state.didConfigureUpdates = true return .run { _ in - await updaterClient.setUpdateChannel(channel) await updaterClient.configure(checks, checkInBackground) } diff --git a/supacodeTests/SettingsFeatureTests.swift b/supacodeTests/SettingsFeatureTests.swift index bd0b8a5a..64c20b57 100644 --- a/supacodeTests/SettingsFeatureTests.swift +++ b/supacodeTests/SettingsFeatureTests.swift @@ -14,7 +14,6 @@ struct SettingsFeatureTests { appearanceMode: .dark, defaultEditorID: OpenWorktreeAction.automaticSettingsID, confirmBeforeQuit: true, - updateChannel: .stable, updatesAutomaticallyCheckForUpdates: false, updatesAutomaticallyDownloadUpdates: true, inAppNotificationsEnabled: false, @@ -39,7 +38,6 @@ struct SettingsFeatureTests { $0.appearanceMode = .dark $0.defaultEditorID = OpenWorktreeAction.automaticSettingsID $0.confirmBeforeQuit = true - $0.updateChannel = .stable $0.updatesAutomaticallyCheckForUpdates = false $0.updatesAutomaticallyDownloadUpdates = true $0.inAppNotificationsEnabled = false @@ -60,7 +58,6 @@ struct SettingsFeatureTests { appearanceMode: .system, defaultEditorID: OpenWorktreeAction.automaticSettingsID, confirmBeforeQuit: true, - updateChannel: .stable, updatesAutomaticallyCheckForUpdates: false, updatesAutomaticallyDownloadUpdates: false, inAppNotificationsEnabled: false, @@ -87,7 +84,6 @@ struct SettingsFeatureTests { appearanceMode: .light, defaultEditorID: initialSettings.defaultEditorID, confirmBeforeQuit: initialSettings.confirmBeforeQuit, - updateChannel: initialSettings.updateChannel, updatesAutomaticallyCheckForUpdates: initialSettings.updatesAutomaticallyCheckForUpdates, updatesAutomaticallyDownloadUpdates: initialSettings.updatesAutomaticallyDownloadUpdates, inAppNotificationsEnabled: initialSettings.inAppNotificationsEnabled, @@ -237,7 +233,6 @@ struct SettingsFeatureTests { appearanceMode: .light, defaultEditorID: OpenWorktreeAction.automaticSettingsID, confirmBeforeQuit: false, - updateChannel: .tip, updatesAutomaticallyCheckForUpdates: false, updatesAutomaticallyDownloadUpdates: true, inAppNotificationsEnabled: false, @@ -255,7 +250,6 @@ struct SettingsFeatureTests { $0.appearanceMode = .light $0.defaultEditorID = OpenWorktreeAction.automaticSettingsID $0.confirmBeforeQuit = false - $0.updateChannel = .tip $0.updatesAutomaticallyCheckForUpdates = false $0.updatesAutomaticallyDownloadUpdates = true $0.inAppNotificationsEnabled = false diff --git a/supacodeTests/UpdatesFeatureTests.swift b/supacodeTests/UpdatesFeatureTests.swift index c0fbbcd8..ace9d947 100644 --- a/supacodeTests/UpdatesFeatureTests.swift +++ b/supacodeTests/UpdatesFeatureTests.swift @@ -8,42 +8,27 @@ import Testing @Suite(.serialized) struct UpdatesFeatureTests { private struct Configuration: Equatable { - var channel: UpdateChannel var checks: Bool var checkInBackground: Bool } @Test(.dependencies) func applySettingsConfiguresAutomaticChecks() async { - let configuredChannel = LockIsolated(nil) let configuration = LockIsolated(nil) let store = TestStore(initialState: UpdatesFeature.State()) { UpdatesFeature() } withDependencies: { - $0.updaterClient.setUpdateChannel = { channel in - configuredChannel.withValue { $0 = channel } - } $0.updaterClient.configure = { checks, checkInBackground in configuration.withValue { - $0 = Configuration( - channel: configuredChannel.value ?? .stable, - checks: checks, - checkInBackground: checkInBackground - ) + $0 = Configuration(checks: checks, checkInBackground: checkInBackground) } } } - await store.send( - .applySettings( - updateChannel: .stable, - automaticallyChecks: true - ) - ) { + await store.send(.applySettings(automaticallyChecks: true)) { $0.didConfigureUpdates = true } - #expect( - configuration.value == Configuration(channel: .stable, checks: true, checkInBackground: true)) + #expect(configuration.value == Configuration(checks: true, checkInBackground: true)) } @Test(.dependencies) func downloadedUpdateEventMarksUpdateReadyToInstall() async { -- 2.51.2