Skip to content

Commit 0191c2a

Browse files
authored
fix(tabs): keep safe-mode confirmation on the active tab (#1781) (#1785)
1 parent ea6ed46 commit 0191c2a

4 files changed

Lines changed: 57 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121

2222
### Fixed
2323

24+
- Safe mode no longer jumps to the first tab after you confirm a query. The confirmation now stays on the tab you ran the query from. (#1781)
2425
- Switching between sidebar tables no longer leaves extra blank space above the list. (#1675)
2526
- SSH tunnels no longer pin a CPU core after the connection drops. A dropped tunnel is now detected and torn down instead of spinning in its relay loop. (#1769)
2627
- Restored table tabs now load with the current page size instead of the page size from the previous session.

TablePro/Core/Services/Execution/Providers/OperationConfirming.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal struct AlertOperationConfirming: OperationConfirming {
1414
@MainActor
1515
func confirm(sql: String, operationDescription: String, connectionId: UUID, isDestructive: Bool) async -> Bool {
1616
NSApp.activate(ignoringOtherApps: true)
17-
let window = WindowLifecycleMonitor.shared.findWindow(for: connectionId)
17+
let window = WindowLifecycleMonitor.shared.activeWindow(for: connectionId, preferring: NSApp.keyWindow)
1818
let preview = Self.preview(of: sql)
1919

2020
if isDestructive {

TablePro/Core/Services/Infrastructure/WindowLifecycleMonitor.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ internal final class WindowLifecycleMonitor {
115115
.first { $0.isVisible }
116116
}
117117

118+
/// The active window for a connection, preferring `candidate` (typically the
119+
/// key window) when it belongs to this connection. Each editor tab is a
120+
/// separate window in a native tab group, so `findWindow` returns an
121+
/// arbitrary tab's window; anchoring a connection-scoped sheet there would
122+
/// switch the selected tab. Preferring the key window keeps the user on the
123+
/// tab they triggered the action from.
124+
internal func activeWindow(for connectionId: UUID, preferring candidate: NSWindow?) -> NSWindow? {
125+
if let candidate, self.connectionId(forWindow: candidate) == connectionId {
126+
return candidate
127+
}
128+
return findWindow(for: connectionId)
129+
}
130+
118131
/// Look up the connectionId for a given windowId.
119132
internal func connectionId(for windowId: UUID) -> UUID? {
120133
purgeStaleEntries()

TableProTests/Core/Services/WindowLifecycleMonitorTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,48 @@ struct WindowLifecycleMonitorTests {
167167
#expect(monitor.findWindow(for: UUID()) == nil)
168168
}
169169

170+
// MARK: - activeWindow
171+
172+
@Test("activeWindow prefers the candidate when it belongs to the connection")
173+
func activeWindowPrefersCandidate() {
174+
let windowId = UUID()
175+
let connectionId = UUID()
176+
let window = NSWindow()
177+
178+
monitor.register(window: window, connectionId: connectionId, windowId: windowId)
179+
defer { cleanup(windowId) }
180+
181+
#expect(monitor.activeWindow(for: connectionId, preferring: window) === window)
182+
}
183+
184+
@Test("activeWindow ignores a candidate from a different connection")
185+
func activeWindowIgnoresForeignCandidate() {
186+
let windowIdA = UUID()
187+
let windowIdB = UUID()
188+
let connectionA = UUID()
189+
let connectionB = UUID()
190+
let windowA = NSWindow()
191+
let windowB = NSWindow()
192+
193+
monitor.register(window: windowA, connectionId: connectionA, windowId: windowIdA)
194+
monitor.register(window: windowB, connectionId: connectionB, windowId: windowIdB)
195+
defer { cleanup(windowIdA, windowIdB) }
196+
197+
#expect(monitor.activeWindow(for: connectionA, preferring: windowB) !== windowB)
198+
}
199+
200+
@Test("activeWindow falls back to findWindow when the candidate is nil")
201+
func activeWindowNilCandidateFallsBack() {
202+
let windowId = UUID()
203+
let connectionId = UUID()
204+
let window = NSWindow()
205+
206+
monitor.register(window: window, connectionId: connectionId, windowId: windowId)
207+
defer { cleanup(windowId) }
208+
209+
#expect(monitor.activeWindow(for: connectionId, preferring: nil) === monitor.findWindow(for: connectionId))
210+
}
211+
170212
// MARK: - windows(for:) empty for unknown
171213

172214
@Test("windows(for:) returns empty array for unknown connectionId")

0 commit comments

Comments
 (0)