Commit 7df36b6
refactor(perf): datagrid signal architecture and row data extraction (#919)
* refactor(perf): decouple persistence and inspector from tabs writes
Phase A: persistence
- Add tabStructureVersion on QueryTabManager, bumped only on tab add/remove/rename/replaceTabContent and on user query dispatch.
- Replace .onChange(of: tabManager.tabs) with .onChange(of: tabManager.tabStructureVersion). Remove handleTabsChange; add handleStructureChange.
- Remove per-keystroke saveLastQuery from queryTextBinding; remove now-unused saveLastQuery on TabPersistenceCoordinator.
Phase B: inspector
- Move updateSidebarEditState inside the 50ms inspector debounce so per-row-click N×M field configuration coalesces.
- Drop coordinator.tableMetadata?.tableName from inspectorTrigger; metadata-driven inspector refresh now flows through the existing .task(id: tableName) modifier with an explicit scheduleInspectorUpdate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(perf): split resultVersion and route row ops through delegate deltas
- Rename QueryTab.resultVersion to schemaVersion (column shape only). ResultSet keeps its own resultVersion for result-set tabs.
- Add dataGridDidInsertRows/dataGridDidRemoveRows/dataGridDidReplaceAllRows to DataGridViewDelegate. DataTabGridDelegate forwards to the table view coordinator.
- TableViewCoordinator gains applyInsertedRows/applyRemovedRows/applyFullReplace that call NSTableView.insertRows / removeRows / reloadData directly, refresh visual state, and invalidate identity so subsequent updateNSView passes don't short-circuit.
- Row ops (add, delete, duplicate, undo, redo, paste) drive the new delta path instead of bumping a counter. RowOperationsManager.deleteSelectedRows now returns physicallyRemovedIndices so soft-deletes stay on the existing reloadVersion path while inserted-row removals get NSTableView animation.
- querySortCache invalidates per tab on row-count changes.
- Sort completion routes a full reload via the delegate; remove the redundant changeManager.reloadVersion bumps.
- applyMultiStatementResults: keep the schemaVersion bump, drop the redundant reloadVersion bump.
- Pin toggle no longer mutates a tab counter; TabDisplayState.resultSets is already @observable.
- Wire MainContentCoordinator.dataTabDelegate weak ref in MainEditorContentView.onAppear / clear in teardown.
* refactor(perf): broaden DataGridIdentity and stop re-wiring delegate per render
- Extend DataGridIdentity with tabType, tableName, and primaryKeyColumns so updateNSView's identity guard catches all configuration fields that should drive a column rebuild. Drop the legacy initializer.
- Move DataTabGridDelegate property assignments out of the dataGridView(tab:) body. Stable refs are set once in onAppear; isEditable / isView / tableName / safeModeLevel drive a focused refresh via .onChange.
- DataGridConfiguration was already Equatable; no change.
* refactor(perf): move row data out of QueryTab into RowDataStore
QueryTab is now pure metadata. Row data (RowBuffer) lives in a RowDataStore keyed by tab.id, owned by MainContentCoordinator. Mutations to row data no longer flow through SwiftUI's @observable tracking on tabManager.tabs.
- New RowDataStore (@mainactor, @observable, store @ObservationIgnored): buffer(for:), existingBuffer(for:), setBuffer, removeBuffer, evict, evictAll(except:), tearDown.
- Drop rowBuffer and the resultRows/resultColumns/columnTypes/columnDefaults/columnForeignKeys/columnEnumValues/columnNullable proxy properties from QueryTab. Update == and init(from:) accordingly.
- Migrate every read and write site (QueryHelpers, MultiStatement, LoadMore, RowOperations, Filtering, FKNavigation, Navigation, Discard, SaveChanges, SidebarActions, SidebarSave, TabSwitch, WindowLifecycle, CommandActions, plus inspector and editor views) to use coordinator.rowDataStore.buffer(for: tab.id).
- replaceTabContent on QueryTabManager no longer resets a per-tab buffer; callers reset via setBuffer.
- Eviction routes through rowDataStore.evictAll(except:); teardown clears the store.
- Tests updated to read row data from the coordinator's rowDataStore.
* fix(perf): correct cachedRowCount ordering and remove tabStructureVersion double-bump
- DataGridCoordinator.applyInsertedRows / applyRemovedRows / applyFullReplace now call updateCache() before mutating the table view, so numberOfRows(in:) returns the post-mutation count when NSTableView synchronously validates.
- QueryTabManager: drop the explicit tabStructureVersion bump from each add* method. The didSet on tabs already bumps when the ID array changes, and the explicit bump made every add count as 2 increments. replaceTabContent keeps its bump because same-id mutation does not change the ID array.
* docs: remove internal datagrid refactor design doc
* test(perf): cover refactor with unit tests, extract RowDeltaApplying protocol
- New protocol RowDeltaApplying captures the row-delta surface (applyInsertedRows/applyRemovedRows/applyFullReplace) so DataTabGridDelegate can be tested with a fake without standing up an NSTableView. TableViewCoordinator conforms via empty extension.
- DataTabGridDelegate.tableViewCoordinator switches to (any RowDeltaApplying)?.
- Unit tests added: RowDataStore (10 cases), RowProviderCache (9 cases), QueryTabManager.tabStructureVersion semantics (10 cases), DataTabGridDelegate forwarding (4 cases).
- Drop a stale comment in QueryTabManager.init() (CLAUDE.md no-comments rule).
- Guard the selectedTabId .onChange against caching a provider over an evicted RowBuffer.
* fix(perf): use existingBuffer in eviction guard, add reorder test
- MainEditorContentView: the selectedTabId onChange and onAppear hooks called rowDataStore.buffer(for:) inside the eviction guard. buffer(for:) creates an empty RowBuffer on miss, so the guard could leak ghost entries (e.g. on first switch to a tab that has not loaded yet, or on view re-appear after teardown). Use existingBuffer(for:) so the guard only runs when a buffer actually exists, and skip cacheRowProvider for fresh / evicted tabs.
- TabStructureVersionTests: cover drag-reorder (tabs.swapAt) so the didSet ID-array-change check is regression-tested.
* refactor: drop dead ResultSet.resultVersion + saveLastQuery feature, fix tests
Dead code removal:
- ResultSet.resultVersion was write-only (3 sites set it, 0 sites read). Field gone, 5 dead writes removed in QueryHelpers, QueryParameters, MultiStatement, and two LoadMore paths.
- TabPersistenceCoordinator.saveLastQuery was deleted in the persistence-decoupling phase but loadLastQuery was left orphaned. Drop loadLastQuery on the coordinator, drop saveLastQuery / loadLastQuery / lastQueryFileURL / lastQueryDirectory on TabDiskActor, drop the legacy lastQuery migration loop and key prefix. Per-keystroke crash recovery is gone; user-dispatched queries still bump tabStructureVersion so committed work persists.
Test fixes:
- TriggerStructTests: drop metadataTableName, rename resultVersion -> schemaVersion. Old signature was broken by phases B and C.
- DataGridIdentityTests: convert hiddenColumns: argument to configuration: DataGridConfiguration. Phase D added tabType / tableName / primaryKeyColumns to the identity through the configuration init.
- CommandActionsDispatchTests: drop selectedRowIndices binding, pass coordinator.selectionState.
- MainStatusBarLayoutTests: pass StatusBarSnapshot instead of QueryTab.
- SaveCompletionTests: drop selectedRowIndices inout from row op calls; assert on coordinator.selectionState.indices instead.
- RowOperationsManagerTests: deleteSelectedRows now returns DeleteRowsResult; assert on .nextRowToSelect.
- AnyChangeManagerTests: drop dataManager: / structureManager: argument labels (single any ChangeManaging init), and switch the changes property to rowChanges.
- TabDiskActorTests + TabPersistenceCoordinatorTests: drop the lastQuery round-trip / nil / empty / whitespace / 500KB-cap tests since the feature is gone.
* test(perf): cover physicallyRemovedIndices, drop stale TabDiskActor doc
- Add 4 RowOperationsManagerTests cases for the DeleteRowsResult.physicallyRemovedIndices contract: empty selection returns empty, deleting only existing rows leaves it empty (soft-delete via change manager), deleting inserted rows reports indices descending, mixed selection reports only the inserted indices.
- Drop the stale 'Last-query strings are stored in a sibling directory' doc comment on TabDiskActor; the directory and feature are gone.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 6dd9e44 commit 7df36b6
52 files changed
Lines changed: 1279 additions & 721 deletions
File tree
- TableProTests
- Core
- ChangeTracking
- Services
- Query
- Storage
- Models/Query
- Views
- Main
- Child
- Results
- TablePro
- Core
- Services
- Infrastructure
- Query
- Storage
- Models/Query
- Views
- Main
- Child
- Extensions
- Results
- Structure
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
27 | 33 | | |
28 | 34 | | |
29 | 35 | | |
| |||
Lines changed: 0 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
120 | 120 | | |
121 | 121 | | |
122 | 122 | | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | 123 | | |
139 | 124 | | |
140 | 125 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
102 | | - | |
103 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
104 | 106 | | |
105 | 107 | | |
106 | 108 | | |
| |||
118 | 120 | | |
119 | 121 | | |
120 | 122 | | |
121 | | - | |
122 | | - | |
123 | | - | |
| 123 | + | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
130 | | - | |
131 | | - | |
132 | 130 | | |
133 | 131 | | |
134 | 132 | | |
135 | | - | |
136 | 133 | | |
137 | 134 | | |
138 | 135 | | |
139 | 136 | | |
140 | | - | |
141 | 137 | | |
142 | | - | |
| 138 | + | |
143 | 139 | | |
144 | | - | |
| 140 | + | |
145 | 141 | | |
| 142 | + | |
146 | 143 | | |
147 | | - | |
| 144 | + | |
148 | 145 | | |
149 | | - | |
| 146 | + | |
150 | 147 | | |
151 | | - | |
| 148 | + | |
152 | 149 | | |
153 | | - | |
| 150 | + | |
154 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
155 | 157 | | |
156 | 158 | | |
157 | 159 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | 23 | | |
27 | 24 | | |
28 | 25 | | |
| |||
31 | 28 | | |
32 | 29 | | |
33 | 30 | | |
34 | | - | |
35 | 31 | | |
36 | 32 | | |
37 | 33 | | |
38 | 34 | | |
39 | 35 | | |
40 | | - | |
41 | 36 | | |
42 | 37 | | |
43 | 38 | | |
44 | 39 | | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
| 40 | + | |
| 41 | + | |
50 | 42 | | |
51 | 43 | | |
52 | 44 | | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
62 | 49 | | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
| 50 | + | |
67 | 51 | | |
68 | 52 | | |
69 | 53 | | |
| |||
111 | 95 | | |
112 | 96 | | |
113 | 97 | | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | 98 | | |
161 | 99 | | |
162 | 100 | | |
| |||
217 | 155 | | |
218 | 156 | | |
219 | 157 | | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
224 | 158 | | |
225 | 159 | | |
226 | | - | |
| 160 | + | |
227 | 161 | | |
228 | 162 | | |
229 | | - | |
| 163 | + | |
230 | 164 | | |
231 | 165 | | |
232 | 166 | | |
233 | 167 | | |
234 | 168 | | |
235 | 169 | | |
236 | 170 | | |
237 | | - | |
238 | 171 | | |
239 | 172 | | |
240 | 173 | | |
241 | | - | |
242 | 174 | | |
243 | 175 | | |
244 | 176 | | |
| |||
255 | 187 | | |
256 | 188 | | |
257 | 189 | | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | 190 | | |
275 | 191 | | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
| 192 | + | |
| 193 | + | |
280 | 194 | | |
281 | 195 | | |
282 | 196 | | |
| |||
0 commit comments