diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 62770690d..a9a6c5ec2 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -61,7 +61,7 @@ import { ScrapeWebpageToolUI } from "@/components/tool-ui/scrape-webpage"; import { RecallMemoryToolUI, SaveMemoryToolUI } from "@/components/tool-ui/user-memory"; import { Skeleton } from "@/components/ui/skeleton"; import { useChatSessionStateSync } from "@/hooks/use-chat-session-state"; -import { useMessagesElectric } from "@/hooks/use-messages-electric"; +import { useMessagesSync } from "@/hooks/use-messages-sync"; import { documentsApiService } from "@/lib/apis/documents-api.service"; // import { WriteTodosToolUI } from "@/components/tool-ui/write-todos"; import { getBearerToken } from "@/lib/auth-utils"; @@ -204,13 +204,13 @@ export default function NewChatPage() { // Get current user for author info in shared chats const { data: currentUser } = useAtomValue(currentUserAtom); - // Live collaboration: sync session state and messages via Electric SQL + // Live collaboration: sync session state and messages via Zero useChatSessionStateSync(threadId); const { data: membersData } = useAtomValue(membersAtom); - const handleElectricMessagesUpdate = useCallback( + const handleSyncedMessagesUpdate = useCallback( ( - electricMessages: { + syncedMessages: { id: number; thread_id: number; role: string; @@ -224,11 +224,11 @@ export default function NewChatPage() { } setMessages((prev) => { - if (electricMessages.length < prev.length) { + if (syncedMessages.length < prev.length) { return prev; } - return electricMessages.map((msg) => { + return syncedMessages.map((msg) => { const member = msg.author_id ? membersData?.find((m) => m.user_id === msg.author_id) : null; @@ -255,7 +255,7 @@ export default function NewChatPage() { [isRunning, membersData] ); - useMessagesElectric(threadId, handleElectricMessagesUpdate); + useMessagesSync(threadId, handleSyncedMessagesUpdate); // Extract search_space_id from URL params const searchSpaceId = useMemo(() => { diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 4f4bf5cea..4e207eee3 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -20,7 +20,7 @@ import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Spinner } from "@/components/ui/spinner"; import { Tabs, TabsContent } from "@/components/ui/tabs"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; -import { useConnectorsElectric } from "@/hooks/use-connectors-electric"; +import { useConnectorsSync } from "@/hooks/use-connectors-sync"; import { PICKER_CLOSE_EVENT, PICKER_OPEN_EVENT } from "@/hooks/use-google-picker"; import { cn } from "@/lib/utils"; import { ConnectorDialogHeader } from "./connector-popup/components/connector-dialog-header"; @@ -157,33 +157,24 @@ export const ConnectorIndicator = forwardRef 0 || (connectorsLoading && !connectorsError); - const connectors = useElectricData ? connectorsFromElectric : allConnectors || []; + const useSyncData = + connectorsFromSync.length > 0 || (connectorsLoading && !connectorsError); + const connectors = useSyncData ? connectorsFromSync : allConnectors || []; - // Manual refresh function that works with both Electric and API const refreshConnectors = async () => { - if (useElectricData) { - await refreshConnectorsElectric(); - } else { - // Fallback: use allConnectors from useConnectorDialog (which uses connectorsAtom) - // The connectorsAtom will handle refetching if needed + if (useSyncData) { + await refreshConnectorsSync(); } }; - // Track indexing state locally - clears automatically when Electric SQL detects last_indexed_at changed + // Track indexing state locally - clears automatically when last_indexed_at changes via real-time sync // Also clears when failed notifications are detected const { indexingConnectorIds, startIndexing, stopIndexing } = useIndexingConnectors( connectors as SearchSourceConnector[], @@ -204,7 +195,7 @@ export const ConnectorIndicator = forwardRef( (connectors || []).map((c: SearchSourceConnector) => c.connector_type) ); @@ -282,7 +273,7 @@ export const ConnectorIndicator = forwardRef c.id === editingConnector.id) ?.last_indexed_at ?? editingConnector.last_indexed_at, diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts index 14183ec75..0b4e43eff 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts @@ -1569,7 +1569,7 @@ export const useConnectorDialog = () => { queryKey: cacheKeys.logs.summary(Number(searchSpaceId)), }); // Note: Don't call stopIndexing here - let useIndexingConnectors hook - // detect when last_indexed_at changes via Electric SQL + // detect when last_indexed_at changes via real-time sync } catch (error) { console.error("Error indexing connector content:", error); toast.error(error instanceof Error ? error.message : "Failed to start indexing"); diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-indexing-connectors.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-indexing-connectors.ts index 5783540d8..fb778fc3b 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-indexing-connectors.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-indexing-connectors.ts @@ -48,13 +48,13 @@ function isTaskTimedOut(startedAt: string | null | undefined): boolean { * * This provides a better UX than polling by: * 1. Setting indexing state immediately when user triggers indexing (optimistic) - * 2. Detecting in_progress notifications from Electric SQL to restore state after remounts + * 2. Detecting in_progress notifications to restore state after remounts * 3. Clearing indexing state when notifications become completed or failed - * 4. Clearing indexing state when Electric SQL detects last_indexed_at changed + * 4. Clearing indexing state when real-time sync detects last_indexed_at changed * 5. Detecting stale/stuck tasks that haven't updated in 15+ minutes * 6. Detecting hard timeout (8h) - tasks that definitely cannot still be running * - * The actual `last_indexed_at` value comes from Electric SQL/PGlite, not local state. + * The actual `last_indexed_at` value comes from real-time sync, not local state. */ export function useIndexingConnectors( connectors: SearchSourceConnector[], @@ -66,7 +66,7 @@ export function useIndexingConnectors( // Track previous last_indexed_at values to detect changes const previousLastIndexedAtRef = useRef>(new Map()); - // Detect when last_indexed_at changes (indexing completed) via Electric SQL + // Detect when last_indexed_at changes (indexing completed) via real-time sync useEffect(() => { const previousValues = previousLastIndexedAtRef.current; diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 8c2b57e75..ac83b86e5 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -91,7 +91,7 @@ import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getToolIcon } from "@/contracts/enums/toolIcons"; import type { Document } from "@/contracts/types/document.types"; import { useBatchCommentsPreload } from "@/hooks/use-comments"; -import { useCommentsElectric } from "@/hooks/use-comments-electric"; +import { useCommentsSync } from "@/hooks/use-comments-sync"; import { useMediaQuery } from "@/hooks/use-media-query"; import { cn } from "@/lib/utils"; @@ -365,8 +365,8 @@ const Composer: FC = () => { const respondingToUserId = sessionState?.respondingToUserId ?? null; const isBlockedByOtherUser = isAiResponding && respondingToUserId !== currentUser?.id; - // Sync comments for the entire thread via Electric SQL (one subscription per thread) - useCommentsElectric(threadId); + // Sync comments for the entire thread via Zero (one subscription per thread) + useCommentsSync(threadId); // Batch-prefetch comments for all assistant messages so individual useComments // hooks never fire their own network requests (eliminates N+1 API calls). diff --git a/surfsense_web/hooks/use-comments-electric.ts b/surfsense_web/hooks/use-comments-sync.ts similarity index 99% rename from surfsense_web/hooks/use-comments-electric.ts rename to surfsense_web/hooks/use-comments-sync.ts index d588504c9..05a6f2b46 100644 --- a/surfsense_web/hooks/use-comments-electric.ts +++ b/surfsense_web/hooks/use-comments-sync.ts @@ -151,7 +151,7 @@ function transformComments( * Syncs ALL comments for a thread in ONE subscription, then updates * React Query cache for each message. This avoids N subscriptions for N messages. */ -export function useCommentsElectric(threadId: number | null) { +export function useCommentsSync(threadId: number | null) { const queryClient = useQueryClient(); const { data: membersData } = useAtomValue(membersAtom); diff --git a/surfsense_web/hooks/use-connectors-electric.ts b/surfsense_web/hooks/use-connectors-sync.ts similarity index 88% rename from surfsense_web/hooks/use-connectors-electric.ts rename to surfsense_web/hooks/use-connectors-sync.ts index 3714e4af0..602c06dfc 100644 --- a/surfsense_web/hooks/use-connectors-electric.ts +++ b/surfsense_web/hooks/use-connectors-sync.ts @@ -9,7 +9,7 @@ import { useQuery } from "@rocicorp/zero/react"; * Syncs connectors for a search space via Zero. * Returns connectors, loading state, error, and a refresh function. */ -export function useConnectorsElectric(searchSpaceId: number | string | null) { +export function useConnectorsSync(searchSpaceId: number | string | null) { const spaceId = searchSpaceId ? Number(searchSpaceId) : -1; const [data, result] = useQuery(queries.connectors.bySpace({ searchSpaceId: spaceId })); @@ -37,9 +37,7 @@ export function useConnectorsElectric(searchSpaceId: number | string | null) { const loading = !searchSpaceId ? false : result.type !== "complete"; const error = !searchSpaceId ? null : null; - const refreshConnectors = async () => { - // Zero handles reactivity automatically — no manual refresh needed - }; + const refreshConnectors = async () => {}; return { connectors, loading, error, refreshConnectors }; } diff --git a/surfsense_web/hooks/use-messages-electric.ts b/surfsense_web/hooks/use-messages-sync.ts similarity index 96% rename from surfsense_web/hooks/use-messages-electric.ts rename to surfsense_web/hooks/use-messages-sync.ts index 151c3ae2a..1d1fb2e25 100644 --- a/surfsense_web/hooks/use-messages-electric.ts +++ b/surfsense_web/hooks/use-messages-sync.ts @@ -9,7 +9,7 @@ import { useQuery } from "@rocicorp/zero/react"; * Syncs chat messages for a thread via Zero. * Calls onMessagesUpdate when messages change. */ -export function useMessagesElectric( +export function useMessagesSync( threadId: number | null, onMessagesUpdate: (messages: RawMessage[]) => void ) {