Skip to content

Commit da9c06a

Browse files
committed
Revert "refactor: replace native window tabs with in-app tab bar (#763)"
This reverts commit bad2466.
1 parent 7161299 commit da9c06a

39 files changed

+753
-1602
lines changed

CHANGELOG.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## [0.32.0] - 2026-04-16
1111

12-
### Added
13-
14-
- In-app tab bar with instant switching, drag reorder, pinned tabs, and dirty indicators
15-
- Reopen closed tab (Cmd+Shift+T), MRU tab selection on close
16-
- Deeplinks and Handoff route to in-app tabs instead of creating duplicate windows
17-
18-
### Changed
19-
20-
- Replace native macOS window tabs with in-app tab bar (600ms+ → instant)
21-
- Tab content preserved across switches (no view destruction/recreation)
22-
- Connection state persisted incrementally (survives force quit)
23-
2412
### Fixed
2513

2614
- Raw SQL injection via external URL scheme deeplinks — now requires user confirmation

TablePro/AppDelegate+ConnectionHandler.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ extension AppDelegate {
9292
do {
9393
try await DatabaseManager.shared.connectToSession(connection)
9494
self.openNewConnectionWindow(for: connection)
95-
self.closeAllWelcomeWindows()
95+
for window in NSApp.windows where self.isWelcomeWindow(window) {
96+
window.close()
97+
}
9698
self.handlePostConnectionActions(parsed, connectionId: connection.id)
9799
} catch {
98100
connectionLogger.error("Database URL connect failed: \(error.localizedDescription)")
@@ -141,7 +143,9 @@ extension AppDelegate {
141143
do {
142144
try await DatabaseManager.shared.connectToSession(connection)
143145
self.openNewConnectionWindow(for: connection)
144-
self.closeAllWelcomeWindows()
146+
for window in NSApp.windows where self.isWelcomeWindow(window) {
147+
window.close()
148+
}
145149
} catch {
146150
connectionLogger.error("SQLite file open failed for '\(filePath, privacy: .public)': \(error.localizedDescription)")
147151
await self.handleConnectionFailure(error)
@@ -189,7 +193,9 @@ extension AppDelegate {
189193
do {
190194
try await DatabaseManager.shared.connectToSession(connection)
191195
self.openNewConnectionWindow(for: connection)
192-
self.closeAllWelcomeWindows()
196+
for window in NSApp.windows where self.isWelcomeWindow(window) {
197+
window.close()
198+
}
193199
} catch {
194200
connectionLogger.error("DuckDB file open failed for '\(filePath, privacy: .public)': \(error.localizedDescription)")
195201
await self.handleConnectionFailure(error)
@@ -237,7 +243,9 @@ extension AppDelegate {
237243
do {
238244
try await DatabaseManager.shared.connectToSession(connection)
239245
self.openNewConnectionWindow(for: connection)
240-
self.closeAllWelcomeWindows()
246+
for window in NSApp.windows where self.isWelcomeWindow(window) {
247+
window.close()
248+
}
241249
} catch {
242250
connectionLogger.error("File open failed for '\(filePath, privacy: .public)' (\(dbType.rawValue)): \(error.localizedDescription)")
243251
await self.handleConnectionFailure(error)
@@ -342,9 +350,7 @@ extension AppDelegate {
342350
tableName: tableName,
343351
isView: parsed.isView
344352
)
345-
if !routeToExistingWindow(connectionId: connectionId, payload: payload) {
346-
WindowOpener.shared.openNativeTab(payload)
347-
}
353+
WindowOpener.shared.openNativeTab(payload)
348354

349355
if parsed.filterColumn != nil || parsed.filterCondition != nil {
350356
await waitForNotification(.refreshData, timeout: .seconds(3))
@@ -469,12 +475,6 @@ extension AppDelegate {
469475
return "\(parsed.type.rawValue):\(parsed.username)@\(parsed.host):\(parsed.port ?? 0)/\(parsed.database)\(rdb)"
470476
}
471477

472-
func closeAllWelcomeWindows() {
473-
for window in NSApp.windows where isWelcomeWindow(window) {
474-
window.close()
475-
}
476-
}
477-
478478
func bringConnectionWindowToFront(_ connectionId: UUID) {
479479
let windows = WindowLifecycleMonitor.shared.windows(for: connectionId)
480480
if let window = windows.first {

TablePro/AppDelegate+FileOpen.swift

Lines changed: 26 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -32,39 +32,31 @@ extension AppDelegate {
3232

3333
let tableName = activity.userInfo?["tableName"] as? String
3434

35-
// Already connected — route to existing window's in-app tab bar
3635
if DatabaseManager.shared.activeSessions[connectionId]?.driver != nil {
3736
if let tableName {
3837
let payload = EditorTabPayload(connectionId: connectionId, tabType: .table, tableName: tableName)
39-
if !routeToExistingWindow(connectionId: connectionId, payload: payload) {
40-
WindowOpener.shared.openNativeTab(payload)
41-
}
38+
WindowOpener.shared.openNativeTab(payload)
4239
} else {
43-
bringConnectionWindowToFront(connectionId)
40+
for window in NSApp.windows where isMainWindow(window) {
41+
window.makeKeyAndOrderFront(nil)
42+
return
43+
}
4444
}
4545
return
4646
}
4747

48-
// Window already pending (e.g., auto-reconnect in progress) — just bring to front
49-
let hasPending = WindowOpener.shared.pendingPayloads.contains { $0.connectionId == connectionId }
50-
if hasPending {
51-
bringConnectionWindowToFront(connectionId)
52-
return
53-
}
54-
55-
// Not connected — create window, connect, then route content as in-app tab
56-
let initialPayload = EditorTabPayload(connectionId: connectionId, intent: .restoreOrDefault)
48+
let initialPayload = EditorTabPayload(connectionId: connectionId)
5749
WindowOpener.shared.openNativeTab(initialPayload)
5850

5951
Task { @MainActor in
6052
do {
6153
try await DatabaseManager.shared.connectToSession(connection)
62-
self.closeAllWelcomeWindows()
54+
for window in NSApp.windows where self.isWelcomeWindow(window) {
55+
window.close()
56+
}
6357
if let tableName {
6458
let payload = EditorTabPayload(connectionId: connectionId, tabType: .table, tableName: tableName)
65-
if !routeToExistingWindow(connectionId: connectionId, payload: payload) {
66-
WindowOpener.shared.openNativeTab(payload)
67-
}
59+
WindowOpener.shared.openNativeTab(payload)
6860
}
6961
} catch {
7062
fileOpenLogger.error("Handoff connect failed: \(error.localizedDescription)")
@@ -94,11 +86,6 @@ extension AppDelegate {
9486
// MARK: - Main Dispatch
9587

9688
func handleOpenURLs(_ urls: [URL]) {
97-
// application(_:open:) fires in the same run loop pass as applicationDidFinishLaunching
98-
// on cold launch from URL. The deferred auto-reconnect Task yields to the next run loop,
99-
// so this flag is guaranteed to be set before the Task checks it.
100-
suppressAutoReconnect = true
101-
10289
let deeplinks = urls.filter { $0.scheme == "tablepro" }
10390
if !deeplinks.isEmpty {
10491
Task { @MainActor in
@@ -156,7 +143,9 @@ extension AppDelegate {
156143
for window in NSApp.windows where isMainWindow(window) {
157144
window.makeKeyAndOrderFront(nil)
158145
}
159-
closeAllWelcomeWindows()
146+
for window in NSApp.windows where isWelcomeWindow(window) {
147+
window.close()
148+
}
160149
NotificationCenter.default.post(name: .openSQLFiles, object: sqlFiles)
161150
endFileOpenSuppression()
162151
} else {
@@ -166,36 +155,6 @@ extension AppDelegate {
166155
}
167156
}
168157

169-
// MARK: - In-App Tab Routing
170-
171-
/// Route content to an existing connection window's in-app tab bar when possible.
172-
/// Returns true if the content was routed to an existing window.
173-
/// Falls back gracefully (returns false) when no coordinator exists for the connection.
174-
@discardableResult
175-
func routeToExistingWindow(
176-
connectionId: UUID,
177-
payload: EditorTabPayload
178-
) -> Bool {
179-
guard let coordinator = MainContentCoordinator.firstCoordinator(for: connectionId) else {
180-
return false
181-
}
182-
switch payload.tabType {
183-
case .table:
184-
if let tableName = payload.tableName {
185-
coordinator.openTableTab(tableName, showStructure: payload.showStructure, isView: payload.isView)
186-
}
187-
case .query:
188-
coordinator.tabManager.addTab(
189-
initialQuery: payload.initialQuery,
190-
databaseName: payload.databaseName ?? coordinator.connection.database
191-
)
192-
default:
193-
coordinator.addNewQueryTab()
194-
}
195-
coordinator.contentWindow?.makeKeyAndOrderFront(nil)
196-
return true
197-
}
198-
199158
// MARK: - Welcome Window Suppression
200159

201160
func suppressWelcomeWindow() {
@@ -257,7 +216,7 @@ extension AppDelegate {
257216
makePayload: (@Sendable (UUID) -> EditorTabPayload)? = nil
258217
) {
259218
guard let connection = DeeplinkHandler.resolveConnection(named: connectionName) else {
260-
fileOpenLogger.error("No connection named '\(connectionName, privacy: .public)'")
219+
fileOpenLogger.error("Deep link: no connection named '\(connectionName, privacy: .public)'")
261220
AlertHelper.showErrorSheet(
262221
title: String(localized: "Connection Not Found"),
263222
message: String(format: String(localized: "No saved connection named \"%@\"."), connectionName),
@@ -266,47 +225,27 @@ extension AppDelegate {
266225
return
267226
}
268227

269-
let hasDriver = DatabaseManager.shared.activeSessions[connection.id]?.driver != nil
270-
let hasCoordinator = MainContentCoordinator.firstCoordinator(for: connection.id) != nil
271-
272-
// Already connected — route to existing window's in-app tab bar
273-
if hasDriver {
228+
if DatabaseManager.shared.activeSessions[connection.id]?.driver != nil {
274229
if let payload = makePayload?(connection.id) {
275-
if !routeToExistingWindow(connectionId: connection.id, payload: payload) {
276-
WindowOpener.shared.openNativeTab(payload)
277-
}
230+
WindowOpener.shared.openNativeTab(payload)
278231
} else {
279-
bringConnectionWindowToFront(connection.id)
232+
for window in NSApp.windows where isMainWindow(window) {
233+
window.makeKeyAndOrderFront(nil)
234+
return
235+
}
280236
}
281237
return
282238
}
283239

284-
// Prevent duplicate connections from rapid deeplink invocations
285-
let hasPendingWindow = WindowOpener.shared.pendingPayloads.contains { $0.connectionId == connection.id }
286-
let isAlreadyConnecting = connectingURLConnectionIds.contains(connection.id)
287-
guard !isAlreadyConnecting, !hasPendingWindow else {
288-
bringConnectionWindowToFront(connection.id)
289-
return
290-
}
291-
292-
// Has coordinator but no driver — window exists, connection may be in progress
293-
if hasCoordinator {
294-
bringConnectionWindowToFront(connection.id)
295-
return
296-
}
297-
298240
let hadExistingMain = NSApp.windows.contains { isMainWindow($0) && $0.isVisible }
299241
if hadExistingMain && !AppSettingsManager.shared.tabs.groupAllConnectionTabs {
300242
NSWindow.allowsAutomaticWindowTabbing = false
301243
}
302244

303-
connectingURLConnectionIds.insert(connection.id)
304-
305-
let deeplinkPayload = EditorTabPayload(connectionId: connection.id, intent: .restoreOrDefault)
245+
let deeplinkPayload = EditorTabPayload(connectionId: connection.id)
306246
WindowOpener.shared.openNativeTab(deeplinkPayload)
307247

308248
Task { @MainActor in
309-
defer { self.connectingURLConnectionIds.remove(connection.id) }
310249
do {
311250
// Confirm pre-connect script if present (deep links are external, so always confirm)
312251
if let script = connection.preConnectScript,
@@ -323,14 +262,14 @@ extension AppDelegate {
323262
}
324263

325264
try await DatabaseManager.shared.connectToSession(connection)
326-
self.closeAllWelcomeWindows()
265+
for window in NSApp.windows where self.isWelcomeWindow(window) {
266+
window.close()
267+
}
327268
if let payload = makePayload?(connection.id) {
328-
if !self.routeToExistingWindow(connectionId: connection.id, payload: payload) {
329-
WindowOpener.shared.openNativeTab(payload)
330-
}
269+
WindowOpener.shared.openNativeTab(payload)
331270
}
332271
} catch {
333-
fileOpenLogger.error("Deeplink connect failed for \"\(connectionName, privacy: .public)\": \(error.localizedDescription, privacy: .public)")
272+
fileOpenLogger.error("Deep link connect failed: \(error.localizedDescription)")
334273
await self.handleConnectionFailure(error)
335274
}
336275
}

0 commit comments

Comments
 (0)