Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5064,6 +5064,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
}

private func confirmCloseMainWindow(_ window: NSWindow) -> Bool {
guard CloseWindowSettings.isEnabled() else { return true }

#if DEBUG
if let debugCloseMainWindowConfirmationHandler {
return debugCloseMainWindowConfirmationHandler(window)
Expand Down
42 changes: 27 additions & 15 deletions Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2827,14 +2827,16 @@ class TabManager: ObservableObject {
func closeOtherTabsInFocusedPaneWithConfirmation() {
guard let plan = closeOtherTabsInFocusedPanePlan() else { return }

let count = plan.panelIds.count
let titleLines = plan.titles.map { "• \($0)" }.joined(separator: "\n")
let message = "This is about to close \(count) tab\(count == 1 ? "" : "s") in this pane:\n\(titleLines)"
guard confirmClose(
title: "Close other tabs?",
message: message,
acceptCmdD: false
) else { return }
if BatchCloseSettings.isEnabled() {
let count = plan.panelIds.count
let titleLines = plan.titles.map { "• \($0)" }.joined(separator: "\n")
let message = "This is about to close \(count) tab\(count == 1 ? "" : "s") in this pane:\n\(titleLines)"
guard confirmClose(
title: "Close other tabs?",
message: message,
acceptCmdD: false
) else { return }
}

for panelId in plan.panelIds {
_ = plan.workspace.closePanel(panelId, force: true)
Expand All @@ -2861,7 +2863,7 @@ class TabManager: ObservableObject {

@discardableResult
func closeWorkspaceWithConfirmation(_ workspace: Workspace) -> Bool {
if workspace.isPinned {
if workspace.isPinned && ClosePinnedWorkspaceSettings.isEnabled() {
guard confirmClose(
title: String(localized: "dialog.closePinnedWorkspace.title", defaultValue: "Close pinned workspace?"),
message: String(
Expand Down Expand Up @@ -2899,15 +2901,25 @@ class TabManager: ObservableObject {
}

let plan = closeWorkspacesPlan(for: workspaces)
guard confirmClose(
title: plan.title,
message: plan.message,
acceptCmdD: plan.acceptCmdD
) else { return }
let batchConfirmed: Bool
if BatchCloseSettings.isEnabled() {
guard confirmClose(
title: plan.title,
message: plan.message,
acceptCmdD: plan.acceptCmdD
) else { return }
batchConfirmed = true
} else {
batchConfirmed = false
}

for workspace in plan.workspaces {
guard tabs.contains(where: { $0.id == workspace.id }) else { continue }
closeWorkspaceIfRunningProcess(workspace, requiresConfirmation: false)
if batchConfirmed {
closeWorkspaceIfRunningProcess(workspace, requiresConfirmation: false)
} else {
_ = closeWorkspaceWithConfirmation(workspace)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Non-batch multi-close now bypasses running-process confirmation for pinned workspaces by routing through a helper that forces requiresConfirmation: false after pinned confirmation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/TabManager.swift, line 2921:

<comment>Non-batch multi-close now bypasses running-process confirmation for pinned workspaces by routing through a helper that forces `requiresConfirmation: false` after pinned confirmation.</comment>

<file context>
@@ -2915,7 +2915,11 @@ class TabManager: ObservableObject {
+            if batchConfirmed {
+                closeWorkspaceIfRunningProcess(workspace, requiresConfirmation: false)
+            } else {
+                _ = closeWorkspaceWithConfirmation(workspace)
+            }
         }
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requiresConfirmation: false after pinned-workspace confirmation in closeWorkspaceWithConfirmation is a pre-existing pattern from before this PR. The only change on that line was adding && ClosePinnedWorkspaceSettings.isEnabled() to the condition. The running-process bypass after pinned confirm predates this PR and is out of scope — can be addressed separately if desired.

}
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6735,7 +6735,7 @@ final class Workspace: Identifiable, ObservableObject {
case .promptIdle:
return false
case .commandRunning:
return true
return CloseRunningProcessSettings.isEnabled()
case .unknown:
return fallbackNeedsConfirmClose
}
Expand Down
124 changes: 124 additions & 0 deletions Sources/cmuxApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3768,6 +3768,70 @@ enum QuitWarningSettings {
}
}

enum CloseRunningProcessSettings {
static let enabledKey = "confirmCloseRunningProcess"
static let defaultEnabled = true

static func isEnabled(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: enabledKey) == nil {
return defaultEnabled
}
return defaults.bool(forKey: enabledKey)
}

static func setEnabled(_ isEnabled: Bool, defaults: UserDefaults = .standard) {
defaults.set(isEnabled, forKey: enabledKey)
}
}

enum ClosePinnedWorkspaceSettings {
static let enabledKey = "confirmClosePinnedWorkspace"
static let defaultEnabled = true

static func isEnabled(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: enabledKey) == nil {
return defaultEnabled
}
return defaults.bool(forKey: enabledKey)
}

static func setEnabled(_ isEnabled: Bool, defaults: UserDefaults = .standard) {
defaults.set(isEnabled, forKey: enabledKey)
}
}

enum CloseWindowSettings {
static let enabledKey = "confirmCloseWindow"
static let defaultEnabled = true

static func isEnabled(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: enabledKey) == nil {
return defaultEnabled
}
return defaults.bool(forKey: enabledKey)
}

static func setEnabled(_ isEnabled: Bool, defaults: UserDefaults = .standard) {
defaults.set(isEnabled, forKey: enabledKey)
}
}

enum BatchCloseSettings {
static let enabledKey = "confirmBatchClose"
static let defaultEnabled = true

static func isEnabled(defaults: UserDefaults = .standard) -> Bool {
if defaults.object(forKey: enabledKey) == nil {
return defaultEnabled
}
return defaults.bool(forKey: enabledKey)
}

static func setEnabled(_ isEnabled: Bool, defaults: UserDefaults = .standard) {
defaults.set(isEnabled, forKey: enabledKey)
}
}

enum CommandPaletteRenameSelectionSettings {
static let selectAllOnFocusKey = "commandPalette.renameSelectAllOnFocus"
static let defaultSelectAllOnFocus = true
Expand Down Expand Up @@ -3932,6 +3996,10 @@ struct SettingsView: View {
@AppStorage(NotificationPaneFlashSettings.enabledKey) private var notificationPaneFlashEnabled = NotificationPaneFlashSettings.defaultEnabled
@AppStorage(MenuBarExtraSettings.showInMenuBarKey) private var showMenuBarExtra = MenuBarExtraSettings.defaultShowInMenuBar
@AppStorage(QuitWarningSettings.warnBeforeQuitKey) private var warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit
@AppStorage(CloseRunningProcessSettings.enabledKey) private var confirmCloseRunningProcess = CloseRunningProcessSettings.defaultEnabled
@AppStorage(ClosePinnedWorkspaceSettings.enabledKey) private var confirmClosePinnedWorkspace = ClosePinnedWorkspaceSettings.defaultEnabled
@AppStorage(CloseWindowSettings.enabledKey) private var confirmCloseWindow = CloseWindowSettings.defaultEnabled
@AppStorage(BatchCloseSettings.enabledKey) private var confirmBatchClose = BatchCloseSettings.defaultEnabled
@AppStorage(CommandPaletteRenameSelectionSettings.selectAllOnFocusKey)
private var commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus
@AppStorage(CommandPaletteSwitcherSearchSettings.searchAllSurfacesKey)
Expand Down Expand Up @@ -4784,6 +4852,58 @@ struct SettingsView: View {

SettingsCardDivider()

SettingsCardRow(
String(localized: "settings.app.confirmCloseRunningProcess", defaultValue: "Confirm Close Running Process"),
subtitle: confirmCloseRunningProcess
? String(localized: "settings.app.confirmCloseRunningProcess.subtitleOn", defaultValue: "Show a confirmation before closing a workspace with a running command.")
: String(localized: "settings.app.confirmCloseRunningProcess.subtitleOff", defaultValue: "Close workspaces without confirmation, even with running commands.")
) {
Toggle("", isOn: $confirmCloseRunningProcess)
.labelsHidden()
.controlSize(.small)
}

SettingsCardDivider()

SettingsCardRow(
String(localized: "settings.app.confirmClosePinnedWorkspace", defaultValue: "Confirm Close Pinned Workspace"),
subtitle: confirmClosePinnedWorkspace
? String(localized: "settings.app.confirmClosePinnedWorkspace.subtitleOn", defaultValue: "Show a confirmation before closing a pinned workspace.")
: String(localized: "settings.app.confirmClosePinnedWorkspace.subtitleOff", defaultValue: "Close pinned workspaces without extra confirmation.")
) {
Toggle("", isOn: $confirmClosePinnedWorkspace)
.labelsHidden()
.controlSize(.small)
}

SettingsCardDivider()

SettingsCardRow(
String(localized: "settings.app.confirmCloseWindow", defaultValue: "Confirm Close Window"),
subtitle: confirmCloseWindow
? String(localized: "settings.app.confirmCloseWindow.subtitleOn", defaultValue: "Show a confirmation before closing a window with workspaces.")
: String(localized: "settings.app.confirmCloseWindow.subtitleOff", defaultValue: "Close windows without confirmation.")
) {
Toggle("", isOn: $confirmCloseWindow)
.labelsHidden()
.controlSize(.small)
}

SettingsCardDivider()

SettingsCardRow(
String(localized: "settings.app.confirmBatchClose", defaultValue: "Confirm Batch Close"),
subtitle: confirmBatchClose
? String(localized: "settings.app.confirmBatchClose.subtitleOn", defaultValue: "Show a confirmation before closing multiple tabs or workspaces at once.")
: String(localized: "settings.app.confirmBatchClose.subtitleOff", defaultValue: "Close multiple tabs or workspaces without confirmation.")
) {
Toggle("", isOn: $confirmBatchClose)
.labelsHidden()
Comment on lines +4855 to +4901
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 New localization keys missing from Localizable.xcstrings

All 12 new String(localized:defaultValue:) keys (settings.app.confirmCloseRunningProcess, .subtitleOn, .subtitleOff, and equivalents for confirmClosePinnedWorkspace, confirmCloseWindow, confirmBatchClose) are absent from Resources/Localizable.xcstrings. Per the project policy, every UI string key must be registered in the catalog with translations for all supported languages — including Japanese. Comparable existing keys (settings.app.warnBeforeQuit, settings.app.telemetry, etc.) all have full catalog entries. Until these are added, Japanese users will always see English text for all 4 new toggle rows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The String(localized:defaultValue:) keys are auto-generated by Xcode's string catalog extraction on build. Japanese translations would be a separate localization effort. Out of scope for this feature PR.

.controlSize(.small)
}

SettingsCardDivider()

SettingsCardRow(
String(localized: "settings.app.renameSelectsName", defaultValue: "Rename Selects Existing Name"),
subtitle: commandPaletteRenameSelectAllOnFocus
Expand Down Expand Up @@ -5910,6 +6030,10 @@ struct SettingsView: View {
notificationPaneFlashEnabled = NotificationPaneFlashSettings.defaultEnabled
showMenuBarExtra = MenuBarExtraSettings.defaultShowInMenuBar
warnBeforeQuitShortcut = QuitWarningSettings.defaultWarnBeforeQuit
confirmCloseRunningProcess = CloseRunningProcessSettings.defaultEnabled
confirmClosePinnedWorkspace = ClosePinnedWorkspaceSettings.defaultEnabled
confirmCloseWindow = CloseWindowSettings.defaultEnabled
confirmBatchClose = BatchCloseSettings.defaultEnabled
commandPaletteRenameSelectAllOnFocus = CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus
commandPaletteSearchAllSurfaces = CommandPaletteSwitcherSearchSettings.defaultSearchAllSurfaces
ShortcutHintDebugSettings.resetVisibilityDefaults()
Expand Down