@@ -61,6 +61,7 @@ struct DataGridView: NSViewRepresentable {
6161 var databaseType : DatabaseType ?
6262 var tableName : String ?
6363 var primaryKeyColumn : String ?
64+ var tabType : TabType ?
6465 var showRowNumbers : Bool = true
6566 var hiddenColumns : Set < String > = [ ]
6667 var onHideColumn : ( ( String ) -> Void ) ?
@@ -271,6 +272,7 @@ struct DataGridView: NSViewRepresentable {
271272 coordinator. databaseType = databaseType
272273 coordinator. tableName = tableName
273274 coordinator. primaryKeyColumn = primaryKeyColumn
275+ coordinator. tabType = tabType
274276
275277 coordinator. rebuildVisualStateCache ( )
276278
@@ -336,15 +338,11 @@ struct DataGridView: NSViewRepresentable {
336338 column. headerCell. setAccessibilityLabel (
337339 String ( localized: " Column: \( columnName) " )
338340 )
339- if let savedWidth = columnLayout. columnWidths [ columnName] {
340- column. width = savedWidth
341- } else {
342- column. width = coordinator. cellFactory. calculateOptimalColumnWidth (
343- for: columnName,
344- columnIndex: index,
345- rowProvider: rowProvider
346- )
347- }
341+ column. width = coordinator. cellFactory. calculateOptimalColumnWidth (
342+ for: columnName,
343+ columnIndex: index,
344+ rowProvider: rowProvider
345+ )
348346 column. minWidth = 30
349347 column. resizingMask = . userResizingMask
350348 column. isEditable = isEditable
@@ -358,20 +356,18 @@ struct DataGridView: NSViewRepresentable {
358356 colIndex < rowProvider. columns. count else { continue }
359357 let columnName = rowProvider. columns [ colIndex]
360358 column. title = columnName
361- if let savedWidth = columnLayout. columnWidths [ columnName] {
362- column. width = savedWidth
363- } else {
364- column. width = coordinator. cellFactory. calculateOptimalColumnWidth (
365- for: columnName,
366- columnIndex: colIndex,
367- rowProvider: rowProvider
368- )
369- }
359+ column. width = coordinator. cellFactory. calculateOptimalColumnWidth (
360+ for: columnName,
361+ columnIndex: colIndex,
362+ rowProvider: rowProvider
363+ )
370364 column. isEditable = isEditable
371365 }
372366 }
373- // Restore user-resized column widths after rebuild (only if user explicitly resized)
374- if coordinator. hasUserResizedColumns, !columnLayout. columnWidths. isEmpty {
367+ let hasSavedLayout = !columnLayout. columnWidths. isEmpty
368+
369+ // Restore saved column widths after rebuild (from user resize or persisted layout)
370+ if hasSavedLayout {
375371 for column in tableView. tableColumns where column. identifier. rawValue != " __rowNumber__ " {
376372 guard let colIndex = Self . columnIndex ( from: column. identifier) ,
377373 colIndex < rowProvider. columns. count else { continue }
@@ -380,16 +376,19 @@ struct DataGridView: NSViewRepresentable {
380376 column. width = savedWidth
381377 }
382378 }
379+ coordinator. hasUserResizedColumns = true
383380 }
384381
385- // Restore saved column order after rebuild (only if user explicitly reordered)
386- if coordinator . hasUserResizedColumns , let savedOrder = columnLayout. columnOrder {
382+ // Restore saved column order after rebuild
383+ if let savedOrder = columnLayout. columnOrder {
387384 DataGridView . applyColumnOrder ( savedOrder, to: tableView, columns: rowProvider. columns)
385+ coordinator. hasUserResizedColumns = true
388386 }
389387
390388 // Persist calculated widths so subsequent tab switches reuse them
391389 // instead of calling the expensive calculateOptimalColumnWidth.
392- if !coordinator. hasUserResizedColumns {
390+ // Skip when saved layout exists to avoid overwriting persisted values.
391+ if !coordinator. hasUserResizedColumns, !hasSavedLayout {
393392 var newWidths : [ String : CGFloat ] = [ : ]
394393 for column in tableView. tableColumns where column. identifier. rawValue != " __rowNumber__ " {
395394 guard let colIndex = Self . columnIndex ( from: column. identifier) ,
@@ -624,6 +623,7 @@ struct DataGridView: NSViewRepresentable {
624623
625624 static func dismantleNSView( _ nsView: NSScrollView , coordinator: TableViewCoordinator ) {
626625 coordinator. overlayEditor? . dismiss ( commit: false )
626+ coordinator. persistColumnLayoutToStorage ( )
627627 if let observer = coordinator. settingsObserver {
628628 NotificationCenter . default. removeObserver ( observer)
629629 coordinator. settingsObserver = nil
@@ -681,6 +681,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
681681 var databaseType : DatabaseType ?
682682 var tableName : String ?
683683 var primaryKeyColumn : String ?
684+ var tabType : TabType ?
684685
685686 /// Check if undo is available
686687 func canUndo( ) -> Bool {
@@ -692,6 +693,32 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
692693 changeManager. canRedo
693694 }
694695
696+ /// Capture current column widths and order from the live NSTableView
697+ /// and persist directly to ColumnLayoutStorage. Called from dismantleNSView
698+ /// to guarantee layout is saved even when the view is torn down without
699+ /// a SwiftUI render cycle (e.g., closing a tab).
700+ func persistColumnLayoutToStorage( ) {
701+ guard tabType == . table else { return }
702+ guard let tableView, let connectionId, let tableName, !tableName. isEmpty else { return }
703+ guard !rowProvider. columns. isEmpty else { return }
704+
705+ var widths : [ String : CGFloat ] = [ : ]
706+ var order : [ String ] = [ ]
707+ for column in tableView. tableColumns where column. identifier. rawValue != " __rowNumber__ " {
708+ guard let colIndex = DataGridView . columnIndex ( from: column. identifier) ,
709+ colIndex < rowProvider. columns. count else { continue }
710+ let name = rowProvider. columns [ colIndex]
711+ widths [ name] = column. width
712+ order. append ( name)
713+ }
714+
715+ guard !widths. isEmpty else { return }
716+ var layout = ColumnLayoutState ( )
717+ layout. columnWidths = widths
718+ layout. columnOrder = order
719+ ColumnLayoutStorage . shared. save ( layout, for: tableName, connectionId: connectionId)
720+ }
721+
695722 weak var tableView : NSTableView ?
696723 let cellFactory = DataGridCellFactory ( )
697724 var overlayEditor : CellOverlayEditor ?
@@ -717,6 +744,8 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
717744 var hasUserResizedColumns : Bool = false
718745 /// Guards against two-frame bounce when async column layout write-back triggers updateNSView
719746 var isWritingColumnLayout : Bool = false
747+ /// Debounced work item for persisting column layout after resize/reorder
748+ var layoutPersistWorkItem : DispatchWorkItem ?
720749
721750 private let cellIdentifier = NSUserInterfaceItemIdentifier ( " DataCell " )
722751 static let rowViewIdentifier = NSUserInterfaceItemIdentifier ( " TableRowView " )
0 commit comments