diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.test.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.test.tsx index d6d37c9e4ed..429eacf82ae 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.test.tsx @@ -51,6 +51,21 @@ describe('ResourceContent table view handoff', () => { }) } + it('hands off a restored view the table is mounted with', () => { + // The table can only honour `initialViewId` while its views query already + // lists that id. Reopening a chat against a cached list from before the + // agent's write would otherwise strand the restored view. + render({ type: 'table', id: 'table-1', title: 'Invoices', viewId: 'view-restored' }) + + expect(useTableViewPinStore.getState().pins['table-1']?.viewId).toBe('view-restored') + }) + + it('does not pin a table opened without a saved view', () => { + render({ type: 'table', id: 'table-1', title: 'Invoices' }) + + expect(useTableViewPinStore.getState().pins['table-1']).toBeUndefined() + }) + it('hands off a saved view that arrives after the embedded table mounts', () => { const table: MothershipResource = { type: 'table', diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index 767b1fa6da0..a49a321a33d 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -179,9 +179,7 @@ export const ResourceContent = memo(function ResourceContent({ visible = true, onBrowserOverlayControllerChange, }: ResourceContentProps) { - const observedTableViewRef = useRef( - resource.type === 'table' ? { tableId: resource.id, viewId: resource.viewId } : null - ) + const observedTableViewRef = useRef<{ tableId: string; viewId?: string } | null>(null) useEffect(() => { const previous = observedTableViewRef.current @@ -192,8 +190,13 @@ export const ResourceContent = memo(function ResourceContent({ return } /** - * `initialViewId` owns the first table adoption. If refreshed chat data - * supplies it later, use the same one-shot handoff as live stream events. + * Pinned on mount as well as on later changes. `initialViewId` alone is not + * enough: the table honours it only while its views query already carries + * that id, and a cached list from before the agent wrote the view resolves + * it to nothing. Adoption then settles on the default and never revisits + * the id, so the restored view is lost until the tab is reopened. The pin + * waits for the refetch instead, and costs nothing when adoption already + * applied the same view — the table consumes it without touching the URL. */ useTableViewPinStore.getState().pin(next.tableId, next.viewId) }, [resource.id, resource.type, resource.viewId])