open-notebook/claude-goal.md

2.8 KiB

Goal: Fix Source List Auto-Refresh After Adding New Source

Issue Reference

GitHub Issue: #526

Problem

When a user adds a new source to a notebook (via URL, file upload, or text), the source list in the notebook page does not auto-refresh. The user sees a toast notification saying "Source Queued" but must manually refresh the browser to see the new source appear in the list.

Root Cause

The notebook page uses useNotebookSources() which queries with key QUERY_KEYS.sourcesInfinite(notebookId) = ['sources', 'infinite', notebookId].

However, the mutation hooks that create sources only invalidate different query keys:

  • useCreateSource invalidates QUERY_KEYS.sources(notebookId) = ['sources', notebookId]
  • useFileUpload invalidates QUERY_KEYS.sources(variables.notebookId) = ['sources', notebookId]

React Query's prefix matching does NOT cascade from ['sources', notebookId] to ['sources', 'infinite', notebookId] because the second element differs (notebookId vs 'infinite').

Note: Other hooks like useDeleteSource, useUpdateSource, etc. correctly use queryClient.invalidateQueries({ queryKey: ['sources'] }) which IS a prefix of all source queries and does cascade properly.

Fix

Add sourcesInfinite query key invalidation in both useCreateSource and useFileUpload hooks in frontend/src/lib/hooks/use-sources.ts.

Key Files

  • frontend/src/lib/hooks/use-sources.ts - Contains all source mutation hooks (main fix location)
  • frontend/src/lib/api/query-client.ts - Defines QUERY_KEYS including sourcesInfinite
  • frontend/src/app/(dashboard)/notebooks/[id]/page.tsx - Notebook page that uses useNotebookSources
  • frontend/src/app/(dashboard)/notebooks/components/SourcesColumn.tsx - Source list UI component

Implementation Details

In useCreateSource (line ~89-139): After each QUERY_KEYS.sources(notebookId) invalidation, also invalidate QUERY_KEYS.sourcesInfinite(notebookId).

In useFileUpload (line ~195-220): After QUERY_KEYS.sources(variables.notebookId) invalidation, also invalidate QUERY_KEYS.sourcesInfinite(variables.notebookId).

Why This Works

  • The backend creates the source record immediately (even for async processing, with title "Processing...")
  • The source is linked to the notebook immediately
  • So invalidating the query will refetch and show the new source
  • SourceCard already has useSourceStatus() that polls every 2 seconds during processing, so the status will auto-update

Gotchas

  • No need for polling/delays - the source record exists in DB when the API returns
  • No need for WebSocket/SSE - simple cache invalidation is sufficient
  • The async path creates a minimal source with "Processing..." title that gets updated when processing completes
  • SourceCard already handles status polling via useSourceStatus() hook