Skip to content

Commit bbd8526

Browse files
committed
wip
1 parent fcce047 commit bbd8526

File tree

3 files changed

+73
-66
lines changed

3 files changed

+73
-66
lines changed

OpenTable/AppDelegate.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
6464
// CRITICAL: Post notification FIRST to allow MainContentView to flush pending saves
6565
// This ensures query text is saved before SwiftUI tears down the view
6666
NotificationCenter.default.post(name: .mainWindowWillClose, object: nil)
67-
68-
// Small delay to allow notification handlers to complete
69-
// This is critical - without it, saves may not complete before view is destroyed
70-
Thread.sleep(forTimeInterval: 0.05) // 50ms
67+
68+
// Allow run loop to process notification handlers synchronously
69+
// This is more elegant than Thread.sleep as it processes pending events
70+
// rather than blocking the main thread entirely
71+
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.05))
7172

7273
// NOTE: We do NOT call saveAllTabStates() here because:
7374
// 1. MainContentView already flushed the correct state via the notification above

OpenTable/Views/MainContentView.swift

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ struct MainContentView: View {
4545
@State private var justRestoredTab = false // Prevent lazy load duplicate execution after restore
4646

4747
// MARK: - Constants
48-
48+
4949
private static let tabSaveDebounceDelay: UInt64 = 500_000_000 // 500ms in nanoseconds
5050
private static let connectionCheckDelay: UInt64 = 100_000_000 // 100ms in nanoseconds
5151
private static let maxConnectionRetries = 50 // Max retries for connection check (5 seconds total)
@@ -369,10 +369,65 @@ struct MainContentView: View {
369369
/// First part of notifications - reduces type-checker complexity
370370
@ViewBuilder
371371
private var bodyContentPart1: some View {
372+
bodyContentPart2
373+
.onReceive(NotificationCenter.default.publisher(for: .deleteSelectedRows)) { _ in
374+
// Delete rows or mark table for deletion
375+
Task { @MainActor in
376+
// First check if we have row selection in data grid
377+
if !selectedRowIndices.isEmpty {
378+
deleteSelectedRows()
379+
}
380+
// Otherwise check if tables are selected in sidebar
381+
else if !selectedTables.isEmpty {
382+
// Batch update to avoid stale copy issues with @Binding
383+
var updatedDeletes = pendingDeletes
384+
var updatedTruncates = pendingTruncates
385+
386+
for table in selectedTables {
387+
updatedTruncates.remove(table.name)
388+
if updatedDeletes.contains(table.name) {
389+
updatedDeletes.remove(table.name)
390+
} else {
391+
updatedDeletes.insert(table.name)
392+
}
393+
}
394+
395+
pendingTruncates = updatedTruncates
396+
pendingDeletes = updatedDeletes
397+
}
398+
}
399+
}
400+
.onReceive(NotificationCenter.default.publisher(for: .databaseDidConnect)) { _ in
401+
// Load schema and update toolbar when connection is established (fixes race condition)
402+
Task { @MainActor in
403+
await loadSchema()
404+
// Update version after connection is fully established
405+
if let driver = DatabaseManager.shared.activeDriver {
406+
toolbarState.databaseVersion = driver.serverVersion
407+
}
408+
}
409+
}
410+
.onReceive(NotificationCenter.default.publisher(for: .showAllTables)) { _ in
411+
// Show all tables metadata when user clicks "Tables" heading in sidebar
412+
Task { @MainActor in
413+
showAllTablesMetadata()
414+
}
415+
}
416+
.onReceive(NotificationCenter.default.publisher(for: .addNewRow)) { _ in
417+
// Add row menu item (Cmd+I)
418+
Task { @MainActor in
419+
addNewRow()
420+
}
421+
}
422+
}
423+
424+
/// Second part of notifications - further reduces type-checker complexity
425+
@ViewBuilder
426+
private var bodyContentPart2: some View {
372427
viewWithToolbar
373428
.task {
374429
await initializeView()
375-
430+
376431
// Restore tabs from disk first (persists across app restarts)
377432
// Fallback to session tabs (persists during app session only)
378433
var didRestoreTabs = false
@@ -381,7 +436,7 @@ struct MainContentView: View {
381436
// Restore from disk
382437
isRestoringTabs = true
383438
defer { isRestoringTabs = false }
384-
439+
385440
let restoredTabs = savedState.tabs.map { QueryTab(from: $0) }
386441
tabManager.tabs = restoredTabs
387442
tabManager.selectedTabId = savedState.selectedTabId
@@ -392,7 +447,7 @@ struct MainContentView: View {
392447
// Fallback: Restore from session (for backward compatibility)
393448
isRestoringTabs = true
394449
defer { isRestoringTabs = false }
395-
450+
396451
tabManager.tabs = session.tabs
397452
tabManager.selectedTabId = session.selectedTabId
398453
didRestoreTabs = true
@@ -402,13 +457,13 @@ struct MainContentView: View {
402457
if let selectedTab = tabManager.selectedTab,
403458
selectedTab.tabType == .table,
404459
!selectedTab.query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
405-
460+
406461
// Wait for connection to be established
407462
var retryCount = 0
408463
while retryCount < Self.maxConnectionRetries {
409464
// Stop waiting if view is being dismissed
410465
guard !isDismissing else { break }
411-
466+
412467
if let session = DatabaseManager.shared.currentSession,
413468
session.isConnected {
414469
// Small delay to ensure everything is initialized
@@ -419,13 +474,13 @@ struct MainContentView: View {
419474
}
420475
break
421476
}
422-
477+
423478
// Wait 100ms and retry
424479
try? await Task.sleep(nanoseconds: 100_000_000)
425480
retryCount += 1
426481
}
427-
428-
if retryCount >= 50 {
482+
483+
if retryCount >= Self.maxConnectionRetries {
429484
print("[MainContentView] ⚠️ Connection timeout, query not executed")
430485
}
431486
}
@@ -462,7 +517,7 @@ struct MainContentView: View {
462517
// Load query from history/bookmark panel into current tab
463518
Task { @MainActor in
464519
guard let query = notification.object as? String else { return }
465-
520+
466521
// Load into the current tab (which was just created by .newTab)
467522
if let tabIndex = tabManager.selectedTabIndex,
468523
tabIndex < tabManager.tabs.count {
@@ -493,67 +548,18 @@ struct MainContentView: View {
493548
} else {
494549
// Cancel any running query to prevent race conditions
495550
currentQueryTask?.cancel()
496-
551+
497552
// Rebuild query for table tabs to ensure fresh data
498553
if let tabIndex = tabManager.selectedTabIndex,
499554
tabManager.tabs[tabIndex].tabType == .table {
500555
rebuildTableQuery(at: tabIndex)
501556
}
502-
557+
503558
// Fetch fresh data from database
504559
runQuery()
505560
}
506561
}
507562
}
508-
.onReceive(NotificationCenter.default.publisher(for: .deleteSelectedRows)) { _ in
509-
// Delete rows or mark table for deletion
510-
Task { @MainActor in
511-
// First check if we have row selection in data grid
512-
if !selectedRowIndices.isEmpty {
513-
deleteSelectedRows()
514-
}
515-
// Otherwise check if tables are selected in sidebar
516-
else if !selectedTables.isEmpty {
517-
// Batch update to avoid stale copy issues with @Binding
518-
var updatedDeletes = pendingDeletes
519-
var updatedTruncates = pendingTruncates
520-
521-
for table in selectedTables {
522-
updatedTruncates.remove(table.name)
523-
if updatedDeletes.contains(table.name) {
524-
updatedDeletes.remove(table.name)
525-
} else {
526-
updatedDeletes.insert(table.name)
527-
}
528-
}
529-
530-
pendingTruncates = updatedTruncates
531-
pendingDeletes = updatedDeletes
532-
}
533-
}
534-
}
535-
.onReceive(NotificationCenter.default.publisher(for: .databaseDidConnect)) { _ in
536-
// Load schema and update toolbar when connection is established (fixes race condition)
537-
Task { @MainActor in
538-
await loadSchema()
539-
// Update version after connection is fully established
540-
if let driver = DatabaseManager.shared.activeDriver {
541-
toolbarState.databaseVersion = driver.serverVersion
542-
}
543-
}
544-
}
545-
.onReceive(NotificationCenter.default.publisher(for: .showAllTables)) { _ in
546-
// Show all tables metadata when user clicks "Tables" heading in sidebar
547-
Task { @MainActor in
548-
showAllTablesMetadata()
549-
}
550-
}
551-
.onReceive(NotificationCenter.default.publisher(for: .addNewRow)) { _ in
552-
// Add row menu item (Cmd+I)
553-
Task { @MainActor in
554-
addNewRow()
555-
}
556-
}
557563
}
558564

559565
// MARK: - Query Tab Content
@@ -590,7 +596,7 @@ struct MainContentView: View {
590596

591597
// Create new debounce task
592598
saveDebounceTask = Task { @MainActor in
593-
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5s
599+
try? await Task.sleep(nanoseconds: Self.tabSaveDebounceDelay)
594600

595601
// Only save if not cancelled and view not being dismissed
596602
guard !Task.isCancelled && !isDismissing else { return }

0 commit comments

Comments
 (0)