Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
resolveThreadPr,
terminalStatusFromRunningIds,
ThreadStatusLabel,
ThreadWorktreeIndicator,
} from "./ThreadStatusIndicators";
import { ProjectFavicon } from "./ProjectFavicon";
import { useAtomValue } from "@effect/atom-react";
Expand Down Expand Up @@ -741,6 +742,7 @@ export const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThr
</TooltipPopup>
</Tooltip>
)}
<ThreadWorktreeIndicator thread={thread} />
{terminalStatus && (
<Tooltip>
<TooltipTrigger
Expand Down
39 changes: 39 additions & 0 deletions apps/web/src/components/ThreadStatusIndicators.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ThreadId } from "@t3tools/contracts";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vite-plus/test";

import { ThreadWorktreeIndicator } from "./ThreadStatusIndicators";

describe("ThreadWorktreeIndicator", () => {
it("renders the worktree folder and branch in an accessible label", () => {
const markup = renderToStaticMarkup(
<ThreadWorktreeIndicator
thread={{
id: ThreadId.make("thread-1"),
branch: "feature/sidebar-indicator",
worktreePath: "/tmp/worktrees/sidebar-indicator",
}}
/>,
);

expect(markup).toContain('role="img"');
expect(markup).toContain(
'aria-label="Worktree: sidebar-indicator (feature/sidebar-indicator)"',
);
expect(markup).toContain('data-testid="thread-worktree-thread-1"');
});

it.each([null, "", " "])("renders nothing for an absent worktree path", (worktreePath) => {
const markup = renderToStaticMarkup(
<ThreadWorktreeIndicator
thread={{
id: ThreadId.make("thread-1"),
branch: "main",
worktreePath,
}}
/>,
);

expect(markup).toBe("");
});
});
37 changes: 36 additions & 1 deletion apps/web/src/components/ThreadStatusIndicators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
scopeThreadRef,
} from "@t3tools/client-runtime/environment";
import type { VcsStatusResult } from "@t3tools/contracts";
import { CloudIcon, GitPullRequestIcon, TerminalIcon } from "lucide-react";
import { CloudIcon, FolderGit2Icon, GitPullRequestIcon, TerminalIcon } from "lucide-react";
import { useMemo } from "react";
import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments";
import { useProject } from "../state/entities";
Expand All @@ -15,6 +15,7 @@ import { useUiStateStore } from "../uiStateStore";
import { resolveChangeRequestPresentation } from "../sourceControlPresentation";
import { resolveThreadStatusPill, type ThreadStatusPill } from "./Sidebar.logic";
import type { SidebarThreadSummary } from "../types";
import { formatWorktreePathForDisplay } from "../worktreeCleanup";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";

export interface PrStatusIndicator {
Expand Down Expand Up @@ -94,6 +95,40 @@ export function terminalStatusFromRunningIds(
};
}

export function ThreadWorktreeIndicator({
thread,
}: {
thread: Pick<SidebarThreadSummary, "id" | "branch" | "worktreePath">;
}) {
const worktreePath = thread.worktreePath?.trim();
if (!worktreePath) {
return null;
}

const displayPath = formatWorktreePathForDisplay(worktreePath);
const tooltip = thread.branch
? `Worktree: ${displayPath} (${thread.branch})`
: `Worktree: ${displayPath}`;

return (
<Tooltip>
<TooltipTrigger
render={
<span
role="img"
aria-label={tooltip}
data-testid={`thread-worktree-${thread.id}`}
className="inline-flex items-center justify-center"
/>
}
>
<FolderGit2Icon className="size-3 text-muted-foreground/40" />
</TooltipTrigger>
<TooltipPopup side="top">{tooltip}</TooltipPopup>
</Tooltip>
);
}

export function ThreadStatusLabel({
status,
compact = false,
Expand Down
Loading