Skip to content

Commit 8fa0033

Browse files
committed
refactor(datagrid): drop reloadVersion / lastIdentity counter bridge (Phase D)
Cell edits, row inserts and removals now drive NSTableView through Delta dispatch directly, so the SwiftUI counter-driven reload path is redundant. Removes: - lastReapplyVersion (already dead) - lastReloadVersion field on TableViewCoordinator - versionChanged branch in reloadAndSyncSelection (delta path already covers this) - reloadVersion field from DataGridIdentity composition - consumeChangedRowIndices on AnyChangeManager / DataChangeManager / StructureChangeManager - changedRowIndices accounting on PendingChanges and StructureChangeManager (14 insert calls and the consume/clear sites) To keep the modified-cell yellow marker correct without the SwiftUI rerun, commitCellEdit and TableViewCoordinator.applyDelta(.cellChanged / .cellsChanged) now call rebuildVisualStateCache before the controller dispatch. This was previously implicit via the versionChanged branch's second reload. Net -259 LOC. Build green, smoke-tested cell edit/undo, add-row, table switch.
1 parent 9ef7f19 commit 8fa0033

13 files changed

Lines changed: 7 additions & 265 deletions

.claude/scheduled_tasks.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"sessionId":"3b48a3d2-8229-4b5e-bf47-30eab596d1ab","pid":18791,"procStart":"Wed Apr 29 01:03:47 2026","acquiredAt":1777434861839}

TablePro/Core/ChangeTracking/AnyChangeManager.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ protocol ChangeManaging: AnyObject {
1919
)
2020
func undoRowDeletion(rowIndex: Int)
2121
func undoRowInsertion(rowIndex: Int)
22-
func consumeChangedRowIndices() -> Set<Int>
2322
}
2423

2524
@Observable
@@ -63,10 +62,6 @@ final class AnyChangeManager {
6362
wrapped.undoRowInsertion(rowIndex: rowIndex)
6463
}
6564

66-
func consumeChangedRowIndices() -> Set<Int> {
67-
wrapped.consumeChangedRowIndices()
68-
}
69-
7065
init(_ manager: any ChangeManaging) {
7166
self.wrapped = manager
7267
}

TablePro/Core/ChangeTracking/DataChangeManager.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,6 @@ final class DataChangeManager: ChangeManaging {
7878
undoManager.setActionName(actionName)
7979
}
8080

81-
// MARK: - Helper Methods
82-
83-
func consumeChangedRowIndices() -> Set<Int> {
84-
pending.consumeChangedRowIndices()
85-
}
86-
8781
// MARK: - Configuration
8882

8983
func clearChanges() {

TablePro/Core/ChangeTracking/PendingChanges.swift

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ struct PendingChanges: Equatable {
1717
private(set) var insertedRowIndices: Set<Int> = []
1818
private(set) var modifiedCells: [Int: Set<Int>] = [:]
1919
private(set) var insertedRowData: [Int: [String?]] = [:]
20-
private(set) var changedRowIndices: Set<Int> = []
2120

2221
private var changeIndex: [RowChangeKey: Int] = [:]
2322

@@ -77,7 +76,6 @@ struct PendingChanges: Equatable {
7776
if let insertIdx = changeIndex[RowChangeKey(rowIndex: rowIndex, type: .insert)] {
7877
updateInsertedCell(at: insertIdx, columnIndex: columnIndex,
7978
columnName: columnName, newValue: newValue)
80-
changedRowIndices.insert(rowIndex)
8179
return true
8280
}
8381

@@ -93,7 +91,6 @@ struct PendingChanges: Equatable {
9391
changeIndex[updateKey] = changes.count - 1
9492
modifiedCells[rowIndex, default: []].insert(columnIndex)
9593
}
96-
changedRowIndices.insert(rowIndex)
9794
return true
9895
}
9996

@@ -103,7 +100,6 @@ struct PendingChanges: Equatable {
103100
modifiedCells.removeValue(forKey: rowIndex)
104101
appendChange(RowChange(rowIndex: rowIndex, type: .delete, originalRow: originalRow))
105102
deletedRowIndices.insert(rowIndex)
106-
changedRowIndices.insert(rowIndex)
107103
}
108104

109105
mutating func recordRowInsertion(rowIndex: Int, values: [String?]) {
@@ -114,7 +110,6 @@ struct PendingChanges: Equatable {
114110
insertedRowData[rowIndex] = values
115111
appendChange(RowChange(rowIndex: rowIndex, type: .insert, cellChanges: []))
116112
insertedRowIndices.insert(rowIndex)
117-
changedRowIndices.insert(rowIndex)
118113
}
119114

120115
// MARK: - Mutate (cancelling pending edits)
@@ -123,7 +118,6 @@ struct PendingChanges: Equatable {
123118
guard deletedRowIndices.contains(rowIndex) else { return false }
124119
removeChange(rowIndex: rowIndex, type: .delete)
125120
deletedRowIndices.remove(rowIndex)
126-
changedRowIndices.insert(rowIndex)
127121
return true
128122
}
129123

@@ -135,7 +129,6 @@ struct PendingChanges: Equatable {
135129
insertedRowData.removeValue(forKey: rowIndex)
136130

137131
shiftRowIndicesDown(at: rowIndex)
138-
changedRowIndices.insert(rowIndex)
139132
return true
140133
}
141134

@@ -159,7 +152,6 @@ struct PendingChanges: Equatable {
159152
removeChange(rowIndex: rowIndex, type: .insert)
160153
insertedRowIndices.remove(rowIndex)
161154
insertedRowData.removeValue(forKey: rowIndex)
162-
changedRowIndices.insert(rowIndex)
163155
}
164156

165157
let sortedRemoved = validRows.sorted()
@@ -188,7 +180,6 @@ struct PendingChanges: Equatable {
188180
modifiedCells.removeValue(forKey: rowIndex)
189181
appendChange(RowChange(rowIndex: rowIndex, type: .delete, originalRow: originalRow))
190182
deletedRowIndices.insert(rowIndex)
191-
changedRowIndices.insert(rowIndex)
192183
}
193184

194185
/// Re-apply a cell edit during undo replay (skips undo registration).
@@ -213,7 +204,6 @@ struct PendingChanges: Equatable {
213204
if let insertIdx = changeIndex[RowChangeKey(rowIndex: rowIndex, type: .insert)] {
214205
updateInsertedCell(at: insertIdx, columnIndex: columnIndex,
215206
columnName: columnName, newValue: newValue)
216-
changedRowIndices.insert(rowIndex)
217207
return
218208
}
219209

@@ -229,7 +219,6 @@ struct PendingChanges: Equatable {
229219
changeIndex[updateKey] = changes.count - 1
230220
modifiedCells[rowIndex, default: []].insert(columnIndex)
231221
}
232-
changedRowIndices.insert(rowIndex)
233222
}
234223

235224
/// Replace an inserted row's cell value during undo replay (no shift, no undo).
@@ -241,7 +230,6 @@ struct PendingChanges: Equatable {
241230
) {
242231
guard let insertIdx = changeIndex[RowChangeKey(rowIndex: rowIndex, type: .insert)] else { return }
243232
updateInsertedCell(at: insertIdx, columnIndex: columnIndex, columnName: columnName, newValue: newValue)
244-
changedRowIndices.insert(rowIndex)
245233
}
246234

247235
/// Restore a cell's value during undo replay when an existing change matches.
@@ -274,7 +262,6 @@ struct PendingChanges: Equatable {
274262
newValue: previousValue
275263
)
276264
}
277-
changedRowIndices.insert(rowIndex)
278265
}
279266

280267
/// Insert a synthetic .insert RowChange for undo replay (e.g., after redoing a deletion's undo).
@@ -291,7 +278,6 @@ struct PendingChanges: Equatable {
291278
if let savedValues {
292279
insertedRowData[rowIndex] = savedValues
293280
}
294-
changedRowIndices.insert(rowIndex)
295281
}
296282

297283
/// Insert a batch of rows (for undo replay of a batch deletion's undo).
@@ -314,7 +300,6 @@ struct PendingChanges: Equatable {
314300
changes.append(RowChange(rowIndex: rowIndex, type: .insert, cellChanges: cellChanges))
315301
insertedRowIndices.insert(rowIndex)
316302
insertedRowData[rowIndex] = values
317-
changedRowIndices.insert(rowIndex)
318303
}
319304
rebuildChangeIndex()
320305
}
@@ -338,23 +323,14 @@ struct PendingChanges: Equatable {
338323
insertedRowIndices.removeAll()
339324
modifiedCells.removeAll()
340325
insertedRowData.removeAll()
341-
changedRowIndices.removeAll()
342326
}
343327

344-
mutating func consumeChangedRowIndices() -> Set<Int> {
345-
let indices = changedRowIndices
346-
changedRowIndices.removeAll()
347-
return indices
348-
}
349-
350-
/// Replace internal state from a serialized snapshot.
351328
mutating func restore(from snapshot: TabChangeSnapshot) {
352329
changes = snapshot.changes
353330
deletedRowIndices = snapshot.deletedRowIndices
354331
insertedRowIndices = snapshot.insertedRowIndices
355332
modifiedCells = snapshot.modifiedCells
356333
insertedRowData = snapshot.insertedRowData
357-
changedRowIndices = []
358334
rebuildChangeIndex()
359335
}
360336

@@ -471,7 +447,6 @@ struct PendingChanges: Equatable {
471447
if changes[updateIdx].cellChanges.isEmpty {
472448
removeChangeAt(updateIdx)
473449
}
474-
changedRowIndices.insert(rowIndex)
475450
return true
476451
}
477452

@@ -494,7 +469,6 @@ struct PendingChanges: Equatable {
494469
}
495470
modifiedCells = newModifiedCells
496471

497-
changedRowIndices = Set(changedRowIndices.map { $0 >= insertionPoint ? $0 + 1 : $0 })
498472
rebuildChangeIndex()
499473
}
500474

TablePro/Core/SchemaTracking/StructureChangeManager.swift

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ final class StructureChangeManager: ChangeManaging {
1818
var hasChanges: Bool { !pendingChanges.isEmpty }
1919
var reloadVersion: Int = 0
2020

21-
// Track which rows changed since last reload for granular updates
22-
private(set) var changedRowIndices: Set<Int> = []
23-
2421
// Current state (loaded from database)
2522
private(set) var currentColumns: [EditableColumnDefinition] = []
2623
private(set) var currentIndexes: [EditableIndexDefinition] = []
@@ -48,13 +45,6 @@ final class StructureChangeManager: ChangeManaging {
4845
var canUndo: Bool { undoManager.canUndo }
4946
var canRedo: Bool { undoManager.canRedo }
5047

51-
/// Consume and clear changed row indices (for granular table reloads)
52-
func consumeChangedRowIndices() -> Set<Int> {
53-
let indices = changedRowIndices
54-
changedRowIndices.removeAll()
55-
return indices
56-
}
57-
5848
// MARK: - Load Schema
5949

6050
func loadSchema(
@@ -261,7 +251,6 @@ final class StructureChangeManager: ChangeManaging {
261251
pendingChanges[key] = .deleteColumn(column)
262252
trackChangeKey(key)
263253
if let rowIndex = workingColumns.firstIndex(where: { $0.id == id }) {
264-
changedRowIndices.insert(rowIndex)
265254
}
266255
} else {
267256
let rowIndex = workingColumns.firstIndex(where: { $0.id == id })
@@ -273,7 +262,6 @@ final class StructureChangeManager: ChangeManaging {
273262
}
274263
if let rowIndex {
275264
for i in rowIndex..<workingColumns.count {
276-
changedRowIndices.insert(i)
277265
}
278266
}
279267
workingColumns.removeAll { $0.id == id }
@@ -334,7 +322,6 @@ final class StructureChangeManager: ChangeManaging {
334322
pendingChanges[key] = .deleteIndex(index)
335323
trackChangeKey(key)
336324
if let rowIndex = workingIndexes.firstIndex(where: { $0.id == id }) {
337-
changedRowIndices.insert(rowIndex)
338325
}
339326
} else {
340327
let rowIndex = workingIndexes.firstIndex(where: { $0.id == id })
@@ -346,7 +333,6 @@ final class StructureChangeManager: ChangeManaging {
346333
}
347334
if let rowIndex {
348335
for i in rowIndex..<workingIndexes.count {
349-
changedRowIndices.insert(i)
350336
}
351337
}
352338
workingIndexes.removeAll { $0.id == id }
@@ -407,7 +393,6 @@ final class StructureChangeManager: ChangeManaging {
407393
pendingChanges[key] = .deleteForeignKey(fk)
408394
trackChangeKey(key)
409395
if let rowIndex = workingForeignKeys.firstIndex(where: { $0.id == id }) {
410-
changedRowIndices.insert(rowIndex)
411396
}
412397
} else {
413398
let rowIndex = workingForeignKeys.firstIndex(where: { $0.id == id })
@@ -419,7 +404,6 @@ final class StructureChangeManager: ChangeManaging {
419404
}
420405
if let rowIndex {
421406
for i in rowIndex..<workingForeignKeys.count {
422-
changedRowIndices.insert(i)
423407
}
424408
}
425409
workingForeignKeys.removeAll { $0.id == id }
@@ -552,7 +536,6 @@ final class StructureChangeManager: ChangeManaging {
552536
pendingChanges.removeAll()
553537
changeOrder.removeAll()
554538
validationErrors.removeAll()
555-
changedRowIndices.removeAll()
556539
resetWorkingState()
557540
reloadVersion += 1
558541
rebuildVisualStateCache()

TablePro/Views/Results/DataGridCoordinator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
6161
@Binding var selectedRowIndices: Set<Int>
6262

6363
var lastIdentity: DataGridIdentity?
64-
var lastReloadVersion: Int = 0
65-
var lastReapplyVersion: Int = -1
6664
private(set) var cachedRowCount: Int = 0
6765
private(set) var cachedColumnCount: Int = 0
6866
private(set) var enumOrSetColumns: Set<Int> = []
@@ -340,6 +338,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
340338
guard row >= 0, row < tableView.numberOfRows else { return }
341339
guard tableColumn >= 0, tableColumn < tableView.numberOfColumns else { return }
342340
invalidateDisplayCache(forDisplayRow: row, column: column)
341+
rebuildVisualStateCache()
343342
tableView.reloadData(
344343
forRowIndexes: IndexSet(integer: row),
345344
columnIndexes: IndexSet(integer: tableColumn)
@@ -359,6 +358,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
359358
invalidateDisplayCache(forDisplayRow: position.row, column: position.column)
360359
}
361360
guard !rowSet.isEmpty, !colSet.isEmpty else { return }
361+
rebuildVisualStateCache()
362362
tableView.reloadData(forRowIndexes: rowSet, columnIndexes: colSet)
363363
case .rowsInserted(let indices):
364364
guard !indices.isEmpty else { return }

TablePro/Views/Results/DataGridView.swift

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ struct RowVisualState {
2323
}
2424

2525
struct DataGridIdentity: Equatable {
26-
let reloadVersion: Int
2726
let schemaVersion: Int
2827
let metadataVersion: Int
2928
let paginationVersion: Int
@@ -35,9 +34,8 @@ struct DataGridIdentity: Equatable {
3534
let primaryKeyColumns: [String]
3635
let hiddenColumns: Set<String>
3736

38-
init(reloadVersion: Int, schemaVersion: Int, metadataVersion: Int, paginationVersion: Int,
37+
init(schemaVersion: Int, metadataVersion: Int, paginationVersion: Int,
3938
rowCount: Int, columnCount: Int, isEditable: Bool, configuration: DataGridConfiguration) {
40-
self.reloadVersion = reloadVersion
4139
self.schemaVersion = schemaVersion
4240
self.metadataVersion = metadataVersion
4341
self.paginationVersion = paginationVersion
@@ -233,7 +231,6 @@ struct DataGridView: NSViewRepresentable {
233231
let columnCount = latestRows.columns.count
234232

235233
let currentIdentity = DataGridIdentity(
236-
reloadVersion: changeManager.reloadVersion,
237234
schemaVersion: schemaVersion,
238235
metadataVersion: metadataVersion,
239236
paginationVersion: paginationVersion,
@@ -262,7 +259,6 @@ struct DataGridView: NSViewRepresentable {
262259
tableView.usesAlternatingRowBackgroundColors = settings.showAlternateRows
263260
}
264261

265-
let versionChanged = coordinator.lastReloadVersion != changeManager.reloadVersion
266262
let metadataChanged = previousIdentity.map { $0.metadataVersion != metadataVersion } ?? false
267263
let oldRowCount = coordinator.cachedRowCount
268264
let oldColumnCount = coordinator.cachedColumnCount
@@ -328,7 +324,6 @@ struct DataGridView: NSViewRepresentable {
328324
coordinator: coordinator,
329325
tableRows: latestRows,
330326
needsFullReload: needsFullReload,
331-
versionChanged: versionChanged,
332327
metadataChanged: metadataChanged,
333328
paginationChanged: paginationChanged
334329
)
@@ -502,7 +497,6 @@ struct DataGridView: NSViewRepresentable {
502497
coordinator: TableViewCoordinator,
503498
tableRows: TableRows,
504499
needsFullReload: Bool,
505-
versionChanged: Bool,
506500
metadataChanged: Bool = false,
507501
paginationChanged: Bool = false
508502
) {
@@ -527,21 +521,8 @@ struct DataGridView: NSViewRepresentable {
527521
tableView.reloadData(forRowIndexes: visibleRows, columnIndexes: fkColumnIndices)
528522
}
529523
}
530-
} else if versionChanged {
531-
let changedRows = changeManager.consumeChangedRowIndices()
532-
if changedRows.count > 500 {
533-
tableView.reloadData()
534-
} else if !changedRows.isEmpty {
535-
let rowIndexSet = IndexSet(changedRows)
536-
let columnIndexSet = IndexSet(integersIn: 0..<tableView.numberOfColumns)
537-
tableView.reloadData(forRowIndexes: rowIndexSet, columnIndexes: columnIndexSet)
538-
} else if !changeManager.hasChanges {
539-
tableView.reloadData()
540-
}
541524
}
542525

543-
coordinator.lastReloadVersion = changeManager.reloadVersion
544-
545526
if paginationChanged && tableView.numberOfRows > 0 {
546527
tableView.scrollRowToVisible(0)
547528
}

TablePro/Views/Results/Extensions/DataGridView+CellCommit.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,12 @@ extension TableViewCoordinator {
3939
}
4040
delegate?.dataGridDidEditCell(row: row, column: columnIndex, newValue: newValue)
4141
invalidateDisplayCache()
42+
rebuildVisualStateCache()
4243

44+
let tableColumnIndex = DataGridView.tableColumnIndex(for: columnIndex)
4345
if storageRow != nil, case .cellChanged = delta {
44-
let displayDelta: Delta = .cellChanged(
45-
row: row,
46-
column: DataGridView.tableColumnIndex(for: columnIndex)
47-
)
48-
tableRowsController.apply(displayDelta)
46+
tableRowsController.apply(.cellChanged(row: row, column: tableColumnIndex))
4947
} else {
50-
let tableColumnIndex = DataGridView.tableColumnIndex(for: columnIndex)
5148
tableView.reloadData(
5249
forRowIndexes: IndexSet(integer: row),
5350
columnIndexes: IndexSet(integer: tableColumnIndex)

0 commit comments

Comments
 (0)