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 @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Terminal PTY writes retry on `EINTR` instead of treating any non-positive return as "we're done". A signal mid-write previously truncated the input the user typed; the loop would exit silently and the keystrokes were partially sent. The new path retries on `EINTR`, logs the byte position and errno on any other non-recoverable failure, and reports a return value of zero distinctly so the cause is visible in Console.
- MCP HTTP transport no longer writes an empty body when JSON encoding of the response envelope fails. Five sites in `MCPInboundExchange` and `MCPHttpRequestRouter` previously fell back to `Data()`, sending zero bytes to the client which then saw a protocol violation and either disconnected or hung. The encode-failure paths now log and substitute a static `{"jsonrpc":"2.0","id":null,"error":{"code":-32603,"message":"internal_error"}}` envelope; the pairing-exchange success path falls back to `internalServerError` with a small JSON error body.
- Closing the last window for a connection no longer flashes "Connection lost" and clears that session's cached schema. The health monitor's reconnect loop previously transitioned to `.failed` when its task was cancelled (clean teardown), and the session-level observer treated `.failed` as a real error: it overwrote `session.status` with the lost-connection alert and called `clearCachedData()`. The `.failed` state was never reachable through any non-cancellation path, so it has been removed from `HealthState` along with the orphaned `resetAfterManualReconnect` reset method that only existed to reset from it. Cancellation now logs cleanly and returns without touching session state.
- Connecting one window no longer triggers `fetchTables()` in every other window for an unrelated connection. Three subscribers to `AppEvents.databaseDidConnect` (`MainContentCommandActions`, `MainContentCoordinator`'s plugin-driver retry, and `ERDiagramViewModel.waitForConnection`) discarded the `connectionId` payload that was already on the event and acted on their own connection. With three windows open against three different databases, opening a fourth caused nine `fetchTables()` calls; if the existing remotes were slow, every window stalled until the new connection's broadcast was processed. Subscribers now compare the payload's `connectionId` against their own and skip otherwise. The ER diagram view also stops resuming `waitForConnection` when an unrelated database connects, which previously made the diagram fail with "No database connection" instead of waiting for its own driver.
- Result-grid cells on rows marked for deletion keep their dropdown / date / JSON / blob chevron visible at reduced opacity instead of hiding it, so the cell type is still legible while clearly inactive. Click on the dimmed chevron is a no-op; FK arrow navigation is unchanged. Matches the macOS HIG "disabled appearance" guideline.
- Foreign key navigation from a table with unsaved edits opens the referenced table in a new window tab to preserve the edit buffer. Closing that new tab no longer wipes the original tab's data grid. Previously the new tab's teardown broadcast a connection-scoped event that other coordinators on the same connection received, causing them to release their cell data.
- Tables sidebar refreshes automatically after a successful SQL import; the refresh notification now fires after the success sheet's dismissal animation, so the main window is key when the observer runs (#1114)
Expand Down
4 changes: 3 additions & 1 deletion TablePro/ViewModels/ERDiagramViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,11 @@ final class ERDiagramViewModel {
continuation.resume()
}

let targetId = self.connectionId
let cancellable = AppEvents.shared.databaseDidConnect
.receive(on: RunLoop.main)
.sink { _ in
.sink { payload in
guard payload.connectionId == targetId else { return }
resumeOnce()
}
cancellableBox.withLock { $0 = cancellable }
Expand Down
5 changes: 4 additions & 1 deletion TablePro/Views/Main/MainContentCommandActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,10 @@ final class MainContentCommandActions {
private func setupDatabaseBroadcastObservers() {
AppEvents.shared.databaseDidConnect
.receive(on: RunLoop.main)
.sink { [weak self] _ in self?.handleDatabaseDidConnect() }
.sink { [weak self] payload in
guard let self, payload.connectionId == self.connection.id else { return }
self.handleDatabaseDidConnect()
}
.store(in: &eventCancellables)
}

Expand Down
5 changes: 3 additions & 2 deletions TablePro/Views/Main/MainContentCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -430,9 +430,10 @@ final class MainContentCoordinator {
if changeManager.pluginDriver == nil {
pluginDriverCancellable = AppEvents.shared.databaseDidConnect
.receive(on: RunLoop.main)
.sink { [weak self] _ in
.sink { [weak self] payload in
guard let self, payload.connectionId == self.connection.id else { return }
Task {
self?.setupPluginDriver()
self.setupPluginDriver()
}
}
}
Expand Down
Loading