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
7 changes: 5 additions & 2 deletions TablePro/Models/Query/TabSessionRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ final class TabSessionRegistry {
sessions[tabId]?.isEvicted ?? false
}

/// Evict row data for a tab. Sets `isEvicted = true` and bumps `loadEpoch`
/// so SwiftUI's `.task(id:)` lazy-load re-fires.
/// Evict row data for a tab. Sets `isEvicted = true`, clears rows, and
/// bumps the session's `loadEpoch`. Note that SwiftUI's `.task(id:)` keys
/// on `QueryTab.loadEpoch` (the value-type tab in `tabManager.tabs`), not
/// on `TabSession.loadEpoch` — so callers that need lazy-load to re-fire
/// must also bump `tabManager.tabs[i].loadEpoch` separately.
///
/// Returns early if the session has no rows to evict — calling `evict` on
/// a tab with empty rows is a no-op (no `isEvicted` change, no epoch bump),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ extension MainContentCoordinator {

for entry in toEvict {
tabSessionRegistry.evict(for: entry.tab.id)
if let index = tabManager.tabs.firstIndex(where: { $0.id == entry.tab.id }) {
// Bump QueryTab.loadEpoch to invalidate the `.task(id:)` key in
// MainEditorContentView, so the next selection of this tab
// triggers lazy-load. The session-side bump is not observed.
tabManager.tabs[index].loadEpoch &+= 1
}
}
Self.lifecycleLogger.debug(
"[switch] evictInactiveTabs evicted=\(toEvict.count) keptInactive=\(maxInactiveLoaded) elapsedMs=\(Int(Date().timeIntervalSince(start) * 1_000))"
Expand Down
7 changes: 6 additions & 1 deletion TablePro/Views/Main/MainContentCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,13 @@ final class MainContentCoordinator {
/// Background tabs are re-fetched automatically when selected.
func evictInactiveRowData() {
let selectedId = tabManager.selectedTabId
for tab in tabManager.tabs where tab.id != selectedId && !tab.pendingChanges.hasChanges {
for (index, tab) in tabManager.tabs.enumerated()
where tab.id != selectedId && !tab.pendingChanges.hasChanges {
tabSessionRegistry.evict(for: tab.id)
// Mirror the session's epoch bump back to the QueryTab so the
// `.task(id:)` in MainEditorContentView re-fires lazy-load on
// re-selection. The session bump alone is not observed by SwiftUI.
tabManager.tabs[index].loadEpoch &+= 1
}
}

Expand Down
7 changes: 6 additions & 1 deletion TablePro/Views/Results/DataGridView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,12 @@ struct DataGridView: NSViewRepresentable {
context.coordinator.tableRowsProvider = tableRowsProvider
context.coordinator.tableRowsMutator = tableRowsMutator
context.coordinator.sortedIDs = sortedIDs
context.coordinator.updateCache()
// Intentionally do not prime cachedRowCount/cachedColumnCount here.
// They represent what NSTableView has actually rendered. Leaving them
// at 0 ensures the first `updateNSView` detects a structure change
// and triggers `reloadData()` — without this, a recreated grid (e.g.
// after a Structure/JSON tab toggle) finds the cache already matching
// the registry rows and skips the reload, leaving the table empty.
context.coordinator.syncDisplayFormats(displayFormats)
context.coordinator.delegate = delegate
delegate?.dataGridAttach(tableViewCoordinator: context.coordinator)
Expand Down
Loading