Skip to content
Merged
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
4 changes: 4 additions & 0 deletions SupacodeSettingsFeature/Reducer/SettingsFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public struct SettingsFeature {
public var agentPresenceBadgesEnabled: Bool
public var autoUpdateAgentIntegrationsEnabled: Bool
public var confirmQuitMode: ConfirmQuitMode
public var confirmCloseSurface: Bool
public var terminateSessionsOnQuit: Bool
public var remoteSessionPersistenceEnabled: Bool
public var appVisibility: AppVisibility
Expand Down Expand Up @@ -131,6 +132,7 @@ public struct SettingsFeature {
agentPresenceBadgesEnabled = settings.agentPresenceBadgesEnabled
autoUpdateAgentIntegrationsEnabled = settings.autoUpdateAgentIntegrationsEnabled
confirmQuitMode = settings.confirmQuitMode
confirmCloseSurface = settings.confirmCloseSurface
terminateSessionsOnQuit = settings.terminateSessionsOnQuit
remoteSessionPersistenceEnabled = settings.remoteSessionPersistenceEnabled
appVisibility = settings.appVisibility
Expand Down Expand Up @@ -174,6 +176,7 @@ public struct SettingsFeature {
agentPresenceBadgesEnabled: agentPresenceBadgesEnabled,
autoUpdateAgentIntegrationsEnabled: autoUpdateAgentIntegrationsEnabled,
confirmQuitMode: confirmQuitMode,
confirmCloseSurface: confirmCloseSurface,
terminateSessionsOnQuit: terminateSessionsOnQuit,
remoteSessionPersistenceEnabled: remoteSessionPersistenceEnabled,
appVisibility: appVisibility
Expand Down Expand Up @@ -311,6 +314,7 @@ public struct SettingsFeature {
state.agentPresenceBadgesEnabled = normalizedSettings.agentPresenceBadgesEnabled
state.autoUpdateAgentIntegrationsEnabled = normalizedSettings.autoUpdateAgentIntegrationsEnabled
state.confirmQuitMode = normalizedSettings.confirmQuitMode
state.confirmCloseSurface = normalizedSettings.confirmCloseSurface
state.terminateSessionsOnQuit = normalizedSettings.terminateSessionsOnQuit
state.remoteSessionPersistenceEnabled = normalizedSettings.remoteSessionPersistenceEnabled
state.appVisibility = normalizedSettings.appVisibility
Expand Down
4 changes: 4 additions & 0 deletions SupacodeSettingsFeature/Views/AppearanceSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ public struct AppearanceSettingsView: View {
Text("Hide Tab Bar for Single Tab")
Text("Automatically hides the tab bar when only one tab is open.")
}
Toggle(isOn: $store.confirmCloseSurface) {
Text("Confirm before Closing Terminals")
Text("Ask before closing a terminal that has a running process.")
}
Picker(selection: $store.automatedActionPolicy.sending(\.setAutomatedActionPolicy)) {
ForEach(AutomatedActionPolicy.allCases, id: \.self) { policy in
Text(policy.displayName).tag(policy)
Expand Down
9 changes: 9 additions & 0 deletions SupacodeSettingsShared/Models/GlobalSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
/// entries from earlier wire-protocol revisions).
public var autoUpdateAgentIntegrationsEnabled: Bool
public var confirmQuitMode: ConfirmQuitMode
/// When true, user-initiated closes ask for confirmation when a terminal
/// surface has foreground work that Ghostty considers unsafe to interrupt.
public var confirmCloseSurface: Bool
/// When true, quitting Supacode also closes every terminal tab and tears
/// down zmx sessions, local and host-side, so nothing keeps running in the
/// background. Default off because persistence is the headline feature.
Expand Down Expand Up @@ -138,6 +141,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
agentPresenceBadgesEnabled: true,
autoUpdateAgentIntegrationsEnabled: true,
confirmQuitMode: .auto,
confirmCloseSurface: true,
terminateSessionsOnQuit: false,
remoteSessionPersistenceEnabled: true,
appVisibility: .dock
Expand Down Expand Up @@ -176,6 +180,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
agentPresenceBadgesEnabled: Bool = true,
autoUpdateAgentIntegrationsEnabled: Bool = true,
confirmQuitMode: ConfirmQuitMode = .auto,
confirmCloseSurface: Bool = true,
terminateSessionsOnQuit: Bool = false,
remoteSessionPersistenceEnabled: Bool = true,
appVisibility: AppVisibility = .dock
Expand Down Expand Up @@ -212,6 +217,7 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
self.agentPresenceBadgesEnabled = agentPresenceBadgesEnabled
self.autoUpdateAgentIntegrationsEnabled = autoUpdateAgentIntegrationsEnabled
self.confirmQuitMode = confirmQuitMode
self.confirmCloseSurface = confirmCloseSurface
self.terminateSessionsOnQuit = terminateSessionsOnQuit
self.remoteSessionPersistenceEnabled = remoteSessionPersistenceEnabled
self.appVisibility = appVisibility
Expand Down Expand Up @@ -376,6 +382,9 @@ public nonisolated struct GlobalSettings: Codable, Equatable, Sendable {
} else {
confirmQuitMode = Self.default.confirmQuitMode
}
confirmCloseSurface =
try container.decodeIfPresent(Bool.self, forKey: .confirmCloseSurface)
?? Self.default.confirmCloseSurface
terminateSessionsOnQuit =
try container.decodeIfPresent(Bool.self, forKey: .terminateSessionsOnQuit)
?? Self.default.terminateSessionsOnQuit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ private let terminalLogger = SupaLogger("Terminal")
@Observable
final class WorktreeTerminalManager {
private let runtime: GhosttyRuntime
@ObservationIgnored private let surfaceBindingActionPerformer: ((GhosttySurfaceView, String) -> Void)?
private(set) var socketServer: AgentHookSocketServer?
private var states: [Worktree.ID: WorktreeTerminalState] = [:]
@ObservationIgnored
Expand Down Expand Up @@ -148,9 +149,11 @@ final class WorktreeTerminalManager {
socketServer: AgentHookSocketServer? = nil,
clock: C = ContinuousClock(),
eventBufferCap: Int = WorktreeTerminalManager.defaultEventBufferCap,
surfaceBindingActionPerformer: ((GhosttySurfaceView, String) -> Void)? = nil
) {
self.eventBufferCap = eventBufferCap
self.runtime = runtime
self.surfaceBindingActionPerformer = surfaceBindingActionPerformer
self.focusedSurfaceBackground = runtime.backgroundColor()
self.hookEventSleep = { duration in try await clock.sleep(for: duration) }
self.layoutDebounceSleep = { duration in try await clock.sleep(for: duration) }
Expand Down Expand Up @@ -524,7 +527,7 @@ final class WorktreeTerminalManager {
existing.enableSetupScriptIfNeeded()
}
// Reload snapshot if the state has no tabs (e.g., setting was just enabled).
// If `hasAttemptedInitialTab` is sticky-true (closeAllTabs path), the snapshot
// If `hasAttemptedInitialTab` is sticky-true (every tab was closed), the snapshot
// stays staged but ensureInitialTab won't consume it; that's intentional.
if existing.tabManager.tabs.isEmpty,
existing.pendingLayoutSnapshot == nil,
Expand All @@ -538,7 +541,8 @@ final class WorktreeTerminalManager {
let state = WorktreeTerminalState(
runtime: runtime,
worktree: worktree,
runSetupScript: runSetupScript
runSetupScript: runSetupScript,
surfaceBindingActionPerformer: surfaceBindingActionPerformer
)
state.socketPath = socketServer?.socketPath
// Load saved layout snapshot for restoration (skip when a setup script is pending).
Expand Down
Loading
Loading