Skip to content

Router Part 3: Rewrite the workspace handling#740

Merged
saddlepaddle merged 4 commits into
mainfrom
settings-route-loaders
Jan 14, 2026
Merged

Router Part 3: Rewrite the workspace handling#740
saddlepaddle merged 4 commits into
mainfrom
settings-route-loaders

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Jan 13, 2026

  • Kinda nice cleanup, removes flicker
  • Done

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling for missing projects and workspaces — clearer not-found behavior and error feedback.
  • New Features

    • Route-driven workspace navigation with persistent "last viewed" memory.
    • Keyboard shortcuts updated to navigate between workspaces and UI views.
    • New dashboard and workspace pages plus a Confirm Close Project dialog.
  • Refactor

    • Switched from local active-workspace state to router/context-driven navigation and a shared query client for more consistent behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 13, 2026

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 586eaa9 and 751adb1.

📒 Files selected for processing (17)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/utils/workspace-navigation.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
📝 Walkthrough

Walkthrough

Refactors 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

Cohort / File(s) Summary
TRPC projects & workspace procedures
apps/desktop/src/lib/trpc/routers/projects/projects.ts, apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts, apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts, apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
Introduced ensureMainWorkspace and git/db helpers; changed get to throw TRPCError(NOT_FOUND) when missing; removed getActive/setActive; added getPreviousWorkspace/getNextWorkspace.
Router & route layouts
apps/desktop/src/renderer/routes/__root.tsx, apps/desktop/src/renderer/routes/_authenticated/layout.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/*, apps/desktop/src/renderer/routes/_authenticated/settings/*
Added dashboard routes and workspace loaders with notFound handling; switched to createRootRouteWithContext to pass QueryClient; wired route-level prefetch loaders for project/workspace settings.
QueryClient centralization & TRPCProvider
apps/desktop/src/renderer/lib/query-client.ts (new), apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx, apps/desktop/src/renderer/index.tsx
Added shared queryClient, removed per-provider client creation, and injected queryClient into router context.
Route-driven navigation (UI hooks & mutations)
many hooks and react-query files (e.g., useCreateWorkspace.ts, useCreateBranchWorkspace.ts, useOpenWorktree.ts, useCloseWorkspace.ts, useDeleteWorkspace.ts, useWorkspaceShortcuts.ts)
Replaced active-workspace mutation flows with useNavigate route navigation, storing lastViewedWorkspaceId in localStorage and navigating to /workspace/$workspaceId on create/open/close/delete. Removed optimistic previousActive handling.
Removed state & hooks for active workspace
apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts (removed), apps/desktop/src/renderer/react-query/workspaces/index.ts, apps/desktop/src/renderer/stores/app-state.ts (removed), apps/desktop/src/renderer/stores/index.ts
Deleted useSetActiveWorkspace and the app-state store and exports; simplified public react-query surface.
MainScreen & WorkspaceView removal / refactor
apps/desktop/src/renderer/screens/main/index.tsx (removed), apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx (removed), related components
Removed MainScreen and top-level WorkspaceView wrapper; moved initialization effects, NewWorkspaceModal, and subscriptions into authenticated layout/dashboard.
WorkspaceSidebar & list components
many components under apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/*, WorkspacesListView/*, WorkspaceListItem/*, WorkspaceRow/*
Removed isActive/isCurrentWorkspace props; derive active state from route match; on click navigate via useNavigate; simplified styling and removed setActive mutations.
Component migration to route params
SidebarControl.tsx, TopBar/index.tsx, TabsContent/*, TabView/*, ChangesView.tsx, WorkspaceSidebar/*, etc.
Replaced trpc.workspaces.getActive usage with useParams() + trpc.workspaces.get({id}) (enabled when present); worktreePath and dependent queries now derive from routed workspaceId.
Agent hooks, hotkeys, and dialogs
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts, ProjectSection/*, CloseProjectDialog.tsx, NewWorkspaceModal.tsx
Updated agent/tab handling to navigate via router; added CloseProjectDialog component and integrated confirm-close flows; NewWorkspaceModal no longer depends on getActive.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Hopped from state to route,

params lead the way,
No more activate or toggle,
Just navigate today.
Bug fixes baked in branches,
Rabbits cheer, hip-hip-hooray!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description lacks substantive content. Only two brief notes ('Kinda nice cleanup, removes flicker' and 'Done') are provided; all required template sections remain empty or unchecked. Add a detailed Description of changes, specify Type of Change (likely Refactor), link any Related Issues, describe Testing performed, and complete other relevant sections per the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Router Part 3: Rewrite the workspace handling' accurately reflects the primary change—a major refactoring of workspace handling to use router-based navigation instead of state mutations.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the workspaceId parameter in the subscribeStatic subscription.

Passing workspaceId: "" will not subscribe to all static port changes as intended. The backend procedure queries for workspaces.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. The onData callback 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 tabOrder individually. 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: !!workspaceId is correct and prevents unnecessary fetches when not on a workspace route. The strict: false option 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 (and 50 on 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 onError handler 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. If section contains 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:

  1. Parsing window.location.pathname with a regex is fragile compared to using TanStack Router's utilities (e.g., useMatches() or router.state.matches to find the workspace param). If the route structure changes, this regex will silently fail.

  2. 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 useAppHotkey calls 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 handleTasksClick always navigates to /tasks, while handleWorkspacesClick toggles between routes. This means clicking the Tasks button when already on /tasks re-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 applying TRPCError consistently to other procedures.

The get procedure correctly uses TRPCError with NOT_FOUND code, which aligns with best practices and enables proper error handling in route loaders. However, other procedures in this file still use throw new Error() for similar not-found cases (e.g., getBranches at line 188, update at line 542, refreshDefaultBranch at line 611, close at line 659).

Consider migrating other not-found error cases to use TRPCError with NOT_FOUND code for consistent error handling across the router. Based on learnings, use TRPCError with 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 await calls 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 getPreviousWorkspace and getNextWorkspace perform 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 throws TRPCError with code: "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.code on the client.

apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts (1)

111-111: Minor: Inconsistent rest parameter spreading.

Line 111 uses ...rest but the signature expects specific positional arguments. This works but differs from the pattern in useOpenWorktree.ts which 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 extracting LoadingSpinner to 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.

allWorkspaces is recalculated via flatMap on every render (line 23), causing this effect to potentially re-run unnecessarily. Since allWorkspaces is derived from workspaces, you can remove it from the dependency array—the effect already guards with if (!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 the hasNoWorkspaces check.

apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx (2)

291-329: Workspace navigation queries run eagerly.

getPreviousWorkspace and getNextWorkspace queries 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 add staleTime to 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.

WorkspacePage handles 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab29c5 and 1beb2cd.

📒 Files selected for processing (52)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/react-query/workspaces/index.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/tasks/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/workspace/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.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/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/app-state.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/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 in src/lib/trpc
Use alias as defined in tsconfig.json when 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/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/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 using any type - 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.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/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.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/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.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/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:fix or biome check --write

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/hooks/usePortsData.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/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.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/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.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/WorkspacePortGroup/WorkspacePortGroup.tsx
  • apps/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.tsx
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/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.ts
  • 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 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.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/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.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/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

Comment on lines +42 to +49
} 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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx | head -70

Repository: superset-sh/superset

Length of output: 1905


🏁 Script executed:

find apps/desktop/src/renderer/routes -type f -name "page.tsx" | grep -i workspace

Repository: superset-sh/superset

Length of output: 400


🏁 Script executed:

cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx

Repository: 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.tsx

Repository: 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 -10

Repository: 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.ts

Repository: superset-sh/superset

Length of output: 8294


🏁 Script executed:

grep -r "TRPCClientError\|TRPCError" apps/desktop/src/renderer --include="*.ts" --include="*.tsx" | head -20

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

cat -n apps/desktop/src/lib/trpc-client.ts | head -50

Repository: superset-sh/superset

Length of output: 131


🏁 Script executed:

find apps/desktop/src -name "*trpc*" -type f | grep -v node_modules

Repository: superset-sh/superset

Length of output: 192


🏁 Script executed:

cat -n apps/desktop/src/renderer/lib/trpc-client.ts

Repository: superset-sh/superset

Length of output: 821


🏁 Script executed:

grep -r "TRPCClientError" apps/desktop --include="*.ts" --include="*.tsx" -A 3 | head -30

Repository: 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
@saddlepaddle saddlepaddle force-pushed the settings-route-loaders branch from 1beb2cd to 586eaa9 Compare January 14, 2026 00:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 throws TRPCError with code: "NOT_FOUND", so checking error.code is 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 when onSuccess executes.

Both getPreviousWorkspace and getNextWorkspace use findIndex to 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 return null, resulting in navigation to /workspace instead of the actual adjacent workspace.

Consider either:

  1. Having the mutation return the target workspace ID
  2. Fetching neighbor IDs in onMutate before 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.setItem can throw (e.g., quota exceeded, private browsing restrictions). An unhandled exception here would prevent navigation and the user's onSuccess callback 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: !!workspaceId to conditionally run the query is correct. The id: 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.setItem can throw QuotaExceededError in 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 useMatch or useParams from @tanstack/react-router to 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:

  1. Uses router's built-in route matching instead of manual regex
  2. Only updates when route actually changes
  3. Avoids accessing window.location directly in React components

115-117: Clarify the comment about async navigation.

The comment is slightly misleading. The code works correctly because useTabsStore is 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 parallel
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx (1)

38-49: Consider adding a guard to handleTasksClick for consistency.

handleWorkspacesClick toggles navigation (closes when already open), but handleTasksClick always navigates to /tasks even 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 the enabled: !!workspace.workspaceId condition. This is harmless but could be cleaner with a non-null assertion since the enabled check guarantees workspaceId is 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 for navigatedTo.

The navigatedTo field 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 getPreviousWorkspace and getNextWorkspace fetches 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 ...rest spread 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) and safeCheckoutBranch (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 tabOrder is updated in a separate query. For projects with many workspaces, this could be optimized with a single UPDATE ... SET tabOrder = tabOrder + 1 query 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 sql from drizzle-orm.


246-261: Good use of TRPCError with NOT_FOUND.

This change properly uses TRPCError with the NOT_FOUND code, 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 Error while the get procedure now uses TRPCError with NOT_FOUND. For consistent client-side error handling, consider updating this and similar cases (lines 655, 724, 772) to use TRPCError.

♻️ 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 TRPCError with 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 unexpected section value 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.

isCollapsed is a function that internally calls get(). While this works because the component subscribes to width, calling functions during render to derive state is unconventional with zustand.

A more idiomatic approach would be to compute isCollapsed as 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 openNewWorkspaceModal takes 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 TRPCError with 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.ts or src/renderer/lib/storage.ts) would eliminate the risk of inconsistencies when this key is referenced.

The route path /workspace/$workspaceId is 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 useAppHotkey calls 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: The enabled checks are redundant.

Since workspaceId comes from route params via Route.useParams(), it's guaranteed to be defined when this component renders. The enabled: !!workspaceId conditions will always be true.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1beb2cd and 586eaa9.

📒 Files selected for processing (53)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModal.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/react-query/workspaces/index.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useOpenWorktree.ts
  • apps/desktop/src/renderer/react-query/workspaces/useSetActiveWorkspace.ts
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/tasks/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/workspace/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/SidebarControl/SidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/TopBar/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.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/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectSection.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/NewWorkspaceButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/stores/app-state.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/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 in src/lib/trpc
Use alias as defined in tsconfig.json when 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/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/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 using any type - 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.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/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.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/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:fix or biome check --write

Files:

  • apps/desktop/src/lib/trpc/routers/workspaces/procedures/status.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/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.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/lib/query-client.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/hooks/useWorkspaceShortcuts.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/react-query/workspaces/useCreateBranchWorkspace.ts
  • apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/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.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/components/MergedPortBadge/MergedPortBadge.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/CloseProjectDialog.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceSidebarHeader/WorkspaceSidebarHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspaces/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/index.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/desktop/src/renderer/routes/__root.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectHeader.tsx
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/providers/TRPCProvider/TRPCProvider.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspacesListView.tsx
  • apps/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.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/lib/query-client.ts
  • 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} : 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.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • 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} : 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.ts
  • 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/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
  • apps/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.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/$workspaceId/page.tsx
  • 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/**/*.{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.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspacesListView/WorkspaceRow/WorkspaceRow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/BranchSwitcher/BranchSwitcher.tsx
  • apps/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.tsx
  • 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 **/*.{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

Comment on lines +88 to +107
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" });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Comment on lines +64 to +68
localStorage.setItem("lastViewedWorkspaceId", data.workspace.id);
navigate({
to: "/workspace/$workspaceId",
params: { workspaceId: data.workspace.id },
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "lastViewedWorkspaceId" --type=ts --type=tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

rg "lastViewedWorkspaceId" -t typescript

Repository: 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 -20

Repository: superset-sh/superset

Length of output: 343


🏁 Script executed:

ls -la apps/desktop/src/renderer/ | head -30

Repository: 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.

Comment on lines +65 to +74
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
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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
@saddlepaddle saddlepaddle merged commit aeaed2d into main Jan 14, 2026
5 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant