Skip to content
18 changes: 9 additions & 9 deletions apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { projects, workspaces, worktrees } from "@superset/local-db";
import { projects, workspaces } from "@superset/local-db";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { eq } from "drizzle-orm";
Expand All @@ -13,6 +13,7 @@ import { getTerminalHostClient } from "main/lib/terminal-host/client";
import { getWorkspaceRuntimeRegistry } from "main/lib/workspace-runtime";
import { z } from "zod";
import { publicProcedure, router } from "../..";
import { resolveWorktreePathWithRepair } from "../workspaces/utils/repair-worktree-path";
import { assertWorkspaceUsable } from "../workspaces/utils/usability";
import { getWorkspacePath } from "../workspaces/utils/worktree";
import { resolveTerminalThemeType } from "./theme-type";
Expand Down Expand Up @@ -91,8 +92,12 @@ export const createTerminalRouter = () => {
.where(eq(workspaces.id, workspaceId))
.get();
const workspacePath = workspace
? (getWorkspacePath(workspace) ?? undefined)
? workspace.type === "worktree" && workspace.worktreeId
? ((await resolveWorktreePathWithRepair(workspace.worktreeId)) ??
undefined)
: (getWorkspacePath(workspace) ?? undefined)
: undefined;

if (workspace?.type === "worktree") {
assertWorkspaceUsable(workspaceId, workspacePath);
}
Expand Down Expand Up @@ -411,7 +416,7 @@ export const createTerminalRouter = () => {

getWorkspaceCwd: publicProcedure
.input(z.string())
.query(({ input: workspaceId }) => {
.query(async ({ input: workspaceId }) => {
const workspace = localDb
.select()
.from(workspaces)
Expand All @@ -425,12 +430,7 @@ export const createTerminalRouter = () => {
return null;
}

const worktree = localDb
.select()
.from(worktrees)
.where(eq(worktrees.id, workspace.worktreeId))
.get();
return worktree?.path ?? null;
return resolveWorktreePathWithRepair(workspace.worktreeId);
}),

stream: publicProcedure
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { existsSync } from "node:fs";
import { workspaces, worktrees } from "@superset/local-db";
import { TRPCError } from "@trpc/server";
import { and, eq, isNull } from "drizzle-orm";
import { localDb } from "main/lib/local-db";
import { z } from "zod";
Expand All @@ -18,6 +19,7 @@ import {
refreshDefaultBranch,
} from "../utils/git";
import { fetchGitHubPRStatus } from "../utils/github";
import { resolveWorktreePathWithRepair } from "../utils/repair-worktree-path";

export const createGitStatusProcedures = () => {
return router({
Expand Down Expand Up @@ -61,8 +63,20 @@ export const createGitStatusProcedures = () => {

await fetchDefaultBranch(project.mainRepoPath, defaultBranch);

// Repair stale worktree path if directory was moved/unnested
const worktreePath =
(await resolveWorktreePathWithRepair(worktree.id)) ?? worktree.path;

if (!existsSync(worktreePath)) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Worktree path does not exist on disk",
cause: { reason: "path_missing", path: worktreePath },
});
}

const { ahead, behind } = await getAheadBehindCount({
repoPath: worktree.path,
repoPath: worktreePath,
defaultBranch,
});

Expand Down Expand Up @@ -117,7 +131,12 @@ export const createGitStatusProcedures = () => {
return null;
}

const freshStatus = await fetchGitHubPRStatus(worktree.path);
const worktreePath = await resolveWorktreePathWithRepair(worktree.id);
if (!worktreePath) {
return null;
}

const freshStatus = await fetchGitHubPRStatus(worktreePath);

if (freshStatus) {
localDb
Expand All @@ -132,7 +151,7 @@ export const createGitStatusProcedures = () => {

getWorktreeInfo: publicProcedure
.input(z.object({ workspaceId: z.string() }))
.query(({ input }) => {
.query(async ({ input }) => {
const workspace = getWorkspace(input.workspaceId);
if (!workspace) {
return null;
Expand All @@ -145,7 +164,9 @@ export const createGitStatusProcedures = () => {
return null;
}

const worktreeName = worktree.path.split("/").pop() ?? worktree.branch;
const worktreePath =
(await resolveWorktreePathWithRepair(worktree.id)) ?? worktree.path;
const worktreeName = worktreePath.split("/").pop() ?? worktree.branch;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Use path.basename() instead of split("/").pop() for cross-platform correctness. The manual split won't extract the correct directory name on Windows paths.

(Based on your team's feedback about using cross-platform path utilities instead of split.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/workspaces/procedures/git-status.ts, line 169:

<comment>Use `path.basename()` instead of `split("/").pop()` for cross-platform correctness. The manual split won't extract the correct directory name on Windows paths.

(Based on your team's feedback about using cross-platform path utilities instead of split.) </comment>

<file context>
@@ -164,7 +164,9 @@ export const createGitStatusProcedures = () => {
-				const worktreeName = worktree.path.split("/").pop() ?? worktree.branch;
+				const worktreePath =
+					(await resolveWorktreePathWithRepair(worktree.id)) ?? worktree.path;
+				const worktreeName = worktreePath.split("/").pop() ?? worktree.branch;
 				const branchName = worktree.branch;
 
</file context>
Fix with Cubic

const branchName = worktree.branch;

return {
Expand Down
58 changes: 33 additions & 25 deletions apps/desktop/src/lib/trpc/routers/workspaces/procedures/query.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
projects,
type SelectWorkspace,
workspaceSections,
workspaces,
worktrees,
Expand All @@ -10,10 +11,23 @@ import { localDb } from "main/lib/local-db";
import { z } from "zod";
import { publicProcedure, router } from "../../..";
import { getWorkspace } from "../utils/db-helpers";
import { resolveWorktreePathWithRepair } from "../utils/repair-worktree-path";
import { computeVisualOrder } from "../utils/visual-order";
import { getWorkspacePath } from "../utils/worktree";

type WorktreePathMap = Map<string, string>;
async function getWorkspacePathForQuery(
workspace: SelectWorkspace,
): Promise<string | null> {
if (workspace.type === "branch") {
return getWorkspacePath(workspace);
}

if (!workspace.worktreeId) {
return null;
}

return resolveWorktreePathWithRepair(workspace.worktreeId);
}

/** Returns workspace IDs in sidebar visual order (by project.tabOrder, then ungrouped workspaces, then sections by tabOrder). */
function getWorkspacesInVisualOrder(): string[] {
Expand Down Expand Up @@ -63,7 +77,7 @@ export const createQueryProcedures = () => {
return {
...workspace,
type: workspace.type as "worktree" | "branch",
worktreePath: getWorkspacePath(workspace) ?? "",
worktreePath: (await getWorkspacePathForQuery(workspace)) ?? "",
project: project
? {
id: project.id,
Expand Down Expand Up @@ -92,7 +106,7 @@ export const createQueryProcedures = () => {
.sort((a, b) => a.tabOrder - b.tabOrder);
}),

getAllGrouped: publicProcedure.query(() => {
getAllGrouped: publicProcedure.query(async () => {
type WorkspaceItem = {
id: string;
projectId: string;
Expand Down Expand Up @@ -125,13 +139,7 @@ export const createQueryProcedures = () => {
.where(isNotNull(projects.tabOrder))
.all();

const allWorktrees = localDb.select().from(worktrees).all();
const worktreePathMap: WorktreePathMap = new Map(
allWorktrees.map((wt) => [wt.id, wt.path]),
);

const allSections = localDb.select().from(workspaceSections).all();

const groupsMap = new Map<
string,
{
Expand All @@ -152,14 +160,14 @@ export const createQueryProcedures = () => {

for (const project of activeProjects) {
const projectSections = allSections
.filter((s) => s.projectId === project.id)
.filter((section) => section.projectId === project.id)
.sort((a, b) => a.tabOrder - b.tabOrder)
.map((s) => ({
id: s.id,
name: s.name,
tabOrder: s.tabOrder,
isCollapsed: s.isCollapsed ?? false,
color: s.color ?? null,
.map((section) => ({
id: section.id,
name: section.name,
tabOrder: section.tabOrder,
isCollapsed: section.isCollapsed ?? false,
color: section.color ?? null,
workspaces: [] as WorkspaceItem[],
}));

Expand Down Expand Up @@ -187,16 +195,16 @@ export const createQueryProcedures = () => {
.all()
.sort((a, b) => a.tabOrder - b.tabOrder);

for (const workspace of allWorkspaces) {
const workspacesWithResolvedPaths = await Promise.all(
allWorkspaces.map(async (workspace) => ({
workspace,
worktreePath: (await getWorkspacePathForQuery(workspace)) ?? "",
})),
);

for (const { workspace, worktreePath } of workspacesWithResolvedPaths) {
const group = groupsMap.get(workspace.projectId);
if (group) {
let worktreePath = "";
if (workspace.type === "worktree" && workspace.worktreeId) {
worktreePath = worktreePathMap.get(workspace.worktreeId) ?? "";
} else if (workspace.type === "branch") {
worktreePath = group.project.mainRepoPath;
}

const item: WorkspaceItem = {
...workspace,
sectionId: workspace.sectionId ?? null,
Expand All @@ -208,7 +216,7 @@ export const createQueryProcedures = () => {

if (workspace.sectionId) {
const section = group.sections.find(
(s) => s.id === workspace.sectionId,
(groupSection) => groupSection.id === workspace.sectionId,
);
if (section) {
section.workspaces.push(item);
Expand Down
Loading
Loading