From cba4250a59d0a35ea391d23edb7b0db7d7a88edb Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Thu, 27 Nov 2025 10:47:13 -0600 Subject: [PATCH 1/3] right click on worktree item --- .../lib/trpc/routers/workspaces/workspaces.ts | 9 +- apps/desktop/src/renderer/globals.css | 2 +- .../TopBar/WorkspaceTabs/WorkspaceGroup.tsx | 2 + .../TopBar/WorkspaceTabs/WorkspaceItem.tsx | 138 +++++++++--------- .../WorkspaceItemContextMenu.tsx | 42 ++++++ .../src/renderer/screens/main/index.tsx | 38 ++--- .../src/resources/public/theme-boot.js | 2 +- 7 files changed, 140 insertions(+), 93 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceItemContextMenu.tsx diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts index 740f94c9d3d..d6e51dc99b6 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts @@ -153,6 +153,7 @@ export const createWorkspacesRouter = () => { id: string; projectId: string; worktreeId: string; + worktreePath: string; name: string; tabOrder: number; createdAt: number; @@ -180,7 +181,13 @@ export const createWorkspacesRouter = () => { for (const workspace of workspaces) { if (groupsMap.has(workspace.projectId)) { - groupsMap.get(workspace.projectId)?.workspaces.push(workspace); + const worktree = db.data.worktrees.find( + (w) => w.id === workspace.worktreeId, + ); + groupsMap.get(workspace.projectId)?.workspaces.push({ + ...workspace, + worktreePath: worktree?.path ?? "", + }); } } diff --git a/apps/desktop/src/renderer/globals.css b/apps/desktop/src/renderer/globals.css index a8fb0c11bb0..bd76cf31cd8 100644 --- a/apps/desktop/src/renderer/globals.css +++ b/apps/desktop/src/renderer/globals.css @@ -71,7 +71,7 @@ --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --tertiary: oklch(0.95 0.003 40); - --tertiary-active: oklch(0.90 0.003 40); + --tertiary-active: oklch(0.9 0.003 40); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); --border: oklch(0.922 0 0); diff --git a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceGroup.tsx b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceGroup.tsx index 41f3796c871..3d15268089e 100644 --- a/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceGroup.tsx +++ b/apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceTabs/WorkspaceGroup.tsx @@ -6,6 +6,7 @@ import { WorkspaceItem } from "./WorkspaceItem"; interface Workspace { id: string; projectId: string; + worktreePath: string; name: string; tabOrder: number; } @@ -71,6 +72,7 @@ export function WorkspaceGroup({ -
- {/* Main workspace button */} - + {needsAttention && ( + + + + + )} + + )} + - -
+ + + void; +} + +export function WorkspaceItemContextMenu({ + children, + worktreePath, + onRename, +}: WorkspaceItemContextMenuProps) { + const openInFinder = trpc.external.openInFinder.useMutation(); + + const handleOpenInFinder = () => { + if (worktreePath) { + openInFinder.mutate(worktreePath); + } + }; + + return ( + + {children} + + Rename + + + Open in Finder + + + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/index.tsx b/apps/desktop/src/renderer/screens/main/index.tsx index 46c38ff384b..5a73fdf33b6 100644 --- a/apps/desktop/src/renderer/screens/main/index.tsx +++ b/apps/desktop/src/renderer/screens/main/index.tsx @@ -29,34 +29,22 @@ export function MainScreen() { const isWorkspaceView = currentView === "workspace"; // Sidebar toggle shortcut - only in workspace view - useHotkeys( - "meta+s", - () => { - if (isWorkspaceView) toggleSidebar(); - }, - [toggleSidebar, isWorkspaceView], - ); + useHotkeys("meta+s", () => { + if (isWorkspaceView) toggleSidebar(); + }, [toggleSidebar, isWorkspaceView]); // Split view shortcuts - only in workspace view - useHotkeys( - "meta+d", - () => { - if (isWorkspaceView && activeWorkspaceId) { - splitTabVertical(activeWorkspaceId); - } - }, - [activeWorkspaceId, splitTabVertical, isWorkspaceView], - ); + useHotkeys("meta+d", () => { + if (isWorkspaceView && activeWorkspaceId) { + splitTabVertical(activeWorkspaceId); + } + }, [activeWorkspaceId, splitTabVertical, isWorkspaceView]); - useHotkeys( - "meta+shift+d", - () => { - if (isWorkspaceView && activeWorkspaceId) { - splitTabHorizontal(activeWorkspaceId); - } - }, - [activeWorkspaceId, splitTabHorizontal, isWorkspaceView], - ); + useHotkeys("meta+shift+d", () => { + if (isWorkspaceView && activeWorkspaceId) { + splitTabHorizontal(activeWorkspaceId); + } + }, [activeWorkspaceId, splitTabHorizontal, isWorkspaceView]); return ( diff --git a/apps/desktop/src/resources/public/theme-boot.js b/apps/desktop/src/resources/public/theme-boot.js index 3cbf9d430cf..b7070f02665 100644 --- a/apps/desktop/src/resources/public/theme-boot.js +++ b/apps/desktop/src/resources/public/theme-boot.js @@ -1,6 +1,6 @@ // Apply saved theme class immediately to prevent flash of wrong colors // This runs before React hydration to ensure correct initial appearance -(function () { +(() => { try { var themeType = localStorage.getItem("theme-type"); document.documentElement.classList.add( From 9a5171bbe981c9e94a1d6a7cdf4a5fb4095ce26d Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Thu, 27 Nov 2025 10:52:34 -0600 Subject: [PATCH 2/3] refactor get path logic --- .../src/lib/trpc/routers/terminal/terminal.ts | 14 ++++---------- .../trpc/routers/workspaces/utils/worktree.ts | 16 ++++++++++++++++ .../lib/trpc/routers/workspaces/workspaces.ts | 6 ++---- 3 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts diff --git a/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts b/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts index 49166e4c337..2a41ebc6471 100644 --- a/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts +++ b/apps/desktop/src/lib/trpc/routers/terminal/terminal.ts @@ -3,6 +3,7 @@ import { db } from "main/lib/db"; import { terminalManager } from "main/lib/terminal-manager"; import { z } from "zod"; import { publicProcedure, router } from "../.."; +import { getWorktreePath } from "../workspaces/utils/worktree"; /** * Terminal router using TerminalManager with node-pty @@ -44,17 +45,10 @@ export const createTerminalRouter = () => { // Get workspace to determine cwd and workspace name const workspace = db.data.workspaces.find((w) => w.id === workspaceId); - let cwd: string | undefined = cwdOverride; const workspaceName = workspace?.name || "Workspace"; - - if (!cwd && workspace) { - const worktree = db.data.worktrees.find( - (wt) => wt.id === workspace.worktreeId, - ); - if (worktree) { - cwd = worktree.path; - } - } + const cwd = + cwdOverride || + (workspace ? getWorktreePath(workspace.worktreeId) : undefined); const result = await terminalManager.createOrAttach({ tabId, diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts new file mode 100644 index 00000000000..6f2dfc86d75 --- /dev/null +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts @@ -0,0 +1,16 @@ +import { db } from "main/lib/db"; + +/** + * Gets the worktree path for a workspace + */ +export function getWorktreePath(worktreeId: string): string | undefined { + const worktree = db.data.worktrees.find((w) => w.id === worktreeId); + return worktree?.path; +} + +/** + * Gets the worktree path for a workspace, falling back to empty string + */ +export function getWorktreePathOrEmpty(worktreeId: string): string { + return getWorktreePath(worktreeId) ?? ""; +} diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts index d6e51dc99b6..0c155417c4a 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts @@ -12,6 +12,7 @@ import { worktreeExists, } from "./utils/git"; import { copySetupFiles, loadSetupConfig } from "./utils/setup"; +import { getWorktreePathOrEmpty } from "./utils/worktree"; export const createWorkspacesRouter = () => { return router({ @@ -181,12 +182,9 @@ export const createWorkspacesRouter = () => { for (const workspace of workspaces) { if (groupsMap.has(workspace.projectId)) { - const worktree = db.data.worktrees.find( - (w) => w.id === workspace.worktreeId, - ); groupsMap.get(workspace.projectId)?.workspaces.push({ ...workspace, - worktreePath: worktree?.path ?? "", + worktreePath: getWorktreePathOrEmpty(workspace.worktreeId), }); } } From 13d9fa4b28360182e23d508b426d669b519d2ba3 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Thu, 27 Nov 2025 10:54:03 -0600 Subject: [PATCH 3/3] clean up --- .../src/lib/trpc/routers/workspaces/utils/worktree.ts | 7 ------- apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts index 6f2dfc86d75..1300840572e 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/worktree.ts @@ -7,10 +7,3 @@ export function getWorktreePath(worktreeId: string): string | undefined { const worktree = db.data.worktrees.find((w) => w.id === worktreeId); return worktree?.path; } - -/** - * Gets the worktree path for a workspace, falling back to empty string - */ -export function getWorktreePathOrEmpty(worktreeId: string): string { - return getWorktreePath(worktreeId) ?? ""; -} diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts index 0c155417c4a..eba89219e89 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts @@ -12,7 +12,7 @@ import { worktreeExists, } from "./utils/git"; import { copySetupFiles, loadSetupConfig } from "./utils/setup"; -import { getWorktreePathOrEmpty } from "./utils/worktree"; +import { getWorktreePath } from "./utils/worktree"; export const createWorkspacesRouter = () => { return router({ @@ -184,7 +184,7 @@ export const createWorkspacesRouter = () => { if (groupsMap.has(workspace.projectId)) { groupsMap.get(workspace.projectId)?.workspaces.push({ ...workspace, - worktreePath: getWorktreePathOrEmpty(workspace.worktreeId), + worktreePath: getWorktreePath(workspace.worktreeId) ?? "", }); } }