Router Part 3: Rewrite the workspace handling#740
Conversation
|
Warning Rate limit exceeded@saddlepaddle has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 15 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughRefactors workspace activation from server-side state to route-driven navigation, removes MainScreen and app-state store, centralizes QueryClient, adds dashboard routes and workspace loaders, updates many components to derive workspaceId from route params, and strengthens TRPC error handling (NOT_FOUND) and server-side workspace creation logic. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server
participant DB
participant Git
Client->>Server: create/open project (openNew/initGitAndOpen/cloneRepo)
Server->>Server: call ensureMainWorkspace(projectId)
alt existing branch workspace found
Server->>DB: touch existing branch workspace (update tabOrder/active)
DB-->>Server: touched workspace row
else none found
Server->>Git: determine current branch (detectMainBranch)
Git-->>Server: branch name
Server->>DB: insert new branch workspace (handle conflict)
DB-->>Server: created workspace row
Server->>DB: reorder other workspaces (update tabOrder) & set active
end
Server-->>Client: return Project with ensured main workspace
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts (1)
25-33: Fix theworkspaceIdparameter in thesubscribeStaticsubscription.Passing
workspaceId: ""will not subscribe to all static port changes as intended. The backend procedure queries forworkspaces.id = input.workspaceId, which returns no results for an empty string, causing the subscription to return an empty cleanup function without setting up any watchers. TheonDatacallback will never fire.Either the backend procedure needs to be modified to handle an empty string as a sentinel meaning "all workspaces", or the caller needs to pass a valid workspaceId or use a different subscription endpoint designed for multi-workspace subscriptions.
🤖 Fix all issues with AI agents
In
@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx:
- Around line 42-49: Replace the current catch block that inspects error.message
with a check for TRPCError.error.code (e.g., if error instanceof TRPCError &&
error.code === "NOT_FOUND") and call notFound() in that case; for all other
errors log the error before re-throwing (e.g., include a descriptive tag like
"[workspace/loader]" in the log) so failures aren’t swallowed. Ensure you import
TRPCError if needed and keep the existing notFound() behavior.
🧹 Nitpick comments (19)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts (1)
45-51: Consider wrapping reorder updates in a transaction.The loop updates each workspace's
tabOrderindividually. If an update fails mid-way, tab orders could be left in an inconsistent state. Wrapping in a transaction would ensure atomicity.This is existing code and not urgent, but worth considering for a future improvement.
♻️ Suggested improvement
- for (let i = 0; i < projectWorkspaces.length; i++) { - localDb - .update(workspaces) - .set({ tabOrder: i }) - .where(eq(workspaces.id, projectWorkspaces[i].id)) - .run(); - } + localDb.transaction((tx) => { + for (let i = 0; i < projectWorkspaces.length; i++) { + tx + .update(workspaces) + .set({ tabOrder: i }) + .where(eq(workspaces.id, projectWorkspaces[i].id)) + .run(); + } + });apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx (1)
28-34: Well-structured route-based workspace fetching.The conditional query pattern with
enabled: !!workspaceIdis correct and prevents unnecessary fetches when not on a workspace route. Thestrict: falseoption is appropriate since this component may render on routes without a workspaceId param.Minor nit: The comment on line 28 still references "active workspace" which is now outdated since this uses route params rather than state-based active workspace selection.
📝 Optional: Update comment to reflect route-based approach
- // Get active workspace for file opening + // Get workspace from route params for file opening const { workspaceId } = useParams({ strict: false });apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx (1)
78-82: Consider extracting magic numbers to named constants.The debounce delay
150(and50on line 157) would benefit from being extracted to named constants at the module top for better self-documentation and easier tuning.const TITLE_DEBOUNCE_MS = 150; const FOCUS_DELAY_MS = 50;apps/desktop/src/renderer/routes/_authenticated/layout.tsx (2)
51-59: Add an error handler for consistency and to avoid silent failures.The workspace subscription (lines 46-48) includes an
onErrorhandler with prefixed logging, but the menu subscription lacks one. Per coding guidelines, errors should never be swallowed silently.♻️ Suggested fix
// Menu navigation subscription trpc.menu.subscribe.useSubscription(undefined, { onData: (event) => { if (event.type === "open-settings") { const section = event.data.section || "account"; navigate({ to: `/settings/${section}` as "/settings/account" }); } }, + onError: (error) => { + console.error("[menu-subscription] Subscription error:", error); + }, });
56-56: Type assertion masks potential routing errors.The cast
as "/settings/account"is a type workaround that bypasses TypeScript's route validation. Ifsectioncontains an invalid route segment, this will fail at runtime without compile-time warnings.Consider defining the valid settings sections as a union type and validating before navigation, or use a type-safe route helper if TanStack Router supports it for dynamic segments.
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
43-49: Manual URL parsing bypasses router utilities and catch block lacks logging.Two concerns with this approach:
Parsing
window.location.pathnamewith a regex is fragile compared to using TanStack Router's utilities (e.g.,useMatches()orrouter.state.matchesto find the workspace param). If the route structure changes, this regex will silently fail.Per coding guidelines, errors should not be swallowed silently. While failure here is unlikely, consider logging for debugging:
Suggested improvement for error logging
try { const location = window.location; const match = location.pathname.match(/\/workspace\/([^/]+)/); currentWorkspaceIdRef.current = match ? match[1] : null; - } catch { + } catch (error) { + debugLog("agent-hooks", "[workspace-id-extraction] Failed to extract workspace ID from URL", { error }); currentWorkspaceIdRef.current = null; }apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts (1)
78-104: Consider extracting hotkey registration to reduce repetition.The 9 sequential
useAppHotkeycalls follow the same pattern. While functional, this could be simplified with a loop or array mapping for better maintainability.♻️ Optional refactor using array iteration
- useAppHotkey("JUMP_TO_WORKSPACE_1", () => switchToWorkspace(0), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_2", () => switchToWorkspace(1), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_3", () => switchToWorkspace(2), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_4", () => switchToWorkspace(3), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_5", () => switchToWorkspace(4), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_6", () => switchToWorkspace(5), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_7", () => switchToWorkspace(6), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_8", () => switchToWorkspace(7), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_9", () => switchToWorkspace(8), undefined, [ - switchToWorkspace, - ]); + // Register workspace jump hotkeys (⌘1-9) + const hotkeyIds = [ + "JUMP_TO_WORKSPACE_1", + "JUMP_TO_WORKSPACE_2", + "JUMP_TO_WORKSPACE_3", + "JUMP_TO_WORKSPACE_4", + "JUMP_TO_WORKSPACE_5", + "JUMP_TO_WORKSPACE_6", + "JUMP_TO_WORKSPACE_7", + "JUMP_TO_WORKSPACE_8", + "JUMP_TO_WORKSPACE_9", + ] as const; + + for (let i = 0; i < hotkeyIds.length; i++) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useAppHotkey(hotkeyIds[i], () => switchToWorkspace(i), undefined, [ + switchToWorkspace, + ]); + }Note: This requires disabling the hooks rule since it's a static loop. Alternatively, keep the explicit calls if hook linting is preferred.
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (1)
138-147: Consider extracting localStorage key to a named constant.The string
"lastViewedWorkspaceId"is a magic value. Per coding guidelines, extracting it to a module-level constant improves maintainability and ensures consistency if used elsewhere.Suggested change
+const LAST_VIEWED_WORKSPACE_KEY = "lastViewedWorkspaceId"; + const WORKSPACE_TYPE = "WORKSPACE";Then in
handleClick:- localStorage.setItem("lastViewedWorkspaceId", id); + localStorage.setItem(LAST_VIEWED_WORKSPACE_KEY, id);apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx (1)
47-49: Consider consistent toggle behavior for Tasks.The
handleTasksClickalways navigates to/tasks, whilehandleWorkspacesClicktoggles between routes. This means clicking the Tasks button when already on/tasksre-navigates to the same route, whereas clicking Workspaces when already open navigates back to/workspace.If this asymmetry is intentional (e.g., Tasks doesn't have a "close" state), the current implementation is fine. Otherwise, consider adding similar toggle logic:
Optional: Add toggle behavior for consistency
const handleTasksClick = () => { + if (isTasksOpen) { + navigate({ to: "/workspace" }); + } else { navigate({ to: "/tasks" }); + } };apps/desktop/src/lib/trpc/routers/projects/projects.ts (1)
148-163: LGTM! Consider applyingTRPCErrorconsistently to other procedures.The
getprocedure correctly usesTRPCErrorwithNOT_FOUNDcode, which aligns with best practices and enables proper error handling in route loaders. However, other procedures in this file still usethrow new Error()for similar not-found cases (e.g.,getBranchesat line 188,updateat line 542,refreshDefaultBranchat line 611,closeat line 659).Consider migrating other not-found error cases to use
TRPCErrorwithNOT_FOUNDcode for consistent error handling across the router. Based on learnings, useTRPCErrorwith appropriate error codes.Example for one procedure
// In getBranches procedure (around line 187-188) if (!project) { - throw new Error(`Project ${input.projectId} not found`); + throw new TRPCError({ + code: "NOT_FOUND", + message: `Project ${input.projectId} not found`, + }); }apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts (1)
40-59: Parallelize workspace neighbor fetches for better UX.The sequential
awaitcalls add unnecessary latency when deleting the currently viewed workspace.♻️ Suggested refactor
if (wasViewingDeleted) { - const prevWorkspaceId = - await utils.workspaces.getPreviousWorkspace.fetch({ id }); - const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({ - id, - }); + const [prevWorkspaceId, nextWorkspaceId] = await Promise.all([ + utils.workspaces.getPreviousWorkspace.fetch({ id }), + utils.workspaces.getNextWorkspace.fetch({ id }), + ]); const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId;apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
10-30: Same fragile error detection pattern.As noted in the project settings page, consider using TRPC error code checking instead of string matching for more robust error detection. This would make the code resilient to error message changes.
apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts (1)
199-235: Consider extracting duplicate query logic.Both
getPreviousWorkspaceandgetNextWorkspaceperform identical database queries and sorting. This duplicated logic could be extracted to a helper function.♻️ Suggested refactor
// Add helper at module level or within createQueryProcedures function getSortedWorkspaces() { return localDb .select() .from(workspaces) .where(isNull(workspaces.deletingAt)) .all() .sort((a, b) => a.tabOrder - b.tabOrder); } // Then simplify procedures: getPreviousWorkspace: publicProcedure .input(z.object({ id: z.string() })) .query(({ input }) => { const allWorkspaces = getSortedWorkspaces(); const currentIndex = allWorkspaces.findIndex((w) => w.id === input.id); return currentIndex > 0 ? allWorkspaces[currentIndex - 1].id : null; }), getNextWorkspace: publicProcedure .input(z.object({ id: z.string() })) .query(({ input }) => { const allWorkspaces = getSortedWorkspaces(); const currentIndex = allWorkspaces.findIndex((w) => w.id === input.id); return currentIndex !== -1 && currentIndex < allWorkspaces.length - 1 ? allWorkspaces[currentIndex + 1].id : null; }),apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (1)
10-44: Consider using TRPC error codes instead of string matching for more robust error detection.The error detection via
error.message.includes("not found")is fragile and could break if error message format changes. Since the server throwsTRPCErrorwithcode: "NOT_FOUND", use the error code property instead:Suggested approach
import { TRPCClientError } from "@trpc/client"; // In the loader catch block: if (error instanceof TRPCClientError && error.data?.code === "NOT_FOUND") { throw notFound(); }This aligns with tRPC v11's error propagation pattern where the server error code is exposed via
data.codeon the client.apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts (1)
111-111: Minor: Inconsistent rest parameter spreading.Line 111 uses
...restbut the signature expects specific positional arguments. This works but differs from the pattern inuseOpenWorktree.tswhich uses(data, ...rest). Consider using the same pattern for consistency across hooks.apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx (2)
10-16: Consider extractingLoadingSpinnerto a separate file.Per coding guidelines, prefer one component per file. This spinner could be extracted to a shared UI component (e.g.,
renderer/components/LoadingSpinner.tsx) for reuse across the codebase.
26-42: Dependency array includes derived value that changes on every render.
allWorkspacesis recalculated viaflatMapon every render (line 23), causing this effect to potentially re-run unnecessarily. SinceallWorkspacesis derived fromworkspaces, you can remove it from the dependency array—the effect already guards withif (!workspaces) return.♻️ Suggested fix
useEffect(() => { if (isLoading || !workspaces) return; + const allWorkspaces = workspaces.flatMap((group) => group.workspaces); if (allWorkspaces.length === 0) return; // Show StartView instead // Try to restore last viewed workspace const lastViewedId = localStorage.getItem("lastViewedWorkspaceId"); const targetWorkspace = allWorkspaces.find((w) => w.id === lastViewedId) ?? allWorkspaces[0]; if (targetWorkspace) { navigate({ to: "/workspace/$workspaceId", params: { workspaceId: targetWorkspace.id }, replace: true, }); } - }, [workspaces, isLoading, navigate, allWorkspaces]); + }, [workspaces, isLoading, navigate]);Also remove line 23 (
const allWorkspaces = ...) from outside the effect, or keep it only for thehasNoWorkspacescheck.apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (2)
291-329: Workspace navigation queries run eagerly.
getPreviousWorkspaceandgetNextWorkspacequeries execute on every workspace page load, even if the user never presses the navigation hotkeys. Consider lazy-fetching only when the hotkey is triggered, or at minimum addstaleTimeto reduce redundant fetches.♻️ Optional: Fetch on-demand instead
- const getPreviousWorkspace = trpc.workspaces.getPreviousWorkspace.useQuery( - { id: workspaceId }, - { enabled: !!workspaceId }, - ); + const utils = trpc.useUtils(); useAppHotkey( "PREV_WORKSPACE", - () => { - const prevWorkspaceId = getPreviousWorkspace.data; + async () => { + const prevWorkspaceId = await utils.workspaces.getPreviousWorkspace.fetch({ id: workspaceId }); if (prevWorkspaceId) { localStorage.setItem("lastViewedWorkspaceId", prevWorkspaceId); navigate({ to: "/workspace/$workspaceId", params: { workspaceId: prevWorkspaceId }, }); } }, undefined, - [getPreviousWorkspace.data, navigate], + [workspaceId, navigate, utils], );Apply similar pattern for
getNextWorkspace.
53-346: Large component with many responsibilities.
WorkspacePagehandles workspace data fetching, initialization state, tab/pane management, and ~15 hotkey bindings (~290 lines). Consider extracting hotkey logic into a custom hook (e.g.,useWorkspaceHotkeys) to improve readability and testability.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (52)
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/react-query/workspaces/index.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.tsapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/tasks/page.tsxapps/desktop/src/renderer/routes/_authenticated/workspace/page.tsxapps/desktop/src/renderer/routes/_authenticated/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/TopBar/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/stores/index.tsapps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
💤 Files with no reviewable changes (12)
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
- apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
- apps/desktop/src/renderer/routes/_authenticated/tasks/page.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
- apps/desktop/src/renderer/screens/main/index.tsx
- apps/desktop/src/renderer/stores/app-state.ts
- apps/desktop/src/renderer/react-query/workspaces/index.ts
- apps/desktop/src/renderer/stores/index.ts
- apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
- apps/desktop/src/renderer/routes/_authenticated/workspaces/page.tsx
- apps/desktop/src/renderer/routes/_authenticated/workspace/page.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from@trpc/server/observableinstead of async generators, as the library explicitly checksisObservable(result)and throws an error otherwise
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid usinganytype - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
apps/desktop/src/renderer/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Drizzle ORM for all database operations - never use raw SQL
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Biome for formatting and linting - run at root level with
bun run lint:fixorbiome check --write
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
One component per file - do not create multi-component files
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
🧠 Learnings (12)
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`
Applied to files:
apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/api/**/*.{ts,tsx} : Use TRPCError with appropriate error codes: NOT_FOUND, UNAUTHORIZED, FORBIDDEN, BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_IMPLEMENTED
Applied to files:
apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from `trpc/server/observable` instead of async generators, as the library explicitly checks `isObservable(result)` and throws an error otherwise
Applied to files:
apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/api/**/*.{ts,tsx} : tRPC procedures and API route handlers should validate and delegate; keep orchestrators thin
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/api/**/*.{ts,tsx} : Extract business logic from tRPC procedures into utility functions when logic exceeds ~50 lines, is used by multiple procedures, or needs independent testing
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/
Applied to files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/electron-router-dom.ts : Do not import Node.js modules like node:path or dotenv in electron-router-dom.ts and similar shared files - they run in both main and renderer processes
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/index.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/*.ts : Never import Node.js modules in shared code like electron-router-dom.ts - it runs in both main and renderer processes
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/**/*.{ts,tsx} : Use Drizzle ORM for all database operations - never use raw SQL
Applied to files:
apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to packages/db/src/**/*.ts : Schema definitions must be in packages/db/src/ using Drizzle ORM
Applied to files:
apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
🧬 Code graph analysis (11)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx (4)
apps/desktop/src/renderer/routes/_authenticated/layout.tsx (1)
Route(20-22)apps/desktop/src/renderer/stores/new-workspace-modal.ts (1)
useOpenNewWorkspaceModal(32-33)apps/desktop/src/renderer/stores/workspace-sidebar-state.ts (3)
useWorkspaceSidebarStore(30-130)COLLAPSED_WORKSPACE_SIDEBAR_WIDTH(5-5)MAX_WORKSPACE_SIDEBAR_WIDTH(7-7)apps/desktop/src/renderer/stores/hotkeys/store.ts (1)
useAppHotkey(316-348)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx (5)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx (1)
Route(4-6)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx (1)
Route(6-8)apps/desktop/src/renderer/routes/_authenticated/layout.tsx (1)
Route(20-22)apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx (1)
WorkspacesListView(18-286)
apps/desktop/src/lib/trpc/routers/projects/projects.ts (2)
apps/desktop/src/main/lib/local-db/index.ts (1)
localDb(82-82)packages/local-db/src/schema/schema.ts (1)
projects(16-42)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (4)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
Route(5-31)apps/desktop/src/renderer/routes/not-found.tsx (1)
NotFound(3-29)apps/desktop/src/renderer/lib/trpc-client.ts (1)
trpcClient(14-16)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx (7)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx (1)
Route(13-15)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx (1)
Route(6-8)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx (1)
Route(4-6)apps/desktop/src/renderer/routes/_authenticated/layout.tsx (1)
Route(20-22)apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (1)
Route(5-45)apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
Route(5-31)
apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (4)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx (1)
Route(6-8)apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (1)
Route(5-45)apps/desktop/src/renderer/routes/not-found.tsx (1)
NotFound(3-29)apps/desktop/src/renderer/lib/trpc-client.ts (1)
trpcClient(14-16)
apps/desktop/src/renderer/routes/_authenticated/layout.tsx (5)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
useAgentHookListener(34-134)apps/desktop/src/renderer/stores/hotkeys/store.ts (1)
useHotkeysSync(277-307)apps/desktop/src/renderer/stores/workspace-init.ts (1)
useWorkspaceInitStore(29-83)apps/desktop/src/renderer/screens/main/components/WorkspaceInitEffects.tsx (1)
WorkspaceInitEffects(23-206)apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx (1)
NewWorkspaceModal(59-458)
apps/desktop/src/renderer/routes/__root.tsx (2)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (1)
Route(5-45)apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
Route(5-31)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx (5)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx (1)
Route(13-15)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx (1)
Route(4-6)apps/desktop/src/renderer/routes/_authenticated/layout.tsx (1)
Route(20-22)apps/desktop/src/renderer/screens/main/components/StartView/index.tsx (1)
StartView(14-243)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts (3)
apps/desktop/src/lib/trpc/index.ts (1)
publicProcedure(49-49)apps/desktop/src/main/lib/local-db/index.ts (1)
localDb(82-82)packages/local-db/src/schema/schema.ts (1)
workspaces(80-121)
apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts (1)
apps/desktop/src/renderer/react-query/workspaces/index.ts (1)
useDeleteWorkspace(4-4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build
| } catch (error) { | ||
| // If workspace not found, throw notFound() to render 404 page | ||
| if (error instanceof Error && error.message.includes("not found")) { | ||
| throw notFound(); | ||
| } | ||
| // Re-throw other errors | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx | head -70Repository: superset-sh/superset
Length of output: 1905
🏁 Script executed:
find apps/desktop/src/renderer/routes -type f -name "page.tsx" | grep -i workspaceRepository: superset-sh/superset
Length of output: 400
🏁 Script executed:
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxRepository: superset-sh/superset
Length of output: 1905
🏁 Script executed:
grep -r "throw notFound" apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/ --include="*.tsx"Repository: superset-sh/superset
Length of output: 333
🏁 Script executed:
wc -l apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxRepository: superset-sh/superset
Length of output: 145
🏁 Script executed:
sed -n '30,49p' 'apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx'Repository: superset-sh/superset
Length of output: 609
🏁 Script executed:
find apps -type f -name "*.ts" -path "*/trpc/*" | head -10Repository: superset-sh/superset
Length of output: 539
🏁 Script executed:
find apps/desktop/src/renderer -name "page.tsx" -exec grep -l "throw notFound" {} \;Repository: superset-sh/superset
Length of output: 328
🏁 Script executed:
cat -n 'apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx'Repository: superset-sh/superset
Length of output: 11798
🏁 Script executed:
find apps/desktop/src/lib/trpc -type f -name "*.ts" | xargs grep -l "workspaces"Repository: superset-sh/superset
Length of output: 1097
🏁 Script executed:
cat -n apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsRepository: superset-sh/superset
Length of output: 8294
🏁 Script executed:
grep -r "TRPCClientError\|TRPCError" apps/desktop/src/renderer --include="*.ts" --include="*.tsx" | head -20Repository: superset-sh/superset
Length of output: 46
🏁 Script executed:
cat -n apps/desktop/src/lib/trpc-client.ts | head -50Repository: superset-sh/superset
Length of output: 131
🏁 Script executed:
find apps/desktop/src -name "*trpc*" -type f | grep -v node_modulesRepository: superset-sh/superset
Length of output: 192
🏁 Script executed:
cat -n apps/desktop/src/renderer/lib/trpc-client.tsRepository: superset-sh/superset
Length of output: 821
🏁 Script executed:
grep -r "TRPCClientError" apps/desktop --include="*.ts" --include="*.tsx" -A 3 | head -30Repository: superset-sh/superset
Length of output: 46
Check error code instead of message string, and log re-thrown errors.
The TRPC procedure throws TRPCError with code: "NOT_FOUND", so check error.code directly rather than string matching. Additionally, re-thrown errors should be logged per the coding guideline: never swallow errors silently. Consider:
} catch (error) {
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
throw notFound();
}
console.error("[workspace/loader] Failed to load workspace:", error);
throw error;
}🤖 Prompt for AI Agents
In
@apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
around lines 42 - 49, Replace the current catch block that inspects
error.message with a check for TRPCError.error.code (e.g., if error instanceof
TRPCError && error.code === "NOT_FOUND") and call notFound() in that case; for
all other errors log the error before re-throwing (e.g., include a descriptive
tag like "[workspace/loader]" in the log) so failures aren’t swallowed. Ensure
you import TRPCError if needed and keep the existing notFound() behavior.
Backend changes: - Move auto-create main workspace logic from client to server - Add ensureMainWorkspace() helper called after project open/init/clone - Ensures every project automatically gets a branch workspace on first open Frontend changes: - Simplify useWorkspaceShortcuts hook (now only handles keyboard shortcuts) - Add confirmation dialog for close project with workspace count warning - Navigate away when closing project if currently viewing its workspace - Fix error when closing project with only default workspace All workspaces now created server-side, eliminating client-side race conditions
1beb2cd to
586eaa9
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In @apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts:
- Around line 88-107: The navigation block that calls
utils.workspaces.getPreviousWorkspace.fetch,
utils.workspaces.getNextWorkspace.fetch and navigate can throw and currently
prevents the user-provided options?.onSuccess from running; wrap the entire
sequence that computes targetWorkspaceId, sets localStorage and calls navigate
in a try/catch (or try/catch/finally) so any exceptions are caught and do not
block execution, and ensure options?.onSuccess is invoked regardless (e.g., call
it in finally or after the catch), keeping the existing logic for determining
targetWorkspaceId and fallback navigation intact.
In @apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts:
- Around line 64-68: Extract the literal "lastViewedWorkspaceId" into a shared
constant and replace direct uses with that constant: create a new constant
LAST_VIEWED_WORKSPACE_ID in apps/desktop/src/renderer/lib/constants.ts and
export it, then import and use it in useCreateWorkspace (replace
localStorage.setItem("lastViewedWorkspaceId", ...) with
localStorage.setItem(LAST_VIEWED_WORKSPACE_ID, ...)). Update other occurrences
across hooks, screens, components, stores and routes to import and use
LAST_VIEWED_WORKSPACE_ID to ensure a single source of truth.
In
@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx:
- Around line 65-74: The catch block in the ProjectHeader navigation check
silently swallows errors; update the catch to capture the thrown error (e.g.,
catch (err)) and log it with context (including params.workspaceId and the
target project id) so failures in utils.workspaces.get.fetch are visible; if a
logger is available in this module use that (otherwise use console.error) and
keep the existing behavior of skipping navigation after logging.
♻️ Duplicate comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
42-49: Check error code instead of message string, and log re-thrown errors.The current approach matches on
error.message.includes("not found")which is fragile—message text can change across versions or environments. The TRPC procedure throwsTRPCErrorwithcode: "NOT_FOUND", so checkingerror.codeis more reliable. Additionally, per coding guidelines, errors should be logged before re-throwing.Suggested fix
+import { TRPCClientError } from "@trpc/client"; + // ... in loader } catch (error) { - // If workspace not found, throw notFound() to render 404 page - if (error instanceof Error && error.message.includes("not found")) { + if ( + error instanceof TRPCClientError && + error.data?.code === "NOT_FOUND" + ) { throw notFound(); } - // Re-throw other errors + console.error("[workspace/loader] Failed to load workspace:", error); throw error; }apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts (1)
85-108: The neighbor workspace lookup fails because the closed workspace is already deleted from the database whenonSuccessexecutes.Both
getPreviousWorkspaceandgetNextWorkspaceusefindIndexto locate the workspace and determine its neighbors. Since the workspace is deleted synchronously during the mutation, it's already gone by the time these fetch calls are made. This causes both procedures to returnnull, resulting in navigation to/workspaceinstead of the actual adjacent workspace.Consider either:
- Having the mutation return the target workspace ID
- Fetching neighbor IDs in
onMutatebefore the mutation executes
🧹 Nitpick comments (24)
apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts (1)
30-36: Consider wrapping localStorage access in try-catch.
localStorage.setItemcan throw (e.g., quota exceeded, private browsing restrictions). An unhandled exception here would prevent navigation and the user'sonSuccesscallback from executing.Suggested improvement
// Navigate to the workspace // Branch workspaces don't need async initialization, so always navigate - localStorage.setItem("lastViewedWorkspaceId", data.workspace.id); + try { + localStorage.setItem("lastViewedWorkspaceId", data.workspace.id); + } catch (error) { + console.warn("[workspace/create] Failed to persist lastViewedWorkspaceId:", error); + } navigate({ to: "/workspace/$workspaceId", params: { workspaceId: data.workspace.id }, });As per coding guidelines, errors should not be swallowed silently - at minimum log them with context.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx (1)
8-13: Route-based workspace fetching looks good.The pattern of using
enabled: !!workspaceIdto conditionally run the query is correct. Theid: workspaceId ?? ""fallback to empty string is safe since the query won't execute when disabled, but consider using a more explicit approach for clarity:♻️ Optional: More explicit disabled state
- const { data: workspace } = trpc.workspaces.get.useQuery( - { id: workspaceId ?? "" }, - { enabled: !!workspaceId }, - ); + const { data: workspace } = trpc.workspaces.get.useQuery( + { id: workspaceId! }, + { enabled: !!workspaceId }, + );apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (1)
138-147: Consider wrapping localStorage access in try-catch.
localStorage.setItemcan throwQuotaExceededErrorin edge cases (e.g., private browsing, storage full). While rare for a single ID, defensive handling aligns with the coding guidelines on error handling.♻️ Suggested improvement
const handleClick = () => { if (!rename.isRenaming) { clearWorkspaceAttentionStatus(id); - localStorage.setItem("lastViewedWorkspaceId", id); + try { + localStorage.setItem("lastViewedWorkspaceId", id); + } catch (error) { + console.warn("[WorkspaceListItem/handleClick] Failed to persist lastViewedWorkspaceId:", error); + } navigate({ to: "/workspace/$workspaceId", params: { workspaceId: id }, }); } };apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (2)
37-49: URL parsing runs on every render and bypasses router APIs.The regex-based URL extraction executes unconditionally on every render, defeating the ref's purpose of maintaining stable state. Consider using
useMatchoruseParamsfrom@tanstack/react-routerto derive the workspace ID reactively:import { useMatch } from "@tanstack/react-router"; // Inside the hook: const match = useMatch({ from: "/workspace/$workspaceId", shouldThrow: false }); const currentWorkspaceId = match?.params?.workspaceId ?? null; const currentWorkspaceIdRef = useRef(currentWorkspaceId); currentWorkspaceIdRef.current = currentWorkspaceId;This approach:
- Uses router's built-in route matching instead of manual regex
- Only updates when route actually changes
- Avoids accessing
window.locationdirectly in React components
115-117: Clarify the comment about async navigation.The comment is slightly misleading. The code works correctly because
useTabsStoreis independent of router state—tab/pane data exists regardless of which route is active. The "freshState" re-reads the tabs store (synchronous), not the router state.Consider clarifying:
- // Set active tab and focused pane after navigation - // (router navigation is async, but state updates are immediate) + // Set active tab and focused pane - tabs store is independent of router, + // so we can update it immediately while navigation completes in parallelapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx (1)
38-49: Consider adding a guard tohandleTasksClickfor consistency.
handleWorkspacesClicktoggles navigation (closes when already open), buthandleTasksClickalways navigates to/taskseven when already there. This asymmetry may be intentional, but adding a guard would be more consistent and avoid unnecessary navigation:💡 Optional: Add guard to skip redundant navigation
const handleTasksClick = () => { + if (isTasksOpen) return; navigate({ to: "/tasks" }); };apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx (1)
36-45: Consider extracting the localStorage key to a constant.The string
"lastViewedWorkspaceId"is repeated in two places (lines 40 and 169). Extracting it to a named constant improves maintainability and reduces risk of typos.♻️ Suggested refactor
+const LAST_VIEWED_WORKSPACE_KEY = "lastViewedWorkspaceId"; + export function WorkspacesListView() {Then update both usages:
- localStorage.setItem("lastViewedWorkspaceId", data.workspace.id); + localStorage.setItem(LAST_VIEWED_WORKSPACE_KEY, data.workspace.id);- localStorage.setItem("lastViewedWorkspaceId", item.workspaceId); + localStorage.setItem(LAST_VIEWED_WORKSPACE_KEY, item.workspaceId);Also applies to: 167-173
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx (1)
45-52: Minor nit: the fallback empty string is unnecessary when query is disabled.The
workspaceId ?? ""fallback passes an empty string when the query is already disabled by theenabled: !!workspace.workspaceIdcondition. This is harmless but could be cleaner with a non-null assertion since theenabledcheck guaranteesworkspaceIdis truthy when the query runs.♻️ Optional cleanup
const { data: githubStatus } = trpc.workspaces.getGitHubStatus.useQuery( - { workspaceId: workspace.workspaceId ?? "" }, + { workspaceId: workspace.workspaceId! }, { enabled: hasHovered && workspace.type === "worktree" && !!workspace.workspaceId,apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts (3)
4-17: Consider using a discriminated union fornavigatedTo.The
navigatedTofield stores both workspace IDs (e.g.,"abc123") and route paths (e.g.,"/workspace"), which conflates two different concepts. A discriminated union would make the intent clearer and prevent accidental misuse.♻️ Suggested improvement
type DeleteContext = { previousGrouped: ReturnType< typeof trpc.useUtils >["workspaces"]["getAllGrouped"]["getData"] extends () => infer R ? R : never; previousAll: ReturnType< typeof trpc.useUtils >["workspaces"]["getAll"]["getData"] extends () => infer R ? R : never; wasViewingDeleted: boolean; - navigatedTo: string | null; + navigatedTo: + | { type: "workspace"; id: string } + | { type: "list" } + | null; };
40-46: Parallelize the workspace fetch calls.The
getPreviousWorkspaceandgetNextWorkspacefetches are independent and can run concurrently. Sequential execution adds unnecessary latency to the deletion flow.♻️ Proposed fix
// If viewing deleted workspace, get navigation target BEFORE optimistic update if (wasViewingDeleted) { - const prevWorkspaceId = - await utils.workspaces.getPreviousWorkspace.fetch({ id }); - const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({ - id, - }); + const [prevWorkspaceId, nextWorkspaceId] = await Promise.all([ + utils.workspaces.getPreviousWorkspace.fetch({ id }), + utils.workspaces.getNextWorkspace.fetch({ id }), + ]); const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId;
101-104: Remove unnecessary rest spread from callback signatures.TanStack Query mutation callbacks have fixed signatures (
onSuccess(data, variables, context),onError(error, variables, context)). The...restspread will always be empty and adds confusion.♻️ Proposed fix
- onSuccess: async (data, variables, ...rest) => { + onSuccess: async (data, variables, context) => { // Navigation already handled in onMutate (optimistic) - await options?.onSuccess?.(data, variables, ...rest); + await options?.onSuccess?.(data, variables, context); },apps/desktop/src/lib/trpc/routers/projects/projects.ts (4)
10-33: Unused imports detected.
isNotNull(line 11) andsafeCheckoutBranch(line 32) are imported but not used in this file.🔧 Suggested fix
-import { and, desc, eq, inArray, isNotNull, isNull, not } from "drizzle-orm"; +import { and, desc, eq, inArray, isNull, not } from "drizzle-orm";import { getCurrentBranch, getDefaultBranch, getGitRoot, refreshDefaultBranch, - safeCheckoutBranch, } from "../workspaces/utils/git";
147-153: Consider batching tabOrder updates.Each workspace's
tabOrderis updated in a separate query. For projects with many workspaces, this could be optimized with a singleUPDATE ... SET tabOrder = tabOrder + 1query with a WHERE clause.♻️ Suggested optimization
- for (const ws of projectWorkspaces) { - localDb - .update(workspaces) - .set({ tabOrder: ws.tabOrder + 1 }) - .where(eq(workspaces.id, ws.id)) - .run(); - } + // Batch update all existing workspaces' tabOrder in one query + localDb + .update(workspaces) + .set({ tabOrder: sql`${workspaces.tabOrder} + 1` }) + .where( + and( + eq(workspaces.projectId, project.id), + not(eq(workspaces.id, newWorkspaceId)), + isNull(workspaces.deletingAt), + ), + ) + .run();Note: This requires importing
sqlfromdrizzle-orm.
246-261: Good use of TRPCError with NOT_FOUND.This change properly uses
TRPCErrorwith theNOT_FOUNDcode, aligning with best practices for tRPC error handling. The error message includes the project ID for debugging.However, other procedures in this file still use plain
throw new Error()for similar "not found" cases (lines 286, 655, 724, 772). Consider updating those for consistency.
285-287: Consider using TRPCError for consistency.This "not found" case throws a plain
Errorwhile thegetprocedure now usesTRPCErrorwithNOT_FOUND. For consistent client-side error handling, consider updating this and similar cases (lines 655, 724, 772) to useTRPCError.♻️ Suggested fix
if (!project) { - throw new Error(`Project ${input.projectId} not found`); + throw new TRPCError({ + code: "NOT_FOUND", + message: `Project ${input.projectId} not found`, + }); }Based on learnings, tRPC procedures should use
TRPCErrorwith appropriate error codes:NOT_FOUND,UNAUTHORIZED,FORBIDDEN,BAD_REQUEST,INTERNAL_SERVER_ERROR,NOT_IMPLEMENTED.apps/desktop/src/renderer/routes/_authenticated/layout.tsx (1)
51-59: Type assertion on dynamic route may cause silent failures.The
as "/settings/account"cast bypasses route type-checking. If the settings route structure changes or an unexpectedsectionvalue is received, navigation could fail silently or navigate to a non-existent route.Consider using a typed lookup or validation:
Suggested improvement
trpc.menu.subscribe.useSubscription(undefined, { onData: (event) => { if (event.type === "open-settings") { - const section = event.data.section || "account"; - navigate({ to: `/settings/${section}` as "/settings/account" }); + const section = event.data.section ?? "account"; + const validSections = ["account", "keyboard", "appearance"] as const; + const targetSection = validSections.includes(section as typeof validSections[number]) + ? section + : "account"; + navigate({ to: `/settings/${targetSection}` as `/settings/${typeof targetSection}` }); } }, });Alternatively, define a union type for valid settings sections and validate against it.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx (2)
21-30: Consider using a derived selector instead of function call during render.
isCollapsedis a function that internally callsget(). While this works because the component subscribes towidth, calling functions during render to derive state is unconventional with zustand.A more idiomatic approach would be to compute
isCollapsedas a derived value:const isCollapsed = width === COLLAPSED_WORKSPACE_SIDEBAR_WIDTH;This makes the dependency explicit and avoids the function call pattern. However, the current implementation is functionally correct.
57-59: Simplify callback by passing function reference directly.The arrow function wrapper is unnecessary since
openNewWorkspaceModaltakes no arguments.Suggested simplification
- useAppHotkey("NEW_WORKSPACE", () => openNewWorkspaceModal(), undefined, [ - openNewWorkspaceModal, - ]); + useAppHotkey("NEW_WORKSPACE", openNewWorkspaceModal, undefined, [ + openNewWorkspaceModal, + ]);apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts (1)
38-40: Consider using TRPCError for consistent error handling.Using
TRPCErrorwith appropriate codes (e.g.,BAD_REQUEST) would provide better client-side error categorization. This applies to similar error throws at lines 68-70 and 85-87 as well.💡 Example refactor
+import { TRPCError } from "@trpc/server"; -throw new Error("Invalid fromIndex or toIndex"); +throw new TRPCError({ + code: "BAD_REQUEST", + message: "Invalid fromIndex or toIndex", +});apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx (1)
100-104: Extract the localStorage key to a named constant for consistency across the codebase.The
"lastViewedWorkspaceId"string is used in 18+ files throughout the renderer process. Per coding guidelines, extract magic strings to named constants at the module top to prevent typos and maintain consistency. Creating a shared constant in a centralized location (e.g.,src/renderer/constants.tsorsrc/renderer/lib/storage.ts) would eliminate the risk of inconsistencies when this key is referenced.The route path
/workspace/$workspaceIdis correct and matches the actual route configuration.apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts (1)
33-59: Consider extracting repetitive hotkey registration into a loop.The 9
useAppHotkeycalls are nearly identical. A loop would reduce duplication and make adding/removing workspace shortcuts easier.♻️ Suggested refactor
- useAppHotkey("JUMP_TO_WORKSPACE_1", () => switchToWorkspace(0), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_2", () => switchToWorkspace(1), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_3", () => switchToWorkspace(2), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_4", () => switchToWorkspace(3), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_5", () => switchToWorkspace(4), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_6", () => switchToWorkspace(5), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_7", () => switchToWorkspace(6), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_8", () => switchToWorkspace(7), undefined, [ - switchToWorkspace, - ]); - useAppHotkey("JUMP_TO_WORKSPACE_9", () => switchToWorkspace(8), undefined, [ - switchToWorkspace, - ]); + const WORKSPACE_HOTKEYS = [ + "JUMP_TO_WORKSPACE_1", + "JUMP_TO_WORKSPACE_2", + "JUMP_TO_WORKSPACE_3", + "JUMP_TO_WORKSPACE_4", + "JUMP_TO_WORKSPACE_5", + "JUMP_TO_WORKSPACE_6", + "JUMP_TO_WORKSPACE_7", + "JUMP_TO_WORKSPACE_8", + "JUMP_TO_WORKSPACE_9", + ] as const; + + for (let i = 0; i < WORKSPACE_HOTKEYS.length; i++) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useAppHotkey(WORKSPACE_HOTKEYS[i], () => switchToWorkspace(i), undefined, [ + switchToWorkspace, + ]); + }Note: This requires disabling the hooks lint rule since hooks can't be called in loops by default. The current explicit approach is also valid if you prefer to avoid that.
apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
22-29: String-based error detection is fragile.The check
error.message.includes("not found")relies on the exact error message format from the server. If the TRPC error message changes (e.g., "Workspace NOT_FOUND" or localized messages), this will break. Consider using TRPC error codes instead.♻️ Suggested improvement using TRPC error codes
+import { TRPCClientError } from "@trpc/client"; } catch (error) { // If workspace not found, throw notFound() to render 404 page - if (error instanceof Error && error.message.includes("not found")) { + if ( + error instanceof TRPCClientError && + error.data?.code === "NOT_FOUND" + ) { throw notFound(); } // Re-throw other errors throw error; }This applies to the project settings page and workspace dashboard page as well (see
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx).apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (2)
174-175: Consider extracting the default app value to a constant.The magic string
"cursor"is used as a fallback. If this default is referenced elsewhere or subject to change, extracting it improves maintainability.const DEFAULT_APP = "cursor"; // ... const { data: lastUsedApp = DEFAULT_APP } = trpc.settings.getLastUsedApp.useQuery();
291-294: Theenabledchecks are redundant.Since
workspaceIdcomes from route params viaRoute.useParams(), it's guaranteed to be defined when this component renders. Theenabled: !!workspaceIdconditions will always betrue.You can simplify by removing the enabled option entirely, or keep it for defensive coding if preferred.
Also applies to: 312-315
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (53)
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/query.tsapps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/index.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/react-query/workspaces/index.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.tsapps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.tsapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/routes/_authenticated/tasks/page.tsxapps/desktop/src/renderer/routes/_authenticated/workspace/page.tsxapps/desktop/src/renderer/routes/_authenticated/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsxapps/desktop/src/renderer/screens/main/components/TopBar/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/screens/main/index.tsxapps/desktop/src/renderer/stores/app-state.tsapps/desktop/src/renderer/stores/index.tsapps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
💤 Files with no reviewable changes (11)
- apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
- apps/desktop/src/renderer/screens/main/index.tsx
- apps/desktop/src/renderer/routes/_authenticated/workspace/page.tsx
- apps/desktop/src/renderer/react-query/workspaces/index.ts
- apps/desktop/src/renderer/stores/app-state.ts
- apps/desktop/src/renderer/routes/_authenticated/workspaces/page.tsx
- apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
- apps/desktop/src/renderer/routes/_authenticated/tasks/page.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
- apps/desktop/src/renderer/stores/index.ts
🚧 Files skipped from review as they are similar to previous changes (12)
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
- apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
- apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
- apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
- apps/desktop/src/renderer/index.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
- apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
- apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)
apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined insrc/lib/trpc
Use alias as defined intsconfig.jsonwhen possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from@trpc/server/observableinstead of async generators, as the library explicitly checksisObservable(result)and throws an error otherwise
Files:
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid usinganytype - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness
Files:
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Drizzle ORM for all database operations - never use raw SQL
Files:
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use Biome for formatting and linting - run at root level with
bun run lint:fixorbiome check --write
Files:
apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/lib/trpc/routers/workspaces/workspaces.tsapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Files:
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/hooks/useWorkspaceShortcuts.tsapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.tsapps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/stores/tabs/useAgentHookListener.tsapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
One component per file - do not create multi-component files
Files:
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsxapps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsxapps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
🧠 Learnings (12)
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`
Applied to files:
apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.tsapps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/lib/query-client.tsapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/api/**/*.{ts,tsx} : Use TRPCError with appropriate error codes: NOT_FOUND, UNAUTHORIZED, FORBIDDEN, BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_IMPLEMENTED
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/api/**/*.{ts,tsx} : Extract business logic from tRPC procedures into utility functions when logic exceeds ~50 lines, is used by multiple procedures, or needs independent testing
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/api/**/*.{ts,tsx} : tRPC procedures and API route handlers should validate and delegate; keep orchestrators thin
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from `trpc/server/observable` instead of async generators, as the library explicitly checks `isObservable(result)` and throws an error otherwise
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsxapps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/**/*.{ts,tsx} : Use Drizzle ORM for all database operations - never use raw SQL
Applied to files:
apps/desktop/src/lib/trpc/routers/projects/projects.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/
Applied to files:
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/electron-router-dom.ts : Do not import Node.js modules like node:path or dotenv in electron-router-dom.ts and similar shared files - they run in both main and renderer processes
Applied to files:
apps/desktop/src/renderer/routes/__root.tsxapps/desktop/src/renderer/routes/_authenticated/layout.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx} : Never swallow errors silently - at minimum log them with context
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/*.ts : Never import Node.js modules in shared code like electron-router-dom.ts - it runs in both main and renderer processes
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
Applied to files:
apps/desktop/src/renderer/routes/_authenticated/layout.tsx
🧬 Code graph analysis (11)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx (7)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
Route(5-31)apps/desktop/src/renderer/stores/new-workspace-modal.ts (1)
useOpenNewWorkspaceModal(32-33)apps/desktop/src/renderer/stores/workspace-sidebar-state.ts (3)
useWorkspaceSidebarStore(30-130)COLLAPSED_WORKSPACE_SIDEBAR_WIDTH(5-5)MAX_WORKSPACE_SIDEBAR_WIDTH(7-7)apps/desktop/src/renderer/stores/hotkeys/store.ts (1)
useAppHotkey(316-348)apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx (1)
TopBar(5-27)apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx (1)
WorkspaceSidebar(12-62)
apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (3)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/not-found.tsx (1)
NotFound(3-29)apps/desktop/src/renderer/lib/trpc-client.ts (1)
trpcClient(14-16)
apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts (1)
apps/desktop/src/renderer/react-query/workspaces/index.ts (1)
useDeleteWorkspace(4-4)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx (1)
apps/desktop/src/shared/types/ports.ts (1)
MergedPort(23-33)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx (1)
packages/ui/src/components/ui/alert-dialog.tsx (6)
AlertDialog(146-146)AlertDialogContent(150-150)AlertDialogHeader(151-151)AlertDialogTitle(153-153)AlertDialogDescription(154-154)AlertDialogFooter(152-152)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx (2)
apps/desktop/src/renderer/stores/workspace-sidebar-state.ts (1)
useWorkspaceSidebarStore(30-130)packages/shared/src/constants.ts (1)
FEATURE_FLAGS(45-48)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx (1)
apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx (1)
WorkspacesListView(18-286)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (3)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/not-found.tsx (1)
NotFound(3-29)apps/desktop/src/renderer/lib/trpc-client.ts (1)
trpcClient(14-16)
apps/desktop/src/renderer/routes/__root.tsx (2)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (1)
Route(5-45)apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
Route(5-31)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (1)
apps/desktop/src/renderer/stores/tabs/utils/resolve-notification-target.ts (1)
resolveNotificationTarget(17-44)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx (7)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx (1)
Route(13-15)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (1)
Route(25-51)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx (1)
Route(6-8)apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx (1)
Route(4-6)apps/desktop/src/renderer/routes/_authenticated/layout.tsx (1)
Route(20-22)apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx (1)
Route(5-45)apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx (1)
Route(5-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build
| const prevWorkspaceId = | ||
| await utils.workspaces.getPreviousWorkspace.fetch({ | ||
| id: variables.id, | ||
| }); | ||
| const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({ | ||
| id: variables.id, | ||
| }); | ||
|
|
||
| const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId; | ||
|
|
||
| if (targetWorkspaceId) { | ||
| localStorage.setItem("lastViewedWorkspaceId", targetWorkspaceId); | ||
| navigate({ | ||
| to: "/workspace/$workspaceId", | ||
| params: { workspaceId: targetWorkspaceId }, | ||
| }); | ||
| } else { | ||
| // No other workspaces, navigate to workspace index (shows StartView) | ||
| navigate({ to: "/workspace" }); | ||
| } |
There was a problem hiding this comment.
Wrap navigation logic in try/catch to ensure user's onSuccess callback is always invoked.
If getPreviousWorkspace.fetch, getNextWorkspace.fetch, or navigate throws, the user-provided options?.onSuccess on line 111 will never be called. Navigation failures shouldn't prevent the caller's callback from executing.
Proposed fix
// If the closed workspace is currently being viewed, navigate away
if (params.workspaceId === variables.id) {
- // Try to navigate to previous workspace first, then next
- const prevWorkspaceId =
- await utils.workspaces.getPreviousWorkspace.fetch({
- id: variables.id,
- });
- const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({
- id: variables.id,
- });
-
- const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId;
-
- if (targetWorkspaceId) {
- localStorage.setItem("lastViewedWorkspaceId", targetWorkspaceId);
- navigate({
- to: "/workspace/$workspaceId",
- params: { workspaceId: targetWorkspaceId },
- });
- } else {
- // No other workspaces, navigate to workspace index (shows StartView)
+ try {
+ // Try to navigate to previous workspace first, then next
+ const prevWorkspaceId =
+ await utils.workspaces.getPreviousWorkspace.fetch({
+ id: variables.id,
+ });
+ const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({
+ id: variables.id,
+ });
+
+ const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId;
+
+ if (targetWorkspaceId) {
+ localStorage.setItem("lastViewedWorkspaceId", targetWorkspaceId);
+ navigate({
+ to: "/workspace/$workspaceId",
+ params: { workspaceId: targetWorkspaceId },
+ });
+ } else {
+ // No other workspaces, navigate to workspace index (shows StartView)
+ navigate({ to: "/workspace" });
+ }
+ } catch (error) {
+ console.error("[useCloseWorkspace/onSuccess] Navigation failed:", error);
navigate({ to: "/workspace" });
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const prevWorkspaceId = | |
| await utils.workspaces.getPreviousWorkspace.fetch({ | |
| id: variables.id, | |
| }); | |
| const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({ | |
| id: variables.id, | |
| }); | |
| const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId; | |
| if (targetWorkspaceId) { | |
| localStorage.setItem("lastViewedWorkspaceId", targetWorkspaceId); | |
| navigate({ | |
| to: "/workspace/$workspaceId", | |
| params: { workspaceId: targetWorkspaceId }, | |
| }); | |
| } else { | |
| // No other workspaces, navigate to workspace index (shows StartView) | |
| navigate({ to: "/workspace" }); | |
| } | |
| try { | |
| // Try to navigate to previous workspace first, then next | |
| const prevWorkspaceId = | |
| await utils.workspaces.getPreviousWorkspace.fetch({ | |
| id: variables.id, | |
| }); | |
| const nextWorkspaceId = await utils.workspaces.getNextWorkspace.fetch({ | |
| id: variables.id, | |
| }); | |
| const targetWorkspaceId = prevWorkspaceId ?? nextWorkspaceId; | |
| if (targetWorkspaceId) { | |
| localStorage.setItem("lastViewedWorkspaceId", targetWorkspaceId); | |
| navigate({ | |
| to: "/workspace/$workspaceId", | |
| params: { workspaceId: targetWorkspaceId }, | |
| }); | |
| } else { | |
| // No other workspaces, navigate to workspace index (shows StartView) | |
| navigate({ to: "/workspace" }); | |
| } | |
| } catch (error) { | |
| console.error("[useCloseWorkspace/onSuccess] Navigation failed:", error); | |
| navigate({ to: "/workspace" }); | |
| } |
🤖 Prompt for AI Agents
In @apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts around
lines 88 - 107, The navigation block that calls
utils.workspaces.getPreviousWorkspace.fetch,
utils.workspaces.getNextWorkspace.fetch and navigate can throw and currently
prevents the user-provided options?.onSuccess from running; wrap the entire
sequence that computes targetWorkspaceId, sets localStorage and calls navigate
in a try/catch (or try/catch/finally) so any exceptions are caught and do not
block execution, and ensure options?.onSuccess is invoked regardless (e.g., call
it in finally or after the catch), keeping the existing logic for determining
targetWorkspaceId and fallback navigation intact.
| localStorage.setItem("lastViewedWorkspaceId", data.workspace.id); | ||
| navigate({ | ||
| to: "/workspace/$workspaceId", | ||
| params: { workspaceId: data.workspace.id }, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "lastViewedWorkspaceId" --type=ts --type=tsxRepository: superset-sh/superset
Length of output: 91
🏁 Script executed:
rg "lastViewedWorkspaceId" -t typescriptRepository: superset-sh/superset
Length of output: 98
🏁 Script executed:
rg "lastViewedWorkspaceId" -- '*.ts' '*.tsx'Repository: superset-sh/superset
Length of output: 154
🏁 Script executed:
rg "lastViewedWorkspaceId"Repository: superset-sh/superset
Length of output: 2863
🏁 Script executed:
find apps/desktop/src/renderer -name "*constant*" -o -name "*const*" | head -20Repository: superset-sh/superset
Length of output: 343
🏁 Script executed:
ls -la apps/desktop/src/renderer/ | head -30Repository: superset-sh/superset
Length of output: 1007
🏁 Script executed:
ls -la apps/desktop/src/renderer/lib/Repository: superset-sh/superset
Length of output: 809
Extract lastViewedWorkspaceId localStorage key to a shared constants file.
This string is used 18+ times across the codebase (hooks, screens, components, stores, routes). Extract it to a shared constants file in apps/desktop/src/renderer/lib/constants.ts to prevent typos and maintain a single source of truth.
Suggested refactor
Create apps/desktop/src/renderer/lib/constants.ts:
+export const STORAGE_KEYS = {
+ LAST_VIEWED_WORKSPACE_ID: "lastViewedWorkspaceId",
+} as const;Then use it:
- localStorage.setItem("lastViewedWorkspaceId", data.workspace.id);
+ localStorage.setItem(STORAGE_KEYS.LAST_VIEWED_WORKSPACE_ID, data.workspace.id);🤖 Prompt for AI Agents
In @apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
around lines 64 - 68, Extract the literal "lastViewedWorkspaceId" into a shared
constant and replace direct uses with that constant: create a new constant
LAST_VIEWED_WORKSPACE_ID in apps/desktop/src/renderer/lib/constants.ts and
export it, then import and use it in useCreateWorkspace (replace
localStorage.setItem("lastViewedWorkspaceId", ...) with
localStorage.setItem(LAST_VIEWED_WORKSPACE_ID, ...)). Update other occurrences
across hooks, screens, components, stores and routes to import and use
LAST_VIEWED_WORKSPACE_ID to ensure a single source of truth.
| if (params.workspaceId) { | ||
| try { | ||
| const currentWorkspace = await utils.workspaces.get.fetch({ | ||
| id: params.workspaceId, | ||
| }); | ||
| shouldNavigate = currentWorkspace?.projectId === id; | ||
| } catch { | ||
| // Workspace might not exist, skip navigation | ||
| } | ||
| } |
There was a problem hiding this comment.
Avoid silently swallowing errors in catch block.
Per coding guidelines, errors should not be swallowed silently. Even if navigation skip is acceptable when the workspace doesn't exist, logging the error provides valuable debugging context.
Proposed fix
if (params.workspaceId) {
try {
const currentWorkspace = await utils.workspaces.get.fetch({
id: params.workspaceId,
});
shouldNavigate = currentWorkspace?.projectId === id;
- } catch {
- // Workspace might not exist, skip navigation
+ } catch (error) {
+ // Workspace might not exist, skip navigation
+ console.debug("[ProjectHeader/closeProject] Failed to fetch workspace for navigation check:", error);
}
}🤖 Prompt for AI Agents
In
@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
around lines 65 - 74, The catch block in the ProjectHeader navigation check
silently swallows errors; update the catch to capture the thrown error (e.g.,
catch (err)) and log it with context (including params.workspaceId and the
target project id) so failures in utils.workspaces.get.fetch are visible; if a
logger is available in this module use that (otherwise use console.error) and
keep the existing behavior of skipping navigation after logging.
Created navigateToWorkspace() utility to ensure consistent workspace navigation
and localStorage persistence across the entire app.
Changes:
- Created workspace-navigation.ts utility in dashboard route utils
- Utility combines navigate() + localStorage.setItem() in single call
- Updated 13 files to use the new utility:
- 5 mutation hooks (useOpenWorktree, useCreateWorkspace, useCreateBranchWorkspace,
useDeleteWorkspace, useCloseWorkspace)
- 8 component/page files (ProjectHeader, useAgentHookListener, WorkspacesListView,
BranchSwitcher, WorkspaceListItem, WorkspacePortGroup, MergedPortBadge, workspace page)
Benefits:
- Prevents forgetting to persist lastViewedWorkspaceId when navigating
- Reduces code duplication (2 lines → 1 line per navigation)
- Co-located in dashboard route for better organization
- Type-safe navigation with proper router params
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Summary by CodeRabbit
Bug Fixes
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.