refactor: rename Electric hooks and clean consumer components

Rename hooks to remove Electric branding:
- use-connectors-electric → use-connectors-sync (useConnectorsSync)
- use-messages-electric → use-messages-sync (useMessagesSync)
- use-comments-electric → use-comments-sync (useCommentsSync)

Clean all Electric/PGlite references in consumer components:
connector-popup.tsx, thread.tsx, page.tsx, use-indexing-connectors.ts,
use-connector-dialog.ts
This commit is contained in:
CREDO23 2026-03-23 19:29:08 +02:00
parent a02bc54e40
commit f04ab89418
8 changed files with 32 additions and 43 deletions

View file

@ -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(() => {

View file

@ -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<ConnectorIndicatorHandle, Connector
};
}, []);
// Fetch connectors using Electric SQL + PGlite for real-time updates
// This provides instant updates when connectors change, without polling
const {
connectors: connectorsFromElectric = [],
connectors: connectorsFromSync = [],
loading: connectorsLoading,
error: connectorsError,
refreshConnectors: refreshConnectorsElectric,
} = useConnectorsElectric(searchSpaceId);
refreshConnectors: refreshConnectorsSync,
} = useConnectorsSync(searchSpaceId);
// Fallback to API if Electric is not available or fails
// Use Electric data if: 1) we have data, or 2) still loading without error
// Use API data if: Electric failed (has error) or finished loading with no data
const useElectricData =
connectorsFromElectric.length > 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<ConnectorIndicatorHandle, Connector
const activeConnectorsCount = connectors.length;
// Check which connectors are already connected
// Using Electric SQL + PGlite for real-time connector updates
// Real-time connector updates via Zero sync
const connectedTypes = new Set<string>(
(connectors || []).map((c: SearchSourceConnector) => c.connector_type)
);
@ -282,7 +273,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
<ConnectorAccountsListView
connectorType={viewingAccountsType.connectorType}
connectorTitle={viewingAccountsType.connectorTitle}
connectors={(connectors || []) as SearchSourceConnector[]} // Using Electric SQL + PGlite for real-time connector updates (all connector types)
connectors={(connectors || []) as SearchSourceConnector[]}
indexingConnectorIds={indexingConnectorIds}
onBack={handleBackFromAccountsList}
onManage={handleStartEdit}
@ -314,7 +305,7 @@ export const ConnectorIndicator = forwardRef<ConnectorIndicatorHandle, Connector
...editingConnector,
config: connectorConfig || editingConnector.config,
name: editingConnector.name,
// Sync last_indexed_at with live data from Electric SQL for real-time updates
// Sync last_indexed_at with live data from real-time sync
last_indexed_at:
(connectors as SearchSourceConnector[]).find((c) => c.id === editingConnector.id)
?.last_indexed_at ?? editingConnector.last_indexed_at,

View file

@ -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");

View file

@ -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<Map<number, string | null>>(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;

View file

@ -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).

View file

@ -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);

View file

@ -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 };
}

View file

@ -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
) {