authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-04-09 12:51:17-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-09 12:51:17-07:00
log7b3cdc6a8c69224aa2314e4969579c3e82b6b0ba
tree910f43d10169c6a825e02519324b61beaafefc1e
parent386eb18a587b0a791991342b0c6bc49b3e73d3d5
signature Signed by PGP key B5690EEEBB952194

Clarify environment and workspace picker labels (#1854)


7 files changed, 241 insertions(+), 60 deletions(-)

apps/web/src/components/BranchToolbar.logic.test.ts+82-1
...@@ -1,14 +1,21 @@...@@ -1,14 +1,21 @@
1import type { GitBranch } from "@t3tools/contracts";1import { EnvironmentId, type GitBranch } from "@t3tools/contracts";
2import { describe, expect, it } from "vitest";2import { describe, expect, it } from "vitest";
3import {3import {
4 dedupeRemoteBranchesWithLocalMatches,4 dedupeRemoteBranchesWithLocalMatches,
5 deriveLocalBranchNameFromRemoteRef,5 deriveLocalBranchNameFromRemoteRef,
6 resolveEnvironmentOptionLabel,
6 resolveBranchSelectionTarget,7 resolveBranchSelectionTarget,
8 resolveCurrentWorkspaceLabel,
7 resolveDraftEnvModeAfterBranchChange,9 resolveDraftEnvModeAfterBranchChange,
10 resolveEffectiveEnvMode,
11 resolveEnvModeLabel,
8 resolveBranchToolbarValue,12 resolveBranchToolbarValue,
9 shouldIncludeBranchPickerItem,13 shouldIncludeBranchPickerItem,
10} from "./BranchToolbar.logic";14} from "./BranchToolbar.logic";
1115
16const localEnvironmentId = EnvironmentId.makeUnsafe("environment-local");
17const remoteEnvironmentId = EnvironmentId.makeUnsafe("environment-remote");
18
12describe("resolveDraftEnvModeAfterBranchChange", () => {19describe("resolveDraftEnvModeAfterBranchChange", () => {
13 it("switches to local mode when returning from an existing worktree to the main worktree", () => {20 it("switches to local mode when returning from an existing worktree to the main worktree", () => {
14 expect(21 expect(
...@@ -76,6 +83,80 @@ describe("resolveBranchToolbarValue", () => {...@@ -76,6 +83,80 @@ describe("resolveBranchToolbarValue", () => {
76 });83 });
77});84});
7885
86describe("resolveEnvironmentOptionLabel", () => {
87 it("prefers the primary environment's machine label", () => {
88 expect(
89 resolveEnvironmentOptionLabel({
90 isPrimary: true,
91 environmentId: localEnvironmentId,
92 runtimeLabel: "Julius's Mac mini",
93 savedLabel: "Local environment",
94 }),
95 ).toBe("Julius's Mac mini");
96 });
97
98 it("falls back to 'This device' for generic primary labels", () => {
99 expect(
100 resolveEnvironmentOptionLabel({
101 isPrimary: true,
102 environmentId: localEnvironmentId,
103 runtimeLabel: "Local environment",
104 savedLabel: "Local",
105 }),
106 ).toBe("This device");
107 });
108
109 it("keeps configured labels for non-primary environments", () => {
110 expect(
111 resolveEnvironmentOptionLabel({
112 isPrimary: false,
113 environmentId: remoteEnvironmentId,
114 runtimeLabel: null,
115 savedLabel: "Build box",
116 }),
117 ).toBe("Build box");
118 });
119});
120
121describe("resolveEffectiveEnvMode", () => {
122 it("treats draft threads already attached to a worktree as current-checkout mode", () => {
123 expect(
124 resolveEffectiveEnvMode({
125 activeWorktreePath: "/repo/.t3/worktrees/feature-a",
126 hasServerThread: false,
127 draftThreadEnvMode: "worktree",
128 }),
129 ).toBe("local");
130 });
131
132 it("keeps explicit new-worktree mode for draft threads without a worktree path", () => {
133 expect(
134 resolveEffectiveEnvMode({
135 activeWorktreePath: null,
136 hasServerThread: false,
137 draftThreadEnvMode: "worktree",
138 }),
139 ).toBe("worktree");
140 });
141});
142
143describe("resolveEnvModeLabel", () => {
144 it("uses explicit workspace labels", () => {
145 expect(resolveEnvModeLabel("local")).toBe("Current checkout");
146 expect(resolveEnvModeLabel("worktree")).toBe("New worktree");
147 });
148});
149
150describe("resolveCurrentWorkspaceLabel", () => {
151 it("describes the main repo checkout when no worktree path is active", () => {
152 expect(resolveCurrentWorkspaceLabel(null)).toBe("Current checkout");
153 });
154
155 it("describes the active checkout as a worktree when one is attached", () => {
156 expect(resolveCurrentWorkspaceLabel("/repo/.t3/worktrees/feature-a")).toBe("Current worktree");
157 });
158});
159
79describe("deriveLocalBranchNameFromRemoteRef", () => {160describe("deriveLocalBranchNameFromRemoteRef", () => {
80 it("strips the remote prefix from a remote ref", () => {161 it("strips the remote prefix from a remote ref", () => {
81 expect(deriveLocalBranchNameFromRemoteRef("origin/feature/demo")).toBe("feature/demo");162 expect(deriveLocalBranchNameFromRemoteRef("origin/feature/demo")).toBe("feature/demo");
apps/web/src/components/BranchToolbar.logic.ts+42-3
...@@ -15,15 +15,54 @@ export interface EnvironmentOption {...@@ -15,15 +15,54 @@ export interface EnvironmentOption {
15export const EnvMode = Schema.Literals(["local", "worktree"]);15export const EnvMode = Schema.Literals(["local", "worktree"]);
16export type EnvMode = typeof EnvMode.Type;16export type EnvMode = typeof EnvMode.Type;
1717
18const GENERIC_LOCAL_ENVIRONMENT_LABELS = new Set(["local", "local environment"]);
19
20function normalizeDisplayLabel(value: string | null | undefined): string | null {
21 const trimmed = value?.trim();
22 return trimmed && trimmed.length > 0 ? trimmed : null;
23}
24
25export function resolveEnvironmentOptionLabel(input: {
26 isPrimary: boolean;
27 environmentId: EnvironmentId;
28 runtimeLabel?: string | null;
29 savedLabel?: string | null;
30}): string {
31 const runtimeLabel = normalizeDisplayLabel(input.runtimeLabel);
32 const savedLabel = normalizeDisplayLabel(input.savedLabel);
33
34 if (input.isPrimary) {
35 const preferredLocalLabel = [runtimeLabel, savedLabel].find((label) => {
36 if (!label) return false;
37 return !GENERIC_LOCAL_ENVIRONMENT_LABELS.has(label.toLowerCase());
38 });
39 return preferredLocalLabel ?? "This device";
40 }
41
42 return runtimeLabel ?? savedLabel ?? input.environmentId;
43}
44
45export function resolveEnvModeLabel(mode: EnvMode): string {
46 return mode === "worktree" ? "New worktree" : "Current checkout";
47}
48
49export function resolveCurrentWorkspaceLabel(activeWorktreePath: string | null): string {
50 return activeWorktreePath ? "Current worktree" : resolveEnvModeLabel("local");
51}
52
18export function resolveEffectiveEnvMode(input: {53export function resolveEffectiveEnvMode(input: {
19 activeWorktreePath: string | null;54 activeWorktreePath: string | null;
20 hasServerThread: boolean;55 hasServerThread: boolean;
21 draftThreadEnvMode: EnvMode | undefined;56 draftThreadEnvMode: EnvMode | undefined;
22}): EnvMode {57}): EnvMode {
23 const { activeWorktreePath, hasServerThread, draftThreadEnvMode } = input;58 const { activeWorktreePath, hasServerThread, draftThreadEnvMode } = input;
24 return activeWorktreePath || (!hasServerThread && draftThreadEnvMode === "worktree")59 if (!hasServerThread) {
25 ? "worktree"60 if (activeWorktreePath) {
26 : "local";61 return "local";
62 }
63 return draftThreadEnvMode === "worktree" ? "worktree" : "local";
64 }
65 return activeWorktreePath ? "worktree" : "local";
27}66}
2867
29export function resolveDraftEnvModeAfterBranchChange(input: {68export function resolveDraftEnvModeAfterBranchChange(input: {
apps/web/src/components/BranchToolbar.tsx+2-1
...@@ -62,6 +62,7 @@ export function BranchToolbar({...@@ -62,6 +62,7 @@ export function BranchToolbar({
62 hasServerThread: serverThread !== undefined,62 hasServerThread: serverThread !== undefined,
63 draftThreadEnvMode: draftThread?.envMode,63 draftThreadEnvMode: draftThread?.envMode,
64 });64 });
65 const envModeLocked = envLocked || (serverThread !== undefined && activeWorktreePath !== null);
6566
66 const showEnvironmentPicker =67 const showEnvironmentPicker =
67 availableEnvironments && availableEnvironments.length > 1 && onEnvironmentChange;68 availableEnvironments && availableEnvironments.length > 1 && onEnvironmentChange;
...@@ -83,7 +84,7 @@ export function BranchToolbar({...@@ -83,7 +84,7 @@ export function BranchToolbar({
83 </>84 </>
84 )}85 )}
85 <BranchToolbarEnvModeSelector86 <BranchToolbarEnvModeSelector
86 envLocked={envLocked}87 envLocked={envModeLocked}
87 effectiveEnvMode={effectiveEnvMode}88 effectiveEnvMode={effectiveEnvMode}
88 activeWorktreePath={activeWorktreePath}89 activeWorktreePath={activeWorktreePath}
89 onEnvModeChange={onEnvModeChange}90 onEnvModeChange={onEnvModeChange}
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+51-27
...@@ -1,13 +1,20 @@...@@ -1,13 +1,20 @@
1import { FolderIcon, GitForkIcon } from "lucide-react";1import { FolderGit2Icon, FolderGitIcon, FolderIcon } from "lucide-react";
2import { memo } from "react";2import { memo, useMemo } from "react";
33
4import type { EnvMode } from "./BranchToolbar.logic";4import {
5import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";5 resolveCurrentWorkspaceLabel,
66 resolveEnvModeLabel,
7const envModeItems = [7 type EnvMode,
8 { value: "local", label: "Local" },8} from "./BranchToolbar.logic";
9 { value: "worktree", label: "New worktree" },9import {
10] as const;10 Select,
11 SelectGroup,
12 SelectGroupLabel,
13 SelectItem,
14 SelectPopup,
15 SelectTrigger,
16 SelectValue,
17} from "./ui/select";
1118
12interface BranchToolbarEnvModeSelectorProps {19interface BranchToolbarEnvModeSelectorProps {
13 envLocked: boolean;20 envLocked: boolean;
...@@ -22,18 +29,26 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe...@@ -22,18 +29,26 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
22 activeWorktreePath,29 activeWorktreePath,
23 onEnvModeChange,30 onEnvModeChange,
24}: BranchToolbarEnvModeSelectorProps) {31}: BranchToolbarEnvModeSelectorProps) {
25 if (envLocked || activeWorktreePath) {32 const envModeItems = useMemo(
33 () => [
34 { value: "local", label: resolveCurrentWorkspaceLabel(activeWorktreePath) },
35 { value: "worktree", label: resolveEnvModeLabel("worktree") },
36 ],
37 [activeWorktreePath],
38 );
39
40 if (envLocked) {
26 return (41 return (
27 <span className="inline-flex items-center gap-1 border border-transparent px-[calc(--spacing(3)-1px)] text-sm font-medium text-muted-foreground/70 sm:text-xs">42 <span className="inline-flex items-center gap-1 border border-transparent px-[calc(--spacing(3)-1px)] text-sm font-medium text-muted-foreground/70 sm:text-xs">
28 {activeWorktreePath ? (43 {activeWorktreePath ? (
29 <>44 <>
30 <GitForkIcon className="size-3" />45 <FolderGitIcon className="size-3" />
31 Worktree46 {resolveCurrentWorkspaceLabel(activeWorktreePath)}
32 </>47 </>
33 ) : (48 ) : (
34 <>49 <>
35 <FolderIcon className="size-3" />50 <FolderIcon className="size-3" />
36 Local51 {resolveCurrentWorkspaceLabel(activeWorktreePath)}
37 </>52 </>
38 )}53 )}
39 </span>54 </span>
...@@ -46,27 +61,36 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe...@@ -46,27 +61,36 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
46 onValueChange={(value) => onEnvModeChange(value as EnvMode)}61 onValueChange={(value) => onEnvModeChange(value as EnvMode)}
47 items={envModeItems}62 items={envModeItems}
48 >63 >
49 <SelectTrigger variant="ghost" size="xs" className="font-medium">64 <SelectTrigger variant="ghost" size="xs" className="font-medium" aria-label="Workspace">
50 {effectiveEnvMode === "worktree" ? (65 {effectiveEnvMode === "worktree" ? (
51 <GitForkIcon className="size-3" />66 <FolderGit2Icon className="size-3" />
67 ) : activeWorktreePath ? (
68 <FolderGitIcon className="size-3" />
52 ) : (69 ) : (
53 <FolderIcon className="size-3" />70 <FolderIcon className="size-3" />
54 )}71 )}
55 <SelectValue />72 <SelectValue />
56 </SelectTrigger>73 </SelectTrigger>
57 <SelectPopup>74 <SelectPopup>
58 <SelectItem value="local">75 <SelectGroup>
59 <span className="inline-flex items-center gap-1.5">76 <SelectGroupLabel>Workspace</SelectGroupLabel>
60 <FolderIcon className="size-3" />77 <SelectItem value="local">
61 Local78 <span className="inline-flex items-center gap-1.5">
62 </span>79 {activeWorktreePath ? (
63 </SelectItem>80 <FolderGitIcon className="size-3" />
64 <SelectItem value="worktree">81 ) : (
65 <span className="inline-flex items-center gap-1.5">82 <FolderIcon className="size-3" />
66 <GitForkIcon className="size-3" />83 )}
67 New worktree84 {resolveCurrentWorkspaceLabel(activeWorktreePath)}
68 </span>85 </span>
69 </SelectItem>86 </SelectItem>
87 <SelectItem value="worktree">
88 <span className="inline-flex items-center gap-1.5">
89 <FolderGit2Icon className="size-3" />
90 {resolveEnvModeLabel("worktree")}
91 </span>
92 </SelectItem>
93 </SelectGroup>
70 </SelectPopup>94 </SelectPopup>
71 </Select>95 </Select>
72 );96 );
apps/web/src/components/BranchToolbarEnvironmentSelector.tsx+39-16
...@@ -1,9 +1,17 @@...@@ -1,9 +1,17 @@
1import type { EnvironmentId } from "@t3tools/contracts";1import type { EnvironmentId } from "@t3tools/contracts";
2import { CloudIcon, FolderIcon, ServerIcon } from "lucide-react";2import { CloudIcon, MonitorIcon } from "lucide-react";
3import { memo, useMemo } from "react";3import { memo, useMemo } from "react";
44
5import type { EnvironmentOption } from "./BranchToolbar.logic";5import type { EnvironmentOption } from "./BranchToolbar.logic";
6import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select";6import {
7 Select,
8 SelectGroup,
9 SelectGroupLabel,
10 SelectItem,
11 SelectPopup,
12 SelectTrigger,
13 SelectValue,
14} from "./ui/select";
715
8interface BranchToolbarEnvironmentSelectorProps {16interface BranchToolbarEnvironmentSelectorProps {
9 envLocked: boolean;17 envLocked: boolean;
...@@ -18,8 +26,8 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir...@@ -18,8 +26,8 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
18 availableEnvironments,26 availableEnvironments,
19 onEnvironmentChange,27 onEnvironmentChange,
20}: BranchToolbarEnvironmentSelectorProps) {28}: BranchToolbarEnvironmentSelectorProps) {
21 const activeEnvironmentLabel = useMemo(() => {29 const activeEnvironment = useMemo(() => {
22 return availableEnvironments.find((env) => env.environmentId === environmentId)?.label ?? null;30 return availableEnvironments.find((env) => env.environmentId === environmentId) ?? null;
23 }, [availableEnvironments, environmentId]);31 }, [availableEnvironments, environmentId]);
2432
25 const environmentItems = useMemo(33 const environmentItems = useMemo(
...@@ -34,8 +42,12 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir...@@ -34,8 +42,12 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
34 if (envLocked) {42 if (envLocked) {
35 return (43 return (
36 <span className="inline-flex items-center gap-1 border border-transparent px-[calc(--spacing(3)-1px)] text-sm font-medium text-muted-foreground/70 sm:text-xs">44 <span className="inline-flex items-center gap-1 border border-transparent px-[calc(--spacing(3)-1px)] text-sm font-medium text-muted-foreground/70 sm:text-xs">
37 <ServerIcon className="size-3" />45 {activeEnvironment?.isPrimary ? (
38 {activeEnvironmentLabel ?? "Environment"}46 <MonitorIcon className="size-3" />
47 ) : (
48 <CloudIcon className="size-3" />
49 )}
50 {activeEnvironment?.label ?? "Run on"}
39 </span>51 </span>
40 );52 );
41 }53 }
...@@ -46,19 +58,30 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir...@@ -46,19 +58,30 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir
46 onValueChange={(value) => onEnvironmentChange(value as EnvironmentId)}58 onValueChange={(value) => onEnvironmentChange(value as EnvironmentId)}
47 items={environmentItems}59 items={environmentItems}
48 >60 >
49 <SelectTrigger variant="ghost" size="xs" className="font-medium">61 <SelectTrigger variant="ghost" size="xs" className="font-medium" aria-label="Run on">
50 <ServerIcon className="size-3" />62 {activeEnvironment?.isPrimary ? (
63 <MonitorIcon className="size-3" />
64 ) : (
65 <CloudIcon className="size-3" />
66 )}
51 <SelectValue />67 <SelectValue />
52 </SelectTrigger>68 </SelectTrigger>
53 <SelectPopup>69 <SelectPopup>
54 {availableEnvironments.map((env) => (70 <SelectGroup>
55 <SelectItem key={env.environmentId} value={env.environmentId}>71 <SelectGroupLabel>Run on</SelectGroupLabel>
56 <span className="inline-flex items-center gap-1.5">72 {availableEnvironments.map((env) => (
57 {env.isPrimary ? <FolderIcon className="size-3" /> : <CloudIcon className="size-3" />}73 <SelectItem key={env.environmentId} value={env.environmentId}>
58 {env.label}74 <span className="inline-flex items-center gap-1.5">
59 </span>75 {env.isPrimary ? (
60 </SelectItem>76 <MonitorIcon className="size-3" />
61 ))}77 ) : (
78 <CloudIcon className="size-3" />
79 )}
80 {env.label}
81 </span>
82 </SelectItem>
83 ))}
84 </SelectGroup>
62 </SelectPopup>85 </SelectPopup>
63 </Select>86 </Select>
64 );87 );
apps/web/src/components/ChatView.browser.tsx+1-1
...@@ -1993,7 +1993,7 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -1993,7 +1993,7 @@ describe("ChatView timeline estimator parity (full app)", () => {
1993 });1993 });
19941994
1995 const mounted = await mountChatView({1995 const mounted = await mountChatView({
1996 viewport: DEFAULT_VIEWPORT,1996 viewport: WIDE_FOOTER_VIEWPORT,
1997 snapshot: withProjectScripts(createDraftOnlySnapshot(), [1997 snapshot: withProjectScripts(createDraftOnlySnapshot(), [
1998 {1998 {
1999 id: "setup",1999 id: "setup",
apps/web/src/components/ChatView.tsx+24-11
...@@ -187,6 +187,7 @@ import { ComposerPrimaryActions } from "./chat/ComposerPrimaryActions";...@@ -187,6 +187,7 @@ import { ComposerPrimaryActions } from "./chat/ComposerPrimaryActions";
187import { ComposerPendingApprovalPanel } from "./chat/ComposerPendingApprovalPanel";187import { ComposerPendingApprovalPanel } from "./chat/ComposerPendingApprovalPanel";
188import { ComposerPendingUserInputPanel } from "./chat/ComposerPendingUserInputPanel";188import { ComposerPendingUserInputPanel } from "./chat/ComposerPendingUserInputPanel";
189import { ComposerPlanFollowUpBanner } from "./chat/ComposerPlanFollowUpBanner";189import { ComposerPlanFollowUpBanner } from "./chat/ComposerPlanFollowUpBanner";
190import { resolveEffectiveEnvMode, resolveEnvironmentOptionLabel } from "./BranchToolbar.logic";
190import {191import {
191 getComposerProviderState,192 getComposerProviderState,
192 renderProviderTraitsMenuContent,193 renderProviderTraitsMenuContent,
...@@ -1065,9 +1066,12 @@ export default function ChatView(props: ChatViewProps) {...@@ -1065,9 +1066,12 @@ export default function ChatView(props: ChatViewProps) {
1065 const isPrimary = p.environmentId === primaryEnvironmentId;1066 const isPrimary = p.environmentId === primaryEnvironmentId;
1066 const savedRecord = savedEnvironmentRegistry[p.environmentId];1067 const savedRecord = savedEnvironmentRegistry[p.environmentId];
1067 const runtimeState = savedEnvironmentRuntimeById[p.environmentId];1068 const runtimeState = savedEnvironmentRuntimeById[p.environmentId];
1068 const label = isPrimary1069 const label = resolveEnvironmentOptionLabel({
1069 ? "Local"1070 isPrimary,
1070 : (runtimeState?.descriptor?.label ?? savedRecord?.label ?? p.environmentId);1071 environmentId: p.environmentId,
1072 runtimeLabel: runtimeState?.descriptor?.label ?? null,
1073 savedLabel: savedRecord?.label ?? null,
1074 });
1071 envs.push({1075 envs.push({
1072 environmentId: p.environmentId,1076 environmentId: p.environmentId,
1073 projectId: p.id,1077 projectId: p.id,
...@@ -2890,12 +2894,12 @@ export default function ChatView(props: ChatViewProps) {...@@ -2890,12 +2894,12 @@ export default function ChatView(props: ChatViewProps) {
2890 return () => window.removeEventListener("keydown", onKeyDown);2894 return () => window.removeEventListener("keydown", onKeyDown);
2891 }, [closeExpandedImage, expandedImage, navigateExpandedImage]);2895 }, [closeExpandedImage, expandedImage, navigateExpandedImage]);
28922896
2893 const activeWorktreePath = activeThread?.worktreePath;2897 const activeWorktreePath = activeThread?.worktreePath ?? null;
2894 const envMode: DraftThreadEnvMode = activeWorktreePath2898 const envMode: DraftThreadEnvMode = resolveEffectiveEnvMode({
2895 ? "worktree"2899 activeWorktreePath,
2896 : isLocalDraftThread2900 hasServerThread: isServerThread,
2897 ? (draftThread?.envMode ?? "local")2901 draftThreadEnvMode: isLocalDraftThread ? draftThread?.envMode : undefined,
2898 : "local";2902 });
28992903
2900 useEffect(() => {2904 useEffect(() => {
2901 if (!activeThreadId) {2905 if (!activeThreadId) {
...@@ -4031,11 +4035,20 @@ export default function ChatView(props: ChatViewProps) {...@@ -4031,11 +4035,20 @@ export default function ChatView(props: ChatViewProps) {
4031 const onEnvModeChange = useCallback(4035 const onEnvModeChange = useCallback(
4032 (mode: DraftThreadEnvMode) => {4036 (mode: DraftThreadEnvMode) => {
4033 if (isLocalDraftThread) {4037 if (isLocalDraftThread) {
4034 setDraftThreadContext(composerDraftTarget, { envMode: mode });4038 setDraftThreadContext(composerDraftTarget, {
4039 envMode: mode,
4040 ...(mode === "worktree" && draftThread?.worktreePath ? { worktreePath: null } : {}),
4041 });
4035 }4042 }
4036 scheduleComposerFocus();4043 scheduleComposerFocus();
4037 },4044 },
4038 [composerDraftTarget, isLocalDraftThread, scheduleComposerFocus, setDraftThreadContext],4045 [
4046 composerDraftTarget,
4047 draftThread?.worktreePath,
4048 isLocalDraftThread,
4049 scheduleComposerFocus,
4050 setDraftThreadContext,
4051 ],
4039 );4052 );
40404053
4041 const applyPromptReplacement = useCallback(4054 const applyPromptReplacement = useCallback(