Skip to content

Commit 8a52f80

Browse files
fix(tables): live layout on new views, prune stored config, order cache writes
1 parent 20f7df6 commit 8a52f80

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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: [],

apps/sim/hooks/queries/tables.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,14 @@ export function useUpdateTableView({ workspaceId, tableId }: RowMutationContext)
14791479
// so `isViewDirty` re-reads true and the Save chip flashes back after a save.
14801480
onSuccess: (view) => {
14811481
queryClient.setQueryData<TableViewWire[]>(tableKeys.views(tableId), (prev) =>
1482-
prev?.map((existing) => (existing.id === view.id ? view : existing))
1482+
prev?.map((existing) => {
1483+
if (existing.id !== view.id) return existing
1484+
// Layout auto-saves and an explicit Save fire concurrently, and their
1485+
// responses can arrive out of order. The DB merge is authoritative, so
1486+
// only let a row at least as new as the cached one win — otherwise a
1487+
// slower response rewinds the cache until the refetch lands.
1488+
return new Date(view.updatedAt) >= new Date(existing.updatedAt) ? view : existing
1489+
})
14831490
)
14841491
},
14851492
onSettled: () => queryClient.invalidateQueries({ queryKey: tableKeys.views(tableId) }),

0 commit comments

Comments
 (0)