From 7ceb82ff5ed69fae27014564353d93c722846bf3 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Thu, 29 Oct 2026 19:19:36 +0700 Subject: [PATCH] fix(structure): keep a saved definition selected and auto-show the inspector from any section --- .../StructureChangeManager.swift | 11 +++++++ .../TablePro/Views/Main/MainContentView.swift | 28 +++++++++++++++- .../StructureDefinitionEditorTests.swift | 32 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/apple/TablePro/Core/SchemaTracking/StructureChangeManager.swift b/apple/TablePro/Core/SchemaTracking/StructureChangeManager.swift index 68e54b12b5..dc271f8c80 100644 --- a/apple/TablePro/Core/SchemaTracking/StructureChangeManager.swift +++ b/apple/TablePro/Core/SchemaTracking/StructureChangeManager.swift @@ -873,10 +873,21 @@ final class StructureChangeManager: ObservableObject, ChangeManaging { } } tableOptions = TableOptionsDraft(original: writtenOptions, isAutoIncrementConfirmed: isAutoIncrementConfirmed) + adoptWrittenDefinitions() discardChanges() return true } + // The written definitions stand in until the re-read lands, so a renamed or new definition keeps its id, its + // selection and the name the re-read is matched by. + private func adoptWrittenDefinitions() { + currentColumns = workingColumns.filter { !isPendingDeletion(.column($0.id)) } + currentIndexes = workingIndexes.filter { !isPendingDeletion(.index($0.id)) } + currentForeignKeys = workingForeignKeys.filter { !isPendingDeletion(.foreignKey($0.id)) } + currentCheckConstraints = workingCheckConstraints.filter { !isPendingDeletion(.checkConstraint($0.id)) } + currentPrimaryKey = workingPrimaryKey + } + // MARK: - Undo/Redo Operations func undo() { diff --git a/apple/TablePro/Views/Main/MainContentView.swift b/apple/TablePro/Views/Main/MainContentView.swift index 3de7ffcf9c..e19b1ee85a 100644 --- a/apple/TablePro/Views/Main/MainContentView.swift +++ b/apple/TablePro/Views/Main/MainContentView.swift @@ -361,7 +361,14 @@ struct MainContentView: View { await coordinator.loadInspectorRowValues(tabId: tabId, rowID: rowID) }) .background { - if let model = coordinator.activeGridModel { + if let editor = coordinator.activeStructureEditor { + StructureSelectionWatcher( + editor: editor, + onAttach: { scheduleInspectorUpdate() }, + onChange: { rowsChanged in handleGridSelectionChange(rowsChanged: rowsChanged) } + ) + .id(ObjectIdentifier(editor)) + } else if let model = coordinator.activeGridModel { GridSelectionWatcher( model: model, onAttach: { scheduleInspectorUpdate() }, @@ -552,6 +559,25 @@ struct MainContentView: View { } // No view of this window observes a session whose Structure view is not on screen, yet Discard and Save change it there. +// The selection moves between four grids, and the window reads only the one holding it. +private struct StructureSelectionWatcher: View { + @ObservedObject var editor: StructureDefinitionEditor + let onAttach: () -> Void + let onChange: (_ rowsChanged: Bool) -> Void + + var body: some View { + let model = editor.focusedModel ?? editor.adapter(for: .columns).model + Color.clear + .frame(width: 0, height: 0) + .accessibilityHidden(true) + .background { + GridSelectionWatcher(model: model, onAttach: onAttach, onChange: onChange) + .id(ObjectIdentifier(model)) + } + .onChange(of: editor.focusedSection) { _ in onChange(true) } + } +} + private struct StructureChangeWatcher: View { @ObservedObject var session: StructureEditingSession let onChange: () -> Void diff --git a/apple/TableProTests/Views/Structure/StructureDefinitionEditorTests.swift b/apple/TableProTests/Views/Structure/StructureDefinitionEditorTests.swift index 01f56ac935..8392e38fdd 100644 --- a/apple/TableProTests/Views/Structure/StructureDefinitionEditorTests.swift +++ b/apple/TableProTests/Views/Structure/StructureDefinitionEditorTests.swift @@ -176,6 +176,38 @@ struct StructureDefinitionEditorTests { #expect(editor.inspectorTarget == .entity(.columns, status)) } + @Test("A saved rename and a saved new column keep their ids and the selection through the re-read") + func saveKeepsRenamedAndNewDefinitions() async throws { + let editor = makeEditor() + let status = id(of: "status", in: editor) + editor.commitInspectorField(section: .columns, id: status, field: 0, value: "phase") + editor.manager.addNewColumn() + let added = try #require(editor.manager.workingColumns.last).id + editor.commitInspectorField(section: .columns, id: added, field: 0, value: "note") + editor.adapter(for: .columns).select(entity: added) + let hold = try #require(editor.manager.holdForSave()) + + #expect(editor.manager.releaseHold(hold, written: true)) + try await waitUntil { editor.adapter(for: .columns).model.configuration.isEditable } + #expect(editor.inspectorTarget == .entity(.columns, added)) + + editor.manager.loadSchema( + tableName: "orders", + columns: ["id", "customer_id", "phase", "note"].map { + ColumnInfo(name: $0, dataType: "INT", isNullable: true, isPrimaryKey: $0 == "id", + defaultValue: nil, extra: nil, charset: nil, collation: nil, comment: nil) + }, + indexes: [IndexInfo(name: "orders_pkey", columns: ["id"], isUnique: true, isPrimary: true, type: "BTREE")], + foreignKeys: [], + primaryKey: ["id"] + ) + try await waitUntil { editor.adapter(for: .columns).rowCount == 4 } + + #expect(id(of: "phase", in: editor) == status) + #expect(id(of: "note", in: editor) == added) + #expect(editor.inspectorTarget == .entity(.columns, added)) + } + @Test("A save's hold closes the grids and its end opens them again") func saveHoldTogglesEditing() async throws { let editor = makeEditor()