diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 432883ef6..c6739324b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -70,6 +70,7 @@ "@xterm/addon-webgl": "^0.18.0", "@xterm/headless": "^5.5.0", "@xterm/xterm": "^5.5.0", + "better-auth": "^1.4.9", "better-sqlite3": "12.5.0", "bindings": "^1.5.0", "clsx": "^2.1.1", diff --git a/apps/desktop/src/lib/trpc/routers/auth/index.ts b/apps/desktop/src/lib/trpc/routers/auth/index.ts index 2b980cfb1..04fc4798c 100644 --- a/apps/desktop/src/lib/trpc/routers/auth/index.ts +++ b/apps/desktop/src/lib/trpc/routers/auth/index.ts @@ -1,62 +1,113 @@ +import crypto from "node:crypto"; +import fs from "node:fs/promises"; import { AUTH_PROVIDERS } from "@superset/shared/constants"; import { observable } from "@trpc/server/observable"; -import { type AuthSession, authService } from "main/lib/auth"; +import { shell } from "electron"; +import { env } from "main/env.main"; import { z } from "zod"; import { publicProcedure, router } from "../.."; - -/** Auth state emitted by onAuthState subscription */ -export type AuthState = (AuthSession & { token: string | null }) | null; +import { + authEvents, + loadToken, + saveToken, + stateStore, + TOKEN_FILE, +} from "./utils/auth-functions"; export const createAuthRouter = () => { return router({ - onAuthState: publicProcedure.subscription(() => { - return observable((emit) => { - const emitCurrent = () => { - const sessionData = authService.getSession(); - const token = authService.getAccessToken(); + /** + * Get initial token from encrypted disk storage. + * Called once on app startup for hydration. + */ + getStoredToken: publicProcedure.query(async () => { + return await loadToken(); + }), - if (!sessionData) { - emit.next(null); - return; + /** + * Persist token to encrypted disk storage. + * Called when renderer saves token to localStorage. + */ + persistToken: publicProcedure + .input( + z.object({ + token: z.string(), + expiresAt: z.string(), + }), + ) + .mutation(async ({ input }) => { + await saveToken(input); + return { success: true }; + }), + + /** + * Subscribe to token changes from deep link callbacks. + * CRITICAL: Notifies renderer when OAuth callback saves new token. + * Without this, renderer wouldn't know to update localStorage after OAuth. + */ + onTokenChanged: publicProcedure.subscription(() => { + return observable<{ token: string; expiresAt: string } | null>((emit) => { + // Emit initial token on subscription + loadToken().then((initial) => { + if (initial.token && initial.expiresAt) { + emit.next({ token: initial.token, expiresAt: initial.expiresAt }); } + }); - emit.next({ ...sessionData, token }); + const handler = (data: { token: string; expiresAt: string }) => { + emit.next(data); }; - emitCurrent(); - - const sessionHandler = () => { - emitCurrent(); - }; - const stateHandler = () => { - emitCurrent(); - }; - - authService.on("session-changed", sessionHandler); - authService.on("state-changed", stateHandler); + authEvents.on("token-saved", handler); return () => { - authService.off("session-changed", sessionHandler); - authService.off("state-changed", stateHandler); + authEvents.off("token-saved", handler); }; }); }), - setActiveOrganization: publicProcedure - .input(z.object({ organizationId: z.string() })) - .mutation(async ({ input }) => { - await authService.setActiveOrganization(input.organizationId); - return { success: true }; - }), - + /** + * Start OAuth sign-in flow. + * Opens browser for OAuth, token delivered via deep link callback. + */ signIn: publicProcedure .input(z.object({ provider: z.enum(AUTH_PROVIDERS) })) .mutation(async ({ input }) => { - return authService.signIn(input.provider); + try { + const state = crypto.randomBytes(32).toString("base64url"); + stateStore.set(state, Date.now()); + + // Clean up old states (older than 10 minutes) + const tenMinutesAgo = Date.now() - 10 * 60 * 1000; + for (const [s, ts] of stateStore) { + if (ts < tenMinutesAgo) stateStore.delete(s); + } + + const connectUrl = new URL( + `${env.NEXT_PUBLIC_API_URL}/api/auth/desktop/connect`, + ); + connectUrl.searchParams.set("provider", input.provider); + connectUrl.searchParams.set("state", state); + await shell.openExternal(connectUrl.toString()); + return { success: true }; + } catch (err) { + return { + success: false, + error: + err instanceof Error ? err.message : "Failed to open browser", + }; + } }), + /** + * Sign out - clears token from disk. + * Renderer should also clear localStorage and call authClient.signOut(). + */ signOut: publicProcedure.mutation(async () => { - await authService.signOut(); + console.log("[auth] Clearing token"); + try { + await fs.unlink(TOKEN_FILE); + } catch {} return { success: true }; }), }); diff --git a/apps/desktop/src/lib/trpc/routers/auth/utils/auth-functions.ts b/apps/desktop/src/lib/trpc/routers/auth/utils/auth-functions.ts new file mode 100644 index 000000000..8401e79b1 --- /dev/null +++ b/apps/desktop/src/lib/trpc/routers/auth/utils/auth-functions.ts @@ -0,0 +1,99 @@ +import { EventEmitter } from "node:events"; +import fs from "node:fs/promises"; +import { join } from "node:path"; +import { PROTOCOL_SCHEMES } from "@superset/shared/constants"; +import { SUPERSET_HOME_DIR } from "main/lib/app-environment"; +import { decrypt, encrypt } from "./crypto-storage"; + +interface StoredAuth { + token: string; + expiresAt: string; +} + +export const TOKEN_FILE = join(SUPERSET_HOME_DIR, "auth-token.enc"); +export const stateStore = new Map(); + +/** + * Event emitter for auth-related events. + * Used by tRPC subscription to notify renderer of token changes. + */ +export const authEvents = new EventEmitter(); + +/** + * Load token from encrypted disk storage. + */ +export async function loadToken(): Promise<{ + token: string | null; + expiresAt: string | null; +}> { + try { + const data = decrypt(await fs.readFile(TOKEN_FILE)); + const parsed: StoredAuth = JSON.parse(data); + console.log("[auth] Token loaded from disk"); + return { token: parsed.token, expiresAt: parsed.expiresAt }; + } catch { + return { token: null, expiresAt: null }; + } +} + +/** + * Persist token to encrypted disk storage and notify subscribers. + */ +export async function saveToken({ + token, + expiresAt, +}: { + token: string; + expiresAt: string; +}): Promise { + const storedAuth: StoredAuth = { token, expiresAt }; + await fs.writeFile(TOKEN_FILE, encrypt(JSON.stringify(storedAuth))); + console.log("[auth] Token saved to disk"); + + // Emit event for onTokenChanged subscription + authEvents.emit("token-saved", { token, expiresAt }); +} + +/** + * Handle OAuth callback from deep link. + * Validates CSRF state and saves token. + */ +export async function handleAuthCallback(params: { + token: string; + expiresAt: string; + state: string; +}): Promise<{ success: boolean; error?: string }> { + if (!stateStore.has(params.state)) { + return { success: false, error: "Invalid or expired auth session" }; + } + stateStore.delete(params.state); + + await saveToken({ token: params.token, expiresAt: params.expiresAt }); + + return { success: true }; +} + +/** + * Parse and validate auth deep link URL. + */ +export function parseAuthDeepLink( + url: string, +): { token: string; expiresAt: string; state: string } | null { + try { + const parsed = new URL(url); + const validProtocols = [ + `${PROTOCOL_SCHEMES.PROD}:`, + `${PROTOCOL_SCHEMES.DEV}:`, + ]; + if (!validProtocols.includes(parsed.protocol)) return null; + if (parsed.host !== "auth" || parsed.pathname !== "/callback") return null; + + const token = parsed.searchParams.get("token"); + const expiresAt = parsed.searchParams.get("expiresAt"); + const state = parsed.searchParams.get("state"); + if (!token || !expiresAt || !state) return null; + return { token, expiresAt, state }; + } catch { + return null; + } +} diff --git a/apps/desktop/src/main/lib/auth/crypto-storage.ts b/apps/desktop/src/lib/trpc/routers/auth/utils/crypto-storage.ts similarity index 100% rename from apps/desktop/src/main/lib/auth/crypto-storage.ts rename to apps/desktop/src/lib/trpc/routers/auth/utils/crypto-storage.ts diff --git a/apps/desktop/src/lib/trpc/routers/index.ts b/apps/desktop/src/lib/trpc/routers/index.ts index 66ad3766f..7a59dad98 100644 --- a/apps/desktop/src/lib/trpc/routers/index.ts +++ b/apps/desktop/src/lib/trpc/routers/index.ts @@ -13,10 +13,8 @@ import { createPortsRouter } from "./ports"; import { createProjectsRouter } from "./projects"; import { createRingtoneRouter } from "./ringtone"; import { createSettingsRouter } from "./settings"; -import { createTasksRouter } from "./tasks"; import { createTerminalRouter } from "./terminal"; import { createUiStateRouter } from "./ui-state"; -import { createUserRouter } from "./user"; import { createWindowRouter } from "./window"; import { createWorkspacesRouter } from "./workspaces"; @@ -25,7 +23,6 @@ export const createAppRouter = (getWindow: () => BrowserWindow | null) => { analytics: createAnalyticsRouter(), auth: createAuthRouter(), autoUpdate: createAutoUpdateRouter(), - user: createUserRouter(), window: createWindowRouter(getWindow), projects: createProjectsRouter(getWindow), workspaces: createWorkspacesRouter(), @@ -40,7 +37,6 @@ export const createAppRouter = (getWindow: () => BrowserWindow | null) => { config: createConfigRouter(), uiState: createUiStateRouter(), ringtone: createRingtoneRouter(), - tasks: createTasksRouter(), }); }; diff --git a/apps/desktop/src/lib/trpc/routers/tasks/index.ts b/apps/desktop/src/lib/trpc/routers/tasks/index.ts deleted file mode 100644 index 1bc27a9bd..000000000 --- a/apps/desktop/src/lib/trpc/routers/tasks/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { apiClient } from "main/lib/api-client"; -import { z } from "zod"; -import { publicProcedure, router } from "../.."; - -const taskPriorityValues = ["urgent", "high", "medium", "low", "none"] as const; - -const updateTaskSchema = z.object({ - id: z.string().uuid(), - title: z.string().min(1).optional(), - description: z.string().nullable().optional(), - status: z.string().optional(), - priority: z.enum(taskPriorityValues).optional(), - repositoryId: z.string().uuid().optional(), - assigneeId: z.string().uuid().nullable().optional(), - branch: z.string().nullable().optional(), - prUrl: z.string().url().nullable().optional(), - estimate: z.number().int().positive().nullable().optional(), - dueDate: z.coerce.date().nullable().optional(), - labels: z.array(z.string()).optional(), -}); - -export const createTasksRouter = () => { - return router({ - update: publicProcedure - .input(updateTaskSchema) - .mutation(async ({ input }) => { - const result = await apiClient.task.update.mutate(input); - return result; - }), - }); -}; diff --git a/apps/desktop/src/lib/trpc/routers/user/index.ts b/apps/desktop/src/lib/trpc/routers/user/index.ts deleted file mode 100644 index e93d1e0f0..000000000 --- a/apps/desktop/src/lib/trpc/routers/user/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { apiClient } from "main/lib/api-client"; -import { publicProcedure, router } from "../.."; - -/** - * User router - proxies to API tRPC endpoints - */ -export const createUserRouter = () => { - return router({ - /** - * Get current user info - */ - me: publicProcedure.query(async () => { - return apiClient.user.me.query(); - }), - - myOrganizations: publicProcedure.query(async () => { - return apiClient.user.myOrganizations.query(); - }), - }); -}; - -export type UserRouter = ReturnType; diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 7517c71e5..1c5b29f37 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -6,11 +6,14 @@ import path from "node:path"; import { settings } from "@superset/local-db"; import { app, BrowserWindow, dialog } from "electron"; import { makeAppSetup } from "lib/electron-app/factories/app/setup"; +import { + handleAuthCallback, + parseAuthDeepLink, +} from "lib/trpc/routers/auth/utils/auth-functions"; import { DEFAULT_CONFIRM_ON_QUIT, PROTOCOL_SCHEME } from "shared/constants"; import { setupAgentHooks } from "./lib/agent-setup"; import { posthog } from "./lib/analytics"; import { initAppState } from "./lib/app-state"; -import { authService, parseAuthDeepLink } from "./lib/auth"; import { setupAutoUpdater } from "./lib/auto-updater"; import { localDb } from "./lib/local-db"; import { ensureShellEnvVars } from "./lib/shell-env"; @@ -41,7 +44,7 @@ async function processDeepLink(url: string): Promise { const authParams = parseAuthDeepLink(url); if (!authParams) return; - const result = await authService.handleAuthCallback(authParams); + const result = await handleAuthCallback(authParams); if (result.success) { focusMainWindow(); } else { @@ -206,7 +209,6 @@ if (!gotTheLock) { await app.whenReady(); await initAppState(); - await authService.initialize(); // Resolve shell environment before setting up agent hooks // This ensures ZDOTDIR and PATH are available for terminal initialization diff --git a/apps/desktop/src/main/lib/api-client.ts b/apps/desktop/src/main/lib/api-client.ts deleted file mode 100644 index 1eac6a438..000000000 --- a/apps/desktop/src/main/lib/api-client.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { AppRouter } from "@superset/trpc"; -import { createTRPCClient, httpBatchLink } from "@trpc/client"; -import { env } from "main/env.main"; -import superjson from "superjson"; -import { authService } from "./auth"; - -/** - * tRPC client for calling the Superset API - * Automatically includes the access token in requests - */ -export const apiClient = createTRPCClient({ - links: [ - httpBatchLink({ - url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, - transformer: superjson, - async headers() { - const token = authService.getAccessToken(); - if (token) { - return { - Authorization: `Bearer ${token}`, - }; - } - return {}; - }, - async fetch(url, options) { - try { - const response = await globalThis.fetch(url, options); - - if (response.status === 401 || response.status === 403) { - console.log("[api-client] Auth error, clearing session"); - await authService.signOut(); - } - - return response; - } catch (error) { - console.log("[api-client] Network error, preserving session", error); - throw error; - } - }, - }), - ], -}); diff --git a/apps/desktop/src/main/lib/auth/auth.ts b/apps/desktop/src/main/lib/auth/auth.ts deleted file mode 100644 index ae7aabac8..000000000 --- a/apps/desktop/src/main/lib/auth/auth.ts +++ /dev/null @@ -1,236 +0,0 @@ -import crypto from "node:crypto"; -import { EventEmitter } from "node:events"; -import fs from "node:fs/promises"; -import { join } from "node:path"; -import { authClient } from "@superset/auth/client"; -import type { AuthProvider } from "@superset/shared/constants"; -import { PROTOCOL_SCHEMES } from "@superset/shared/constants"; -import { shell } from "electron"; -import { env } from "main/env.main"; -import { SUPERSET_HOME_DIR } from "../app-environment"; -import { decrypt, encrypt } from "./crypto-storage"; - -export interface SignInResult { - success: boolean; - error?: string; -} - -const TOKEN_FILE = join(SUPERSET_HOME_DIR, "auth-token.enc"); -const stateStore = new Map(); - -export function parseAuthDeepLink( - url: string, -): { token: string; expiresAt: string; state: string } | null { - try { - const parsed = new URL(url); - const validProtocols = [ - `${PROTOCOL_SCHEMES.PROD}:`, - `${PROTOCOL_SCHEMES.DEV}:`, - ]; - if (!validProtocols.includes(parsed.protocol)) return null; - if (parsed.host !== "auth" || parsed.pathname !== "/callback") return null; - - const token = parsed.searchParams.get("token"); - const expiresAt = parsed.searchParams.get("expiresAt"); - const state = parsed.searchParams.get("state"); - if (!token || !expiresAt || !state) return null; - return { token, expiresAt, state }; - } catch { - return null; - } -} - -interface StoredAuth { - token: string; - expiresAt: string; -} - -type SessionResponse = Awaited>; -/** Session data from the auth API */ -export type AuthSession = NonNullable; -type Session = AuthSession; - -class AuthService extends EventEmitter { - private token: string | null = null; - private expiresAt: Date | null = null; - private session: Session | null = null; - - async initialize(): Promise { - try { - const data = decrypt(await fs.readFile(TOKEN_FILE)); - const parsed: StoredAuth = JSON.parse(data); - this.token = parsed.token; - this.expiresAt = new Date(parsed.expiresAt); - - if (this.isExpired()) { - console.log("[auth] Session expired, clearing"); - await this.signOut(); - } else { - console.log("[auth] Session restored"); - // Fetch session data from API - await this.refreshSession(); - } - } catch { - this.token = null; - this.expiresAt = null; - } - } - - private isExpired(): boolean { - if (!this.expiresAt) return true; - // Consider expired 5 minutes before actual expiry for safety - const bufferMs = 5 * 60 * 1000; - return Date.now() > this.expiresAt.getTime() - bufferMs; - } - - getState() { - const state = { - isSignedIn: !!this.token && !this.isExpired(), - expiresAt: this.expiresAt?.toISOString() ?? null, - }; - console.log("[auth] getState called:", { - hasToken: !!this.token, - isExpired: this.isExpired(), - isSignedIn: state.isSignedIn, - }); - return state; - } - - getAccessToken(): string | null { - const expired = this.isExpired(); - const token = expired ? null : this.token; - console.log("[auth] getAccessToken called:", { - hasToken: !!this.token, - isExpired: expired, - returning: token ? "token" : "null", - }); - return token; - } - - getSession(): Session | null { - return this.session; - } - - private async refreshSession(): Promise { - const token = this.getAccessToken(); - if (!token) { - this.session = null; - return; - } - - try { - const { data: session, error } = await authClient.getSession({ - fetchOptions: { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - }); - - if (error) { - console.error("[auth] Failed to refresh session:", error); - this.session = null; - return; - } - - this.session = session; - this.emit("session-changed", this.session); - } catch (error) { - console.error("[auth] Failed to refresh session:", error); - this.session = null; - } - } - - async setActiveOrganization(organizationId: string): Promise { - const token = this.getAccessToken(); - if (!token) throw new Error("Not authenticated"); - - const { error } = await authClient.organization.setActive({ - organizationId, - fetchOptions: { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - }); - - if (error) { - throw new Error(`Failed to set active organization: ${error.message}`); - } - - // Refresh session to get updated activeOrganizationId - await this.refreshSession(); - } - - async signIn(provider: AuthProvider): Promise { - try { - const state = crypto.randomBytes(32).toString("base64url"); - stateStore.set(state, Date.now()); - - const tenMinutesAgo = Date.now() - 10 * 60 * 1000; - for (const [s, ts] of stateStore) { - if (ts < tenMinutesAgo) stateStore.delete(s); - } - - const connectUrl = new URL( - `${env.NEXT_PUBLIC_API_URL}/api/auth/desktop/connect`, - ); - connectUrl.searchParams.set("provider", provider); - connectUrl.searchParams.set("state", state); - await shell.openExternal(connectUrl.toString()); - return { success: true }; - } catch (err) { - return { - success: false, - error: err instanceof Error ? err.message : "Failed to open browser", - }; - } - } - - async handleAuthCallback(params: { - token: string; - expiresAt: string; - state: string; - }): Promise { - if (!stateStore.has(params.state)) { - return { success: false, error: "Invalid or expired auth session" }; - } - stateStore.delete(params.state); - - this.token = params.token; - this.expiresAt = new Date(params.expiresAt); - - const storedAuth: StoredAuth = { - token: this.token, - expiresAt: params.expiresAt, - }; - await fs.writeFile(TOKEN_FILE, encrypt(JSON.stringify(storedAuth))); - - console.log("[auth] Token saved, fetching session..."); - - // Fetch session data from API before emitting state change - await this.refreshSession(); - - console.log("[auth] Session fetched, emitting state change"); - const state = this.getState(); - console.log("[auth] EMIT state-changed from handleAuthCallback:", state); - this.emit("state-changed", state); - - return { success: true }; - } - - async signOut(): Promise { - console.log("[auth] signOut called"); - this.token = null; - this.expiresAt = null; - this.session = null; - try { - await fs.unlink(TOKEN_FILE); - } catch {} - const state = this.getState(); - console.log("[auth] EMIT state-changed from signOut:", state); - this.emit("state-changed", state); - } -} - -export const authService = new AuthService(); diff --git a/apps/desktop/src/main/lib/auth/index.ts b/apps/desktop/src/main/lib/auth/index.ts deleted file mode 100644 index 7741ad341..000000000 --- a/apps/desktop/src/main/lib/auth/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { AuthSession, SignInResult } from "./auth"; -export { authService, parseAuthDeepLink } from "./auth"; diff --git a/apps/desktop/src/renderer/components/ConfigFilePreview/ConfigFilePreview.tsx b/apps/desktop/src/renderer/components/ConfigFilePreview/ConfigFilePreview.tsx index 83c29d834..e70e44d40 100644 --- a/apps/desktop/src/renderer/components/ConfigFilePreview/ConfigFilePreview.tsx +++ b/apps/desktop/src/renderer/components/ConfigFilePreview/ConfigFilePreview.tsx @@ -3,7 +3,7 @@ import { Button } from "@superset/ui/button"; import { cn } from "@superset/ui/utils"; import { HiArrowTopRightOnSquare } from "react-icons/hi2"; import { OpenInButton } from "renderer/components/OpenInButton"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { CONFIG_FILE_NAME, CONFIG_TEMPLATE, @@ -23,7 +23,7 @@ export function ConfigFilePreview({ configFilePath, className, }: ConfigFilePreviewProps) { - const { data: configData } = trpc.config.getConfigContent.useQuery( + const { data: configData } = electronTrpc.config.getConfigContent.useQuery( { projectId }, { enabled: !!projectId }, ); diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx index 9d69a3857..ec17f725c 100644 --- a/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx @@ -31,8 +31,8 @@ import debounce from "lodash/debounce"; import { useEffect, useMemo, useRef, useState } from "react"; import { GoGitBranch } from "react-icons/go"; import { HiCheck, HiChevronDown, HiChevronUpDown } from "react-icons/hi2"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { formatRelativeTime } from "renderer/lib/formatRelativeTime"; -import { trpc } from "renderer/lib/trpc"; import { useCreateWorkspace } from "renderer/react-query/workspaces"; import { useCloseNewWorkspaceModal, @@ -94,13 +94,15 @@ export function NewWorkspaceModal() { debouncedSetTitle(value); // Debounced update for derived state }; - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); - const { data: recentProjects = [] } = trpc.projects.getRecents.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); + const { data: recentProjects = [] } = + electronTrpc.projects.getRecents.useQuery(); const { data: branchData, isLoading: isBranchesLoading, isError: isBranchesError, - } = trpc.projects.getBranches.useQuery( + } = electronTrpc.projects.getBranches.useQuery( { projectId: selectedProjectId ?? "" }, { enabled: !!selectedProjectId }, ); diff --git a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx index cb9280161..5f85b5084 100644 --- a/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx +++ b/apps/desktop/src/renderer/components/NewWorkspaceModal/components/ExistingWorktreesList/ExistingWorktreesList.tsx @@ -2,7 +2,7 @@ import { Button } from "@superset/ui/button"; import { toast } from "@superset/ui/sonner"; import { formatDistanceToNow } from "date-fns"; import { LuGitBranch } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useOpenWorktree } from "renderer/react-query/workspaces"; interface ExistingWorktreesListProps { @@ -15,7 +15,7 @@ export function ExistingWorktreesList({ onOpenSuccess, }: ExistingWorktreesListProps) { const { data: worktrees = [], isLoading } = - trpc.workspaces.getWorktreesByProject.useQuery({ projectId }); + electronTrpc.workspaces.getWorktreesByProject.useQuery({ projectId }); const openWorktree = useOpenWorktree(); const closedWorktrees = worktrees diff --git a/apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx b/apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx index 7804bc176..b635671b2 100644 --- a/apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx +++ b/apps/desktop/src/renderer/components/OpenInButton/OpenInButton.tsx @@ -37,7 +37,7 @@ import vscodeInsidersIcon from "renderer/assets/app-icons/vscode-insiders.svg"; import warpIcon from "renderer/assets/app-icons/warp.png"; import webstormIcon from "renderer/assets/app-icons/webstorm.svg"; import xcodeIcon from "renderer/assets/app-icons/xcode.svg"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useHotkeyText } from "renderer/stores/hotkeys"; interface AppOption { @@ -110,7 +110,7 @@ export function OpenInButton({ showShortcuts = false, }: OpenInButtonProps) { const [isOpen, setIsOpen] = useState(false); - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const openInShortcut = useHotkeyText("OPEN_IN_APP"); const copyPathShortcut = useHotkeyText("COPY_PATH"); const showOpenInShortcut = showShortcuts && openInShortcut !== "Unassigned"; @@ -118,12 +118,12 @@ export function OpenInButton({ showShortcuts && copyPathShortcut !== "Unassigned"; const { data: lastUsedApp = "cursor" } = - trpc.settings.getLastUsedApp.useQuery(); + electronTrpc.settings.getLastUsedApp.useQuery(); - const openInApp = trpc.external.openInApp.useMutation({ + const openInApp = electronTrpc.external.openInApp.useMutation({ onSuccess: () => utils.settings.getLastUsedApp.invalidate(), }); - const copyPath = trpc.external.copyPath.useMutation(); + const copyPath = electronTrpc.external.copyPath.useMutation(); const currentApp = getAppOption(lastUsedApp); diff --git a/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx b/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx index 04deabb0a..8178e22c4 100644 --- a/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx +++ b/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { trpc } from "renderer/lib/trpc"; import { posthog } from "../../lib/posthog"; @@ -7,7 +8,7 @@ const AUTH_COMPLETED_KEY = "superset_auth_completed"; export function PostHogUserIdentifier() { const { data: user, isSuccess } = trpc.user.me.useQuery(); - const { mutate: setUserId } = trpc.analytics.setUserId.useMutation(); + const { mutate: setUserId } = electronTrpc.analytics.setUserId.useMutation(); useEffect(() => { if (user) { diff --git a/apps/desktop/src/renderer/components/SetupConfigModal/SetupConfigModal.tsx b/apps/desktop/src/renderer/components/SetupConfigModal/SetupConfigModal.tsx index f2b0e1cb7..177e09167 100644 --- a/apps/desktop/src/renderer/components/SetupConfigModal/SetupConfigModal.tsx +++ b/apps/desktop/src/renderer/components/SetupConfigModal/SetupConfigModal.tsx @@ -9,7 +9,7 @@ import { } from "@superset/ui/dialog"; import { HiArrowTopRightOnSquare } from "react-icons/hi2"; import { OpenInButton } from "renderer/components/OpenInButton"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCloseConfigModal, useConfigModalOpen, @@ -26,15 +26,16 @@ export function SetupConfigModal() { const projectId = useConfigModalProjectId(); const closeModal = useCloseConfigModal(); - const { data: project } = trpc.projects.get.useQuery( + const { data: project } = electronTrpc.projects.get.useQuery( { id: projectId ?? "" }, { enabled: !!projectId }, ); - const { data: configFilePath } = trpc.config.getConfigFilePath.useQuery( - { projectId: projectId ?? "" }, - { enabled: !!projectId }, - ); + const { data: configFilePath } = + electronTrpc.config.getConfigFilePath.useQuery( + { projectId: projectId ?? "" }, + { enabled: !!projectId }, + ); const projectName = project?.name ?? "your-project"; diff --git a/apps/desktop/src/renderer/components/UpdateRequiredPage/UpdateRequiredPage.tsx b/apps/desktop/src/renderer/components/UpdateRequiredPage/UpdateRequiredPage.tsx index a284e54fa..637ab3e52 100644 --- a/apps/desktop/src/renderer/components/UpdateRequiredPage/UpdateRequiredPage.tsx +++ b/apps/desktop/src/renderer/components/UpdateRequiredPage/UpdateRequiredPage.tsx @@ -1,7 +1,7 @@ import { Button } from "@superset/ui/button"; import { useState } from "react"; import { HiArrowPath, HiExclamationTriangle } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { AppFrame } from "renderer/screens/main/components/AppFrame"; import { Background } from "renderer/screens/main/components/Background"; import { @@ -21,9 +21,9 @@ export function UpdateRequiredPage({ minimumVersion, message, }: UpdateRequiredPageProps) { - const openUrl = trpc.external.openUrl.useMutation(); - const checkMutation = trpc.autoUpdate.check.useMutation(); - const installMutation = trpc.autoUpdate.install.useMutation(); + const openUrl = electronTrpc.external.openUrl.useMutation(); + const checkMutation = electronTrpc.autoUpdate.check.useMutation(); + const installMutation = electronTrpc.autoUpdate.install.useMutation(); // Track update status via subscription for real-time updates const [updateStatus, setUpdateStatus] = useState<{ @@ -32,7 +32,7 @@ export function UpdateRequiredPage({ }>({ status: AUTO_UPDATE_STATUS.IDLE }); // Subscribe to auto-update status changes - trpc.autoUpdate.subscribe.useSubscription(undefined, { + electronTrpc.autoUpdate.subscribe.useSubscription(undefined, { onData: (event) => { setUpdateStatus({ status: event.status, error: event.error }); }, diff --git a/apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx b/apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx index 0d6f8946b..3bc173bec 100644 --- a/apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx +++ b/apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx @@ -1,7 +1,7 @@ import { Button } from "@superset/ui/button"; import { toast } from "@superset/ui/sonner"; import { HiMiniXMark } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { AUTO_UPDATE_STATUS, RELEASES_URL } from "shared/auto-update"; interface UpdateToastProps { @@ -17,9 +17,9 @@ export function UpdateToast({ version, error, }: UpdateToastProps) { - const openUrl = trpc.external.openUrl.useMutation(); - const installMutation = trpc.autoUpdate.install.useMutation(); - const dismissMutation = trpc.autoUpdate.dismiss.useMutation({ + const openUrl = electronTrpc.external.openUrl.useMutation(); + const installMutation = electronTrpc.autoUpdate.install.useMutation(); + const dismissMutation = electronTrpc.autoUpdate.dismiss.useMutation({ onSuccess: () => { toast.dismiss(toastId); }, diff --git a/apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx b/apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx index 34c8584ce..a15131aa3 100644 --- a/apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx +++ b/apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx @@ -1,13 +1,13 @@ import { toast } from "@superset/ui/sonner"; import { useRef } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { AUTO_UPDATE_STATUS } from "shared/auto-update"; import { UpdateToast } from "./UpdateToast"; export function useUpdateListener() { const toastIdRef = useRef(null); - trpc.autoUpdate.subscribe.useSubscription(undefined, { + electronTrpc.autoUpdate.subscribe.useSubscription(undefined, { onData: (event) => { const { status, version, error } = event; diff --git a/apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts b/apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts index acd914cb1..5fd08cfe2 100644 --- a/apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts +++ b/apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCreateBranchWorkspace, useSetActiveWorkspace, @@ -16,8 +16,10 @@ import { useAppHotkey } from "renderer/stores/hotkeys"; * - Auto-create main workspace for new projects */ export function useWorkspaceShortcuts() { - const { data: groups = [] } = trpc.workspaces.getAllGrouped.useQuery(); - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: groups = [] } = + electronTrpc.workspaces.getAllGrouped.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const activeWorkspaceId = activeWorkspace?.id || null; const setActiveWorkspace = useSetActiveWorkspace(); const createBranchWorkspace = useCreateBranchWorkspace(); diff --git a/apps/desktop/src/renderer/lib/auth-client.ts b/apps/desktop/src/renderer/lib/auth-client.ts new file mode 100644 index 000000000..d4a21c520 --- /dev/null +++ b/apps/desktop/src/renderer/lib/auth-client.ts @@ -0,0 +1,32 @@ +import { organizationClient } from "better-auth/client/plugins"; +import { createAuthClient } from "better-auth/react"; +import { env } from "renderer/env.renderer"; + +let authToken: string | null = null; + +export function setAuthToken(token: string | null) { + authToken = token; +} + +export function getAuthToken(): string | null { + return authToken; +} + +/** + * Better Auth client for Electron desktop app. + * + * Security: Token stored in memory only (not localStorage). + * - Better Auth reads token via getter function + * - AuthProvider manages token in React context + * - Token persisted only to encrypted disk storage (main process) + */ +export const authClient = createAuthClient({ + baseURL: env.NEXT_PUBLIC_API_URL, + plugins: [organizationClient()], + fetchOptions: { + auth: { + type: "Bearer", + token: () => authToken || "", + }, + }, +}); diff --git a/apps/desktop/src/renderer/lib/electron-trpc.ts b/apps/desktop/src/renderer/lib/electron-trpc.ts new file mode 100644 index 000000000..c7a4f24e7 --- /dev/null +++ b/apps/desktop/src/renderer/lib/electron-trpc.ts @@ -0,0 +1,10 @@ +import { createTRPCReact } from "@trpc/react-query"; +import type { inferRouterOutputs } from "@trpc/server"; +import type { AppRouter } from "lib/trpc/routers"; + +/** + * tRPC React client for Electron IPC communication with main process. + * For desktop-specific operations: workspaces, terminal, auth, etc. + */ +export const electronTrpc = createTRPCReact(); +export type ElectronRouterOutputs = inferRouterOutputs; diff --git a/apps/desktop/src/renderer/lib/trpc-client.ts b/apps/desktop/src/renderer/lib/trpc-client.ts index 8dc7cc2d0..c7b868b5c 100644 --- a/apps/desktop/src/renderer/lib/trpc-client.ts +++ b/apps/desktop/src/renderer/lib/trpc-client.ts @@ -2,15 +2,15 @@ import { createTRPCProxyClient } from "@trpc/client"; import type { AppRouter } from "lib/trpc/routers"; import superjson from "superjson"; import { ipcLink } from "trpc-electron/renderer"; +import { electronTrpc } from "./electron-trpc"; import { sessionIdLink } from "./session-id-link"; -import { trpc } from "./trpc"; -/** tRPC client for React hooks (used by TRPCProvider). */ -export const reactClient = trpc.createClient({ +/** Electron tRPC React client for React hooks (used by ElectronTRPCProvider). */ +export const electronReactClient = electronTrpc.createClient({ links: [sessionIdLink(), ipcLink({ transformer: superjson })], }); -/** tRPC proxy client for imperative calls from stores/utilities. */ -export const trpcClient = createTRPCProxyClient({ +/** Electron tRPC proxy client for imperative calls from stores/utilities. */ +export const electronTrpcClient = createTRPCProxyClient({ links: [sessionIdLink(), ipcLink({ transformer: superjson })], }); diff --git a/apps/desktop/src/renderer/lib/trpc-storage.ts b/apps/desktop/src/renderer/lib/trpc-storage.ts index edefa71b0..1144ba44e 100644 --- a/apps/desktop/src/renderer/lib/trpc-storage.ts +++ b/apps/desktop/src/renderer/lib/trpc-storage.ts @@ -1,6 +1,6 @@ import type { HotkeysState } from "shared/hotkeys"; import { createJSONStorage, type StateStorage } from "zustand/middleware"; -import { trpcClient } from "./trpc-client"; +import { electronTrpcClient } from "./trpc-client"; /** * Flag to skip the next hotkeys persist operation. @@ -54,9 +54,9 @@ function createTrpcStorageAdapter(config: TrpcStorageConfig): StateStorage { */ export const trpcTabsStorage = createJSONStorage(() => createTrpcStorageAdapter({ - get: () => trpcClient.uiState.tabs.get.query(), + get: () => electronTrpcClient.uiState.tabs.get.query(), // biome-ignore lint/suspicious/noExplicitAny: Zustand persist passes unknown, tRPC expects typed input - set: (input) => trpcClient.uiState.tabs.set.mutate(input as any), + set: (input) => electronTrpcClient.uiState.tabs.set.mutate(input as any), }), ); @@ -65,9 +65,9 @@ export const trpcTabsStorage = createJSONStorage(() => */ export const trpcThemeStorage = createJSONStorage(() => createTrpcStorageAdapter({ - get: () => trpcClient.uiState.theme.get.query(), + get: () => electronTrpcClient.uiState.theme.get.query(), // biome-ignore lint/suspicious/noExplicitAny: Zustand persist passes unknown, tRPC expects typed input - set: (input) => trpcClient.uiState.theme.set.mutate(input as any), + set: (input) => electronTrpcClient.uiState.theme.set.mutate(input as any), }), ); @@ -77,7 +77,7 @@ export const trpcThemeStorage = createJSONStorage(() => export const trpcHotkeysStorage = createJSONStorage(() => createTrpcStorageAdapter({ get: async () => { - const hotkeysState = await trpcClient.uiState.hotkeys.get.query(); + const hotkeysState = await electronTrpcClient.uiState.hotkeys.get.query(); return { hotkeysState }; }, set: (input) => { @@ -87,7 +87,7 @@ export const trpcHotkeysStorage = createJSONStorage(() => return Promise.resolve(); } const state = input as { hotkeysState: HotkeysState }; - return trpcClient.uiState.hotkeys.set.mutate(state.hotkeysState); + return electronTrpcClient.uiState.hotkeys.set.mutate(state.hotkeysState); }, }), ); @@ -100,12 +100,12 @@ export const trpcRingtoneStorage = createJSONStorage(() => createTrpcStorageAdapter({ get: async () => { const ringtoneId = - await trpcClient.settings.getSelectedRingtoneId.query(); + await electronTrpcClient.settings.getSelectedRingtoneId.query(); return { selectedRingtoneId: ringtoneId }; }, set: async (input) => { const state = input as { selectedRingtoneId: string }; - await trpcClient.settings.setSelectedRingtoneId.mutate({ + await electronTrpcClient.settings.setSelectedRingtoneId.mutate({ ringtoneId: state.selectedRingtoneId, }); }, diff --git a/apps/desktop/src/renderer/lib/trpc.ts b/apps/desktop/src/renderer/lib/trpc.ts index 12fd8fae8..f655abc9f 100644 --- a/apps/desktop/src/renderer/lib/trpc.ts +++ b/apps/desktop/src/renderer/lib/trpc.ts @@ -1,6 +1,7 @@ +import type { AppRouter } from "@superset/trpc"; import { createTRPCReact } from "@trpc/react-query"; -import type { inferRouterOutputs } from "@trpc/server"; -import type { AppRouter } from "lib/trpc/routers"; +/** + * tRPC React client for calling the Superset API directly over HTTP. + */ export const trpc = createTRPCReact(); -export type RouterOutputs = inferRouterOutputs; diff --git a/apps/desktop/src/renderer/providers/AuthProvider/AuthProvider.tsx b/apps/desktop/src/renderer/providers/AuthProvider/AuthProvider.tsx index 1af207f10..749ea41e2 100644 --- a/apps/desktop/src/renderer/providers/AuthProvider/AuthProvider.tsx +++ b/apps/desktop/src/renderer/providers/AuthProvider/AuthProvider.tsx @@ -5,38 +5,70 @@ import { useEffect, useState, } from "react"; -import type { RouterOutputs } from "../../lib/trpc"; -import { trpc } from "../../lib/trpc"; +import { authClient, setAuthToken } from "renderer/lib/auth-client"; +import { electronTrpc } from "../../lib/electron-trpc"; -type AuthState = RouterOutputs["auth"]["onAuthState"]; - -interface AuthContextValue { +interface AuthTokenContextValue { token: string | null; - session: AuthState; - isInitialized: boolean; } -const AuthContext = createContext(null); +const AuthTokenContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { - const { data: authState } = trpc.auth.onAuthState.useSubscription(); - const [isInitialized, setIsInitialized] = useState(false); + const [isHydrated, setIsHydrated] = useState(false); + const [token, setToken] = useState(null); + + const { data: session } = authClient.useSession(); + + const { data: storedToken } = electronTrpc.auth.getStoredToken.useQuery( + undefined, + { + refetchOnWindowFocus: false, + refetchOnReconnect: false, + }, + ); + + const persistMutation = electronTrpc.auth.persistToken.useMutation(); + + electronTrpc.auth.onTokenChanged.useSubscription(undefined, { + onData: (data) => { + if (data?.token && data?.expiresAt) { + setToken(data.token); + setAuthToken(data.token); + persistMutation.mutate({ + token: data.token, + expiresAt: data.expiresAt, + }); + } + }, + }); - // Mark as initialized once we receive the first auth state useEffect(() => { - if (authState !== undefined && !isInitialized) { - setIsInitialized(true); + if (storedToken && !isHydrated) { + if (storedToken.token && storedToken.expiresAt) { + setToken(storedToken.token); + setAuthToken(storedToken.token); + } + setIsHydrated(true); } - }, [authState, isInitialized]); + }, [storedToken, isHydrated]); - const value: AuthContextValue = { - token: authState?.token ?? null, - session: authState ?? null, - isInitialized, - }; + useEffect(() => { + if (token) { + setAuthToken(token); + } else { + setAuthToken(null); + } + }, [token]); + + useEffect(() => { + if (!session?.user && token) { + setToken(null); + setAuthToken(null); + } + }, [session, token]); - // Show loading spinner until auth state is initialized - if (!isInitialized) { + if (!isHydrated) { return (
@@ -44,13 +76,17 @@ export function AuthProvider({ children }: { children: ReactNode }) { ); } - return {children}; + return ( + + {children} + + ); } -export function useAuth(): AuthContextValue { - const context = useContext(AuthContext); +export function useAuthToken(): string | null { + const context = useContext(AuthTokenContext); if (!context) { - throw new Error("useAuth must be used within AuthProvider"); + throw new Error("useAuthToken must be used within AuthProvider"); } - return context; + return context.token; } diff --git a/apps/desktop/src/renderer/providers/AuthProvider/index.ts b/apps/desktop/src/renderer/providers/AuthProvider/index.ts index 487e3204b..8ec6b18fa 100644 --- a/apps/desktop/src/renderer/providers/AuthProvider/index.ts +++ b/apps/desktop/src/renderer/providers/AuthProvider/index.ts @@ -1 +1 @@ -export { AuthProvider, useAuth } from "./AuthProvider"; +export { AuthProvider, useAuthToken } from "./AuthProvider"; diff --git a/apps/desktop/src/renderer/providers/ElectronTRPCProvider/ElectronTRPCProvider.tsx b/apps/desktop/src/renderer/providers/ElectronTRPCProvider/ElectronTRPCProvider.tsx new file mode 100644 index 000000000..08972eed0 --- /dev/null +++ b/apps/desktop/src/renderer/providers/ElectronTRPCProvider/ElectronTRPCProvider.tsx @@ -0,0 +1,39 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useState } from "react"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { electronReactClient } from "../../lib/trpc-client"; + +/** + * Provider for Electron IPC tRPC client. + * For desktop-specific operations: workspaces, terminal, auth, etc. + */ +export function ElectronTRPCProvider({ + children, +}: { + children: React.ReactNode; +}) { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + networkMode: "always", + retry: false, + }, + mutations: { + networkMode: "always", + retry: false, + }, + }, + }), + ); + + return ( + + {children} + + ); +} diff --git a/apps/desktop/src/renderer/providers/ElectronTRPCProvider/index.ts b/apps/desktop/src/renderer/providers/ElectronTRPCProvider/index.ts new file mode 100644 index 000000000..4483d5b6f --- /dev/null +++ b/apps/desktop/src/renderer/providers/ElectronTRPCProvider/index.ts @@ -0,0 +1 @@ +export { ElectronTRPCProvider } from "./ElectronTRPCProvider"; diff --git a/apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx b/apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx index 13c289686..a784c06e2 100644 --- a/apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx +++ b/apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx @@ -1,9 +1,13 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { trpc } from "lib/trpc"; -import { useState } from "react"; -import { reactClient } from "../../lib/trpc-client"; +import { httpBatchLink } from "@trpc/client"; +import type { ReactNode } from "react"; +import { useMemo, useState } from "react"; +import { env } from "renderer/env.renderer"; +import { getAuthToken } from "renderer/lib/auth-client"; +import { trpc } from "renderer/lib/trpc"; +import superjson from "superjson"; -export function TRPCProvider({ children }: { children: React.ReactNode }) { +export function TRPCProvider({ children }: { children: ReactNode }) { const [queryClient] = useState( () => new QueryClient({ @@ -20,8 +24,23 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) { }), ); + const apiClient = useMemo(() => { + return trpc.createClient({ + links: [ + httpBatchLink({ + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + const token = getAuthToken(); + return token ? { Authorization: `Bearer ${token}` } : {}; + }, + transformer: superjson, + }), + ], + }); + }, []); + return ( - + {children} ); diff --git a/apps/desktop/src/renderer/react-query/presets/index.ts b/apps/desktop/src/renderer/react-query/presets/index.ts index c800e1286..97ace8013 100644 --- a/apps/desktop/src/renderer/react-query/presets/index.ts +++ b/apps/desktop/src/renderer/react-query/presets/index.ts @@ -1,13 +1,13 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; function useCreateTerminalPreset( options?: Parameters< - typeof trpc.settings.createTerminalPreset.useMutation + typeof electronTrpc.settings.createTerminalPreset.useMutation >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.settings.createTerminalPreset.useMutation({ + return electronTrpc.settings.createTerminalPreset.useMutation({ ...options, onSuccess: async (...args) => { await utils.settings.getTerminalPresets.invalidate(); @@ -18,12 +18,12 @@ function useCreateTerminalPreset( function useUpdateTerminalPreset( options?: Parameters< - typeof trpc.settings.updateTerminalPreset.useMutation + typeof electronTrpc.settings.updateTerminalPreset.useMutation >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.settings.updateTerminalPreset.useMutation({ + return electronTrpc.settings.updateTerminalPreset.useMutation({ ...options, onSuccess: async (...args) => { await utils.settings.getTerminalPresets.invalidate(); @@ -34,12 +34,12 @@ function useUpdateTerminalPreset( function useDeleteTerminalPreset( options?: Parameters< - typeof trpc.settings.deleteTerminalPreset.useMutation + typeof electronTrpc.settings.deleteTerminalPreset.useMutation >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.settings.deleteTerminalPreset.useMutation({ + return electronTrpc.settings.deleteTerminalPreset.useMutation({ ...options, onSuccess: async (...args) => { await utils.settings.getTerminalPresets.invalidate(); @@ -49,11 +49,13 @@ function useDeleteTerminalPreset( } function useSetDefaultPreset( - options?: Parameters[0], + options?: Parameters< + typeof electronTrpc.settings.setDefaultPreset.useMutation + >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.settings.setDefaultPreset.useMutation({ + return electronTrpc.settings.setDefaultPreset.useMutation({ ...options, onSuccess: async (...args) => { await utils.settings.getTerminalPresets.invalidate(); @@ -69,9 +71,10 @@ function useSetDefaultPreset( */ export function usePresets() { const { data: presets = [], isLoading } = - trpc.settings.getTerminalPresets.useQuery(); + electronTrpc.settings.getTerminalPresets.useQuery(); - const { data: defaultPreset } = trpc.settings.getDefaultPreset.useQuery(); + const { data: defaultPreset } = + electronTrpc.settings.getDefaultPreset.useQuery(); const createPreset = useCreateTerminalPreset(); const updatePreset = useUpdateTerminalPreset(); diff --git a/apps/desktop/src/renderer/react-query/projects/useOpenNew.ts b/apps/desktop/src/renderer/react-query/projects/useOpenNew.ts index 3dc7e1b92..8e2d26839 100644 --- a/apps/desktop/src/renderer/react-query/projects/useOpenNew.ts +++ b/apps/desktop/src/renderer/react-query/projects/useOpenNew.ts @@ -1,15 +1,15 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; /** * Mutation hook for opening a new project * Creates a Project record if it doesn't exist */ export function useOpenNew( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.projects.openNew.useMutation({ + return electronTrpc.projects.openNew.useMutation({ ...options, onSuccess: async (...args) => { // Auto-invalidate projects query diff --git a/apps/desktop/src/renderer/react-query/projects/useUpdateProject.ts b/apps/desktop/src/renderer/react-query/projects/useUpdateProject.ts index 716ff1c0c..46ba3b019 100644 --- a/apps/desktop/src/renderer/react-query/projects/useUpdateProject.ts +++ b/apps/desktop/src/renderer/react-query/projects/useUpdateProject.ts @@ -1,15 +1,15 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; /** * Mutation hook for updating a project (name, color, etc.) * Automatically invalidates project + workspace queries on success */ export function useUpdateProject( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.projects.update.useMutation({ + return electronTrpc.projects.update.useMutation({ ...options, onSuccess: async (...args) => { await Promise.all([ diff --git a/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts index 27deddeca..ccb175c32 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts @@ -1,18 +1,18 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; type CloseContext = { previousGrouped: ReturnType< - typeof trpc.useUtils + typeof electronTrpc.useUtils >["workspaces"]["getAllGrouped"]["getData"] extends () => infer R ? R : never; previousAll: ReturnType< - typeof trpc.useUtils + typeof electronTrpc.useUtils >["workspaces"]["getAll"]["getData"] extends () => infer R ? R : never; previousActive: ReturnType< - typeof trpc.useUtils + typeof electronTrpc.useUtils >["workspaces"]["getActive"]["getData"] extends () => infer R ? R : never; @@ -24,11 +24,11 @@ type CloseContext = { * then performs actual close in background. */ export function useCloseWorkspace( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.workspaces.close.useMutation({ + return electronTrpc.workspaces.close.useMutation({ ...options, onMutate: async ({ id }) => { // Cancel outgoing refetches to avoid overwriting optimistic update diff --git a/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts index dd77a99cf..834a02f50 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts @@ -1,4 +1,4 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; /** @@ -8,12 +8,12 @@ import { useTabsStore } from "renderer/stores/tabs/store"; */ export function useCreateBranchWorkspace( options?: Parameters< - typeof trpc.workspaces.createBranchWorkspace.useMutation + typeof electronTrpc.workspaces.createBranchWorkspace.useMutation >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.workspaces.createBranchWorkspace.useMutation({ + return electronTrpc.workspaces.createBranchWorkspace.useMutation({ ...options, onSuccess: async (data, ...rest) => { // Auto-invalidate all workspace queries diff --git a/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts index a9da12d19..53af0a07c 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts @@ -1,4 +1,4 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useWorkspaceInitStore } from "renderer/stores/workspace-init"; import type { WorkspaceInitProgress } from "shared/types/workspace-init"; @@ -17,15 +17,15 @@ import type { WorkspaceInitProgress } from "shared/types/workspace-init"; * to survive dialog unmounts. This hook just adds to the global pending store. */ export function useCreateWorkspace( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const addPendingTerminalSetup = useWorkspaceInitStore( (s) => s.addPendingTerminalSetup, ); const updateProgress = useWorkspaceInitStore((s) => s.updateProgress); - return trpc.workspaces.create.useMutation({ + return electronTrpc.workspaces.create.useMutation({ ...options, onSuccess: async (data, ...rest) => { // Optimistically set init progress BEFORE query invalidation to prevent diff --git a/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts index bfa21e95f..c9a396508 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts @@ -1,18 +1,18 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; type DeleteContext = { previousGrouped: ReturnType< - typeof trpc.useUtils + typeof electronTrpc.useUtils >["workspaces"]["getAllGrouped"]["getData"] extends () => infer R ? R : never; previousAll: ReturnType< - typeof trpc.useUtils + typeof electronTrpc.useUtils >["workspaces"]["getAll"]["getData"] extends () => infer R ? R : never; previousActive: ReturnType< - typeof trpc.useUtils + typeof electronTrpc.useUtils >["workspaces"]["getActive"]["getData"] extends () => infer R ? R : never; @@ -23,11 +23,11 @@ type DeleteContext = { * Server marks `deletingAt` immediately so refetches stay correct during slow git operations. */ export function useDeleteWorkspace( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.workspaces.delete.useMutation({ + return electronTrpc.workspaces.delete.useMutation({ ...options, onMutate: async ({ id }) => { await Promise.all([ diff --git a/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorktree.ts b/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorktree.ts index fd48f5ec3..a517073fd 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorktree.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorktree.ts @@ -1,15 +1,17 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; /** * Mutation hook for deleting a closed worktree (one without an active workspace). * Handles cache invalidation for worktree-related queries. */ export function useDeleteWorktree( - options?: Parameters[0], + options?: Parameters< + typeof electronTrpc.workspaces.deleteWorktree.useMutation + >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.workspaces.deleteWorktree.useMutation({ + return electronTrpc.workspaces.deleteWorktree.useMutation({ ...options, onSettled: async (...args) => { // Invalidate worktree queries to refresh the list diff --git a/apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts b/apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts index 4ef7e1632..1d22d0f07 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts @@ -1,5 +1,5 @@ import { toast } from "@superset/ui/sonner"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useOpenConfigModal } from "renderer/stores/config-modal"; import { useTabsStore } from "renderer/stores/tabs/store"; @@ -10,16 +10,19 @@ import { useTabsStore } from "renderer/stores/tabs/store"; * Shows config toast if no setup commands are configured */ export function useOpenWorktree( - options?: Parameters[0], + options?: Parameters< + typeof electronTrpc.workspaces.openWorktree.useMutation + >[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const addTab = useTabsStore((state) => state.addTab); const setTabAutoTitle = useTabsStore((state) => state.setTabAutoTitle); - const createOrAttach = trpc.terminal.createOrAttach.useMutation(); + const createOrAttach = electronTrpc.terminal.createOrAttach.useMutation(); const openConfigModal = useOpenConfigModal(); - const dismissConfigToast = trpc.config.dismissConfigToast.useMutation(); + const dismissConfigToast = + electronTrpc.config.dismissConfigToast.useMutation(); - return trpc.workspaces.openWorktree.useMutation({ + return electronTrpc.workspaces.openWorktree.useMutation({ ...options, onSuccess: async (data, ...rest) => { // Auto-invalidate all workspace queries diff --git a/apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts b/apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts index 35bbe675e..9666ffbd2 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useReorderWorkspaces.ts @@ -1,15 +1,15 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; /** * Mutation hook for reordering workspaces * Automatically invalidates workspace queries on success */ export function useReorderWorkspaces( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.workspaces.reorder.useMutation({ + return electronTrpc.workspaces.reorder.useMutation({ ...options, onSuccess: async (...args) => { await utils.workspaces.getAll.invalidate(); diff --git a/apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts index debb3a08b..02b8cd758 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts @@ -1,5 +1,5 @@ import { toast } from "@superset/ui/sonner"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; /** * Mutation hook for setting the active workspace @@ -7,10 +7,10 @@ import { trpc } from "renderer/lib/trpc"; * Shows undo toast if workspace was marked as unread (auto-cleared on switch) */ export function useSetActiveWorkspace( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); - const setUnread = trpc.workspaces.setUnread.useMutation({ + const utils = electronTrpc.useUtils(); + const setUnread = electronTrpc.workspaces.setUnread.useMutation({ onSuccess: () => { utils.workspaces.getAllGrouped.invalidate(); }, @@ -22,7 +22,7 @@ export function useSetActiveWorkspace( }, }); - return trpc.workspaces.setActive.useMutation({ + return electronTrpc.workspaces.setActive.useMutation({ ...options, onError: (error, variables, context, meta) => { console.error("[workspace/setActive] Failed to set active workspace:", { diff --git a/apps/desktop/src/renderer/react-query/workspaces/useUpdateWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useUpdateWorkspace.ts index d8e3ce650..d9a0e0a6e 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useUpdateWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useUpdateWorkspace.ts @@ -1,15 +1,15 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; /** * Mutation hook for updating a workspace * Automatically invalidates all workspace queries on success */ export function useUpdateWorkspace( - options?: Parameters[0], + options?: Parameters[0], ) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - return trpc.workspaces.update.useMutation({ + return electronTrpc.workspaces.update.useMutation({ ...options, onSuccess: async (...args) => { // Auto-invalidate all workspace queries diff --git a/apps/desktop/src/renderer/routes/_authenticated/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/layout.tsx index 3f9d97800..d56b35a95 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/layout.tsx @@ -1,7 +1,7 @@ import { createFileRoute, Navigate, Outlet } from "@tanstack/react-router"; import { DndProvider } from "react-dnd"; +import { authClient } from "renderer/lib/auth-client"; import { dragDropManager } from "renderer/lib/dnd"; -import { useAuth } from "renderer/providers/AuthProvider"; import { CollectionsProvider } from "./providers/CollectionsProvider"; import { OrganizationsProvider } from "./providers/OrganizationsProvider"; @@ -10,8 +10,8 @@ export const Route = createFileRoute("/_authenticated")({ }); function AuthenticatedLayout() { - const { session, token } = useAuth(); - const isSignedIn = !!token && !!session?.user; + const { data: session } = authClient.useSession(); + const isSignedIn = !!session?.user; if (!isSignedIn) { return ; diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx index 837c94385..f65a9a56a 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/CollectionsProvider.tsx @@ -1,5 +1,6 @@ import { createContext, type ReactNode, useContext, useMemo } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { authClient } from "renderer/lib/auth-client"; +import { useAuthToken } from "renderer/providers/AuthProvider"; import { getCollections } from "./collections"; type Collections = ReturnType; @@ -7,17 +8,15 @@ type Collections = ReturnType; const CollectionsContext = createContext(null); export function CollectionsProvider({ children }: { children: ReactNode }) { - const { data: authState } = trpc.auth.onAuthState.useSubscription(); - - const activeOrganizationId = authState?.session?.activeOrganizationId; - const token = authState?.token; + const { data: session } = authClient.useSession(); + const token = useAuthToken(); + const activeOrganizationId = session?.session?.activeOrganizationId; const collections = useMemo(() => { if (!token || !activeOrganizationId) { return null; } - // Get cached collections for this org (or create if first time) return getCollections(activeOrganizationId, token); }, [token, activeOrganizationId]); diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/OrganizationsProvider/OrganizationsProvider.tsx b/apps/desktop/src/renderer/routes/_authenticated/providers/OrganizationsProvider/OrganizationsProvider.tsx index c3956b004..0fa5d20b4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/OrganizationsProvider/OrganizationsProvider.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/OrganizationsProvider/OrganizationsProvider.tsx @@ -1,7 +1,10 @@ +import type { AppRouter } from "@superset/trpc"; +import type { inferRouterOutputs } from "@trpc/server"; import { createContext, type ReactNode, useContext } from "react"; -import { type RouterOutputs, trpc } from "renderer/lib/trpc"; +import { trpc } from "renderer/lib/trpc"; -export type Organization = RouterOutputs["user"]["myOrganizations"][number]; +type ApiRouterOutputs = inferRouterOutputs; +export type Organization = ApiRouterOutputs["user"]["myOrganizations"][number]; interface OrganizationsContextValue { organizations: Organization[]; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx index 59ef6fc09..bd8052cff 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/account/page.tsx @@ -3,6 +3,7 @@ import { Button } from "@superset/ui/button"; import { Skeleton } from "@superset/ui/skeleton"; import { toast } from "@superset/ui/sonner"; import { createFileRoute } from "@tanstack/react-router"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { trpc } from "renderer/lib/trpc"; export const Route = createFileRoute("/_authenticated/settings/account/")({ @@ -11,7 +12,7 @@ export const Route = createFileRoute("/_authenticated/settings/account/")({ function AccountSettingsPage() { const { data: user, isLoading } = trpc.user.me.useQuery(); - const signOutMutation = trpc.auth.signOut.useMutation({ + const signOutMutation = electronTrpc.auth.signOut.useMutation({ onSuccess: () => toast.success("Signed out"), }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx index ccba2afab..5da5289a6 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/behavior/page.tsx @@ -9,19 +9,19 @@ import { } from "@superset/ui/select"; import { Switch } from "@superset/ui/switch"; import { createFileRoute } from "@tanstack/react-router"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; export const Route = createFileRoute("/_authenticated/settings/behavior/")({ component: BehaviorSettingsPage, }); function BehaviorSettingsPage() { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); // Confirm on quit setting const { data: confirmOnQuit, isLoading: isConfirmLoading } = - trpc.settings.getConfirmOnQuit.useQuery(); - const setConfirmOnQuit = trpc.settings.setConfirmOnQuit.useMutation({ + electronTrpc.settings.getConfirmOnQuit.useQuery(); + const setConfirmOnQuit = electronTrpc.settings.setConfirmOnQuit.useMutation({ onMutate: async ({ enabled }) => { await utils.settings.getConfirmOnQuit.cancel(); const previous = utils.settings.getConfirmOnQuit.getData(); @@ -44,10 +44,10 @@ function BehaviorSettingsPage() { // Terminal link behavior setting const { data: terminalLinkBehavior, isLoading: isLoadingLinkBehavior } = - trpc.settings.getTerminalLinkBehavior.useQuery(); + electronTrpc.settings.getTerminalLinkBehavior.useQuery(); const setTerminalLinkBehavior = - trpc.settings.setTerminalLinkBehavior.useMutation({ + electronTrpc.settings.setTerminalLinkBehavior.useMutation({ onMutate: async ({ behavior }) => { await utils.settings.getTerminalLinkBehavior.cancel(); const previous = utils.settings.getTerminalLinkBehavior.getData(); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx index 3ee7cffa8..b0ecf3e98 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/components/SettingsSidebar/components/ProjectsSettings/ProjectsSettings.tsx @@ -2,10 +2,11 @@ import { cn } from "@superset/ui/utils"; import { Link, useMatchRoute } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { HiChevronDown, HiChevronRight } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; export function ProjectsSettings() { - const { data: groups = [] } = trpc.workspaces.getAllGrouped.useQuery(); + const { data: groups = [] } = + electronTrpc.workspaces.getAllGrouped.useQuery(); const matchRoute = useMatchRoute(); const [expandedProjects, setExpandedProjects] = useState>( new Set(), diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx index 7ae403a81..2d0a72f63 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/keyboard/page.tsx @@ -13,7 +13,7 @@ import { toast } from "@superset/ui/sonner"; import { createFileRoute } from "@tanstack/react-router"; import { useEffect, useMemo, useState } from "react"; import { HiMagnifyingGlass } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { captureHotkeyFromEvent, getHotkeyConflict, @@ -116,8 +116,8 @@ function KeyboardShortcutsPage() { ); const hotkeysByCategory = useHotkeysByCategory(); - const exportMutation = trpc.hotkeys.export.useMutation(); - const importMutation = trpc.hotkeys.import.useMutation(); + const exportMutation = electronTrpc.hotkeys.export.useMutation(); + const importMutation = electronTrpc.hotkeys.import.useMutation(); const showHotkeysDisplay = useHotkeyDisplay("SHOW_HOTKEYS"); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx index d5cfec260..9eb7789d3 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx @@ -1,5 +1,5 @@ import { createFileRoute, Outlet } from "@tanstack/react-router"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { SettingsSidebar } from "./components/SettingsSidebar"; export const Route = createFileRoute("/_authenticated/settings")({ @@ -7,7 +7,7 @@ export const Route = createFileRoute("/_authenticated/settings")({ }); function SettingsLayout() { - const { data: platform } = trpc.window.getPlatform.useQuery(); + const { data: platform } = electronTrpc.window.getPlatform.useQuery(); const isMac = platform === undefined || platform === "darwin"; return ( diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx index eb4dcba1e..0d199d365 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx @@ -8,18 +8,19 @@ export const Route = createFileRoute( import { HiOutlineCog6Tooth, HiOutlineFolder } from "react-icons/hi2"; import { ConfigFilePreview } from "renderer/components/ConfigFilePreview"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; function ProjectSettingsPage() { const { projectId } = Route.useParams(); - const { data: project, isLoading } = trpc.projects.get.useQuery({ + const { data: project, isLoading } = electronTrpc.projects.get.useQuery({ id: projectId, }); - const { data: configFilePath } = trpc.config.getConfigFilePath.useQuery( - { projectId }, - { enabled: !!projectId }, - ); + const { data: configFilePath } = + electronTrpc.config.getConfigFilePath.useQuery( + { projectId }, + { enabled: !!projectId }, + ); if (isLoading) { return ( diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx index df4941687..050811f2f 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/ringtones/page.tsx @@ -2,7 +2,7 @@ import { cn } from "@superset/ui/utils"; import { createFileRoute } from "@tanstack/react-router"; import { useCallback, useEffect, useRef, useState } from "react"; import { HiBellSlash, HiCheck, HiPlay, HiStop } from "react-icons/hi2"; -import { trpcClient } from "renderer/lib/trpc-client"; +import { electronTrpcClient } from "renderer/lib/trpc-client"; import { AVAILABLE_RINGTONES, type Ringtone, @@ -167,7 +167,7 @@ function RingtonesSettingsPage() { clearTimeout(previewTimerRef.current); } // Stop any in-progress preview when navigating away - trpcClient.ringtone.stop.mutate().catch(() => { + electronTrpcClient.ringtone.stop.mutate().catch(() => { // Ignore errors during cleanup }); }; @@ -188,7 +188,7 @@ function RingtonesSettingsPage() { // If this ringtone is already playing, stop it if (playingId === ringtone.id) { try { - await trpcClient.ringtone.stop.mutate(); + await electronTrpcClient.ringtone.stop.mutate(); } catch (error) { console.error("Failed to stop ringtone:", error); } @@ -198,7 +198,7 @@ function RingtonesSettingsPage() { // Stop any currently playing sound first try { - await trpcClient.ringtone.stop.mutate(); + await electronTrpcClient.ringtone.stop.mutate(); } catch (error) { console.error("Failed to stop ringtone:", error); } @@ -207,7 +207,7 @@ function RingtonesSettingsPage() { setPlayingId(ringtone.id); try { - await trpcClient.ringtone.preview.mutate({ + await electronTrpcClient.ringtone.preview.mutate({ filename: ringtone.filename, }); } catch (error) { diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/MemberActions.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/MemberActions.tsx index 526c67a0a..c2af99415 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/MemberActions.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/team/components/MemberActions/MemberActions.tsx @@ -1,4 +1,3 @@ -import { authClient } from "@superset/auth/client"; import { Button } from "@superset/ui/button"; import { Dialog, @@ -17,6 +16,7 @@ import { import { toast } from "@superset/ui/sonner"; import { useState } from "react"; import { HiEllipsisVertical, HiOutlineTrash } from "react-icons/hi2"; +import { authClient } from "renderer/lib/auth-client"; export interface MemberDetails { memberId: string; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx index 8c2fc5ec9..da4483830 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/team/page.tsx @@ -12,7 +12,7 @@ import { import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; import { createFileRoute } from "@tanstack/react-router"; -import { useAuth } from "renderer/providers/AuthProvider"; +import { authClient } from "renderer/lib/auth-client"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import { InviteMemberButton } from "./components/InviteMemberButton"; import { MemberActions } from "./components/MemberActions"; @@ -22,7 +22,7 @@ export const Route = createFileRoute("/_authenticated/settings/team/")({ }); function TeamSettingsPage() { - const { session } = useAuth(); + const { data: session } = authClient.useSession(); const collections = useCollections(); const { data: membersData, isLoading } = useLiveQuery( diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx index b09edbacb..e5b80e094 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx @@ -9,12 +9,12 @@ export const Route = createFileRoute( import { Input } from "@superset/ui/input"; import { HiOutlineFolder, HiOutlinePencilSquare } from "react-icons/hi2"; import { LuGitBranch } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useWorkspaceRename } from "renderer/screens/main/hooks/useWorkspaceRename"; function WorkspaceSettingsPage() { const { workspaceId } = Route.useParams(); - const { data: workspace, isLoading } = trpc.workspaces.get.useQuery({ + const { data: workspace, isLoading } = electronTrpc.workspaces.get.useQuery({ id: workspaceId, }); diff --git a/apps/desktop/src/renderer/routes/layout.tsx b/apps/desktop/src/renderer/routes/layout.tsx index 517874222..c4e6a401e 100644 --- a/apps/desktop/src/renderer/routes/layout.tsx +++ b/apps/desktop/src/renderer/routes/layout.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react"; import { PostHogUserIdentifier } from "renderer/components/PostHogUserIdentifier"; import { ThemedToaster } from "renderer/components/ThemedToaster"; import { AuthProvider } from "renderer/providers/AuthProvider"; +import { ElectronTRPCProvider } from "renderer/providers/ElectronTRPCProvider"; import { MonacoProvider } from "renderer/providers/MonacoProvider"; import { PostHogProvider } from "renderer/providers/PostHogProvider"; import { TRPCProvider } from "renderer/providers/TRPCProvider"; @@ -9,15 +10,17 @@ import { TRPCProvider } from "renderer/providers/TRPCProvider"; export function RootLayout({ children }: { children: ReactNode }) { return ( - + - - {children} - - + + + {children} + + + - + ); } diff --git a/apps/desktop/src/renderer/routes/sign-in/page.tsx b/apps/desktop/src/renderer/routes/sign-in/page.tsx index 2cbddbcc7..dcdf3a2d7 100644 --- a/apps/desktop/src/renderer/routes/sign-in/page.tsx +++ b/apps/desktop/src/renderer/routes/sign-in/page.tsx @@ -4,9 +4,9 @@ import { createFileRoute, Navigate } from "@tanstack/react-router"; import { useEffect } from "react"; import { FaGithub } from "react-icons/fa"; import { FcGoogle } from "react-icons/fc"; +import { authClient } from "renderer/lib/auth-client"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { posthog } from "renderer/lib/posthog"; -import { trpc } from "renderer/lib/trpc"; -import { useAuth } from "renderer/providers/AuthProvider"; import { SupersetLogo } from "./components/SupersetLogo"; export const Route = createFileRoute("/sign-in/")({ @@ -14,14 +14,14 @@ export const Route = createFileRoute("/sign-in/")({ }); function SignInPage() { - const { session, token } = useAuth(); - const signInMutation = trpc.auth.signIn.useMutation(); + const { data: session } = authClient.useSession(); + const signInMutation = electronTrpc.auth.signIn.useMutation(); useEffect(() => { posthog.capture("desktop_opened"); }, []); - const isSignedIn = !!token && !!session?.user; + const isSignedIn = !!session?.user; if (isSignedIn) { return ; } diff --git a/apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx b/apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx index 7fc8eada5..edea34ac5 100644 --- a/apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx +++ b/apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx @@ -4,7 +4,7 @@ import { cn } from "@superset/ui/utils"; import { useCallback } from "react"; import { LuGitCompareArrows } from "react-icons/lu"; import { HotkeyTooltipContent } from "renderer/components/HotkeyTooltipContent"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useSidebarStore } from "renderer/stores"; import { useChangesStore } from "renderer/stores/changes"; import { useTabsStore } from "renderer/stores/tabs/store"; @@ -25,27 +25,28 @@ export function SidebarControl() { const { isSidebarOpen, toggleSidebar } = useSidebarStore(); // Get active workspace for file opening - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const workspaceId = activeWorkspace?.id; const worktreePath = activeWorkspace?.worktreePath; // Get base branch for changes query const { baseBranch, selectFile } = useChangesStore(); - const { data: branchData } = trpc.changes.getBranches.useQuery( + const { data: branchData } = electronTrpc.changes.getBranches.useQuery( { worktreePath: worktreePath || "" }, { enabled: !!worktreePath && !isSidebarOpen }, ); const effectiveBaseBranch = baseBranch ?? branchData?.defaultBranch ?? "main"; // Get changes status - only query when sidebar is closed (we need it to open first file) - const { data: status } = trpc.changes.getStatus.useQuery( + const { data: status } = electronTrpc.changes.getStatus.useQuery( { worktreePath: worktreePath || "", defaultBranch: effectiveBaseBranch }, { enabled: !!worktreePath && !isSidebarOpen }, ); // Access tabs store for file opening const addFileViewerPane = useTabsStore((s) => s.addFileViewerPane); - const trpcUtils = trpc.useUtils(); + const trpcUtils = electronTrpc.useUtils(); const invalidateFileContent = useCallback( (filePath: string) => { diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx index 2c0deffab..2babbe0ca 100644 --- a/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx +++ b/apps/desktop/src/renderer/screens/main/components/StartView/CloneRepoDialog.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCreateWorkspace } from "renderer/react-query/workspaces"; interface CloneRepoDialogProps { @@ -14,8 +14,8 @@ export function CloneRepoDialog({ onError, }: CloneRepoDialogProps) { const [url, setUrl] = useState(""); - const utils = trpc.useUtils(); - const cloneRepo = trpc.projects.cloneRepo.useMutation(); + const utils = electronTrpc.useUtils(); + const cloneRepo = electronTrpc.projects.cloneRepo.useMutation(); const createWorkspace = useCreateWorkspace(); const handleClone = async () => { diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/InitGitDialog.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/InitGitDialog.tsx index 0a9688fcf..8052610a5 100644 --- a/apps/desktop/src/renderer/screens/main/components/StartView/InitGitDialog.tsx +++ b/apps/desktop/src/renderer/screens/main/components/StartView/InitGitDialog.tsx @@ -1,6 +1,6 @@ import { Button } from "@superset/ui/button"; import { useCallback, useEffect, useId, useRef, useState } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCreateWorkspace } from "renderer/react-query/workspaces"; function getBasename(path: string): string { @@ -30,8 +30,8 @@ export function InitGitDialog({ onClose, onError, }: InitGitDialogProps) { - const utils = trpc.useUtils(); - const initGitAndOpen = trpc.projects.initGitAndOpen.useMutation(); + const utils = electronTrpc.useUtils(); + const initGitAndOpen = electronTrpc.projects.initGitAndOpen.useMutation(); const createWorkspace = useCreateWorkspace(); // Track the entire async sequence to keep modal locked diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx index d652ff67b..aa9d2f762 100644 --- a/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx +++ b/apps/desktop/src/renderer/screens/main/components/StartView/StartTopBar.tsx @@ -1,9 +1,10 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { SettingsButton } from "../SettingsButton"; import { WindowControls } from "../TopBar/WindowControls"; export function StartTopBar() { - const { data: platform, isLoading } = trpc.window.getPlatform.useQuery(); + const { data: platform, isLoading } = + electronTrpc.window.getPlatform.useQuery(); const isMac = !isLoading && platform === "darwin"; const showWindowControls = !isLoading && !isMac; diff --git a/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx b/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx index 8f72eed14..f3ccd2d99 100644 --- a/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/StartView/index.tsx @@ -2,8 +2,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useState } from "react"; import { HiExclamationTriangle } from "react-icons/hi2"; import { LuChevronUp, LuFolderGit, LuFolderOpen, LuX } from "react-icons/lu"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { formatPathWithProject } from "renderer/lib/formatPath"; -import { trpc } from "renderer/lib/trpc"; import { useOpenNew } from "renderer/react-query/projects"; import { useCreateBranchWorkspace } from "renderer/react-query/workspaces"; import { ActionCard } from "./ActionCard"; @@ -12,8 +12,9 @@ import { InitGitDialog } from "./InitGitDialog"; import { StartTopBar } from "./StartTopBar"; export function StartView() { - const { data: recentProjects = [] } = trpc.projects.getRecents.useQuery(); - const { data: homeDir } = trpc.window.getHomeDir.useQuery(); + const { data: recentProjects = [] } = + electronTrpc.projects.getRecents.useQuery(); + const { data: homeDir } = electronTrpc.window.getHomeDir.useQuery(); const openNew = useOpenNew(); const createBranchWorkspace = useCreateBranchWorkspace(); const [error, setError] = useState(null); diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/OpenInMenuButton.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/OpenInMenuButton.tsx index 698e7d366..e152554b3 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/OpenInMenuButton.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/OpenInMenuButton.tsx @@ -24,7 +24,7 @@ import { JETBRAINS_OPTIONS, VSCODE_OPTIONS, } from "renderer/components/OpenInButton"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useHotkeyText } from "renderer/stores/hotkeys"; interface OpenInMenuButtonProps { @@ -36,16 +36,16 @@ export const OpenInMenuButton = memo(function OpenInMenuButton({ worktreePath, branch, }: OpenInMenuButtonProps) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const { data: lastUsedApp = "cursor" } = - trpc.settings.getLastUsedApp.useQuery(undefined, { + electronTrpc.settings.getLastUsedApp.useQuery(undefined, { staleTime: 30000, }); - const openInApp = trpc.external.openInApp.useMutation({ + const openInApp = electronTrpc.external.openInApp.useMutation({ onSuccess: () => utils.settings.getLastUsedApp.invalidate(), onError: (error) => toast.error(`Failed to open: ${error.message}`), }); - const copyPath = trpc.external.copyPath.useMutation({ + const copyPath = electronTrpc.external.copyPath.useMutation({ onSuccess: () => toast.success("Path copied to clipboard"), onError: (error) => toast.error(`Failed to copy path: ${error.message}`), }); diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WindowControls.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WindowControls.tsx index 272bdd77a..a7f0a36fb 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/WindowControls.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WindowControls.tsx @@ -1,10 +1,10 @@ import { HiMiniMinus, HiMiniStop, HiMiniXMark } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; export function WindowControls() { - const minimizeMutation = trpc.window.minimize.useMutation(); - const maximizeMutation = trpc.window.maximize.useMutation(); - const closeMutation = trpc.window.close.useMutation(); + const minimizeMutation = electronTrpc.window.minimize.useMutation(); + const maximizeMutation = electronTrpc.window.maximize.useMutation(); + const closeMutation = electronTrpc.window.close.useMutation(); const handleMinimize = () => { minimizeMutation.mutate(); diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx index 8d1d46029..f100ef074 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx @@ -1,11 +1,12 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { OpenInMenuButton } from "./OpenInMenuButton"; import { SupportMenu } from "./SupportMenu"; import { WindowControls } from "./WindowControls"; export function TopBar() { - const { data: platform } = trpc.window.getPlatform.useQuery(); - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: platform } = electronTrpc.window.getPlatform.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); // Default to Mac layout while loading to avoid overlap with traffic lights const isMac = platform === undefined || platform === "darwin"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx index 3cce23d23..4480d91e6 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx @@ -1,6 +1,6 @@ import { toast } from "@superset/ui/sonner"; import { useCallback, useEffect, useRef } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useOpenConfigModal } from "renderer/stores/config-modal"; import { useTabsStore } from "renderer/stores/tabs/store"; import { @@ -35,10 +35,11 @@ export function WorkspaceInitEffects() { const addTab = useTabsStore((state) => state.addTab); const setTabAutoTitle = useTabsStore((state) => state.setTabAutoTitle); - const createOrAttach = trpc.terminal.createOrAttach.useMutation(); + const createOrAttach = electronTrpc.terminal.createOrAttach.useMutation(); const openConfigModal = useOpenConfigModal(); - const dismissConfigToast = trpc.config.dismissConfigToast.useMutation(); - const utils = trpc.useUtils(); + const dismissConfigToast = + electronTrpc.config.dismissConfigToast.useMutation(); + const utils = electronTrpc.useUtils(); // Helper to create terminal with setup commands const handleTerminalSetup = useCallback( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx index 4aaeca42b..d99c5b462 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx @@ -1,6 +1,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { LuExternalLink } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; import type { MergedPort } from "shared/types"; import { STROKE_WIDTH } from "../../../constants"; @@ -16,8 +16,8 @@ export function MergedPortBadge({ }: MergedPortBadgeProps) { const setActiveTab = useTabsStore((s) => s.setActiveTab); const setFocusedPane = useTabsStore((s) => s.setFocusedPane); - const setActiveMutation = trpc.workspaces.setActive.useMutation(); - const utils = trpc.useUtils(); + const setActiveMutation = electronTrpc.workspaces.setActive.useMutation(); + const utils = electronTrpc.useUtils(); const portNumberColor = port.isActive ? "text-muted-foreground" diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx index 9c49ae2dd..694914ce3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx @@ -1,4 +1,4 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import type { MergedWorkspaceGroup } from "../../hooks/usePortsData"; import { MergedPortBadge } from "../MergedPortBadge"; @@ -7,8 +7,8 @@ interface WorkspacePortGroupProps { } export function WorkspacePortGroup({ group }: WorkspacePortGroupProps) { - const setActiveMutation = trpc.workspaces.setActive.useMutation(); - const utils = trpc.useUtils(); + const setActiveMutation = electronTrpc.workspaces.setActive.useMutation(); + const utils = electronTrpc.useUtils(); const handleWorkspaceClick = async () => { if (group.isCurrentWorkspace) return; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts index b9498c837..878b7c04d 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts @@ -1,6 +1,6 @@ import { toast } from "@superset/ui/sonner"; import { useEffect, useMemo, useRef } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { usePortsStore } from "renderer/stores"; import type { MergedPort } from "shared/types"; import { mergePorts } from "../utils"; @@ -13,18 +13,20 @@ export interface MergedWorkspaceGroup { } export function usePortsData() { - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); - const { data: allWorkspaces } = trpc.workspaces.getAll.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); + const { data: allWorkspaces } = electronTrpc.workspaces.getAll.useQuery(); const ports = usePortsStore((s) => s.ports); const setPorts = usePortsStore((s) => s.setPorts); const addPort = usePortsStore((s) => s.addPort); const removePort = usePortsStore((s) => s.removePort); - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - const { data: allStaticPortsData } = trpc.ports.getAllStatic.useQuery(); + const { data: allStaticPortsData } = + electronTrpc.ports.getAllStatic.useQuery(); - trpc.ports.subscribeStatic.useSubscription( + electronTrpc.ports.subscribeStatic.useSubscription( { workspaceId: activeWorkspace?.id ?? "" }, { enabled: !!activeWorkspace?.id, @@ -34,7 +36,7 @@ export function usePortsData() { }, ); - const { data: initialPorts } = trpc.ports.getAll.useQuery(); + const { data: initialPorts } = electronTrpc.ports.getAll.useQuery(); useEffect(() => { if (initialPorts) { @@ -42,7 +44,7 @@ export function usePortsData() { } }, [initialPorts, setPorts]); - trpc.ports.subscribe.useSubscription(undefined, { + electronTrpc.ports.subscribe.useSubscription(undefined, { onData: (event) => { if (event.type === "add") { addPort(event.port); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx index 6627b5933..f4b85a1a6 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx @@ -14,7 +14,7 @@ import { cn } from "@superset/ui/utils"; import { useNavigate } from "@tanstack/react-router"; import { HiChevronRight, HiMiniPlus } from "react-icons/hi2"; import { LuFolderOpen, LuPalette, LuSettings, LuX } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useUpdateProject } from "renderer/react-query/projects/useUpdateProject"; import { PROJECT_COLOR_DEFAULT, @@ -50,10 +50,10 @@ export function ProjectHeader({ workspaceCount, onNewWorkspace, }: ProjectHeaderProps) { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const navigate = useNavigate(); - const closeProject = trpc.projects.close.useMutation({ + const closeProject = electronTrpc.projects.close.useMutation({ onSuccess: (data) => { utils.workspaces.getAllGrouped.invalidate(); utils.workspaces.getActive.invalidate(); @@ -67,7 +67,7 @@ export function ProjectHeader({ }, }); - const openInFinder = trpc.external.openInFinder.useMutation({ + const openInFinder = electronTrpc.external.openInFinder.useMutation({ onError: (error) => toast.error(`Failed to open: ${error.message}`), }); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx index af7a94231..9dcc8f2b3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail/ProjectThumbnail.tsx @@ -1,6 +1,6 @@ import { cn } from "@superset/ui/utils"; import { useState } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { PROJECT_COLOR_DEFAULT } from "shared/constants/project-colors"; interface ProjectThumbnailProps { @@ -41,7 +41,7 @@ export function ProjectThumbnail({ }: ProjectThumbnailProps) { const [imageError, setImageError] = useState(false); - const { data: avatarData } = trpc.projects.getGitHubAvatar.useQuery( + const { data: avatarData } = electronTrpc.projects.getGitHubAvatar.useQuery( { id: projectId }, { staleTime: 1000 * 60 * 5, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx index f688b7979..cea6c3aa1 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx @@ -19,7 +19,7 @@ import { useMemo, useState } from "react"; import { useDrag, useDrop } from "react-dnd"; import { HiMiniXMark } from "react-icons/hi2"; import { LuEye, LuEyeOff, LuFolder, LuFolderGit2 } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useReorderWorkspaces, useSetActiveWorkspace, @@ -88,11 +88,11 @@ export function WorkspaceListItem({ const clearWorkspaceAttentionStatus = useTabsStore( (s) => s.clearWorkspaceAttentionStatus, ); - const utils = trpc.useUtils(); - const openInFinder = trpc.external.openInFinder.useMutation({ + const utils = electronTrpc.useUtils(); + const openInFinder = electronTrpc.external.openInFinder.useMutation({ onError: (error) => toast.error(`Failed to open: ${error.message}`), }); - const setUnread = trpc.workspaces.setUnread.useMutation({ + const setUnread = electronTrpc.workspaces.setUnread.useMutation({ onSuccess: () => { utils.workspaces.getAllGrouped.invalidate(); }, @@ -105,13 +105,14 @@ export function WorkspaceListItem({ useWorkspaceDeleteHandler(); // Lazy-load GitHub status on hover to avoid N+1 queries - const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( - { workspaceId: id }, - { - enabled: hasHovered && type === "worktree", - staleTime: GITHUB_STATUS_STALE_TIME, - }, - ); + const { data: githubStatus } = + electronTrpc.workspaces.getGitHubStatus.useQuery( + { workspaceId: id }, + { + enabled: hasHovered && type === "worktree", + staleTime: GITHUB_STATUS_STALE_TIME, + }, + ); // Memoize workspace pane IDs to avoid recalculating on every render const workspacePaneIds = useMemo(() => { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx index bc75bf43b..6ab4274b6 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx @@ -11,7 +11,7 @@ import { cn } from "@superset/ui/utils"; import { useMemo, useState } from "react"; import { HiCheck, HiChevronUpDown } from "react-icons/hi2"; import { LuGitBranch, LuGitFork, LuLoader } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useSetActiveWorkspace } from "renderer/react-query/workspaces"; import { STROKE_WIDTH } from "../../../constants"; @@ -29,21 +29,22 @@ export function BranchSwitcher({ const [isOpen, setIsOpen] = useState(false); const [search, setSearch] = useState(""); - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const setActiveWorkspace = useSetActiveWorkspace(); // Fetch branches when dropdown opens const { data: branchesData, isLoading } = - trpc.workspaces.getBranches.useQuery( + electronTrpc.workspaces.getBranches.useQuery( { projectId, fetch: false }, { enabled: isOpen }, ); - const switchBranch = trpc.workspaces.switchBranchWorkspace.useMutation({ - onSuccess: () => { - utils.workspaces.invalidate(); - }, - }); + const switchBranch = + electronTrpc.workspaces.switchBranchWorkspace.useMutation({ + onSuccess: () => { + utils.workspaces.invalidate(); + }, + }); // Branches in use by worktrees (branch -> workspaceId) const inUseWorkspaces = useMemo(() => { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/DeleteWorkspaceDialog/DeleteWorkspaceDialog.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/DeleteWorkspaceDialog/DeleteWorkspaceDialog.tsx index dd36f6cad..7398cb993 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/DeleteWorkspaceDialog/DeleteWorkspaceDialog.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/DeleteWorkspaceDialog/DeleteWorkspaceDialog.tsx @@ -9,7 +9,7 @@ import { import { Button } from "@superset/ui/button"; import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCloseWorkspace, useDeleteWorkspace, @@ -35,7 +35,7 @@ export function DeleteWorkspaceDialog({ const closeWorkspace = useCloseWorkspace(); const { data: gitStatusData, isLoading: isLoadingGitStatus } = - trpc.workspaces.canDelete.useQuery( + electronTrpc.workspaces.canDelete.useQuery( { id: workspaceId }, { enabled: open, @@ -43,13 +43,14 @@ export function DeleteWorkspaceDialog({ }, ); - const { data: terminalCountData } = trpc.workspaces.canDelete.useQuery( - { id: workspaceId, skipGitChecks: true }, - { - enabled: open, - refetchInterval: open ? 2000 : false, - }, - ); + const { data: terminalCountData } = + electronTrpc.workspaces.canDelete.useQuery( + { id: workspaceId, skipGitChecks: true }, + { + enabled: open, + refetchInterval: open ? 2000 : false, + }, + ); const canDeleteData = gitStatusData ? { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx index 2e80d967e..76942c2ac 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx @@ -6,7 +6,7 @@ import { LuLoaderCircle, LuTriangleAlert, } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { usePRStatus } from "renderer/screens/main/hooks"; import { STROKE_WIDTH } from "../../../constants"; import { ChecksList } from "./components/ChecksList"; @@ -23,10 +23,11 @@ export function WorkspaceHoverCardContent({ workspaceId, workspaceAlias, }: WorkspaceHoverCardContentProps) { - const { data: worktreeInfo } = trpc.workspaces.getWorktreeInfo.useQuery( - { workspaceId }, - { enabled: !!workspaceId }, - ); + const { data: worktreeInfo } = + electronTrpc.workspaces.getWorktreeInfo.useQuery( + { workspaceId }, + { enabled: !!workspaceId }, + ); const { pr, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx index 1d36399b0..231ad54c8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx @@ -1,6 +1,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { LuPlus } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; import { STROKE_WIDTH_THICK } from "../constants"; @@ -13,7 +13,7 @@ export function NewWorkspaceButton({ }: NewWorkspaceButtonProps) { const openModal = useOpenNewWorkspaceModal(); const { data: activeWorkspace, isLoading } = - trpc.workspaces.getActive.useQuery(); + electronTrpc.workspaces.getActive.useQuery(); const handleClick = () => { // projectId may be undefined if no workspace is active or query failed diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx index 82659eea0..91405aeb3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/OrganizationDropdown.tsx @@ -18,8 +18,8 @@ import { HiChevronUpDown, HiOutlineArrowRightOnRectangle, } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; -import { useAuth } from "renderer/providers/AuthProvider"; +import { authClient } from "renderer/lib/auth-client"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; interface OrganizationDropdownProps { @@ -29,10 +29,9 @@ interface OrganizationDropdownProps { export function OrganizationDropdown({ isCollapsed = false, }: OrganizationDropdownProps) { - const { session } = useAuth(); + const { data: session } = authClient.useSession(); const collections = useCollections(); - const setActiveOrg = trpc.auth.setActiveOrganization.useMutation(); - const signOut = trpc.auth.signOut.useMutation(); + const signOutMutation = electronTrpc.auth.signOut.useMutation(); const navigate = useNavigate(); const activeOrganizationId = session?.session?.activeOrganizationId; @@ -50,11 +49,14 @@ export function OrganizationDropdown({ const orgName = activeOrganization?.name ?? "No Organization"; const switchOrganization = async (newOrgId: string) => { - await setActiveOrg.mutateAsync({ organizationId: newOrgId }); + await authClient.organization.setActive({ + organizationId: newOrgId, + }); }; - const handleSignOut = () => { - signOut.mutate(); + const handleSignOut = async () => { + await authClient.signOut(); + signOutMutation.mutate(); }; const trigger = isCollapsed ? ( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx index 3b98e6bd1..c9a500dee 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx @@ -22,7 +22,7 @@ import { useIsDarkTheme, } from "renderer/assets/app-icons/preset-icons"; import { HotkeyTooltipContent } from "renderer/components/HotkeyTooltipContent"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { usePresets } from "renderer/react-query/presets"; import { useTabsStore } from "renderer/stores/tabs/store"; import { useTabsWithPresets } from "renderer/stores/tabs/useTabsWithPresets"; @@ -30,7 +30,8 @@ import { type ActivePaneStatus, pickHigherStatus } from "shared/tabs-types"; import { GroupItem } from "./GroupItem"; export function GroupStrip() { - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const activeWorkspaceId = activeWorkspace?.id; const allTabs = useTabsStore((s) => s.tabs); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileContent/useFileContent.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileContent/useFileContent.ts index 2c4bbd68f..0eca02940 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileContent/useFileContent.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileContent/useFileContent.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import type { ChangeCategory } from "shared/changes-types"; interface UseFileContentParams { @@ -25,14 +25,14 @@ export function useFileContent({ originalContentRef, originalDiffContentRef, }: UseFileContentParams) { - const { data: branchData } = trpc.changes.getBranches.useQuery( + const { data: branchData } = electronTrpc.changes.getBranches.useQuery( { worktreePath }, { enabled: !!worktreePath && diffCategory === "against-base" }, ); const effectiveBaseBranch = branchData?.defaultBranch ?? "main"; const { data: rawFileData, isLoading: isLoadingRaw } = - trpc.changes.readWorkingFile.useQuery( + electronTrpc.changes.readWorkingFile.useQuery( { worktreePath, filePath }, { enabled: viewMode !== "diff" && !!filePath && !!worktreePath, @@ -40,7 +40,7 @@ export function useFileContent({ ); const { data: diffData, isLoading: isLoadingDiff } = - trpc.changes.getFileContents.useQuery( + electronTrpc.changes.getFileContents.useQuery( { worktreePath, filePath, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileSave/useFileSave.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileSave/useFileSave.ts index 074553ac2..247326417 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileSave/useFileSave.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/hooks/useFileSave/useFileSave.ts @@ -1,6 +1,6 @@ import type * as Monaco from "monaco-editor"; import { type MutableRefObject, useCallback, useRef } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; import type { ChangeCategory } from "shared/changes-types"; @@ -29,9 +29,9 @@ export function useFileSave({ }: UseFileSaveParams) { const savingFromRawRef = useRef(false); const savingDiffContentRef = useRef(null); - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); - const saveFileMutation = trpc.changes.saveFile.useMutation({ + const saveFileMutation = electronTrpc.changes.saveFile.useMutation({ onSuccess: () => { setIsDirty(false); if (editorRef.current) { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx index 19e8d8e1b..c24f296fe 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx @@ -8,7 +8,7 @@ import { type MosaicNode, } from "react-mosaic-component"; import { dragDropManager } from "renderer/lib/dnd"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; import type { Tab } from "renderer/stores/tabs/types"; import { useTabsWithPresets } from "renderer/stores/tabs/useTabsWithPresets"; @@ -37,7 +37,8 @@ export function TabView({ tab }: TabViewProps) { const allPanes = useTabsStore((s) => s.panes); // Get worktree path for file viewer panes - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const worktreePath = activeWorkspace?.worktreePath ?? ""; // Get tabs in the same workspace for move targets diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/DirectoryNavigator/DirectoryNavigator.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/DirectoryNavigator/DirectoryNavigator.tsx index 8b492b38e..e7c08d017 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/DirectoryNavigator/DirectoryNavigator.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/DirectoryNavigator/DirectoryNavigator.tsx @@ -2,7 +2,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@superset/ui/popover"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useCallback, useState } from "react"; import { HiChevronRight, HiFolder, HiFolderOpen } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; interface DirectoryNavigatorProps { paneId: string; @@ -18,15 +18,15 @@ export function DirectoryNavigator({ const [isOpen, setIsOpen] = useState(false); const [browsePath, setBrowsePath] = useState(null); - const { data: homeDir } = trpc.window.getHomeDir.useQuery(); - const writeMutation = trpc.terminal.write.useMutation(); + const { data: homeDir } = electronTrpc.window.getHomeDir.useQuery(); + const writeMutation = electronTrpc.terminal.write.useMutation(); // Navigation enabled when we have any cwd (seeded or confirmed) const hasCwd = !!currentCwd; const displayPath = browsePath || currentCwd; const { data: directoryData, isLoading } = - trpc.terminal.listDirectory.useQuery( + electronTrpc.terminal.listDirectory.useQuery( { dirPath: displayPath || "/" }, { enabled: isOpen && hasCwd && !!displayPath }, ); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx index 027cf9eb4..b2d86b1fe 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx @@ -5,8 +5,8 @@ import type { Terminal as XTerm } from "@xterm/xterm"; import "@xterm/xterm/css/xterm.css"; import debounce from "lodash/debounce"; import { useCallback, useEffect, useRef, useState } from "react"; -import { trpc } from "renderer/lib/trpc"; -import { trpcClient } from "renderer/lib/trpc-client"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { electronTrpcClient } from "renderer/lib/trpc-client"; import { useAppHotkey } from "renderer/stores/hotkeys"; import { useTabsStore } from "renderer/stores/tabs/store"; import { useTerminalCallbacksStore } from "renderer/stores/tabs/terminal-callbacks"; @@ -80,11 +80,11 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => { clearPaneInitialDataRef.current = clearPaneInitialData; const { data: workspaceCwd } = - trpc.terminal.getWorkspaceCwd.useQuery(workspaceId); + electronTrpc.terminal.getWorkspaceCwd.useQuery(workspaceId); // Query terminal link behavior setting const { data: terminalLinkBehavior } = - trpc.settings.getTerminalLinkBehavior.useQuery(); + electronTrpc.settings.getTerminalLinkBehavior.useQuery(); // Handler for file link clicks - uses current setting value const handleFileLinkClick = useCallback( @@ -93,7 +93,7 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => { // Helper to open in external editor const openInExternalEditor = () => { - trpcClient.external.openFileInEditor + electronTrpcClient.external.openFileInEditor .mutate({ path, line, @@ -202,11 +202,13 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => { const updateCwdRef = useRef(updateCwdFromData); updateCwdRef.current = updateCwdFromData; - const createOrAttachMutation = trpc.terminal.createOrAttach.useMutation(); - const writeMutation = trpc.terminal.write.useMutation(); - const resizeMutation = trpc.terminal.resize.useMutation(); - const detachMutation = trpc.terminal.detach.useMutation(); - const clearScrollbackMutation = trpc.terminal.clearScrollback.useMutation(); + const createOrAttachMutation = + electronTrpc.terminal.createOrAttach.useMutation(); + const writeMutation = electronTrpc.terminal.write.useMutation(); + const resizeMutation = electronTrpc.terminal.resize.useMutation(); + const detachMutation = electronTrpc.terminal.detach.useMutation(); + const clearScrollbackMutation = + electronTrpc.terminal.clearScrollback.useMutation(); const createOrAttachRef = useRef(createOrAttachMutation.mutate); const writeRef = useRef(writeMutation.mutate); @@ -276,7 +278,7 @@ export const Terminal = ({ tabId, workspaceId }: TerminalProps) => { } }; - trpc.terminal.stream.useSubscription(paneId, { + electronTrpc.terminal.stream.useSubscription(paneId, { onData: handleStreamData, enabled: true, }); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts index fe8d2e8e9..71bf3c3f1 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts @@ -14,7 +14,7 @@ globalThis.localStorage = mockLocalStorage; // Mock trpc-client to avoid electronTRPC dependency mock.module("renderer/lib/trpc-client", () => ({ - trpcClient: { + electronTrpcClient: { external: { openUrl: { mutate: mock(() => Promise.resolve()) }, openFileInEditor: { mutate: mock(() => Promise.resolve()) }, @@ -26,6 +26,7 @@ mock.module("renderer/lib/trpc-client", () => ({ }, }, }, + electronReactClient: {}, })); // Import after mocks are set up diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts index fbd1b980b..86dee149c 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts @@ -8,7 +8,7 @@ import { WebglAddon } from "@xterm/addon-webgl"; import type { ITheme } from "@xterm/xterm"; import { Terminal as XTerm } from "@xterm/xterm"; import { debounce } from "lodash"; -import { trpcClient } from "renderer/lib/trpc-client"; +import { electronTrpcClient } from "renderer/lib/trpc-client"; import { getHotkeyKeys, isAppHotkeyEvent } from "renderer/stores/hotkeys"; import { toXtermTheme } from "renderer/stores/theme/utils"; import { isTerminalReservedEvent, matchesHotkeyEvent } from "shared/hotkeys"; @@ -141,7 +141,7 @@ export function createTerminalInstance( const cleanupQuerySuppression = suppressQueryResponses(xterm); const urlLinkProvider = new UrlLinkProvider(xterm, (_event, uri) => { - trpcClient.external.openUrl.mutate(uri).catch((error) => { + electronTrpcClient.external.openUrl.mutate(uri).catch((error) => { console.error("[Terminal] Failed to open URL:", uri, error); toast.error("Failed to open URL", { description: @@ -160,7 +160,7 @@ export function createTerminalInstance( onFileLinkClick(path, line, column); } else { // Fallback to default behavior (external editor) - trpcClient.external.openFileInEditor + electronTrpcClient.external.openFileInEditor .mutate({ path, line, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx index ae8087438..5e9516baf 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useSidebarStore } from "renderer/stores"; import { MAX_SIDEBAR_WIDTH, @@ -12,7 +12,8 @@ import { EmptyTabView } from "./EmptyTabView"; import { TabView } from "./TabView"; export function TabsContent() { - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const activeWorkspaceId = activeWorkspace?.id; const allTabs = useTabsStore((s) => s.tabs); const activeTabIds = useTabsStore((s) => s.activeTabIds); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx index 482891153..d26f0fec0 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx @@ -25,7 +25,7 @@ import { LuSearch, LuX, } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import type { Tab } from "renderer/stores/tabs/types"; export interface EditorActions { @@ -64,7 +64,7 @@ export function EditorContextMenu({ (t) => t.id !== paneActions.currentTabId, ); - const { data: platform } = trpc.window.getPlatform.useQuery(); + const { data: platform } = electronTrpc.window.getPlatform.useQuery(); const isMac = platform === "darwin"; const cmdKey = isMac ? "Cmd" : "Ctrl"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx index 00494621e..2df6fff88 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx @@ -3,7 +3,7 @@ import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useEffect, useState } from "react"; import { HiMiniMinus, HiMiniPlus } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useChangesStore } from "renderer/stores/changes"; import type { ChangeCategory, ChangedFile } from "shared/changes-types"; @@ -32,11 +32,12 @@ export function ChangesView({ onFileOpen, onFileOpenPinned, }: ChangesViewProps) { - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const worktreePath = activeWorkspace?.worktreePath; const { baseBranch } = useChangesStore(); - const { data: branchData } = trpc.changes.getBranches.useQuery( + const { data: branchData } = electronTrpc.changes.getBranches.useQuery( { worktreePath: worktreePath || "" }, { enabled: !!worktreePath }, ); @@ -47,7 +48,7 @@ export function ChangesView({ data: status, isLoading, refetch, - } = trpc.changes.getStatus.useQuery( + } = electronTrpc.changes.getStatus.useQuery( { worktreePath: worktreePath || "", defaultBranch: effectiveBaseBranch }, { enabled: !!worktreePath, @@ -57,7 +58,7 @@ export function ChangesView({ ); const { data: githubStatus, refetch: refetchGithubStatus } = - trpc.workspaces.getGitHubStatus.useQuery( + electronTrpc.workspaces.getGitHubStatus.useQuery( { workspaceId: activeWorkspace?.id ?? "" }, { enabled: !!activeWorkspace?.id, @@ -70,7 +71,7 @@ export function ChangesView({ refetchGithubStatus(); }; - const stageAllMutation = trpc.changes.stageAll.useMutation({ + const stageAllMutation = electronTrpc.changes.stageAll.useMutation({ onSuccess: () => refetch(), onError: (error) => { console.error("Failed to stage all files:", error); @@ -78,7 +79,7 @@ export function ChangesView({ }, }); - const unstageAllMutation = trpc.changes.unstageAll.useMutation({ + const unstageAllMutation = electronTrpc.changes.unstageAll.useMutation({ onSuccess: () => refetch(), onError: (error) => { console.error("Failed to unstage all files:", error); @@ -86,7 +87,7 @@ export function ChangesView({ }, }); - const stageFileMutation = trpc.changes.stageFile.useMutation({ + const stageFileMutation = electronTrpc.changes.stageFile.useMutation({ onSuccess: () => refetch(), onError: (error, variables) => { console.error(`Failed to stage file ${variables.filePath}:`, error); @@ -94,7 +95,7 @@ export function ChangesView({ }, }); - const unstageFileMutation = trpc.changes.unstageFile.useMutation({ + const unstageFileMutation = electronTrpc.changes.unstageFile.useMutation({ onSuccess: () => refetch(), onError: (error, variables) => { console.error(`Failed to unstage file ${variables.filePath}:`, error); @@ -102,24 +103,26 @@ export function ChangesView({ }, }); - const discardChangesMutation = trpc.changes.discardChanges.useMutation({ - onSuccess: () => refetch(), - onError: (error, variables) => { - console.error( - `Failed to discard changes for ${variables.filePath}:`, - error, - ); - toast.error(`Failed to discard changes: ${error.message}`); - }, - }); + const discardChangesMutation = + electronTrpc.changes.discardChanges.useMutation({ + onSuccess: () => refetch(), + onError: (error, variables) => { + console.error( + `Failed to discard changes for ${variables.filePath}:`, + error, + ); + toast.error(`Failed to discard changes: ${error.message}`); + }, + }); - const deleteUntrackedMutation = trpc.changes.deleteUntracked.useMutation({ - onSuccess: () => refetch(), - onError: (error, variables) => { - console.error(`Failed to delete ${variables.filePath}:`, error); - toast.error(`Failed to delete file: ${error.message}`); - }, - }); + const deleteUntrackedMutation = + electronTrpc.changes.deleteUntracked.useMutation({ + onSuccess: () => refetch(), + onError: (error, variables) => { + console.error(`Failed to delete ${variables.filePath}:`, error); + toast.error(`Failed to delete file: ${error.message}`); + }, + }); const handleDiscard = (file: ChangedFile) => { if (!worktreePath) return; @@ -160,7 +163,7 @@ export function ChangesView({ setExpandedCommits(new Set()); }, [worktreePath]); - const commitFilesQueries = trpc.useQueries((t) => + const commitFilesQueries = electronTrpc.useQueries((t) => Array.from(expandedCommits).map((hash) => t.changes.getCommitFiles({ worktreePath: worktreePath || "", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx index d29454f00..172baed40 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsx @@ -10,7 +10,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useEffect, useRef, useState } from "react"; import { HiArrowPath } from "react-icons/hi2"; import { LuLoaderCircle } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { PRIcon } from "renderer/screens/main/components/PRIcon"; import { usePRStatus } from "renderer/screens/main/hooks"; import { useChangesStore } from "renderer/stores/changes"; @@ -60,10 +60,11 @@ export function ChangesHeader({ const { baseBranch, setBaseBranch } = useChangesStore(); - const { data: branchData, isLoading } = trpc.changes.getBranches.useQuery( - { worktreePath }, - { enabled: !!worktreePath }, - ); + const { data: branchData, isLoading } = + electronTrpc.changes.getBranches.useQuery( + { worktreePath }, + { enabled: !!worktreePath }, + ); const { pr, isLoading: isPRLoading } = usePRStatus({ workspaceId, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/CommitInput/CommitInput.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/CommitInput/CommitInput.tsx index 61deca284..5ffa60369 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/CommitInput/CommitInput.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/CommitInput/CommitInput.tsx @@ -19,7 +19,7 @@ import { HiCheck, HiChevronDown, } from "react-icons/hi2"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; interface CommitInputProps { worktreePath: string; @@ -47,7 +47,7 @@ export function CommitInput({ const [commitMessage, setCommitMessage] = useState(""); const [isOpen, setIsOpen] = useState(false); - const commitMutation = trpc.changes.commit.useMutation({ + const commitMutation = electronTrpc.changes.commit.useMutation({ onSuccess: () => { toast.success("Committed"); setCommitMessage(""); @@ -56,7 +56,7 @@ export function CommitInput({ onError: (error) => toast.error(`Commit failed: ${error.message}`), }); - const pushMutation = trpc.changes.push.useMutation({ + const pushMutation = electronTrpc.changes.push.useMutation({ onSuccess: () => { toast.success("Pushed"); onRefresh(); @@ -64,7 +64,7 @@ export function CommitInput({ onError: (error) => toast.error(`Push failed: ${error.message}`), }); - const pullMutation = trpc.changes.pull.useMutation({ + const pullMutation = electronTrpc.changes.pull.useMutation({ onSuccess: () => { toast.success("Pulled"); onRefresh(); @@ -72,7 +72,7 @@ export function CommitInput({ onError: (error) => toast.error(`Pull failed: ${error.message}`), }); - const syncMutation = trpc.changes.sync.useMutation({ + const syncMutation = electronTrpc.changes.sync.useMutation({ onSuccess: () => { toast.success("Synced"); onRefresh(); @@ -80,7 +80,7 @@ export function CommitInput({ onError: (error) => toast.error(`Sync failed: ${error.message}`), }); - const createPRMutation = trpc.changes.createPR.useMutation({ + const createPRMutation = electronTrpc.changes.createPR.useMutation({ onSuccess: () => { toast.success("Opening GitHub..."); onRefresh(); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileItem/FileItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileItem/FileItem.tsx index 90dacb7dc..9b5aa6a39 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileItem/FileItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileItem/FileItem.tsx @@ -27,7 +27,7 @@ import { LuTrash2, LuUndo2, } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import type { ChangedFile } from "shared/changes-types"; import { getStatusColor, getStatusIndicator } from "../../utils"; @@ -93,8 +93,9 @@ export function FileItem({ const hasIndent = level > 0; const hasAction = onStage || onUnstage; - const openInFinderMutation = trpc.external.openInFinder.useMutation(); - const openInEditorMutation = trpc.external.openFileInEditor.useMutation(); + const openInFinderMutation = electronTrpc.external.openInFinder.useMutation(); + const openInEditorMutation = + electronTrpc.external.openFileInEditor.useMutation(); const absolutePath = worktreePath ? `${worktreePath}/${file.path}` : null; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx index e4ca68cbe..d7db5054f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx @@ -1,15 +1,16 @@ -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; import type { ChangeCategory, ChangedFile } from "shared/changes-types"; import { ChangesView } from "./ChangesView"; export function Sidebar() { - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const workspaceId = activeWorkspace?.id; const worktreePath = activeWorkspace?.worktreePath; const addFileViewerPane = useTabsStore((s) => s.addFileViewerPane); - const trpcUtils = trpc.useUtils(); + const trpcUtils = electronTrpc.useUtils(); // Invalidate file content queries to ensure fresh data when clicking a file const invalidateFileContent = (filePath: string) => { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceInitializingView/WorkspaceInitializingView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceInitializingView/WorkspaceInitializingView.tsx index 703d24edc..2e0a09aac 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceInitializingView/WorkspaceInitializingView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/WorkspaceInitializingView/WorkspaceInitializingView.tsx @@ -11,7 +11,7 @@ import { cn } from "@superset/ui/utils"; import { useEffect, useState } from "react"; import { HiExclamationTriangle } from "react-icons/hi2"; import { LuCheck, LuCircle, LuGitBranch, LuLoader } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useHasWorkspaceFailed, useWorkspaceInitProgress, @@ -55,9 +55,9 @@ export function WorkspaceInitializingView({ setShowInterruptedUI(false); }, [isInterrupted, progress]); - const retryMutation = trpc.workspaces.retryInit.useMutation(); - const deleteMutation = trpc.workspaces.delete.useMutation(); - const utils = trpc.useUtils(); + const retryMutation = electronTrpc.workspaces.retryInit.useMutation(); + const deleteMutation = electronTrpc.workspaces.delete.useMutation(); + const utils = electronTrpc.useUtils(); const handleRetry = () => { retryMutation.mutate( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx index 46a60bce5..56f85b4c2 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useAppHotkey } from "renderer/stores/hotkeys"; import { useTabsStore } from "renderer/stores/tabs/store"; import { useTabsWithPresets } from "renderer/stores/tabs/useTabsWithPresets"; @@ -12,7 +12,8 @@ import { ContentView } from "./ContentView"; import { WorkspaceInitializingView } from "./WorkspaceInitializingView"; export function WorkspaceView() { - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); const activeWorkspaceId = activeWorkspace?.id; // Check if active workspace is initializing or failed @@ -140,8 +141,8 @@ export function WorkspaceView() { // Open in last used app shortcut const { data: lastUsedApp = "cursor" } = - trpc.settings.getLastUsedApp.useQuery(); - const openInApp = trpc.external.openInApp.useMutation(); + electronTrpc.settings.getLastUsedApp.useQuery(); + const openInApp = electronTrpc.external.openInApp.useMutation(); useAppHotkey( "OPEN_IN_APP", () => { @@ -157,7 +158,7 @@ export function WorkspaceView() { ); // Copy path shortcut - const copyPath = trpc.external.copyPath.useMutation(); + const copyPath = electronTrpc.external.copyPath.useMutation(); useAppHotkey( "COPY_PATH", () => { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/DeleteWorktreeDialog.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/DeleteWorktreeDialog.tsx index e25b2beef..d99554ecd 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/DeleteWorktreeDialog.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/DeleteWorktreeDialog.tsx @@ -9,7 +9,7 @@ import { import { Button } from "@superset/ui/button"; import { toast } from "@superset/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useDeleteWorktree } from "renderer/react-query/workspaces/useDeleteWorktree"; interface DeleteWorktreeDialogProps { @@ -28,7 +28,7 @@ export function DeleteWorktreeDialog({ const deleteWorktree = useDeleteWorktree(); const { data: canDeleteData, isLoading } = - trpc.workspaces.canDeleteWorktree.useQuery( + electronTrpc.workspaces.canDeleteWorktree.useQuery( { worktreeId }, { enabled: open, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx index a3728c5b0..91dee0381 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx @@ -13,7 +13,7 @@ import { LuFolderGit2, LuRotateCw, } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useWorkspaceDeleteHandler } from "renderer/react-query/workspaces/useWorkspaceDeleteHandler"; import { STROKE_WIDTH } from "../../WorkspaceSidebar/constants"; import { DeleteWorkspaceDialog } from "../../WorkspaceSidebar/WorkspaceListItem/components/DeleteWorkspaceDialog/DeleteWorkspaceDialog"; @@ -44,14 +44,17 @@ export function WorkspaceRow({ useWorkspaceDeleteHandler(); // Lazy-load GitHub status on hover to avoid N+1 queries - const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( - { workspaceId: workspace.workspaceId ?? "" }, - { - enabled: - hasHovered && workspace.type === "worktree" && !!workspace.workspaceId, - staleTime: GITHUB_STATUS_STALE_TIME, - }, - ); + const { data: githubStatus } = + electronTrpc.workspaces.getGitHubStatus.useQuery( + { workspaceId: workspace.workspaceId ?? "" }, + { + enabled: + hasHovered && + workspace.type === "worktree" && + !!workspace.workspaceId, + staleTime: GITHUB_STATUS_STALE_TIME, + }, + ); const pr = githubStatus?.pr; const showDiffStats = pr && (pr.additions > 0 || pr.deletions > 0); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx index 583bc8c13..e5211b1e5 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx @@ -4,7 +4,7 @@ import { toast } from "@superset/ui/sonner"; import { cn } from "@superset/ui/utils"; import { useMemo, useState } from "react"; import { LuSearch, LuX } from "react-icons/lu"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useSetActiveWorkspace } from "renderer/react-query/workspaces"; import { useCloseWorkspacesList } from "renderer/stores/app-state"; import type { FilterMode, ProjectGroup, WorkspaceItem } from "./types"; @@ -19,15 +19,18 @@ const FILTER_OPTIONS: { value: FilterMode; label: string }[] = [ export function WorkspacesListView() { const [searchQuery, setSearchQuery] = useState(""); const [filterMode, setFilterMode] = useState("all"); - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); // Fetch all data - const { data: groups = [] } = trpc.workspaces.getAllGrouped.useQuery(); - const { data: allProjects = [] } = trpc.projects.getRecents.useQuery(); - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: groups = [] } = + electronTrpc.workspaces.getAllGrouped.useQuery(); + const { data: allProjects = [] } = + electronTrpc.projects.getRecents.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); // Fetch worktrees for all projects - const worktreeQueries = trpc.useQueries((t) => + const worktreeQueries = electronTrpc.useQueries((t) => allProjects.map((project) => t.workspaces.getWorktreesByProject({ projectId: project.id }), ), @@ -36,7 +39,7 @@ export function WorkspacesListView() { const setActiveWorkspace = useSetActiveWorkspace(); const closeWorkspacesList = useCloseWorkspacesList(); - const openWorktree = trpc.workspaces.openWorktree.useMutation({ + const openWorktree = electronTrpc.workspaces.openWorktree.useMutation({ onSuccess: () => { utils.workspaces.getAllGrouped.invalidate(); utils.workspaces.getActive.invalidate(); diff --git a/apps/desktop/src/renderer/screens/main/hooks/usePRStatus/usePRStatus.ts b/apps/desktop/src/renderer/screens/main/hooks/usePRStatus/usePRStatus.ts index 713d7ff91..6e6ae9d9a 100644 --- a/apps/desktop/src/renderer/screens/main/hooks/usePRStatus/usePRStatus.ts +++ b/apps/desktop/src/renderer/screens/main/hooks/usePRStatus/usePRStatus.ts @@ -1,5 +1,5 @@ import type { GitHubStatus } from "@superset/local-db"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; interface UsePRStatusOptions { workspaceId: string | undefined; @@ -28,7 +28,7 @@ export function usePRStatus({ data: githubStatus, isLoading, refetch, - } = trpc.workspaces.getGitHubStatus.useQuery( + } = electronTrpc.workspaces.getGitHubStatus.useQuery( { workspaceId: workspaceId ?? "" }, { enabled: enabled && !!workspaceId, diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx index da47ba8c4..d96e491bf 100644 --- a/apps/desktop/src/renderer/screens/main/index.tsx +++ b/apps/desktop/src/renderer/screens/main/index.tsx @@ -9,7 +9,7 @@ import { SetupConfigModal } from "renderer/components/SetupConfigModal"; import { UpdateRequiredPage } from "renderer/components/UpdateRequiredPage"; import { useUpdateListener } from "renderer/components/UpdateToast"; import { useVersionCheck } from "renderer/hooks/useVersionCheck"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCurrentView } from "renderer/stores/app-state"; import { useAppHotkey, useHotkeysSync } from "renderer/stores/hotkeys"; import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal"; @@ -44,7 +44,7 @@ function LoadingSpinner() { } export function MainScreen() { - const utils = trpc.useUtils(); + const utils = electronTrpc.useUtils(); const { isLoading: isVersionLoading, @@ -53,7 +53,7 @@ export function MainScreen() { } = useVersionCheck(); const updateInitProgress = useWorkspaceInitStore((s) => s.updateProgress); - trpc.workspaces.onInitProgress.useSubscription(undefined, { + electronTrpc.workspaces.onInitProgress.useSubscription(undefined, { onData: (progress) => { updateInitProgress(progress); if (progress.step === "ready" || progress.step === "failed") { @@ -87,7 +87,7 @@ export function MainScreen() { isError, failureCount, refetch, - } = trpc.workspaces.getActive.useQuery(); + } = electronTrpc.workspaces.getActive.useQuery(); const [isRetrying, setIsRetrying] = useState(false); const { splitPaneAuto, splitPaneVertical, splitPaneHorizontal } = useTabsWithPresets(); @@ -100,7 +100,7 @@ export function MainScreen() { useUpdateListener(); useHotkeysSync(); - trpc.menu.subscribe.useSubscription(undefined, { + electronTrpc.menu.subscribe.useSubscription(undefined, { onData: (event) => { if (event.type === "open-settings") { const section = event.data.section || "account"; diff --git a/apps/desktop/src/renderer/stores/hotkeys/store.ts b/apps/desktop/src/renderer/stores/hotkeys/store.ts index 7c481313e..c64f927b2 100644 --- a/apps/desktop/src/renderer/stores/hotkeys/store.ts +++ b/apps/desktop/src/renderer/stores/hotkeys/store.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef } from "react"; -import { trpc } from "renderer/lib/trpc"; -import { trpcClient } from "renderer/lib/trpc-client"; +import { electronTrpc } from "renderer/lib/electron-trpc"; +import { electronTrpcClient } from "renderer/lib/trpc-client"; import { setSkipNextHotkeysPersist, trpcHotkeysStorage, @@ -278,9 +278,9 @@ export function useHotkeysSync() { const platform = useHotkeysStore((state) => state.platform); const replace = useHotkeysStore((state) => state.replaceHotkeysState); - trpc.uiState.hotkeys.subscribe.useSubscription(undefined, { + electronTrpc.uiState.hotkeys.subscribe.useSubscription(undefined, { onData: () => { - trpcClient.uiState.hotkeys.get + electronTrpcClient.uiState.hotkeys.get .query() .then((state: HotkeysState) => { const current = useHotkeysStore.getState().hotkeysState; diff --git a/apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts b/apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts index 19e44b17a..5fd87aa0f 100644 --- a/apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts +++ b/apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts @@ -1,5 +1,5 @@ import { useRef } from "react"; -import { trpc } from "renderer/lib/trpc"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useSetActiveWorkspace } from "renderer/react-query/workspaces/useSetActiveWorkspace"; import { NOTIFICATION_EVENTS } from "shared/constants"; import { debugLog } from "shared/debug"; @@ -34,13 +34,14 @@ import { resolveNotificationTarget } from "./utils/resolve-notification-target"; */ export function useAgentHookListener() { const setActiveWorkspace = useSetActiveWorkspace(); - const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery(); + const { data: activeWorkspace } = + electronTrpc.workspaces.getActive.useQuery(); // Use ref to avoid stale closure in subscription callback const activeWorkspaceRef = useRef(activeWorkspace); activeWorkspaceRef.current = activeWorkspace; - trpc.notifications.subscribe.useSubscription(undefined, { + electronTrpc.notifications.subscribe.useSubscription(undefined, { onData: (event) => { if (!event.data) return; diff --git a/apps/desktop/src/renderer/stores/tabs/utils/terminal-cleanup.ts b/apps/desktop/src/renderer/stores/tabs/utils/terminal-cleanup.ts index 25e36afa3..741943df8 100644 --- a/apps/desktop/src/renderer/stores/tabs/utils/terminal-cleanup.ts +++ b/apps/desktop/src/renderer/stores/tabs/utils/terminal-cleanup.ts @@ -1,10 +1,10 @@ -import { trpcClient } from "../../../lib/trpc-client"; +import { electronTrpcClient } from "../../../lib/trpc-client"; /** * Uses standalone tRPC client to avoid React hook dependencies */ export const killTerminalForPane = (paneId: string): void => { - trpcClient.terminal.kill.mutate({ paneId }).catch((error) => { + electronTrpcClient.terminal.kill.mutate({ paneId }).catch((error) => { console.warn(`Failed to kill terminal for pane ${paneId}:`, error); }); }; diff --git a/bun.lock b/bun.lock index 2ded206b2..8bd758772 100644 --- a/bun.lock +++ b/bun.lock @@ -122,7 +122,7 @@ }, "apps/desktop": { "name": "@superset/desktop", - "version": "0.0.53", + "version": "0.0.54", "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -161,6 +161,7 @@ "@xterm/addon-webgl": "^0.18.0", "@xterm/headless": "^5.5.0", "@xterm/xterm": "^5.5.0", + "better-auth": "^1.4.9", "better-sqlite3": "12.5.0", "bindings": "^1.5.0", "clsx": "^2.1.1", diff --git a/packages/auth/package.json b/packages/auth/package.json index ff8acaec5..ab250364d 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -11,6 +11,10 @@ "./client": { "types": "./src/client.ts", "default": "./src/client.ts" + }, + "./electron-client": { + "types": "./src/electron-client.ts", + "default": "./src/electron-client.ts" } }, "scripts": {