From a80d96cdc44c8886707118dfee444c02f011acd1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 3 Sep 2026 13:55:34 -0700 Subject: [PATCH] fix(copilot): pin a restored table view so a stale views list cannot strand it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reopening a chat hands the embedded table its saved view through `initialViewId`, which the table honours only while its views query already lists that id. A cached list from before the agent created the view resolves it to nothing, so adoption settles on the default and stamps itself closed — nothing revisits the id when the refetch lands, and the restored view is lost until the tab is reopened. Pin on mount as well as on later changes, so the handoff waits for the list that carries the view. When adoption already applied the same view the table consumes the pin without touching the URL, and a table opened with no saved view still pins nothing. This also makes a first mount agree with a tab switch, which already re-pins the saved view through the same path. --- .../resource-content/resource-content.test.tsx | 15 +++++++++++++++ .../resource-content/resource-content.tsx | 13 ++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) 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])