Skip to content

Commit 1c698c7

Browse files
authored
Merge pull request #600 from TableProApp/refactor/timing-hacks
refactor: replace timing hacks with structured signals
2 parents ab8e269 + 49bf36a commit 1c698c7

File tree

9 files changed

+68
-42
lines changed

9 files changed

+68
-42
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Use sheet presentation for all file open/save panels instead of free-floating dialogs
2424
- Replace event monitor with native SwiftUI .onKeyPress() in connection switcher
2525
- Extract reusable SearchFieldView component from 4 custom search field implementations
26+
- Replace timing hacks with structured signals (polling loops, arbitrary delays)
2627

2728
### Changed
2829

TablePro/AppDelegate+ConnectionHandler.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,18 @@ extension AppDelegate {
264264
Task { @MainActor [weak self] in
265265
defer { self?.isProcessingQueuedURLs = false }
266266

267-
var ready = false
268-
for _ in 0..<25 {
269-
if WindowOpener.shared.openWindow != nil { ready = true; break }
270-
try? await Task.sleep(for: .milliseconds(200))
267+
let ready = await withTaskGroup(of: Bool.self) { group in
268+
group.addTask {
269+
await WindowOpener.shared.waitUntilReady()
270+
return true
271+
}
272+
group.addTask {
273+
try? await Task.sleep(for: .seconds(5))
274+
return false
275+
}
276+
let result = await group.next() ?? false
277+
group.cancelAll()
278+
return result
271279
}
272280
guard let self else { return }
273281
if !ready {

TablePro/AppDelegate+FileOpen.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,8 @@ extension AppDelegate {
244244

245245
private func handleConnectionShareFile(_ url: URL) {
246246
openWelcomeWindow()
247-
// Delay to ensure WelcomeWindowView's .onReceive is registered after window renders
248-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
249-
NotificationCenter.default.post(name: .connectionShareFileOpened, object: url)
250-
}
247+
pendingConnectionShareURL = url
248+
NotificationCenter.default.post(name: .connectionShareFileOpened, object: url)
251249
}
252250

253251
// MARK: - Plugin Install

TablePro/AppDelegate+WindowConfig.swift

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -151,22 +151,6 @@ extension AppDelegate {
151151
NotificationCenter.default.post(name: .openWelcomeWindow, object: nil)
152152
}
153153

154-
func configureWelcomeWindow() {
155-
Task { @MainActor [weak self] in
156-
for _ in 0 ..< 5 {
157-
guard let self else { return }
158-
let found = NSApp.windows.contains(where: { self.isWelcomeWindow($0) })
159-
if found {
160-
for window in NSApp.windows where self.isWelcomeWindow(window) {
161-
self.configureWelcomeWindowStyle(window)
162-
}
163-
return
164-
}
165-
try? await Task.sleep(for: .milliseconds(50))
166-
}
167-
}
168-
}
169-
170154
private func configureWelcomeWindowStyle(_ window: NSWindow) {
171155
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
172156
window.standardWindowButton(.zoomButton)?.isHidden = true

TablePro/AppDelegate.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
5959
/// Prevents duplicate connections when the same file is opened twice rapidly.
6060
var connectingFilePaths = Set<String>()
6161

62+
/// Connection share file URL pending consumption by WelcomeViewModel.setUp()
63+
var pendingConnectionShareURL: URL?
64+
6265
// MARK: - NSApplicationDelegate
6366

6467
func application(_ application: NSApplication, open urls: [URL]) {
@@ -105,8 +108,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
105108
_ = QueryHistoryStorage.shared
106109
}
107110

108-
configureWelcomeWindow()
109-
110111
let settings = AppSettingsStorage.shared.loadGeneral()
111112
if settings.startupBehavior == .reopenLast,
112113
let lastConnectionId = AppSettingsStorage.shared.loadLastConnectionId() {

TablePro/Core/Services/Infrastructure/WindowOpener.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,30 @@ internal final class WindowOpener {
1515

1616
internal static let shared = WindowOpener()
1717

18+
private var readyContinuation: CheckedContinuation<Void, Never>?
19+
1820
/// Set on appear by ContentView, WelcomeViewModel, or ConnectionFormView.
1921
/// Safe to store — OpenWindowAction is app-scoped, not view-scoped.
20-
internal var openWindow: OpenWindowAction?
22+
internal var openWindow: OpenWindowAction? {
23+
didSet {
24+
if openWindow != nil {
25+
readyContinuation?.resume()
26+
readyContinuation = nil
27+
}
28+
}
29+
}
30+
31+
/// Suspends until openWindow is set. Returns immediately if already available.
32+
internal func waitUntilReady() async {
33+
if openWindow != nil { return }
34+
await withCheckedContinuation { continuation in
35+
if openWindow != nil {
36+
continuation.resume()
37+
} else {
38+
readyContinuation = continuation
39+
}
40+
}
41+
}
2142

2243
/// Ordered queue of pending payloads — windows requested via openNativeTab
2344
/// but not yet acknowledged by MainContentView.configureWindow.

TablePro/ViewModels/WelcomeViewModel.swift

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ final class WelcomeViewModel {
181181

182182
loadConnections()
183183
linkedConnections = LinkedFolderWatcher.shared.linkedConnections
184+
185+
if let appDelegate = NSApp.delegate as? AppDelegate,
186+
let pendingURL = appDelegate.pendingConnectionShareURL {
187+
appDelegate.pendingConnectionShareURL = nil
188+
activeSheet = .importFile(pendingURL)
189+
}
184190
}
185191

186192
deinit {
@@ -504,15 +510,25 @@ final class WelcomeViewModel {
504510
}
505511

506512
func focusConnectionFormWindow() {
513+
if let window = NSApp.windows.first(where: { $0.identifier?.rawValue == "connection-form" }) {
514+
window.makeKeyAndOrderFront(nil)
515+
return
516+
}
517+
518+
var observer: NSObjectProtocol?
519+
observer = NotificationCenter.default.addObserver(
520+
forName: NSWindow.didBecomeKeyNotification,
521+
object: nil,
522+
queue: .main
523+
) { notification in
524+
guard let window = notification.object as? NSWindow,
525+
window.identifier?.rawValue == "connection-form" else { return }
526+
if let observer { NotificationCenter.default.removeObserver(observer) }
527+
}
528+
507529
Task { @MainActor in
508-
for _ in 0..<10 {
509-
for window in NSApp.windows where
510-
window.identifier?.rawValue == "connection-form" {
511-
window.makeKeyAndOrderFront(nil)
512-
return
513-
}
514-
try? await Task.sleep(for: .milliseconds(20))
515-
}
530+
try? await Task.sleep(for: .milliseconds(500))
531+
if let observer { NotificationCenter.default.removeObserver(observer) }
516532
}
517533
}
518534

TablePro/Views/Main/Extensions/MainContentCoordinator+QueryHelpers.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,10 @@ extension MainContentCoordinator {
250250
) {
251251
let isNonSQL = PluginManager.shared.editorLanguage(for: connectionType) != .sql
252252

253-
// Phase 2a: Exact row count
253+
// Phase 2a: Exact row count (background priority to let Phase 1 render first)
254254
// Redis/non-SQL drivers don't support SELECT COUNT(*); use approximate count instead.
255-
Task { [weak self] in
255+
Task(priority: .background) { [weak self] in
256256
guard let self else { return }
257-
try? await Task.sleep(nanoseconds: 200_000_000)
258257
guard !self.isTearingDown else { return }
259258
guard let mainDriver = DatabaseManager.shared.driver(for: connectionId) else { return }
260259

@@ -289,9 +288,8 @@ extension MainContentCoordinator {
289288
// Phase 2b: Fetch enum/set values (not applicable for non-SQL databases)
290289
guard !isNonSQL else { return }
291290
guard let enumDriver = DatabaseManager.shared.driver(for: connectionId) else { return }
292-
Task { [weak self] in
291+
Task(priority: .background) { [weak self] in
293292
guard let self else { return }
294-
try? await Task.sleep(nanoseconds: 200_000_000)
295293
guard !self.isTearingDown else { return }
296294

297295
// Use schema if available, otherwise fetch column info for enum parsing

TablePro/Views/Main/MainContentView.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,7 @@ struct MainContentView: View {
277277
.onChange(of: tabManager.selectedTabId) { _, newTabId in
278278
pendingTabSwitch?.cancel()
279279
pendingTabSwitch = Task { @MainActor in
280-
// Let other onChange handlers (tabs, resultColumns) settle first
281-
try? await Task.sleep(for: .milliseconds(16))
280+
await Task.yield()
282281
guard !Task.isCancelled else { return }
283282
handleTabSelectionChange(from: previousSelectedTabId, to: newTabId)
284283
previousSelectedTabId = newTabId

0 commit comments

Comments
 (0)