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
28 changes: 26 additions & 2 deletions apps/web/src/components/BranchToolbar.logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,24 @@ describe("resolveEffectiveEnvMode", () => {
}),
).toBe("worktree");
});

it("keeps a server thread in worktree mode while its worktree is still being created", () => {
expect(
resolveEffectiveEnvMode({
activeWorktreePath: null,
hasServerThread: true,
draftThreadEnvMode: undefined,
preparingWorktree: true,
}),
).toBe("worktree");
expect(
resolveEffectiveEnvMode({
activeWorktreePath: null,
hasServerThread: true,
draftThreadEnvMode: undefined,
}),
).toBe("local");
});
});

describe("resolveEnvModeLabel", () => {
Expand All @@ -510,11 +528,17 @@ describe("resolveCurrentWorkspaceLabel", () => {

describe("resolveLockedWorkspaceLabel", () => {
it("uses a shorter label for the main repo checkout", () => {
expect(resolveLockedWorkspaceLabel(null)).toBe("Local checkout");
expect(resolveLockedWorkspaceLabel(null, "local")).toBe("Local checkout");
});

it("uses a shorter label for an attached worktree", () => {
expect(resolveLockedWorkspaceLabel("/repo/.t3/worktrees/feature-a")).toBe("Worktree");
expect(resolveLockedWorkspaceLabel("/repo/.t3/worktrees/feature-a", "worktree")).toBe(
"Worktree",
);
});

it("describes a worktree that is still being created as a new worktree", () => {
expect(resolveLockedWorkspaceLabel(null, "worktree")).toBe("New worktree");
});
});

Expand Down
19 changes: 15 additions & 4 deletions apps/web/src/components/BranchToolbar.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,14 @@ export function resolveCurrentWorkspaceLabel(activeWorktreePath: string | null):
return activeWorktreePath ? "Current worktree" : resolveEnvModeLabel("local");
}

export function resolveLockedWorkspaceLabel(activeWorktreePath: string | null): string {
return activeWorktreePath ? "Worktree" : "Local checkout";
// A locked thread in worktree mode with no path is still creating its
// worktree, so it reads as a new worktree rather than the project checkout.
export function resolveLockedWorkspaceLabel(
activeWorktreePath: string | null,
effectiveEnvMode: EnvMode,
): string {
if (activeWorktreePath) return "Worktree";
return effectiveEnvMode === "worktree" ? resolveEnvModeLabel("worktree") : "Local checkout";
}

export interface PreviousWorktreeSeed {
Expand Down Expand Up @@ -159,15 +165,20 @@ export function resolveEffectiveEnvMode(input: {
activeWorktreePath: string | null;
hasServerThread: boolean;
draftThreadEnvMode: EnvMode | undefined;
/**
* The server is still creating this thread's worktree. The thread exists
* from the start of that setup but gets its worktree path only at the end.
*/
preparingWorktree?: boolean;
}): EnvMode {
const { activeWorktreePath, hasServerThread, draftThreadEnvMode } = input;
const { activeWorktreePath, hasServerThread, draftThreadEnvMode, preparingWorktree } = input;
if (!hasServerThread) {
if (activeWorktreePath) {
return "local";
}
return draftThreadEnvMode === "worktree" ? "worktree" : "local";
}
return activeWorktreePath ? "worktree" : "local";
return activeWorktreePath || preparingWorktree ? "worktree" : "local";
}

export function resolveDraftEnvModeAfterBranchChange(input: {
Expand Down
22 changes: 6 additions & 16 deletions apps/web/src/components/BranchToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {
resolveContextStripLabelsCompact,
resolveCurrentWorkspaceLabel,
resolveEnvModeLabel,
resolveEffectiveEnvMode,
resolveLockedWorkspaceLabel,
resolvePreviousWorktreeLabel,
resolvePreviousWorktreeSeed,
Expand Down Expand Up @@ -74,7 +73,8 @@ interface BranchToolbarProps {
showGitControls: boolean;
draftId?: DraftId;
onEnvModeChange: (mode: EnvMode) => void;
effectiveEnvModeOverride?: EnvMode;
/** The thread's env mode as ChatView resolves it. */
envMode: EnvMode;
activeThreadBranchOverride?: string | null;
onActiveThreadBranchOverrideChange?: (branch: string | null) => void;
startFromOrigin: boolean;
Expand Down Expand Up @@ -141,7 +141,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
const workspaceLabel = forceNewWorktree
? resolveEnvModeLabel("worktree")
: envModeLocked
? resolveLockedWorkspaceLabel(activeWorktreePath)
? resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)
: effectiveEnvMode === "worktree"
? resolveEnvModeLabel("worktree")
: resolveCurrentWorkspaceLabel(activeWorktreePath);
Expand Down Expand Up @@ -511,7 +511,7 @@ export const BranchToolbar = memo(function BranchToolbar({
showGitControls,
draftId,
onEnvModeChange,
effectiveEnvModeOverride,
envMode,
activeThreadBranchOverride,
onActiveThreadBranchOverrideChange,
startFromOrigin,
Expand Down Expand Up @@ -546,13 +546,7 @@ export const BranchToolbar = memo(function BranchToolbar({
const activeWorktreePath = forceNewWorktree
? null
: (serverThread?.worktreePath ?? draftThread?.worktreePath ?? null);
const effectiveEnvMode =
(forceNewWorktree ? "worktree" : effectiveEnvModeOverride) ??
resolveEffectiveEnvMode({
activeWorktreePath,
hasServerThread: serverThread !== null,
draftThreadEnvMode: draftThread?.envMode,
});
const effectiveEnvMode = forceNewWorktree ? "worktree" : envMode;
const envModeLocked = envLocked || (serverThread !== null && activeWorktreePath !== null);

// "Previous worktree" hops a draft into the most recently active worktree
Expand Down Expand Up @@ -720,11 +714,7 @@ export const BranchToolbar = memo(function BranchToolbar({
threadId={threadId}
{...(draftId ? { draftId } : {})}
envLocked={envLocked}
{...(forceNewWorktree
? { effectiveEnvModeOverride: "worktree" }
: effectiveEnvModeOverride
? { effectiveEnvModeOverride }
: {})}
effectiveEnvModeOverride={effectiveEnvMode}
{...(activeThreadBranchOverride !== undefined ? { activeThreadBranchOverride } : {})}
{...(onActiveThreadBranchOverrideChange ? { onActiveThreadBranchOverrideChange } : {})}
startFromOrigin={startFromOrigin}
Expand Down
12 changes: 5 additions & 7 deletions apps/web/src/components/BranchToolbarEnvModeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-1.75 font-normal text-muted-foreground/70 text-xs sm:h-6"
data-composer-context-control
>
{forceNewWorktree ? (
<FolderGit2Icon className="size-3 shrink-0" />
) : activeWorktreePath ? (
{activeWorktreePath ? (
<FolderGitIcon className="size-3 shrink-0" />
) : effectiveEnvMode === "worktree" ? (
<FolderGit2Icon className="size-3 shrink-0" />
) : (
<FolderIcon className="size-3 shrink-0" />
)}
Expand All @@ -79,16 +79,14 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
data-composer-label-motion
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-drawer group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
{forceNewWorktree
? resolveEnvModeLabel("worktree")
: resolveLockedWorkspaceLabel(activeWorktreePath)}
{resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)}
</span>
</span>
</TooltipTrigger>
<TooltipPopup>
{forceNewWorktree
? "Each model starts in its own worktree."
: resolveLockedWorkspaceLabel(activeWorktreePath)}
: resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)}
</TooltipPopup>
</Tooltip>
);
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5845,6 +5845,7 @@ export default function ChatView(props: ChatViewProps) {
activeWorktreePath,
hasServerThread: isServerThread,
draftThreadEnvMode: isLocalDraftThread ? draftThread?.envMode : undefined,
preparingWorktree: isPreparingWorktree,
});
const canOverrideServerThreadEnvMode = Boolean(
isServerThread &&
Expand Down Expand Up @@ -10238,9 +10239,7 @@ export default function ChatView(props: ChatViewProps) {
onEnvModeChange={onEnvModeChange}
startFromOrigin={startFromOrigin}
onStartFromOriginChange={onStartFromOriginChange}
{...(canOverrideServerThreadEnvMode
? { effectiveEnvModeOverride: envMode }
: {})}
envMode={envMode}
{...(canOverrideServerThreadEnvMode
? {
activeThreadBranchOverride: activeThreadBranch,
Expand Down
Loading