diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts index 0d4b46f1fb8..6d7fa21927e 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts @@ -406,6 +406,12 @@ export function useTableEventStream({ else if (entry.event?.kind === 'definition') { void queryClient.invalidateQueries({ queryKey: tableKeys.detail(tableId), exact: true }) } + // A collaborator changed the table's shared saved views (create/rename/delete/ + // re-save): refetch the views list alone. Views are presentation state layered on + // the already-loaded table, so no rows/definition refetch is needed. + else if (entry.event?.kind === 'views') { + void queryClient.invalidateQueries({ queryKey: tableKeys.views(tableId) }) + } } catch (err) { logger.warn('Failed to parse table event', { tableId, err }) } diff --git a/apps/sim/lib/table/events.ts b/apps/sim/lib/table/events.ts index 0fda87f6d1f..be2a16990a7 100644 --- a/apps/sim/lib/table/events.ts +++ b/apps/sim/lib/table/events.ts @@ -147,6 +147,16 @@ export type TableEvent = tableId: string reason: 'locks' } + | { + /** A user created, renamed, deleted, or re-saved a shared saved view (a named + * filter/sort/layout preset). Views are table-wide collaborative state — every + * reader of the table sees every view — so peers refetch the views list to pick + * up the change live. Value-less, same refetch-in-own-format rationale as + * {@link kind} `edit`; no rows/definition refetch, since a view is presentation + * state layered on top of the already-loaded table. */ + kind: 'views' + tableId: string + } export interface TableEventEntry { eventId: number @@ -203,6 +213,15 @@ export function signalTableMetadataChanged(tableId: string): void { void appendTableEvent({ kind: 'metadata', tableId }) } +/** + * Signal collaborators that a user changed the table's shared saved views (created, + * renamed, deleted, or re-saved one) so they refetch the views list live. Fire-and-forget + * for the same reason as {@link signalTableRowsChanged}. + */ +export function signalTableViewsChanged(tableId: string): void { + void appendTableEvent({ kind: 'views', tableId }) +} + /** * The latest eventId assigned for a table, or 0 when the buffer is empty or * expired. Used by the stream route to tail from "now" when a client connects diff --git a/apps/sim/lib/table/views/service.ts b/apps/sim/lib/table/views/service.ts index 1c8684a414c..1487be7dd35 100644 --- a/apps/sim/lib/table/views/service.ts +++ b/apps/sim/lib/table/views/service.ts @@ -16,6 +16,7 @@ import { generateId } from '@sim/utils/id' import { and, asc, eq, ne, sql } from 'drizzle-orm' import { getColumnId } from '@/lib/table/column-keys' import { NAME_PATTERN } from '@/lib/table/constants' +import { signalTableViewsChanged } from '@/lib/table/events' import { filterRulesToPredicate, filterToRules } from '@/lib/table/query-builder/converters' import type { ColumnDefinition, @@ -181,6 +182,8 @@ export async function createTableView(data: CreateTableViewData): Promise 0) logger.info('Deleted table view', { tableId, viewId }) + if (deleted.length > 0) { + logger.info('Deleted table view', { tableId, viewId }) + // Only signal a real deletion — a missing view (nothing deleted) changed nothing. + signalTableViewsChanged(tableId) + } return deleted.length > 0 }