diff --git a/apps/web/src/components/BranchToolbar.logic.test.ts b/apps/web/src/components/BranchToolbar.logic.test.ts
index ff4bb76bf12a..bd1b11a6cd28 100644
--- a/apps/web/src/components/BranchToolbar.logic.test.ts
+++ b/apps/web/src/components/BranchToolbar.logic.test.ts
@@ -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", () => {
@@ -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");
});
});
diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts
index 85d306f919ae..cbc887954019 100644
--- a/apps/web/src/components/BranchToolbar.logic.ts
+++ b/apps/web/src/components/BranchToolbar.logic.ts
@@ -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 {
@@ -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: {
diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx
index 1e5cb85a9737..eca4fab601d0 100644
--- a/apps/web/src/components/BranchToolbar.tsx
+++ b/apps/web/src/components/BranchToolbar.tsx
@@ -28,7 +28,6 @@ import {
resolveContextStripLabelsCompact,
resolveCurrentWorkspaceLabel,
resolveEnvModeLabel,
- resolveEffectiveEnvMode,
resolveLockedWorkspaceLabel,
resolvePreviousWorktreeLabel,
resolvePreviousWorktreeSeed,
@@ -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;
@@ -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);
@@ -511,7 +511,7 @@ export const BranchToolbar = memo(function BranchToolbar({
showGitControls,
draftId,
onEnvModeChange,
- effectiveEnvModeOverride,
+ envMode,
activeThreadBranchOverride,
onActiveThreadBranchOverrideChange,
startFromOrigin,
@@ -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
@@ -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}
diff --git a/apps/web/src/components/BranchToolbarEnvModeSelector.tsx b/apps/web/src/components/BranchToolbarEnvModeSelector.tsx
index 1d72ddf87e18..40692063e979 100644
--- a/apps/web/src/components/BranchToolbarEnvModeSelector.tsx
+++ b/apps/web/src/components/BranchToolbarEnvModeSelector.tsx
@@ -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 ? (
-
- ) : activeWorktreePath ? (
+ {activeWorktreePath ? (
+ ) : effectiveEnvMode === "worktree" ? (
+
) : (
)}
@@ -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)}
{forceNewWorktree
? "Each model starts in its own worktree."
- : resolveLockedWorkspaceLabel(activeWorktreePath)}
+ : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)}
);
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
index cd36675860df..f5c3825cee33 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -5845,6 +5845,7 @@ export default function ChatView(props: ChatViewProps) {
activeWorktreePath,
hasServerThread: isServerThread,
draftThreadEnvMode: isLocalDraftThread ? draftThread?.envMode : undefined,
+ preparingWorktree: isPreparingWorktree,
});
const canOverrideServerThreadEnvMode = Boolean(
isServerThread &&
@@ -10238,9 +10239,7 @@ export default function ChatView(props: ChatViewProps) {
onEnvModeChange={onEnvModeChange}
startFromOrigin={startFromOrigin}
onStartFromOriginChange={onStartFromOriginChange}
- {...(canOverrideServerThreadEnvMode
- ? { effectiveEnvModeOverride: envMode }
- : {})}
+ envMode={envMode}
{...(canOverrideServerThreadEnvMode
? {
activeThreadBranchOverride: activeThreadBranch,