Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Internal: thread `KeychainHelper` through `ConnectionStorage`, `SSHProfileStorage`, `AIKeyStorage`, and `LicenseStorage` via init (default `.shared`). The 24 raw `KeychainHelper.shared` reads inside those classes are gone, matching the existing injected-dependency pattern (`syncTracker`, `appSettings`).
- Internal: extend `AppServices` with `queryHistoryManager`, `dateFormattingService`, `copilotService`, and `mcpServerManager`. `AppSettingsManager` now takes its eight cross-singleton dependencies (`AppSettingsStorage`, `ThemeEngine`, `SyncChangeTracker`, `AppEvents`, `DateFormattingService`, `QueryHistoryManager`, `MCPServerManager`, `CopilotService`) via init with `.shared` defaults. The 32 raw `.shared` reads inside `AppSettingsManager` are gone, including the four `Task { ... }` capture closures that previously dialed `CopilotService.shared` / `MCPServerManager.shared` mid-`didSet`. The 49 caller-side `AppSettingsManager.shared` reads are unchanged in this PR; they will be threaded as their owners adopt `services` in subsequent waves.
- Internal: `AppEvents.connectionUpdated` payload changes from `Void` to `UUID?`. Single-connection senders (`ConnectionFormCoordinator`, sample-DB launcher, per-connection iCloud-sync toggle) now pass the affected id; bulk senders (sync pull, multi-import, multi-select sync toggle) pass `nil`. The current `WelcomeViewModel` subscriber refreshes on every event regardless, but per-connection subscribers added in the future can filter by id without re-shaping the bus.
- Internal: `AppEvents.sqlFavoritesDidUpdate` payload changes from `Void` to `UUID?` and the per-connection subscribers (`ConnectionDataCache`, `SQLEditorView`) now filter on it. Previously, editing a favorite for connection A in one window forced `ConnectionDataCache` for every other open connection to refetch its favorite list and `SQLEditorView` for every other window to refresh its keyword map. The senders pass `favorite.connectionId` / `folder.connectionId` for adds and updates, and `nil` for deletes (where the affected connection isn't easily recoverable post-delete). `nil` payloads still trigger a refresh on every subscriber, preserving correctness for cross-connection favorites and bulk deletes.
- Internal: extend `AppServices` with `tagStorage`, `sshProfileStorage`, `licenseManager`, `conflictResolver`, and `syncMetadataStorage`. `SyncCoordinator` now takes `services: AppServices` in init (default `.live`); 34 raw `.shared` reads of those types inside `SyncCoordinator` are routed through `services.*`.
- Internal: Redis sidebar key tree uses SwiftUI `OutlineGroup` instead of recursive `DisclosureGroup` + `ForEach` wrapped in `AnyView`. Expansion state is now managed natively per branch identifier; the explicit `expandedPrefixes` set is gone.
- Result-grid cells render via direct `draw(_:)` on a layer-backed `NSView` instead of an `NSTableCellView` wrapping an `NSTextField` plus an `NSButton` accessory. Per cell during scroll there is no Auto Layout solving, no `NSTextField` re-layout, and no `NSButton` tracking-area work. Editing for plain-text columns now opens the overlay editor (the same surface previously used for multi-line cells) rather than an inline text field.
Expand Down
7 changes: 6 additions & 1 deletion TablePro/Core/Events/AppEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ final class AppEvents {

let queryHistoryDidUpdate = PassthroughSubject<Void, Never>()

let sqlFavoritesDidUpdate = PassthroughSubject<Void, Never>()
/// SQL favorites or favorite folders changed.
/// Payload is the affected connection's id, or `nil` for cross-connection
/// favorites (`favorite.connectionId == nil`) and bulk operations
/// (multi-favorite delete) where the sender doesn't track a single id.
/// Per-connection subscribers should refresh on `payload == nil || payload == self.connectionId`.
let sqlFavoritesDidUpdate = PassthroughSubject<UUID?, Never>()

let linkedFoldersDidUpdate = PassthroughSubject<Void, Never>()

Expand Down
18 changes: 9 additions & 9 deletions TablePro/Core/Storage/SQLFavoriteManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,31 @@ internal final class SQLFavoriteManager: @unchecked Sendable {
func addFavorite(_ favorite: SQLFavorite) async -> Bool {
let result = await storage.addFavorite(favorite)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: favorite.connectionId)
}
return result
}

func updateFavorite(_ favorite: SQLFavorite) async -> Bool {
let result = await storage.updateFavorite(favorite)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: favorite.connectionId)
}
return result
}

func deleteFavorite(id: UUID) async -> Bool {
let result = await storage.deleteFavorite(id: id)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: nil)
}
return result
}

func deleteFavorites(ids: [UUID]) async {
let result = await storage.deleteFavorites(ids: ids)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: nil)
}
}

Expand All @@ -68,23 +68,23 @@ internal final class SQLFavoriteManager: @unchecked Sendable {
func addFolder(_ folder: SQLFavoriteFolder) async -> Bool {
let result = await storage.addFolder(folder)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: folder.connectionId)
}
return result
}

func updateFolder(_ folder: SQLFavoriteFolder) async -> Bool {
let result = await storage.updateFolder(folder)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: folder.connectionId)
}
return result
}

func deleteFolder(id: UUID) async -> Bool {
let result = await storage.deleteFolder(id: id)
if result {
postUpdateNotification()
postUpdateNotification(connectionId: nil)
}
return result
}
Expand Down Expand Up @@ -150,9 +150,9 @@ internal final class SQLFavoriteManager: @unchecked Sendable {

// MARK: - Notifications

private func postUpdateNotification() {
private func postUpdateNotification(connectionId: UUID?) {
Task { @MainActor in
AppEvents.shared.sqlFavoritesDidUpdate.send(())
AppEvents.shared.sqlFavoritesDidUpdate.send(connectionId)
}
}
}
6 changes: 5 additions & 1 deletion TablePro/ViewModels/ConnectionDataCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ internal final class ConnectionDataCache {

AppEvents.shared.sqlFavoritesDidUpdate
.receive(on: RunLoop.main)
.sink { [weak self] _ in self?.scheduleRefresh() }
.sink { [weak self] payload in
guard let self else { return }
guard payload == nil || payload == self.connectionId else { return }
self.scheduleRefresh()
}
.store(in: &cancellables)

AppEvents.shared.linkedSQLFoldersDidUpdate
Expand Down
5 changes: 4 additions & 1 deletion TablePro/Views/Editor/SQLEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ struct SQLEditorView: View {
}
AppEvents.shared.sqlFavoritesDidUpdate
.receive(on: RunLoop.main)
.sink { _ in refresh() }
.sink { payload in
guard payload == nil || payload == connectionId else { return }
refresh()
}
.store(in: &favoritesCancellables)
AppEvents.shared.linkedSQLFoldersDidUpdate
.receive(on: RunLoop.main)
Expand Down
Loading