@@ -445,6 +445,12 @@ export function TableGrid({
445445 const [ pinnedColumns , setPinnedColumns ] = useState < string [ ] > ( [ ] )
446446 const pinnedColumnsRef = useRef ( pinnedColumns )
447447 pinnedColumnsRef . current = pinnedColumns
448+ /** Current layout owner (view id, or null for All) for capture-at-dispatch
449+ * guards: a mutation callback persisting layout must compare the owner it was
450+ * dispatched under against this, or a mid-flight view switch writes the
451+ * origin's layout into the destination — same rule as undo's entryOwnsLayout. */
452+ const viewLayoutKeyRef = useRef ( viewLayoutKey )
453+ viewLayoutKeyRef . current = viewLayoutKey
448454 const metadataSeededRef = useRef ( false )
449455 /** Which layout source the grid last seeded from, so a view switch re-seeds. */
450456 const seededLayoutKeyRef = useRef < string | null > ( null )
@@ -3239,18 +3245,20 @@ export function TableGrid({
32393245 const index = schemaColumnsRef . current . findIndex ( ( c ) => getColumnId ( c ) === columnId )
32403246 if ( index === - 1 ) return
32413247 const name = generateColumnName ( )
3248+ const owner = viewLayoutKeyRef . current
32423249 addColumnMutation . mutate (
32433250 { name, type : 'string' , position : index } ,
32443251 {
32453252 onSuccess : ( result ) => {
32463253 const newId = result . data . columns . find ( ( c ) => c . name === name ) ?. id ?? name
3247- pushUndoRef . current ( {
3248- type : 'create-column' ,
3249- columnName : name ,
3250- columnId : newId ,
3251- position : index ,
3252- } )
3253- insertColumnInOrder ( columnId , newId , 'left' )
3254+ pushUndoRef . current (
3255+ { type : 'create-column' , columnName : name , columnId : newId , position : index } ,
3256+ owner
3257+ )
3258+ // Skipped after a mid-flight view switch: the destination re-seeded
3259+ // its own order, and the append effect places the new column there
3260+ // when the schema refetch lands.
3261+ if ( owner === viewLayoutKeyRef . current ) insertColumnInOrder ( columnId , newId , 'left' )
32543262 } ,
32553263 }
32563264 )
@@ -3264,18 +3272,17 @@ export function TableGrid({
32643272 if ( index === - 1 ) return
32653273 const name = generateColumnName ( )
32663274 const position = index + 1
3275+ const owner = viewLayoutKeyRef . current
32673276 addColumnMutation . mutate (
32683277 { name, type : 'string' , position } ,
32693278 {
32703279 onSuccess : ( result ) => {
32713280 const newId = result . data . columns . find ( ( c ) => c . name === name ) ?. id ?? name
3272- pushUndoRef . current ( {
3273- type : 'create-column' ,
3274- columnName : name ,
3275- columnId : newId ,
3276- position,
3277- } )
3278- insertColumnInOrder ( columnId , newId , 'right' )
3281+ pushUndoRef . current (
3282+ { type : 'create-column' , columnName : name , columnId : newId , position } ,
3283+ owner
3284+ )
3285+ if ( owner === viewLayoutKeyRef . current ) insertColumnInOrder ( columnId , newId , 'right' )
32793286 } ,
32803287 }
32813288 )
@@ -3421,6 +3428,9 @@ export function TableGrid({
34213428 originalPositions . set ( name , { position : def ? cols . indexOf ( def ) : cols . length , def } )
34223429 }
34233430 const deletedOriginalPositions : number [ ] = [ ]
3431+ // Layout owner when the chain was dispatched — every onDeleted below fires
3432+ // from a mutation callback, so it must not trust the sink current by then.
3433+ const owner = viewLayoutKeyRef . current
34243434
34253435 const deleteNext = ( index : number ) => {
34263436 if ( index >= columnsToDelete . length ) return
@@ -3438,24 +3448,36 @@ export function TableGrid({
34383448
34393449 const onDeleted = ( ) => {
34403450 deletedOriginalPositions . push ( entry . position )
3441- pushUndoRef . current ( {
3442- type : 'delete-column' ,
3443- // `columnToDelete` is the stable id; record the display name for re-create.
3444- columnName : entry . def ?. name ?? columnToDelete ,
3445- columnId : columnToDelete ,
3446- columnType : entry . def ?. type ?? 'string' ,
3447- columnPosition : adjustedPosition >= 0 ? adjustedPosition : cols . length ,
3448- columnUnique : entry . def ?. unique ?? false ,
3449- columnRequired : entry . def ?. required ?? false ,
3450- // Without these a deleted select column can't be re-created — it is
3451- // invalid with no options, and the saved cell data is option ids.
3452- ...( entry . def ?. options ? { columnOptions : entry . def . options } : { } ) ,
3453- ...( entry . def ?. multiple ? { columnMultiple : true } : { } ) ,
3454- cellData,
3455- previousOrder : orderSnapshot ,
3456- previousWidth,
3457- previousPinnedColumns : pinnedSnapshot ,
3458- } )
3451+ pushUndoRef . current (
3452+ {
3453+ type : 'delete-column' ,
3454+ // `columnToDelete` is the stable id; record the display name for re-create.
3455+ columnName : entry . def ?. name ?? columnToDelete ,
3456+ columnId : columnToDelete ,
3457+ columnType : entry . def ?. type ?? 'string' ,
3458+ columnPosition : adjustedPosition >= 0 ? adjustedPosition : cols . length ,
3459+ columnUnique : entry . def ?. unique ?? false ,
3460+ columnRequired : entry . def ?. required ?? false ,
3461+ // Without these a deleted select column can't be re-created — it is
3462+ // invalid with no options, and the saved cell data is option ids.
3463+ ...( entry . def ?. options ? { columnOptions : entry . def . options } : { } ) ,
3464+ ...( entry . def ?. multiple ? { columnMultiple : true } : { } ) ,
3465+ cellData,
3466+ previousOrder : orderSnapshot ,
3467+ previousWidth,
3468+ previousPinnedColumns : pinnedSnapshot ,
3469+ } ,
3470+ owner
3471+ )
3472+
3473+ // The cleanup below edits the ORIGIN view's layout. After a mid-flight
3474+ // switch the grid has re-seeded to the destination; dangling keys for
3475+ // the deleted column there are pruned on read, so skip rather than push
3476+ // origin snapshots into the destination's state or its stored config.
3477+ if ( owner !== viewLayoutKeyRef . current ) {
3478+ deleteNext ( index + 1 )
3479+ return
3480+ }
34593481
34603482 const { [ columnToDelete ] : _removedWidth , ...cleanedWidths } = columnWidthsRef . current
34613483 setColumnWidths ( cleanedWidths )
0 commit comments