From 20a735678e904d60251ba7290e4b1d7494f66780 Mon Sep 17 00:00:00 2001 From: tk1475 <118763009+tk1475@users.noreply.github.com> Date: Fri, 9 Oct 2026 01:28:54 +0500 Subject: [PATCH 1/2] Add an opt-in setting to always confirm closing workspaces app.alwaysConfirmWorkspaceClose (off by default) makes every workspace close ask first, including idle workspaces, multi-workspace closes, and closing the last workspace with the window. It overrides warnBeforeClosingWorkspace for these prompts. "Don't ask again" on such a prompt turns the new setting off, so the next close really doesn't ask. Closes #16921 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01FsxA6uRfPk8v6h673Dx5N1 --- .../CmuxConfigSchema.generated.swift | 5 + .../CmuxSettings/Keys/AppCatalogSection.swift | 24 ++ .../Stores/CloseTabWarningStore.swift | 10 +- .../Values/CloseWarningKinds.swift | 3 + .../CuratedSettingEntry+Default.swift | 1 + .../CmuxSettingsUI/Sections/AppSection.swift | 18 +- .../SettingsRowAnchorResolutionTests.swift | 1 + Resources/Localizable.xcstrings | 260 ++++++++++++++++++ ...CmuxSettingsFileStore+SupportedPaths.swift | 1 + Sources/CmuxSettingsJSONPathSupport.swift | 4 + .../CommandPaletteSettingsToggle.swift | 3 + ...rdShortcutSettingsFileStore+Template.swift | 1 + Sources/SettingsSearchAliases.swift | 4 + Sources/SettingsSearchIndex.swift | 7 + Sources/TabManager.swift | 8 + cmuxTests/TabManagerUnitTests.swift | 78 ++++++ skills/cmux-settings/references/all-keys.md | 1 + web/data/cmux.schema.json | 5 + 18 files changed, 432 insertions(+), 2 deletions(-) diff --git a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift index 8713d07f824d..154506e61bdc 100644 --- a/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift +++ b/Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/ConfigValidation/CmuxConfigSchema.generated.swift @@ -871,6 +871,11 @@ enum CmuxEmbeddedConfigSchema { "default": true, "description": "Show a confirmation before closing a workspace with a running process, or several workspaces at once. Pinned workspaces still ask when this is off." }, + "alwaysConfirmWorkspaceClose": { + "type": "boolean", + "default": false, + "description": "Ask before every workspace close, even when nothing in it is running. Overrides warnBeforeClosingWorkspace for workspace closes." + }, "warnBeforeClosingWindow": { "type": "boolean", "default": true, diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/AppCatalogSection.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/AppCatalogSection.swift index f8fe2327d70b..0d21be7c2ec8 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/AppCatalogSection.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/AppCatalogSection.swift @@ -245,6 +245,30 @@ public struct AppCatalogSection: SettingCatalogSection { ) ) + /// Opt-in: ask before every workspace close, even when nothing in the + /// workspace is running. Overrides `warnBeforeClosingWorkspace` for the + /// workspace prompts, the way pinning does for a single workspace. + public let alwaysConfirmWorkspaceClose = DefaultsKey( + id: "app.alwaysConfirmWorkspaceClose", + defaultValue: false, + userDefaultsKey: "alwaysConfirmWorkspaceClose", + userFacing: UserFacingSettingDescriptor( + title: String( + localized: "settings.app.alwaysConfirmWorkspaceClose", + defaultValue: "Always Confirm Closing Workspaces" + ), + section: .app, + searchID: "always-confirm-workspace-close", + searchKeywords: ["always", "close", "workspace", "confirmation", "idle", "protect"], + control: .toggle(.init( + commandPalette: .init( + id: "alwaysConfirmWorkspaceClose", + keywords: ["always", "confirm", "close", "workspace"] + ) + )) + ) + ) + /// Gates the "Close window?" prompts (the Close Window command, and closing /// every workspace in a window at once). public let warnBeforeClosingWindow = DefaultsKey( diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Stores/CloseTabWarningStore.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Stores/CloseTabWarningStore.swift index 4553bfd9f5cd..3ce1c676410a 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Stores/CloseTabWarningStore.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Stores/CloseTabWarningStore.swift @@ -3,7 +3,7 @@ import Foundation /// Repository for the close warning settings, persisted in `UserDefaults` /// under the catalog's `app.warnBeforeClosingTab`, /// `app.warnBeforeClosingTabXButton`, `app.warnBeforeClosingAgentSession`, -/// `app.warnBeforeClosingWorkspace`, +/// `app.warnBeforeClosingWorkspace`, `app.alwaysConfirmWorkspaceClose`, /// `app.warnBeforeClosingWindow`, and /// `app.hideTabCloseButton` keys. /// @@ -59,6 +59,11 @@ public struct CloseTabWarningStore: CloseTabWarningReading { keys.warnBeforeClosingWorkspace.value(in: defaults) } + /// Whether every workspace close asks first, even when nothing is running. + public var alwaysConfirmsWorkspaceClose: Bool { + keys.alwaysConfirmWorkspaceClose.value(in: defaults) + } + /// Turns off the given warnings, for a dialog's "Don't ask again" checkbox. public func disableWarnings(_ kinds: CloseWarningKinds) { // An agent-session prompt owns its own suppression choice. Keep a @@ -80,6 +85,9 @@ public struct CloseTabWarningStore: CloseTabWarningReading { if kinds.contains(.workspace) { keys.warnBeforeClosingWorkspace.set(false, in: defaults) } + if kinds.contains(.alwaysConfirmWorkspace) { + keys.alwaysConfirmWorkspaceClose.set(false, in: defaults) + } if kinds.contains(.window) { keys.warnBeforeClosingWindow.set(false, in: defaults) } diff --git a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Values/CloseWarningKinds.swift b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Values/CloseWarningKinds.swift index 2080726859cf..5ac2d2ca6107 100644 --- a/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Values/CloseWarningKinds.swift +++ b/Packages/macOS/CmuxSettings/Sources/CmuxSettings/Values/CloseWarningKinds.swift @@ -28,4 +28,7 @@ public struct CloseWarningKinds: OptionSet, Sendable, Hashable { /// `app.warnBeforeClosingAgentSession`: an agent session that is mid-turn. public static let agentSession = CloseWarningKinds(rawValue: 1 << 5) + + /// `app.alwaysConfirmWorkspaceClose`: every workspace close, idle or not. + public static let alwaysConfirmWorkspace = CloseWarningKinds(rawValue: 1 << 6) } diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift index 468dc11a2159..0cb0b85d096a 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift @@ -123,6 +123,7 @@ extension Array where Element == CuratedSettingEntry { .init(userFacing: catalog.app.warnBeforeClosingAgentSession), .init(section: .app, id: "warn-before-closing-tab-x-button", title: String(localized: "settings.app.warnBeforeClosingTabXButton", defaultValue: "Warn Before Tab Close Button"), synonyms: "Warn Before Tab Close Button app.warnBeforeClosingTabXButton x button close tab confirmation terminal surface"), .init(userFacing: catalog.app.warnBeforeClosingWorkspace), + .init(userFacing: catalog.app.alwaysConfirmWorkspaceClose), .init(userFacing: catalog.app.warnBeforeClosingWindow), .init(userFacing: catalog.app.hideTabCloseButton), .init(userFacing: catalog.app.renameSelectsExistingName), diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift index a5d9dc644772..bb36996129d6 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift @@ -74,6 +74,7 @@ public struct AppSection: View { @State private var warnCloseAgentSession: DefaultsValueModel @State private var warnCloseX: DefaultsValueModel @State private var warnCloseWorkspace: DefaultsValueModel + @State private var alwaysConfirmCloseWorkspace: DefaultsValueModel @State private var warnCloseWindow: DefaultsValueModel @State private var hideCloseButton: DefaultsValueModel @State private var renameSelects: DefaultsValueModel @@ -149,6 +150,7 @@ public struct AppSection: View { _warnCloseAgentSession = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.warnBeforeClosingAgentSession)) _warnCloseX = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.warnBeforeClosingTabXButton)) _warnCloseWorkspace = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.warnBeforeClosingWorkspace)) + _alwaysConfirmCloseWorkspace = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.alwaysConfirmWorkspaceClose)) _warnCloseWindow = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.warnBeforeClosingWindow)) _hideCloseButton = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.hideTabCloseButton)) _renameSelects = State(initialValue: DefaultsValueModel(store: defaultsStore, key: catalog.app.renameSelectsExistingName)) @@ -174,7 +176,7 @@ public struct AppSection: View { mainCard } .task { - startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, paneFlashOnTyping, paneFlashThemeColor, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseAgentSession, warnCloseX, warnCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) + startSettingsObservation([language, appIcon, placement, inheritDir, minimalMode, keepWorkspaceOpen, firstClick, focusHistoryIncludesPanesAndTabs, equalizeSplitsOnCreate, fileDrop, preferredEditor, openSupported, openMarkdown, globalFontMagnification, markdownFontSize, markdownFontFamily, markdownMaxWidth, canvasPaneGap, canvasSnapping, fileEditorWordWrap, fileEditorSyntaxHighlighting, fileEditorLineNumbers, fileEditorIndentGuides, fileEditorCurrentLineHighlight, fileEditorTabWidth, iMessage, reorder, dockBadge, menuBarOnly, showInMenuBar, paneRing, paneFlash, paneFlashDoubleBlink, paneFlashOnTyping, paneFlashThemeColor, desktopNotifications, agentPermissionPrompt, agentTurnComplete, agentIdleReminder, soundName, soundWhenFocused, soundCommand, customSoundFile, soundOverrides, telemetry, confirmQuit, warnCloseTab, warnCloseAgentSession, warnCloseX, warnCloseWorkspace, alwaysConfirmCloseWorkspace, warnCloseWindow, hideCloseButton, renameSelects, paletteAllSurfaces]) await soundAgentCache.loadIfNeeded { await hostActions.notificationSoundAgentOptions() } if languageAtAppear == nil { languageAtAppear = language.current }; if telemetryAtAppear == nil { telemetryAtAppear = telemetry.current } } @@ -954,6 +956,20 @@ public struct AppSection: View { } SettingsCardDivider() + // Always Confirm Closing Workspaces + SettingsCardRow( + configurationReview: .json(catalog.app.alwaysConfirmWorkspaceClose.id), + catalog.app.alwaysConfirmWorkspaceClose.userFacing!.title, + subtitle: alwaysConfirmCloseWorkspace.current + ? String(localized: "settings.app.alwaysConfirmWorkspaceClose.subtitleOn", defaultValue: "Every workspace close asks first, even when nothing is running.") + : String(localized: "settings.app.alwaysConfirmWorkspaceClose.subtitleOff", defaultValue: "Idle workspaces close without asking. Turn on to protect long-lived workspaces.") + ) { + Toggle("", isOn: Binding(get: { alwaysConfirmCloseWorkspace.current }, set: { alwaysConfirmCloseWorkspace.set($0) })) + .labelsHidden() + .controlSize(.small) + } + SettingsCardDivider() + // Warn Before Closing Window SettingsCardRow( configurationReview: .json(catalog.app.warnBeforeClosingWindow.id), diff --git a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift index cac6c01c33db..10a94704aa92 100644 --- a/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift +++ b/Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift @@ -61,6 +61,7 @@ struct SettingsRowAnchorResolutionTests { "app.warnBeforeClosingTabXButton", "app.warnBeforeClosingWindow", "app.warnBeforeClosingWorkspace", + "app.alwaysConfirmWorkspaceClose", "app.workspaceInheritWorkingDirectory", "agentMessages.enabled", "automation.agentAutoResume", diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 61a76e1caee5..071dabaa3df9 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -566890,6 +566890,71 @@ } } }, + "settings.app.alwaysConfirmWorkspaceClose": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Always Confirm Closing Workspaces" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "التأكيد دائمًا قبل إغلاق مساحات العمل" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Schließen von Arbeitsbereichen immer bestätigen" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Confirmar siempre al cerrar espacios de trabajo" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Toujours confirmer la fermeture des espaces de travail" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ワークスペースを閉じるときは常に確認" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "워크스페이스를 닫을 때 항상 확인" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Завжди підтверджувати закриття робочих областей" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "关闭工作区时始终确认" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "關閉工作區時一律確認" + } + } + } + }, "settings.app.warnBeforeClosingWorkspace.subtitleOff": { "extractionState": "manual", "localizations": { @@ -566955,6 +567020,71 @@ } } }, + "settings.app.alwaysConfirmWorkspaceClose.subtitleOff": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Idle workspaces close without asking. Turn on to protect long-lived workspaces." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "تُغلق مساحات العمل الخاملة دون سؤال. فعّل هذا الخيار لحماية مساحات العمل طويلة الأمد." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Inaktive Arbeitsbereiche werden ohne Nachfrage geschlossen. Aktivieren, um langlebige Arbeitsbereiche zu schützen." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Los espacios de trabajo inactivos se cierran sin preguntar. Actívalo para proteger los espacios de trabajo de larga duración." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Les espaces de travail inactifs se ferment sans demander. Activez cette option pour protéger les espaces de travail de longue durée." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "アイドル状態のワークスペースは確認なしで閉じます。長く使うワークスペースを守るにはオンにします。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "유휴 워크스페이스는 확인 없이 닫힙니다. 오래 사용하는 워크스페이스를 보호하려면 켜세요." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Неактивні робочі області закриваються без запиту. Увімкніть, щоб захистити довготривалі робочі області." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "空闲的工作区会直接关闭。开启后可保护长期使用的工作区。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "閒置的工作區會直接關閉。開啟後可保護長期使用的工作區。" + } + } + } + }, "settings.app.warnBeforeClosingWorkspace.subtitleOn": { "extractionState": "manual", "localizations": { @@ -567020,6 +567150,71 @@ } } }, + "settings.app.alwaysConfirmWorkspaceClose.subtitleOn": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Every workspace close asks first, even when nothing is running." + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "يطلب كل إغلاق لمساحة عمل التأكيد أولًا، حتى عندما لا يكون هناك شيء قيد التشغيل." + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Jedes Schließen eines Arbeitsbereichs fragt zuerst nach, auch wenn nichts läuft." + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Cada cierre de un espacio de trabajo pide confirmación, aunque no haya nada en ejecución." + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Chaque fermeture d’un espace de travail demande d’abord confirmation, même quand rien ne s’exécute." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "何も実行されていなくても、ワークスペースを閉じる前に必ず確認します。" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "실행 중인 것이 없어도 워크스페이스를 닫기 전에 항상 확인합니다." + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Кожне закриття робочої області спершу запитує підтвердження, навіть якщо нічого не запущено." + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "每次关闭工作区都会先确认,即使没有正在运行的内容。" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "每次關閉工作區都會先確認,即使沒有正在執行的內容。" + } + } + } + }, "settings.search.alias.setting.app.warn-before-closing-window": { "extractionState": "manual", "localizations": { @@ -567150,6 +567345,71 @@ } } }, + "settings.search.alias.setting.app.always-confirm-workspace-close": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + } + } + } + }, "settings.terminal.ghostty.backgroundBlur": { "extractionState": "manual", "localizations": { diff --git a/Sources/CmuxSettingsFileStore+SupportedPaths.swift b/Sources/CmuxSettingsFileStore+SupportedPaths.swift index 22d02f455104..f1595c228d05 100644 --- a/Sources/CmuxSettingsFileStore+SupportedPaths.swift +++ b/Sources/CmuxSettingsFileStore+SupportedPaths.swift @@ -37,6 +37,7 @@ extension CmuxSettingsFileStore { "app.warnBeforeClosingAgentSession", "app.warnBeforeClosingTabXButton", "app.warnBeforeClosingWorkspace", + "app.alwaysConfirmWorkspaceClose", "app.warnBeforeClosingWindow", "app.hideTabCloseButton", "app.tabBarVisibility", diff --git a/Sources/CmuxSettingsJSONPathSupport.swift b/Sources/CmuxSettingsJSONPathSupport.swift index 8f32a9a09652..dcb0a8f9f72a 100644 --- a/Sources/CmuxSettingsJSONPathSupport.swift +++ b/Sources/CmuxSettingsJSONPathSupport.swift @@ -220,6 +220,10 @@ enum AppSettingsFileMapping { jsonKey: "warnBeforeClosingWorkspace", defaultsKey: app.warnBeforeClosingWorkspace.userDefaultsKey ), + .init( + jsonKey: "alwaysConfirmWorkspaceClose", + defaultsKey: app.alwaysConfirmWorkspaceClose.userDefaultsKey + ), .init( jsonKey: "warnBeforeClosingWindow", defaultsKey: app.warnBeforeClosingWindow.userDefaultsKey diff --git a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift index 878bccdd82fb..d36bb45c3ed1 100644 --- a/Sources/CommandPalette/CommandPaletteSettingsToggle.swift +++ b/Sources/CommandPalette/CommandPaletteSettingsToggle.swift @@ -506,6 +506,9 @@ enum CommandPaletteSettingsToggleCommands { CommandPaletteSettingToggleDescriptor( userFacing: SettingCatalog().app.warnBeforeClosingWorkspace ), + CommandPaletteSettingToggleDescriptor( + userFacing: SettingCatalog().app.alwaysConfirmWorkspaceClose + ), CommandPaletteSettingToggleDescriptor( userFacing: SettingCatalog().app.warnBeforeClosingWindow ), diff --git a/Sources/KeyboardShortcutSettingsFileStore+Template.swift b/Sources/KeyboardShortcutSettingsFileStore+Template.swift index 398b1cec0dce..bf43afbf9ea5 100644 --- a/Sources/KeyboardShortcutSettingsFileStore+Template.swift +++ b/Sources/KeyboardShortcutSettingsFileStore+Template.swift @@ -87,6 +87,7 @@ extension CmuxSettingsFileStore { "warnBeforeClosingTabXButton": AppCatalogSection().warnBeforeClosingTabXButton.defaultValue, "warnBeforeClosingAgentSession": AppCatalogSection().warnBeforeClosingAgentSession.defaultValue, "warnBeforeClosingWorkspace": AppCatalogSection().warnBeforeClosingWorkspace.defaultValue, + "alwaysConfirmWorkspaceClose": AppCatalogSection().alwaysConfirmWorkspaceClose.defaultValue, "warnBeforeClosingWindow": AppCatalogSection().warnBeforeClosingWindow.defaultValue, "hideTabCloseButton": AppCatalogSection().hideTabCloseButton.defaultValue, "tabBarVisibility": AppCatalogSection().tabBarVisibility.defaultValue.rawValue, diff --git a/Sources/SettingsSearchAliases.swift b/Sources/SettingsSearchAliases.swift index c31bbcaad348..1e7b5f73f5e9 100644 --- a/Sources/SettingsSearchAliases.swift +++ b/Sources/SettingsSearchAliases.swift @@ -111,6 +111,10 @@ enum SettingsSearchAliasIndex { "settings.search.alias.setting.app.warn-before-closing-workspace", defaultValue: "app.warnBeforeClosingWorkspace close workspace confirmation command-shift-w cmd-shift-w running process agent" ), + "app:always-confirm-workspace-close": localized( + "settings.search.alias.setting.app.always-confirm-workspace-close", + defaultValue: "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + ), "app:warn-before-closing-window": localized( "settings.search.alias.setting.app.warn-before-closing-window", defaultValue: "app.warnBeforeClosingWindow close window confirmation command-control-w cmd-ctrl-w running process" diff --git a/Sources/SettingsSearchIndex.swift b/Sources/SettingsSearchIndex.swift index 39ae7702a50f..1bc9d4aab2c7 100644 --- a/Sources/SettingsSearchIndex.swift +++ b/Sources/SettingsSearchIndex.swift @@ -82,6 +82,12 @@ enum SettingsSearchIndex { String(localized: "settings.app.warnBeforeClosingWorkspace", defaultValue: "Warn Before Closing Workspace"), "cmd shift w close workspace confirmation running process" ), + setting( + .app, + "always-confirm-workspace-close", + String(localized: "settings.app.alwaysConfirmWorkspaceClose", defaultValue: "Always Confirm Closing Workspaces"), + "always confirm close workspace idle protect confirmation" + ), setting( .app, "warn-before-closing-window", @@ -295,6 +301,7 @@ enum SettingsSearchIndex { "app.warnBeforeClosingAgentSession": settingID(for: .app, idSuffix: "warn-before-closing-agent-session"), "app.warnBeforeClosingTabXButton": settingID(for: .app, idSuffix: "warn-before-closing-tab-x-button"), "app.warnBeforeClosingWorkspace": settingID(for: .app, idSuffix: "warn-before-closing-workspace"), + "app.alwaysConfirmWorkspaceClose": settingID(for: .app, idSuffix: "always-confirm-workspace-close"), "app.warnBeforeClosingWindow": settingID(for: .app, idSuffix: "warn-before-closing-window"), "app.hideTabCloseButton": settingID(for: .app, idSuffix: "hide-tab-close-button"), "app.renameSelectsExistingName": settingID(for: .app, idSuffix: "rename-selects-name"), diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index e226007ffd4f..9fb30411a747 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -2964,6 +2964,7 @@ class TabManager: ObservableObject { && AppDelegate.shared?.existingWindowDock(for: self)?.needsConfirmClose() == true let hasActiveProcess = plan.workspaces.contains(where: workspaceNeedsConfirmClose) || windowDockNeedsConfirmation + let alwaysConfirm = CloseTabWarningStore(defaults: closeTabWarningDefaults).alwaysConfirmsWorkspaceClose let showsBatchConfirmation: Bool var dontAskAgain: CloseWarningKinds if containsPinned { @@ -2975,12 +2976,16 @@ class TabManager: ObservableObject { // policy. The tab warning must not suppress this prompt. showsBatchConfirmation = CloseTabWarningStore(defaults: closeTabWarningDefaults).warnsBeforeClosingWindow || hasActiveProcess + || alwaysConfirm dontAskAgain = .window + if alwaysConfirm { dontAskAgain.insert(.alwaysConfirmWorkspace) } if hasActiveProcess { dontAskAgain.insert(.safety) } } else { showsBatchConfirmation = shouldConfirmWorkspaceClose(requiresConfirmation: true, source: .tabClose) || hasActiveProcess + || alwaysConfirm dontAskAgain = .workspace + if alwaysConfirm { dontAskAgain.insert(.alwaysConfirmWorkspace) } if hasActiveProcess { dontAskAgain.insert(.safety) } } if showsBatchConfirmation { @@ -3271,13 +3276,16 @@ class TabManager: ObservableObject { && AppDelegate.shared?.existingWindowDock(for: self)?.needsConfirmClose() == true let needsCloseConfirmation = workspaceNeedsConfirmClose(workspace) || windowDockNeedsConfirmation + let alwaysConfirm = CloseTabWarningStore(defaults: closeTabWarningDefaults).alwaysConfirmsWorkspaceClose let showsCloseConfirmation = requiresConfirmation && (needsCloseConfirmation + || alwaysConfirm || shouldConfirmWorkspaceClose( requiresConfirmation: needsCloseConfirmation, source: source )) var dontAskAgain: CloseWarningKinds = .workspace + if alwaysConfirm { dontAskAgain.insert(.alwaysConfirmWorkspace) } if needsCloseConfirmation { dontAskAgain.insert(.safety) } if showsCloseConfirmation, !confirmClose( diff --git a/cmuxTests/TabManagerUnitTests.swift b/cmuxTests/TabManagerUnitTests.swift index 33bf1fbe34d9..f7a124af54f2 100644 --- a/cmuxTests/TabManagerUnitTests.swift +++ b/cmuxTests/TabManagerUnitTests.swift @@ -1835,6 +1835,84 @@ final class TabManagerWarnBeforeClosingWorkspaceTests: XCTestCase { ) XCTAssertTrue(manager.tabs.contains(where: { $0.id == workspace.id })) } + + private func setAlwaysConfirm(_ manager: TabManager, _ isEnabled: Bool) { + manager.closeTabWarningDefaults.set(isEnabled, forKey: AppCatalogSection().alwaysConfirmWorkspaceClose.userDefaultsKey) + } + + func testIdleWorkspaceClosesWithoutPromptByDefault() { + let manager = makeManager(warnBeforeClosingWorkspace: nil) + let workspace = manager.tabs[1] + + var promptCount = 0 + manager.confirmCloseHandler = { _, _, _ in + promptCount += 1 + return false + } + + XCTAssertTrue(manager.closeWorkspaceWithConfirmation(workspace)) + XCTAssertEqual(promptCount, 0) + } + + func testAlwaysConfirmAsksBeforeClosingIdleWorkspaceEvenWithWarningDisabled() { + let manager = makeManager(warnBeforeClosingWorkspace: false) + setAlwaysConfirm(manager, true) + let workspace = manager.tabs[1] + + var prompts: [String] = [] + manager.confirmCloseHandler = { title, _, _ in + prompts.append(title) + return false + } + + XCTAssertFalse(manager.closeWorkspaceWithConfirmation(workspace)) + XCTAssertEqual(prompts, [closeWorkspaceTitle]) + XCTAssertTrue(manager.tabs.contains(where: { $0.id == workspace.id })) + } + + func testAlwaysConfirmAsksBeforeClosingSeveralIdleWorkspaces() { + let manager = makeManager(warnBeforeClosingWorkspace: false) + setAlwaysConfirm(manager, true) + let targets = [manager.tabs[0].id, manager.tabs[1].id] + let originalIds = manager.tabs.map(\.id) + + var promptCount = 0 + manager.confirmCloseHandler = { _, _, _ in + promptCount += 1 + return false + } + + manager.closeWorkspacesWithConfirmation(targets, allowPinned: true) + + XCTAssertEqual(promptCount, 1) + XCTAssertEqual(manager.tabs.map(\.id), originalIds) + } + + func testDontAskAgainOnAlwaysConfirmPromptTurnsTheSettingOff() { + let manager = makeManager(warnBeforeClosingWorkspace: nil) + setAlwaysConfirm(manager, true) + let first = manager.tabs[0] + let second = manager.tabs[1] + + var promptCount = 0 + var offered: [CloseWarningKinds] = [] + manager.confirmCloseHandler = { _, _, _ in + promptCount += 1 + return true + } + manager.confirmCloseDontAskAgainHandler = { kinds in + offered.append(kinds) + return true + } + + XCTAssertTrue(manager.closeWorkspaceWithConfirmation(first)) + XCTAssertEqual(offered, [[.workspace, .alwaysConfirmWorkspace]]) + XCTAssertFalse(AppCatalogSection().alwaysConfirmWorkspaceClose.value(in: manager.closeTabWarningDefaults)) + + drainMainQueue() + XCTAssertTrue(manager.closeWorkspaceWithConfirmation(second)) + XCTAssertEqual(promptCount, 1, "An idle workspace closes without asking once the setting is off") + } } @MainActor diff --git a/skills/cmux-settings/references/all-keys.md b/skills/cmux-settings/references/all-keys.md index 4201ad337169..885a023e00ec 100644 --- a/skills/cmux-settings/references/all-keys.md +++ b/skills/cmux-settings/references/all-keys.md @@ -42,6 +42,7 @@ General app preferences from Settings > App. | `app.confirmQuit` | `"always"` or `"dirty-only"` or `"never"` | `"always"` | Control when cmux asks for confirmation before quitting. DEV builds always quit immediately regardless of this setting. Legacy app.warnBeforeQuit is still accepted as a boolean fallback. | | `app.warnBeforeClosingTabXButton` | boolean | `false` | Show a confirmation before closing a tab with the tab close button. | | `app.warnBeforeClosingWorkspace` | boolean | `true` | Show a confirmation before closing a workspace with a running process, or several workspaces at once. Pinned workspaces still ask when this is off. | +| `app.alwaysConfirmWorkspaceClose` | boolean | `false` | Ask before every workspace close, even when nothing in it is running. Overrides warnBeforeClosingWorkspace for workspace closes. | | `app.warnBeforeClosingWindow` | boolean | `true` | Show a confirmation before closing a window with a running process, or all of a window's workspaces at once. | | `app.hideTabCloseButton` | boolean | `false` | Hide tab close buttons in the pane tab bar. | | `app.tabBarVisibility` | `"always"` or `"multiple-tabs"` | `"always"` | Control when each pane's surface tab bar is shown. `"always"` shows it even when the pane has a single tab; `"multiple-tabs"` hides the bar until the pane has two or more tabs. Minimal mode (app.minimalMode) always shows it, because there the top tab bar is the titlebar row. | diff --git a/web/data/cmux.schema.json b/web/data/cmux.schema.json index 8681e251bb65..f4d47fea65f8 100644 --- a/web/data/cmux.schema.json +++ b/web/data/cmux.schema.json @@ -861,6 +861,11 @@ "default": true, "description": "Show a confirmation before closing a workspace with a running process, or several workspaces at once. Pinned workspaces still ask when this is off." }, + "alwaysConfirmWorkspaceClose": { + "type": "boolean", + "default": false, + "description": "Ask before every workspace close, even when nothing in it is running. Overrides warnBeforeClosingWorkspace for workspace closes." + }, "warnBeforeClosingWindow": { "type": "boolean", "default": true, From 26a1c7d3691ec72bd9d43728c7606e115cfb52ca Mon Sep 17 00:00:00 2001 From: tk1475 <118763009+tk1475@users.noreply.github.com> Date: Sun, 11 Oct 2026 14:00:30 +0500 Subject: [PATCH 2/2] Scope "Don't ask again" to always-confirm, localize alias keywords When a workspace close prompt shows only because of app.alwaysConfirmWorkspaceClose, "Don't ask again" now turns off just that setting and leaves warnBeforeClosingWorkspace and warnBeforeClosingWindow alone. When the ordinary warning would have asked too, it turns off both. Translate the search alias keywords per locale. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01FsxA6uRfPk8v6h673Dx5N1 --- Resources/Localizable.xcstrings | 18 ++++----- Sources/TabManager.swift | 61 +++++++++++++++++++---------- cmuxTests/TabManagerUnitTests.swift | 36 ++++++++++++++++- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 071dabaa3df9..4e327dcb0537 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -567357,55 +567357,55 @@ "ar": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation التأكيد دائمًا إغلاق مساحة العمل خامل حماية" } }, "de": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation immer bestätigen arbeitsbereich schließen inaktiv schützen bestätigung" } }, "es": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation confirmar siempre cerrar espacio de trabajo inactivo proteger confirmación" } }, "fr": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation toujours confirmer fermer espace de travail inactif protéger confirmation" } }, "ja": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation 常に確認 ワークスペースを閉じる アイドル 保護" } }, "ko": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation 항상 확인 워크스페이스 닫기 유휴 보호" } }, "uk": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation завжди підтверджувати закрити робочу область неактивна захист" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation 始终确认 关闭工作区 空闲 保护" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation" + "value": "app.alwaysConfirmWorkspaceClose always confirm close workspace idle protect confirmation 一律確認 關閉工作區 閒置 保護" } } } diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 9fb30411a747..f93d05744809 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -2966,7 +2966,7 @@ class TabManager: ObservableObject { || windowDockNeedsConfirmation let alwaysConfirm = CloseTabWarningStore(defaults: closeTabWarningDefaults).alwaysConfirmsWorkspaceClose let showsBatchConfirmation: Bool - var dontAskAgain: CloseWarningKinds + let dontAskAgain: CloseWarningKinds if containsPinned { showsBatchConfirmation = CloseTabWarningStore(defaults: closeTabWarningDefaults) .shouldConfirmCloseIncludingSafety(requiresConfirmation: true, source: .shortcut) @@ -2974,19 +2974,23 @@ class TabManager: ObservableObject { } else if plan.willCloseWindow { // A batch that closes the whole window follows the window warning // policy. The tab warning must not suppress this prompt. - showsBatchConfirmation = CloseTabWarningStore(defaults: closeTabWarningDefaults).warnsBeforeClosingWindow + let windowWarns = CloseTabWarningStore(defaults: closeTabWarningDefaults).warnsBeforeClosingWindow || hasActiveProcess - || alwaysConfirm - dontAskAgain = .window - if alwaysConfirm { dontAskAgain.insert(.alwaysConfirmWorkspace) } - if hasActiveProcess { dontAskAgain.insert(.safety) } + showsBatchConfirmation = windowWarns || alwaysConfirm + dontAskAgain = Self.workspaceCloseDontAskAgain( + ordinary: hasActiveProcess ? [.window, .safety] : .window, + ordinaryWarns: windowWarns, + alwaysConfirm: alwaysConfirm + ) } else { - showsBatchConfirmation = shouldConfirmWorkspaceClose(requiresConfirmation: true, source: .tabClose) + let workspaceWarns = shouldConfirmWorkspaceClose(requiresConfirmation: true, source: .tabClose) || hasActiveProcess - || alwaysConfirm - dontAskAgain = .workspace - if alwaysConfirm { dontAskAgain.insert(.alwaysConfirmWorkspace) } - if hasActiveProcess { dontAskAgain.insert(.safety) } + showsBatchConfirmation = workspaceWarns || alwaysConfirm + dontAskAgain = Self.workspaceCloseDontAskAgain( + ordinary: hasActiveProcess ? [.workspace, .safety] : .workspace, + ordinaryWarns: workspaceWarns, + alwaysConfirm: alwaysConfirm + ) } if showsBatchConfirmation { guard confirmClose( @@ -3277,16 +3281,18 @@ class TabManager: ObservableObject { let needsCloseConfirmation = workspaceNeedsConfirmClose(workspace) || windowDockNeedsConfirmation let alwaysConfirm = CloseTabWarningStore(defaults: closeTabWarningDefaults).alwaysConfirmsWorkspaceClose + let warnsWithoutAlwaysConfirm = needsCloseConfirmation + || shouldConfirmWorkspaceClose( + requiresConfirmation: needsCloseConfirmation, + source: source + ) let showsCloseConfirmation = requiresConfirmation - && (needsCloseConfirmation - || alwaysConfirm - || shouldConfirmWorkspaceClose( - requiresConfirmation: needsCloseConfirmation, - source: source - )) - var dontAskAgain: CloseWarningKinds = .workspace - if alwaysConfirm { dontAskAgain.insert(.alwaysConfirmWorkspace) } - if needsCloseConfirmation { dontAskAgain.insert(.safety) } + && (warnsWithoutAlwaysConfirm || alwaysConfirm) + let dontAskAgain = Self.workspaceCloseDontAskAgain( + ordinary: needsCloseConfirmation ? [.workspace, .safety] : .workspace, + ordinaryWarns: warnsWithoutAlwaysConfirm, + alwaysConfirm: alwaysConfirm + ) if showsCloseConfirmation, !confirmClose( title: String(localized: "dialog.closeWorkspace.title", defaultValue: "Close workspace?"), @@ -3341,6 +3347,21 @@ class TabManager: ObservableObject { && shouldConfirmConfiguredClose(requiresConfirmation: requiresConfirmation, source: source) } + /// What "Don't ask again" turns off on a workspace close prompt. When the + /// prompt shows only because of `app.alwaysConfirmWorkspaceClose`, it turns + /// off just that setting and leaves the ordinary workspace and window + /// warnings alone. When the ordinary warning would have asked too, it turns + /// off both, so the same close does not ask next time. + static func workspaceCloseDontAskAgain( + ordinary: CloseWarningKinds, + ordinaryWarns: Bool, + alwaysConfirm: Bool + ) -> CloseWarningKinds { + guard alwaysConfirm else { return ordinary } + guard ordinaryWarns else { return .alwaysConfirmWorkspace } + return ordinary.union(.alwaysConfirmWorkspace) + } + /// Applies the user-configured warning toggles without turning the /// `requiresConfirmation` argument into a safety warning. Workspace and /// batch gates use this seam before they have established whether any diff --git a/cmuxTests/TabManagerUnitTests.swift b/cmuxTests/TabManagerUnitTests.swift index f7a124af54f2..7443580a8b32 100644 --- a/cmuxTests/TabManagerUnitTests.swift +++ b/cmuxTests/TabManagerUnitTests.swift @@ -1888,6 +1888,39 @@ final class TabManagerWarnBeforeClosingWorkspaceTests: XCTestCase { XCTAssertEqual(manager.tabs.map(\.id), originalIds) } + func testDontAskAgainWhenOrdinaryWarningAlsoAppliesTurnsBothOff() { + let manager = makeManager(warnBeforeClosingWorkspace: nil) + setAlwaysConfirm(manager, true) + let workspace = manager.tabs[1] + markRunningProcess(workspace) + + var offered: [CloseWarningKinds] = [] + manager.confirmCloseHandler = { _, _, _ in false } + manager.confirmCloseDontAskAgainHandler = { kinds in + offered.append(kinds) + return true + } + + XCTAssertFalse(manager.closeWorkspaceWithConfirmation(workspace)) + XCTAssertEqual(offered, [[.workspace, .safety, .alwaysConfirmWorkspace]]) + } + + func testDontAskAgainOnIdleWindowBatchKeepsWindowWarning() { + let manager = makeManager(warnBeforeClosingWorkspace: nil) + setAlwaysConfirm(manager, true) + manager.closeTabWarningDefaults.set(false, forKey: AppCatalogSection().warnBeforeClosingWindow.userDefaultsKey) + + var offered: [CloseWarningKinds] = [] + manager.confirmCloseHandler = { _, _, _ in false } + manager.confirmCloseDontAskAgainHandler = { kinds in + offered.append(kinds) + return false + } + + manager.closeWorkspacesWithConfirmation(manager.tabs.map(\.id), allowPinned: true) + XCTAssertEqual(offered, [[.alwaysConfirmWorkspace]]) + } + func testDontAskAgainOnAlwaysConfirmPromptTurnsTheSettingOff() { let manager = makeManager(warnBeforeClosingWorkspace: nil) setAlwaysConfirm(manager, true) @@ -1906,8 +1939,9 @@ final class TabManagerWarnBeforeClosingWorkspaceTests: XCTestCase { } XCTAssertTrue(manager.closeWorkspaceWithConfirmation(first)) - XCTAssertEqual(offered, [[.workspace, .alwaysConfirmWorkspace]]) + XCTAssertEqual(offered, [[.alwaysConfirmWorkspace]], "An idle prompt only offers to turn off the setting that caused it") XCTAssertFalse(AppCatalogSection().alwaysConfirmWorkspaceClose.value(in: manager.closeTabWarningDefaults)) + XCTAssertTrue(AppCatalogSection().warnBeforeClosingWorkspace.value(in: manager.closeTabWarningDefaults)) drainMainQueue() XCTAssertTrue(manager.closeWorkspaceWithConfirmation(second))