@@ -370,8 +370,10 @@ struct DataGridView: NSViewRepresentable {
370370 column. isEditable = isEditable
371371 }
372372 }
373- // Restore user-resized column widths after rebuild (only if user explicitly resized)
374- if coordinator. hasUserResizedColumns, !columnLayout. columnWidths. isEmpty {
373+ let hasSavedLayout = !columnLayout. columnWidths. isEmpty
374+
375+ // Restore saved column widths after rebuild (from user resize or persisted layout)
376+ if hasSavedLayout {
375377 for column in tableView. tableColumns where column. identifier. rawValue != " __rowNumber__ " {
376378 guard let colIndex = Self . columnIndex ( from: column. identifier) ,
377379 colIndex < rowProvider. columns. count else { continue }
@@ -380,16 +382,19 @@ struct DataGridView: NSViewRepresentable {
380382 column. width = savedWidth
381383 }
382384 }
385+ coordinator. hasUserResizedColumns = true
383386 }
384387
385- // Restore saved column order after rebuild (only if user explicitly reordered)
386- if coordinator . hasUserResizedColumns , let savedOrder = columnLayout. columnOrder {
388+ // Restore saved column order after rebuild
389+ if let savedOrder = columnLayout. columnOrder {
387390 DataGridView . applyColumnOrder ( savedOrder, to: tableView, columns: rowProvider. columns)
391+ coordinator. hasUserResizedColumns = true
388392 }
389393
390394 // Persist calculated widths so subsequent tab switches reuse them
391395 // instead of calling the expensive calculateOptimalColumnWidth.
392- if !coordinator. hasUserResizedColumns {
396+ // Skip when saved layout exists to avoid overwriting persisted values.
397+ if !coordinator. hasUserResizedColumns, !hasSavedLayout {
393398 var newWidths : [ String : CGFloat ] = [ : ]
394399 for column in tableView. tableColumns where column. identifier. rawValue != " __rowNumber__ " {
395400 guard let colIndex = Self . columnIndex ( from: column. identifier) ,
@@ -624,6 +629,7 @@ struct DataGridView: NSViewRepresentable {
624629
625630 static func dismantleNSView( _ nsView: NSScrollView , coordinator: TableViewCoordinator ) {
626631 coordinator. overlayEditor? . dismiss ( commit: false )
632+ coordinator. persistColumnLayoutToStorage ( )
627633 if let observer = coordinator. settingsObserver {
628634 NotificationCenter . default. removeObserver ( observer)
629635 coordinator. settingsObserver = nil
@@ -692,6 +698,31 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
692698 changeManager. canRedo
693699 }
694700
701+ /// Capture current column widths and order from the live NSTableView
702+ /// and persist directly to ColumnLayoutStorage. Called from dismantleNSView
703+ /// to guarantee layout is saved even when the view is torn down without
704+ /// a SwiftUI render cycle (e.g., closing a tab).
705+ func persistColumnLayoutToStorage( ) {
706+ guard let tableView, let connectionId, let tableName, !tableName. isEmpty else { return }
707+ guard !rowProvider. columns. isEmpty else { return }
708+
709+ var widths : [ String : CGFloat ] = [ : ]
710+ var order : [ String ] = [ ]
711+ for column in tableView. tableColumns where column. identifier. rawValue != " __rowNumber__ " {
712+ guard let colIndex = DataGridView . columnIndex ( from: column. identifier) ,
713+ colIndex < rowProvider. columns. count else { continue }
714+ let name = rowProvider. columns [ colIndex]
715+ widths [ name] = column. width
716+ order. append ( name)
717+ }
718+
719+ guard !widths. isEmpty else { return }
720+ var layout = ColumnLayoutState ( )
721+ layout. columnWidths = widths
722+ layout. columnOrder = order
723+ ColumnLayoutStorage . shared. save ( layout, for: tableName, connectionId: connectionId)
724+ }
725+
695726 weak var tableView : NSTableView ?
696727 let cellFactory = DataGridCellFactory ( )
697728 var overlayEditor : CellOverlayEditor ?
0 commit comments