| author | |
| committer | |
| log | 13d6b30515e9d44bb4cc2d223b8b3179c2ba4c79 |
| tree | 91a36941c75dd71d98cb7915c680cb4a25b3f6e8 |
| parent | b3de243d5ed44783c4e7990d8e63cb519d390a03 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>5 files changed, 54 insertions(+), 32 deletions(-)
apps/web/src/components/BranchToolbar.logic.test.ts+26-2| ... | ... | @@ -489,6 +489,24 @@ describe("resolveEffectiveEnvMode", () => { |
| 489 | 489 | }), |
| 490 | 490 | ).toBe("worktree"); |
| 491 | 491 | }); |
| 492 | ||
| 493 | it("keeps a server thread in worktree mode while its worktree is still being created", () => { | |
| 494 | expect( | |
| 495 | resolveEffectiveEnvMode({ | |
| 496 | activeWorktreePath: null, | |
| 497 | hasServerThread: true, | |
| 498 | draftThreadEnvMode: undefined, | |
| 499 | preparingWorktree: true, | |
| 500 | }), | |
| 501 | ).toBe("worktree"); | |
| 502 | expect( | |
| 503 | resolveEffectiveEnvMode({ | |
| 504 | activeWorktreePath: null, | |
| 505 | hasServerThread: true, | |
| 506 | draftThreadEnvMode: undefined, | |
| 507 | }), | |
| 508 | ).toBe("local"); | |
| 509 | }); | |
| 492 | 510 | }); |
| 493 | 511 | |
| 494 | 512 | describe("resolveEnvModeLabel", () => { |
| ... | ... | @@ -510,11 +528,17 @@ describe("resolveCurrentWorkspaceLabel", () => { |
| 510 | 528 | |
| 511 | 529 | describe("resolveLockedWorkspaceLabel", () => { |
| 512 | 530 | it("uses a shorter label for the main repo checkout", () => { |
| 513 | expect(resolveLockedWorkspaceLabel(null)).toBe("Local checkout"); | |
| 531 | expect(resolveLockedWorkspaceLabel(null, "local")).toBe("Local checkout"); | |
| 514 | 532 | }); |
| 515 | 533 | |
| 516 | 534 | it("uses a shorter label for an attached worktree", () => { |
| 517 | expect(resolveLockedWorkspaceLabel("/repo/.t3/worktrees/feature-a")).toBe("Worktree"); | |
| 535 | expect(resolveLockedWorkspaceLabel("/repo/.t3/worktrees/feature-a", "worktree")).toBe( | |
| 536 | "Worktree", | |
| 537 | ); | |
| 538 | }); | |
| 539 | ||
| 540 | it("describes a worktree that is still being created as a new worktree", () => { | |
| 541 | expect(resolveLockedWorkspaceLabel(null, "worktree")).toBe("New worktree"); | |
| 518 | 542 | }); |
| 519 | 543 | }); |
| 520 | 544 |
apps/web/src/components/BranchToolbar.logic.ts+15-4| ... | ... | @@ -104,8 +104,14 @@ export function resolveCurrentWorkspaceLabel(activeWorktreePath: string | null): |
| 104 | 104 | return activeWorktreePath ? "Current worktree" : resolveEnvModeLabel("local"); |
| 105 | 105 | } |
| 106 | 106 | |
| 107 | export function resolveLockedWorkspaceLabel(activeWorktreePath: string | null): string { | |
| 108 | return activeWorktreePath ? "Worktree" : "Local checkout"; | |
| 107 | // A locked thread in worktree mode with no path is still creating its | |
| 108 | // worktree, so it reads as a new worktree rather than the project checkout. | |
| 109 | export function resolveLockedWorkspaceLabel( | |
| 110 | activeWorktreePath: string | null, | |
| 111 | effectiveEnvMode: EnvMode, | |
| 112 | ): string { | |
| 113 | if (activeWorktreePath) return "Worktree"; | |
| 114 | return effectiveEnvMode === "worktree" ? resolveEnvModeLabel("worktree") : "Local checkout"; | |
| 109 | 115 | } |
| 110 | 116 | |
| 111 | 117 | export interface PreviousWorktreeSeed { |
| ... | ... | @@ -159,15 +165,20 @@ export function resolveEffectiveEnvMode(input: { |
| 159 | 165 | activeWorktreePath: string | null; |
| 160 | 166 | hasServerThread: boolean; |
| 161 | 167 | draftThreadEnvMode: EnvMode | undefined; |
| 168 | /** | |
| 169 | * The server is still creating this thread's worktree. The thread exists | |
| 170 | * from the start of that setup but gets its worktree path only at the end. | |
| 171 | */ | |
| 172 | preparingWorktree?: boolean; | |
| 162 | 173 | }): EnvMode { |
| 163 | const { activeWorktreePath, hasServerThread, draftThreadEnvMode } = input; | |
| 174 | const { activeWorktreePath, hasServerThread, draftThreadEnvMode, preparingWorktree } = input; | |
| 164 | 175 | if (!hasServerThread) { |
| 165 | 176 | if (activeWorktreePath) { |
| 166 | 177 | return "local"; |
| 167 | 178 | } |
| 168 | 179 | return draftThreadEnvMode === "worktree" ? "worktree" : "local"; |
| 169 | 180 | } |
| 170 | return activeWorktreePath ? "worktree" : "local"; | |
| 181 | return activeWorktreePath || preparingWorktree ? "worktree" : "local"; | |
| 171 | 182 | } |
| 172 | 183 | |
| 173 | 184 | export function resolveDraftEnvModeAfterBranchChange(input: { |
apps/web/src/components/BranchToolbar.tsx+6-16| ... | ... | @@ -28,7 +28,6 @@ import { |
| 28 | 28 | resolveContextStripLabelsCompact, |
| 29 | 29 | resolveCurrentWorkspaceLabel, |
| 30 | 30 | resolveEnvModeLabel, |
| 31 | resolveEffectiveEnvMode, | |
| 32 | 31 | resolveLockedWorkspaceLabel, |
| 33 | 32 | resolvePreviousWorktreeLabel, |
| 34 | 33 | resolvePreviousWorktreeSeed, |
| ... | ... | @@ -74,7 +73,8 @@ interface BranchToolbarProps { |
| 74 | 73 | showGitControls: boolean; |
| 75 | 74 | draftId?: DraftId; |
| 76 | 75 | onEnvModeChange: (mode: EnvMode) => void; |
| 77 | effectiveEnvModeOverride?: EnvMode; | |
| 76 | /** The thread's env mode as ChatView resolves it. */ | |
| 77 | envMode: EnvMode; | |
| 78 | 78 | activeThreadBranchOverride?: string | null; |
| 79 | 79 | onActiveThreadBranchOverrideChange?: (branch: string | null) => void; |
| 80 | 80 | startFromOrigin: boolean; |
| ... | ... | @@ -141,7 +141,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({ |
| 141 | 141 | const workspaceLabel = forceNewWorktree |
| 142 | 142 | ? resolveEnvModeLabel("worktree") |
| 143 | 143 | : envModeLocked |
| 144 | ? resolveLockedWorkspaceLabel(activeWorktreePath) | |
| 144 | ? resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode) | |
| 145 | 145 | : effectiveEnvMode === "worktree" |
| 146 | 146 | ? resolveEnvModeLabel("worktree") |
| 147 | 147 | : resolveCurrentWorkspaceLabel(activeWorktreePath); |
| ... | ... | @@ -511,7 +511,7 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 511 | 511 | showGitControls, |
| 512 | 512 | draftId, |
| 513 | 513 | onEnvModeChange, |
| 514 | effectiveEnvModeOverride, | |
| 514 | envMode, | |
| 515 | 515 | activeThreadBranchOverride, |
| 516 | 516 | onActiveThreadBranchOverrideChange, |
| 517 | 517 | startFromOrigin, |
| ... | ... | @@ -546,13 +546,7 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 546 | 546 | const activeWorktreePath = forceNewWorktree |
| 547 | 547 | ? null |
| 548 | 548 | : (serverThread?.worktreePath ?? draftThread?.worktreePath ?? null); |
| 549 | const effectiveEnvMode = | |
| 550 | (forceNewWorktree ? "worktree" : effectiveEnvModeOverride) ?? | |
| 551 | resolveEffectiveEnvMode({ | |
| 552 | activeWorktreePath, | |
| 553 | hasServerThread: serverThread !== null, | |
| 554 | draftThreadEnvMode: draftThread?.envMode, | |
| 555 | }); | |
| 549 | const effectiveEnvMode = forceNewWorktree ? "worktree" : envMode; | |
| 556 | 550 | const envModeLocked = envLocked || (serverThread !== null && activeWorktreePath !== null); |
| 557 | 551 | |
| 558 | 552 | // "Previous worktree" hops a draft into the most recently active worktree |
| ... | ... | @@ -720,11 +714,7 @@ export const BranchToolbar = memo(function BranchToolbar({ |
| 720 | 714 | threadId={threadId} |
| 721 | 715 | {...(draftId ? { draftId } : {})} |
| 722 | 716 | envLocked={envLocked} |
| 723 | {...(forceNewWorktree | |
| 724 | ? { effectiveEnvModeOverride: "worktree" } | |
| 725 | : effectiveEnvModeOverride | |
| 726 | ? { effectiveEnvModeOverride } | |
| 727 | : {})} | |
| 717 | effectiveEnvModeOverride={effectiveEnvMode} | |
| 728 | 718 | {...(activeThreadBranchOverride !== undefined ? { activeThreadBranchOverride } : {})} |
| 729 | 719 | {...(onActiveThreadBranchOverrideChange ? { onActiveThreadBranchOverrideChange } : {})} |
| 730 | 720 | startFromOrigin={startFromOrigin} |
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+5-7| ... | ... | @@ -64,10 +64,10 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe |
| 64 | 64 | 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" |
| 65 | 65 | data-composer-context-control |
| 66 | 66 | > |
| 67 | {forceNewWorktree ? ( | |
| 68 | <FolderGit2Icon className="size-3 shrink-0" /> | |
| 69 | ) : activeWorktreePath ? ( | |
| 67 | {activeWorktreePath ? ( | |
| 70 | 68 | <FolderGitIcon className="size-3 shrink-0" /> |
| 69 | ) : effectiveEnvMode === "worktree" ? ( | |
| 70 | <FolderGit2Icon className="size-3 shrink-0" /> | |
| 71 | 71 | ) : ( |
| 72 | 72 | <FolderIcon className="size-3 shrink-0" /> |
| 73 | 73 | )} |
| ... | ... | @@ -79,16 +79,14 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe |
| 79 | 79 | data-composer-label-motion |
| 80 | 80 | 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" |
| 81 | 81 | > |
| 82 | {forceNewWorktree | |
| 83 | ? resolveEnvModeLabel("worktree") | |
| 84 | : resolveLockedWorkspaceLabel(activeWorktreePath)} | |
| 82 | {resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)} | |
| 85 | 83 | </span> |
| 86 | 84 | </span> |
| 87 | 85 | </TooltipTrigger> |
| 88 | 86 | <TooltipPopup> |
| 89 | 87 | {forceNewWorktree |
| 90 | 88 | ? "Each model starts in its own worktree." |
| 91 | : resolveLockedWorkspaceLabel(activeWorktreePath)} | |
| 89 | : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)} | |
| 92 | 90 | </TooltipPopup> |
| 93 | 91 | </Tooltip> |
| 94 | 92 | ); |
apps/web/src/components/ChatView.tsx+2-3| ... | ... | @@ -5845,6 +5845,7 @@ export default function ChatView(props: ChatViewProps) { |
| 5845 | 5845 | activeWorktreePath, |
| 5846 | 5846 | hasServerThread: isServerThread, |
| 5847 | 5847 | draftThreadEnvMode: isLocalDraftThread ? draftThread?.envMode : undefined, |
| 5848 | preparingWorktree: isPreparingWorktree, | |
| 5848 | 5849 | }); |
| 5849 | 5850 | const canOverrideServerThreadEnvMode = Boolean( |
| 5850 | 5851 | isServerThread && |
| ... | ... | @@ -10238,9 +10239,7 @@ export default function ChatView(props: ChatViewProps) { |
| 10238 | 10239 | onEnvModeChange={onEnvModeChange} |
| 10239 | 10240 | startFromOrigin={startFromOrigin} |
| 10240 | 10241 | onStartFromOriginChange={onStartFromOriginChange} |
| 10241 | {...(canOverrideServerThreadEnvMode | |
| 10242 | ? { effectiveEnvModeOverride: envMode } | |
| 10243 | : {})} | |
| 10242 | envMode={envMode} | |
| 10244 | 10243 | {...(canOverrideServerThreadEnvMode |
| 10245 | 10244 | ? { |
| 10246 | 10245 | activeThreadBranchOverride: activeThreadBranch, |