Skip to content

fix(datagrid): isolate per-tab state in structure view#1142

Merged
datlechin merged 2 commits into
mainfrom
fix/structure-tab-state-pollution
May 9, 2026
Merged

fix(datagrid): isolate per-tab state in structure view#1142
datlechin merged 2 commits into
mainfrom
fix/structure-tab-state-pollution

Conversation

@datlechin

Copy link
Copy Markdown
Member

Fixes #1110

Root cause

Two distinct bugs from the same architectural assumption: a single DataGridView instance was being reused across the Columns / Indexes / Foreign Keys sub-tabs of the structure view, but state scoped to "this DataGridView" was bleeding between tabs.

Bug 1: stale data on tab switch

DataGridCoordinator.displayCache is keyed by RowIDKey(row.id). TableRows.from(queryRows:...) mints IDs as .existing(0), .existing(1), ... by array index, so row 0 of Columns and row 0 of Indexes share the same key. When SwiftUI re-renders to swap datasets between tabs, updateNSView rebuilt identitySchema and reloaded the table, but never flushed displayCache. Each cell call hit the cache and rendered the previous tab's value.

The DDL → back fix worked by accident: routing through a non-grid view forces SwiftUI to tear down the NSTableView, so makeNSView built a fresh coordinator with an empty cache.

applyFullReplace() already invalidates this cache, but it only fires on change-manager Delta events. Tab switches go through SwiftUI re-render and bypass that path entirely.

Bug 2: badge counts disappear on refresh

onRefreshData cleared loadedTabs then only re-fetched columns and the active tab. tabLabel(for:) shows a count only when the tab is in loadedTabs, so the two non-active tabs lost their badges. loadInitialData loads all three by contrast, which is why counts appear correct on first open.

Cross-tab state pollution

While tracing the cache bug, the same single-instance assumption showed up in two more places:

  • selectedRows (@State on TableStructureView) carried selection across tabs even though row N has no shared meaning between Columns and Indexes.
  • structureColumnLayout (@State) was a single ColumnLayoutState, so DataGridColumnPool.reconcile saw willRestoreWidths = true for the new tab, looked up Indexes column names in the Columns widths dict, missed, and fell back to a hardcoded width of 100 instead of running the auto-fit widthCalculator.

Fixes

  1. DataGridCoordinator.rebuildColumnMetadataCache now returns whether the schema changed and invalidates displayCache when it does. DataGridView.updateNSView rolls a schema change into needsFullReload. This generalizes — query tabs that re-execute with a different schema benefit too.
  2. TableStructureView+DataLoading.onRefreshData re-fetches columns + indexes + foreign keys (when supported), so badge counts persist across refresh just like initial load.
  3. onSelectedTabChanged resets selectedRows = [].
  4. structureColumnLayout becomes [StructureTab: ColumnLayoutState] with a per-tab Binding, so each sub-tab keeps its own widths, order, and hidden columns without polluting siblings.

Test plan

  • PostgreSQL table with both columns and indexes: open Structure → Indexes. Indexes data should show on first display, no need to detour through DDL.
  • Switch back to Columns. Columns data should be correct.
  • Resize a column in Columns tab, switch to Indexes, resize there, switch back to Columns. Each tab should remember its own widths.
  • Select row 2 in Columns, switch to Indexes. Indexes should open with no selection, not row 2 highlighted.
  • Open a table with all three tabs populated. Confirm all three badges show counts. Hit Cmd+R / refresh. All three counts should still show after the reload.
  • Click DDL tab and back to Columns: still works (regression check).
  • Run a query in a query tab, then run a different query with a different schema in the same tab. Result grid should show new data, not stale rows from the previous query (this is the latent benefit of fix 1).

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit 1aa9f03 into main May 9, 2026
2 checks passed
@datlechin datlechin deleted the fix/structure-tab-state-pollution branch May 9, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lỗi hiển thị structure

1 participant