mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
## Summary - The assistant's `create_notebook`/`update_notebook` tools run entirely server-side, so an open notebook tab's React Query cache and Valtio store never learn a write happened — the tab keeps showing stale content until a manual reload. - Adds `collectNotebookCacheEffects`/`applyNotebookCacheEffects` (`apps/studio/lib/ai/notebook-cache-invalidation.ts`), which scan finished assistant messages for completed `create_notebook`/`update_notebook` tool calls and evict the affected notebook via `evictNotebookFromCaches` (`apps/studio/data/content/notebooks/notebook-cache.ts`), plus invalidate the nav list. - Wired into `createChatInstance`'s `onFinish` in `state/ai-assistant-state.tsx`, with per-chat dedupe so replayed history isn't reprocessed. - Removes the cache entry outright rather than invalidating it, since a remounting `useNotebookQuery` would otherwise read the stale cached value synchronously before its refetch lands. - Explicitly skips eviction when the open tab has unsaved local edits, so an assistant write can't silently discard them. Related: [FE-4235](https://linear.app/supabase/issue/FE-4235) **Out of scope:** this only protects the client-side cache/store from being clobbered after the fact. Preventing the assistant's `update_notebook` tool call itself from overwriting a user's unsaved edits (a data-layer conflict, not a cache-freshness one) is tracked separately in [FE-4255](https://linear.app/supabase/issue/FE-4255). ## Test plan - [x] `pnpm test:studio -- notebook-cache notebook-cache-invalidation ai-assistant-state.notebook-cache-invalidation ExplorerNotebookTab.assistant-cache-invalidation ExplorerNotebookTabCoordinator` — all passing - [x] Reproduction-first component test (`ExplorerNotebookTab.assistant-cache-invalidation.test.tsx`) — verified it fails without the fix (stale content persists) and passes with it - [x] Regression test for the dirty-notebook guard (an edited, unsaved notebook is left untouched by an assistant write) - [x] `pnpm typecheck --filter=studio` / `pnpm lint --filter=studio` clean <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Notebook changes made through the AI assistant now appear correctly in open notebook tabs and after reopening them. * Saved notebook caches are refreshed after completed create or update actions, preventing stale content from being displayed. * Unsaved notebook changes are preserved during cache cleanup. * Closing a notebook tab now consistently removes its cached content. * **Tests** * Added coverage for assistant-driven updates, remounts, duplicate actions, project context changes, and cache behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
56 lines
2.0 KiB
TypeScript
56 lines
2.0 KiB
TypeScript
import type { QueryClient } from '@tanstack/react-query'
|
|
import type { Snapshot } from 'valtio'
|
|
|
|
import { contentKeys } from '@/data/content/keys'
|
|
import { notebooksState } from '@/state/notebooks/notebooks-state'
|
|
import type { StateNotebook } from '@/state/notebooks/types'
|
|
import { hasUnsavedChanges } from '@/state/sql-editor/sql-editor-lifecycle'
|
|
|
|
/**
|
|
* Whether a notebook has edits worth discarding on close: anything not yet
|
|
* saved, except a never-persisted notebook that's still empty (nothing to
|
|
* lose by closing it).
|
|
*/
|
|
export function hasDiscardableChanges(
|
|
stateNotebook: StateNotebook | Snapshot<StateNotebook> | undefined
|
|
): boolean {
|
|
if (!hasUnsavedChanges(stateNotebook?.status)) return false
|
|
|
|
const isEmptyNewNotebook =
|
|
stateNotebook?.status === 'new' && (stateNotebook.notebook.content?.cells.length ?? 0) === 0
|
|
return !isEmptyNewNotebook
|
|
}
|
|
|
|
/**
|
|
* Evicts a notebook from the React Query cache and the Valtio store together.
|
|
* Applies to a persisted notebook (always safe to refetch) and to a dirty
|
|
* unsaved notebook (safe once the caller has confirmed discarding it).
|
|
*
|
|
* Removes the query entry outright rather than invalidating it: a mounted
|
|
* `useNotebookQuery` observer would otherwise read the stale cached value
|
|
* synchronously, before its refetch lands, and `notebooksState.setNotebook`'s
|
|
* merge guard would treat that stale merge as already-loaded and drop the
|
|
* real update.
|
|
*
|
|
* @returns A boolean indicating whether the notebook was successfully evicted
|
|
* from the cache.
|
|
*/
|
|
export async function evictNotebookFromCaches({
|
|
queryClient,
|
|
projectRef,
|
|
id,
|
|
}: {
|
|
queryClient: QueryClient
|
|
projectRef: string
|
|
id: string
|
|
}): Promise<boolean> {
|
|
const stateNotebook = notebooksState.notebooks[id]
|
|
const canEvict = stateNotebook?.status === 'saved' || hasDiscardableChanges(stateNotebook)
|
|
if (!canEvict) return false
|
|
|
|
notebooksState.removeNotebook({ id })
|
|
queryClient.removeQueries({ queryKey: contentKeys.resource(projectRef, id) })
|
|
|
|
return true
|
|
}
|