Conversation
…s single source of truth
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f709bbe696
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| guard !activeTabIds.contains(tab.id), | ||
| tab.execution.lastExecutedAt != nil, |
There was a problem hiding this comment.
Exclude selected tab from deferred eviction candidates
handleTabChange now defers eviction with Task { ... }, so rapid tab switches can queue eviction jobs that run after selection has changed; this candidate guard only checks the captured activeTabIds and does not exclude the current selectedTabId, so a stale task can evict the tab the user is actively viewing and clear its rows unexpectedly. Add a guard against tab.id == tabManager.selectedTabId inside the eviction filter (or cancel/coalesce prior eviction tasks) to prevent active-tab data loss during fast navigation.
Useful? React with 👍 / 👎.
| } | ||
| .task(id: TabLoadKey( | ||
| tabId: tabManager.selectedTabId, | ||
| loadEpoch: tabManager.selectedTab?.loadEpoch ?? 0 |
There was a problem hiding this comment.
Drive TabLoadKey from the epoch that eviction actually updates
The lazy-load task key uses tabManager.selectedTab?.loadEpoch, but eviction increments TabSession.loadEpoch in TabSessionRegistry and never propagates that change back to QueryTab.loadEpoch; as a result, the epoch portion of this task ID never changes after eviction, so .task(id:) will not re-fire when rows are evicted without a tab-ID change. This breaks the intended eviction-triggered reload path and can leave an evicted tab blank until a manual refresh.
Useful? React with 👍 / 👎.
…dupe stale-isExecuting clear, document invariants
Summary
TabSession(@Observable @MainActorclass) instead of being scattered across 13 stores. TheMainContentCoordinatorgod-object shrinks substantially; query execution is extracted into a focusedQueryExecutorservice; lifecycle work moves to Apple-documented hooks.selectTab(number:)now usesNSWindowTabGroup.selectedWindowwrapped inNSAnimationContext.runAnimationGroup(duration: 0)so AppKit doesn't queue a CAAnimation per press.windowDidBecomeKeyis now the lightweight Apple-documented contract — lazy-load runs through SwiftUI's.task(id:)view-appearance lifecycle instead.What landed (per phase)
The refactor was done as a strangler-fig migration tracked across 5 logical phases. See
docs/architecture/tab-subsystem-rewrite.mdfor the full design doc with Apple-source citations and the migration plan.TabSession.swift(foundation type), design doc, testsTabSessionRegistry(UUID→TabSession bridge),loadEpochfield on QueryTab + TabSession,TableRowsStorebecomes facadeQueryExecutor.swift,QuerySqlParser.swift, 22 testsMainContentCoordinatordecomposedselectTab(number:)usesNSWindowTabGroup.selectedWindow+NSAnimationContext;lazyLoadCurrentTabIfNeededon coordinator;.task(id: TabLoadKey)onMainEditorContentView; deferred eviction sortonWindowBecameKey/onTeardown/onWindowWillClosestored closures,lastResignKeyDatefield, 200msisMenuBounceguardTabSessionRegistryTableRowsStore.swift(75 lines)ColumnVisibilityPersistence.swift,MainContentCoordinator+ColumnVisibility.swiftrewrittenModels/UI/ColumnVisibilityManager.swift(83 lines)MainContentCoordinator+FilterState.swift(per-tab helpers), TabFilterState now single-source-of-truth onQueryTab.filterStatemirrored toTabSessionFilterStateManagerclass (350 lines) — keptFilterLogicModeenum +TabFilterStateextMainContentCoordinator+MongoDB.swift(empty)Cmd+Number lag bug
The user-visible "switching continues after key release" symptom is fixed in PR4 by the
NSAnimationContext.runAnimationGroup(duration: 0)wrapping (Apple-documented anti-animation idiom — not a custom debouncer/coalescer). Per-switch AppKit overhead remains because TablePro intentionally uses one NSWindow per tab (.tabbingMode = .preferred) for native integration; this is an accepted platform trade-off documented indocs/architecture/tab-subsystem-rewrite.mdD2.State migration: before → after
FilterStateManager(3 copies: live, snapshot, UserDefaults)QueryTab.filterStatemirrored toTabSession.filterState;FilterStatePersistenceenum for UserDefaultsColumnVisibilityManagerglobal, swap on tab switchQueryTab.columnLayout.hiddenColumnsmirrored toTabSession.columnLayout;ColumnVisibilityPersistenceenum for UserDefaultsTableRowsStore[id]keyed by tab UUIDTabSession.tableRows, accessed viaTabSessionRegistryTabSession.loadEpoch(bumped on eviction; drives.task(id:)re-fire)onWindowBecameKey,onTeardown,onWindowWillClosesyncSidebarToSelectedTabmethodOut of scope (future work)
ConnectionToolbarStateper-tab semantics — currently per-connection, migrating to per-tab would change UX (toolbar state would differ between tabs in same window). UX decision needed first.MainContentCoordinatorinto a separateTabGroupCoordinatorper the original design doc target — coordinator's responsibilities are now scoped properly via the per-domain helpers; deeper split deferred.Test plan