@@ -555,13 +555,33 @@ export function Table({
555555 [ activeView , tableData ?. metadata , effectiveFilter , sortQuery , effectiveHiddenColumns ]
556556 )
557557
558+ /**
559+ * The active view's stored config, pruned against the live columns exactly as
560+ * the local state is. The server prunes on read, but the cached copy is not
561+ * re-pruned when the schema changes here — so without this, deleting a hidden or
562+ * sorted column makes the two sides disagree and lights Save with no user edit.
563+ */
564+ const storedViewConfig = useMemo < TableViewConfig | null > ( ( ) => {
565+ if ( ! activeView ) return null
566+ const stored = activeView . config
567+ if ( columns . length === 0 ) return stored
568+ return {
569+ ...stored ,
570+ hiddenColumns : ( stored . hiddenColumns ?? [ ] ) . filter ( ( id ) => liveColumnIds . has ( id ) ) ,
571+ sort :
572+ stored . sort && Object . keys ( stored . sort ) . every ( ( id ) => liveColumnIds . has ( id ) )
573+ ? stored . sort
574+ : null ,
575+ }
576+ } , [ activeView , columns . length , liveColumnIds ] )
577+
558578 /**
559579 * Whether the live state diverges from what the active view stores (or, on
560580 * "All", whether anything is applied at all). Drives the Save button — it is
561581 * the only affordance that persists, so ad-hoc exploration stays throwaway.
562582 */
563- const isViewDirty = activeView
564- ? ! isSameViewConfig ( currentViewConfig , activeView . config )
583+ const isViewDirty = storedViewConfig
584+ ? ! isSameViewConfig ( currentViewConfig , storedViewConfig )
565585 : Boolean ( effectiveFilter ) || Boolean ( sortQuery ) || effectiveHiddenColumns . length > 0
566586
567587 /** Rename targets a live view rather than a snapshot, so a concurrent rename or
@@ -648,7 +668,13 @@ export function Table({
648668 const blank = viewModal ?. blank === true
649669 const config : TableViewConfig = blank
650670 ? {
671+ // `liveLayoutRef` last, so a resize/reorder/pin still in flight wins over
672+ // the cached copy — otherwise the new view stores the pre-drag layout and
673+ // the grid snaps back to it on selection. (With no active view the grid
674+ // writes table metadata directly, which `useUpdateTableMetadata` updates
675+ // optimistically, so that branch is already current.)
651676 ...( activeView ?. config ?? tableData ?. metadata ) ,
677+ ...liveLayoutRef . current ,
652678 filter : null ,
653679 sort : null ,
654680 hiddenColumns : [ ] ,
0 commit comments