feat(desktop,mobile,trpc): replace Electric sync with plain tRPC + React Query

Merged
SA
3mo ago
+9653-8374

Electric (the company) is winding down; this is PR 1 of 2 removing our dependency on it. This PR cuts Electric out of the app: every synced read becomes a plain tRPC + React Query query, dead code paths found during the audit are deleted, and nothing new is built — no push channel, no sync engine, no client data layer. PR 2 decommissions the infrastructure (apps/electric-proxy, docker-compose service, env/CSP plumbing, remaining deps) once the release ramp drains, because old desktop builds keep dialing the proxy until they age out — that's also why the proxy's shape allowlist still serves tables whose app-side features this PR deletes.

What this PR does

Deletions (dead code found during the audit)

  • Dormant Durable Streams chat path: apps/api chat REST/SSE routes (rode Electric Cloud's durable-streams service), DURABLE_STREAMS_*/TAVILY_* env + deploy plumbing, @durable-streams/client + @tavily/core, and the ChatPane cloud-session lifecycle.
  • agent_commands control plane: zero insert sites in the repo (the writer died with MCP v1) — useCommandWatcher, the collection, and the agent router are gone.
  • v2_workspaces pre-R1 fallback (hosts have been authoritative since R1), v2_clients (zero consumers), v1 workspaces collection (zero consumers), mobile-only v2_projects.
  • The always-on network logger (Chromium netLog of the whole session partition with includeSensitive, up to 4GB/user); a boot sweep reclaims stranded log dirs.
  • The Electric-only jwt-refresh 401 gate and desktop's @electric-sql/* deps.

Replacement (deliberately boring)

  • cloudTrpc: createTRPCReact<AppRouter> over httpBatchStreamLink with bearer auth, sharing the renderer's existing QueryClient (IndexedDB persistence + Electron focus wiring already there).
  • Org-scoped read procedures porting electric-proxy/where.ts scoping + column restrictions verbatim: organization.list/listMembers/listInvitations/listTeams, apiKey.list, chat.listSessions, v2Host.listMembers, project.list, automation.latestRuns, integration.github.listOrganizationPullRequests, plus shape widenings all verified within the proxy's column-restriction spec.
  • ~100 desktop useLiveQuery sites rewritten to useQuery: settings screens fetch on mount + focus; always-visible surfaces (host presence, PR chips) poll at 30s; actively-watched screens (automation runs, tasks) at 5–15s; dropdown-gated queries use enabled: open.
  • useOptimisticCollectionActions keeps its exact API; internals now do React Query optimistic patches + tRPC mutations + invalidation.
  • collections.ts retains only the 6 localStorage UI-state collections and their quota/heal hardening. Org switch drops org-scoped cloud queries from the QueryClient (replacing per-org collection eviction; analytics/device roots excluded — they exist on the electron router too).
  • Mobile (not actively used, non-gating): collections layer deleted; six consumed collections became plain useQuery hooks.

Behavior changes made deliberately

  • automation.get/getPrompt reads are org-scoped (Electric shape parity; the Team tab links to teammates' automations). Mutations stay owner-scoped.
  • The paywall gate defers on unresolved plan data (ensureData) instead of resolving on an unknown: fail-open leaks gated actions to free users, fail-closed paywalls trialing orgs on every cold start.
  • Billing screens now treat trialing as active (aligning with useCurrentPlan/host-gating, which already did) and pick subscriptions deterministically (newest-first) instead of Electric arrival order.

Verification

  • Full-repo typecheck passes; bun run lint exits 0 (verified exit code).
  • Desktop test suite failure set is byte-identical to the base commit (30 pre-existing environment failures, zero regressions). CollectionsProvider/hooks/tasks suites pass.
  • Grep sweep: zero remaining consumers of any Electric collection.

Follow-ups (deliberately not in this PR)

  • Align session.plan (packages/auth) with ACTIVE_SUBSCRIPTION_STATUSES + deterministic ordering — it currently disagrees with billing.activePlan for trialing orgs (touches web + mobile).
  • TASKS gating is client-side only (no server-side plan check on the task router) — the deferred gate closes the cold-start window, but server enforcement is the real fix.
  • CurrentPlanCard renders "Renews " for trials where the date is trial end.
  • Narrow organization.listMembers' nested user shape (full row is Electric parity, incl. peers' organizationIds, but tighter is better).
  • task.list is capped at 500 rows (Electric synced all); paginate if any org approaches that.
  • Cold-start: tasks/PR views show a brief load where Electric replayed from SQLite — add tRPC keys to the persister whitelist if it bothers anyone.

PR 2 checklist (after adoption drains)

Delete apps/electric-proxy + deploy job, docker-compose electric + Caddyfile, ELECTRIC_*/NEXT_PUBLIC_ELECTRIC_URL/EXPO_PUBLIC_ELECTRIC_URL plumbing (turbo/vite/env/CSP/build secrets/setup scripts), clearElectricCache, main-process SQLite persistence layer (now write-only), dead ELECTRIC_TASKS_ACCESS flag, packages/local-db synced-tables block; drop agent_commands/v2_clients tables (v2_workspaces after host-heartbeat retirement); update subprocessors.mdx (legal — remove both Electric and Tavily; Tavily's last consumer was the deleted web-search route, Slack's web search is Anthropic's server-side tool), changelog, AGENTS.md rule 9, and the 5 docs referencing OPTIMISTIC_ELECTRIC_UPDATES.md.

https://claude.ai/code/session_01Rq18Ske93pkaTqTHByebtq

Rebase note (2026-08-10)

Now stacked on #6331 (client version header) — merge that first; this PR's base is feat/client-version-header and should be retargeted to main after #6331 lands. Rebased across main's session-workspaces, kanban board, and workspace-tombstone work: their new Electric reads (automations stat cards, run→workspace linking, session/archived flows in useAccessibleV2Workspaces) were ported to the same tRPC procedures, with automation.latestRuns widened to carry v2WorkspaceId/chatSessionId/terminalSessionId. One deliberate narrowing: the automations page's 7-day created/failed stat cards now derive from the latest run per automation rather than all runs in the window (an org-scoped run-history procedure restores exact counts if wanted). Desktop full-suite deltas vs base are three notifyQuotaExhausted tests that pass in isolation — test-order mock leakage from the changed file set, not a code regression.

Runtime footgun audit (post-rebase, prompted by a live repro)

A dev-run surfaced Subscriptions are unsupported by httpBatchStreamLink — cloudTrpc lacked a dedicated React context, so its Provider shadowed electronTrpc's hooks (the PR #5877 trap). Fixed, plus an audit of every always-mounted cloud query:

  • chat.listSessions capped at recent-200 for unfiltered reads; pane/run-link callers pass sessionIds and fetch exactly what they name.
  • listOrganizationPullRequests (unbounded, org-wide) deleted — zero consumers after main moved PR chips to per-host queries.
  • Tasks are paginated: new task.listPage (keyset, 100/page, Load-more on table/board, 200-row picker windows); task.list untouched because released CLI/SDK/MCP clients depend on its array shape. Optimistic task patches cover both listPage cache shapes.
  • Verified acceptable: known-hosts 30s (rows≈hosts), failed-automations 30s (rows≈automations), dashboard sidebar does no cloud polling (local + host EventBus), intervals pause on window blur.

Summary by CodeRabbit

  • New Features
    • Added cloud-powered data loading across desktop and mobile for tasks, chats, hosts, organizations, members, billing, automations, and pull requests.
    • Added paginated task loading with automatic loading for filtered and scrolling views.
    • Added refreshed billing, API-key, team, invitation, and organization data views.
  • Bug Fixes
    • Improved stale-data handling when switching organizations.
    • Workspace cleanup now completes locally without legacy cloud deletion.
  • Removed
    • Removed web workspace and terminal experiences, web search, and agent command tools.