diff --git a/TablePro/ContentView.swift b/TablePro/ContentView.swift index ec5d2e4ea..5235e4947 100644 --- a/TablePro/ContentView.swift +++ b/TablePro/ContentView.swift @@ -193,6 +193,11 @@ struct ContentView: View { AppState.shared.supportsDatabaseSwitching = true } } + .onChange(of: sessionState?.toolbarState.safeModeLevel) { _, newLevel in + if let level = newLevel { + AppState.shared.safeModeLevel = level + } + } } // MARK: - View Components diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index beb5538c5..4e6b215ad 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -321,7 +321,7 @@ struct MainEditorContentView: View { changeManager: currentChangeManager, resultVersion: tab.resultVersion, metadataVersion: tab.metadataVersion, - isEditable: tab.isEditable && !tab.isView && !connection.safeModeLevel.blocksAllWrites, + isEditable: tab.isEditable && !tab.isView && !coordinator.safeModeLevel.blocksAllWrites, onRefresh: onRefresh, onCellEdit: onCellEdit, onUndo: { [binding = _selectedRowIndices, coordinator] in diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+Discard.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+Discard.swift index 5abed35d9..a1d08ee69 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+Discard.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+Discard.swift @@ -17,7 +17,7 @@ extension MainContentCoordinator { let sqlPreview = statements.map(\.sql).joined(separator: "\n") let window = await MainActor.run { NSApp.keyWindow } let permission = await SafeModeGuard.checkPermission( - level: connection.safeModeLevel, + level: safeModeLevel, isWriteOperation: true, sql: sqlPreview, operationDescription: String(localized: "Save Sidebar Changes"), diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+RowOperations.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+RowOperations.swift index e4afe6edd..b88b2c3e9 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+RowOperations.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+RowOperations.swift @@ -11,7 +11,7 @@ extension MainContentCoordinator { // MARK: - Row Operations func addNewRow(selectedRowIndices: inout Set, editingCell: inout CellPosition?) { - guard !connection.safeModeLevel.blocksAllWrites, + guard !safeModeLevel.blocksAllWrites, let tabIndex = tabManager.selectedTabIndex, tabIndex < tabManager.tabs.count else { return } @@ -31,7 +31,7 @@ extension MainContentCoordinator { } func deleteSelectedRows(indices: Set, selectedRowIndices: inout Set) { - guard !connection.safeModeLevel.blocksAllWrites, + guard !safeModeLevel.blocksAllWrites, let tabIndex = tabManager.selectedTabIndex, tabIndex < tabManager.tabs.count, tabManager.tabs[tabIndex].isEditable, @@ -53,7 +53,7 @@ extension MainContentCoordinator { } func duplicateSelectedRow(index: Int, selectedRowIndices: inout Set, editingCell: inout CellPosition?) { - guard !connection.safeModeLevel.blocksAllWrites, + guard !safeModeLevel.blocksAllWrites, let tabIndex = tabManager.selectedTabIndex, tabIndex < tabManager.tabs.count else { return } @@ -154,7 +154,7 @@ extension MainContentCoordinator { } func pasteRows(selectedRowIndices: inout Set, editingCell: inout CellPosition?) { - guard !connection.safeModeLevel.blocksAllWrites, + guard !safeModeLevel.blocksAllWrites, let index = tabManager.selectedTabIndex else { return } var tab = tabManager.tabs[index] diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift index 3ce1d2178..0d9835710 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift @@ -17,7 +17,7 @@ extension MainContentCoordinator { pendingDeletes: inout Set, tableOperationOptions: inout [String: TableOperationOptions] ) { - guard !connection.safeModeLevel.blocksAllWrites else { + guard !safeModeLevel.blocksAllWrites else { if let index = tabManager.selectedTabIndex { tabManager.tabs[index].errorMessage = "Cannot save changes: connection is read-only" } @@ -60,7 +60,7 @@ extension MainContentCoordinator { return } - let level = connection.safeModeLevel + let level = safeModeLevel if level.requiresConfirmation { let sqlPreview = allStatements.map(\.sql).joined(separator: "\n") // Snapshot inout values before clearing — needed for executeCommitStatements diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift index 36d3fc0b8..111bb21a6 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift @@ -13,7 +13,7 @@ extension MainContentCoordinator { // MARK: - View Operations func createView() { - guard !connection.safeModeLevel.blocksAllWrites else { return } + guard !safeModeLevel.blocksAllWrites else { return } let driver = DatabaseManager.shared.driver(for: connection.id) let template = driver?.createViewTemplate() @@ -63,7 +63,7 @@ extension MainContentCoordinator { } func openImportDialog() { - guard !connection.safeModeLevel.blocksAllWrites else { return } + guard !safeModeLevel.blocksAllWrites else { return } guard PluginManager.shared.supportsImport(for: connection.type) else { AlertHelper.showErrorSheet( title: String(localized: "Import Not Supported"), diff --git a/TablePro/Views/Main/Extensions/MainContentView+Bindings.swift b/TablePro/Views/Main/Extensions/MainContentView+Bindings.swift index ed7675ba0..8b46dd4e0 100644 --- a/TablePro/Views/Main/Extensions/MainContentView+Bindings.swift +++ b/TablePro/Views/Main/Extensions/MainContentView+Bindings.swift @@ -34,7 +34,7 @@ extension MainContentView { /// Determine if sidebar should be in editable mode var isSidebarEditable: Bool { - guard !coordinator.connection.safeModeLevel.blocksAllWrites, + guard !coordinator.safeModeLevel.blocksAllWrites, let tab = coordinator.tabManager.selectedTab, tab.tabType == .table || tab.tableName != nil, !selectedRowIndices.isEmpty else { diff --git a/TablePro/Views/Main/MainContentCoordinator.swift b/TablePro/Views/Main/MainContentCoordinator.swift index c1937ce06..62c044094 100644 --- a/TablePro/Views/Main/MainContentCoordinator.swift +++ b/TablePro/Views/Main/MainContentCoordinator.swift @@ -49,6 +49,9 @@ final class MainContentCoordinator { let connection: DatabaseConnection var connectionId: UUID { connection.id } + /// Live safe mode level — reads from toolbar state (user-editable), + /// not from the immutable connection snapshot. + var safeModeLevel: SafeModeLevel { toolbarState.safeModeLevel } let tabManager: QueryTabManager let changeManager: DataChangeManager let filterStateManager: FilterStateManager @@ -508,7 +511,7 @@ final class MainContentCoordinator { guard !statements.isEmpty else { return } // Safe mode enforcement for query execution - let level = connection.safeModeLevel + let level = safeModeLevel if level == .readOnly { let writeStatements = statements.filter { isWriteQuery($0) } @@ -584,7 +587,7 @@ final class MainContentCoordinator { let sql = tabManager.tabs[index].query guard !sql.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return } - let level = connection.safeModeLevel + let level = safeModeLevel if level.appliesToAllQueries && level.requiresConfirmation, tabManager.tabs[index].lastExecutedAt == nil { @@ -688,7 +691,7 @@ final class MainContentCoordinator { let statements = SQLStatementScanner.allStatements(in: trimmed) guard let stmt = statements.first else { return } - let level = connection.safeModeLevel + let level = safeModeLevel let needsConfirmation = level.appliesToAllQueries && level.requiresConfirmation // Multi-variant EXPLAIN: use plugin-declared variants if available