| author | |
| committer | |
| log | 7b3cdc6a8c69224aa2314e4969579c3e82b6b0ba |
| tree | 910f43d10169c6a825e02519324b61beaafefc1e |
| parent | 386eb18a587b0a791991342b0c6bc49b3e73d3d5 |
| signature | Signed by PGP key B5690EEEBB952194 |
7 files changed, 241 insertions(+), 60 deletions(-)
apps/web/src/components/BranchToolbar.logic.test.ts+82-1| ... | ... | @@ -1,14 +1,21 @@ |
| 1 | import type { GitBranch } from "@t3tools/contracts"; | |
| 1 | import { EnvironmentId, type GitBranch } from "@t3tools/contracts"; | |
| 2 | 2 | import { describe, expect, it } from "vitest"; |
| 3 | 3 | import { |
| 4 | 4 | dedupeRemoteBranchesWithLocalMatches, |
| 5 | 5 | deriveLocalBranchNameFromRemoteRef, |
| 6 | resolveEnvironmentOptionLabel, | |
| 6 | 7 | resolveBranchSelectionTarget, |
| 8 | resolveCurrentWorkspaceLabel, | |
| 7 | 9 | resolveDraftEnvModeAfterBranchChange, |
| 10 | resolveEffectiveEnvMode, | |
| 11 | resolveEnvModeLabel, | |
| 8 | 12 | resolveBranchToolbarValue, |
| 9 | 13 | shouldIncludeBranchPickerItem, |
| 10 | 14 | } from "./BranchToolbar.logic"; |
| 11 | 15 | |
| 16 | const localEnvironmentId = EnvironmentId.makeUnsafe("environment-local"); | |
| 17 | const remoteEnvironmentId = EnvironmentId.makeUnsafe("environment-remote"); | |
| 18 | ||
| 12 | 19 | describe("resolveDraftEnvModeAfterBranchChange", () => { |
| 13 | 20 | it("switches to local mode when returning from an existing worktree to the main worktree", () => { |
| 14 | 21 | expect( |
| ... | ... | @@ -76,6 +83,80 @@ describe("resolveBranchToolbarValue", () => { |
| 76 | 83 | }); |
| 77 | 84 | }); |
| 78 | 85 | |
| 86 | describe("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 | ||
| 121 | describe("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 | ||
| 143 | describe("resolveEnvModeLabel", () => { | |
| 144 | it("uses explicit workspace labels", () => { | |
| 145 | expect(resolveEnvModeLabel("local")).toBe("Current checkout"); | |
| 146 | expect(resolveEnvModeLabel("worktree")).toBe("New worktree"); | |
| 147 | }); | |
| 148 | }); | |
| 149 | ||
| 150 | describe("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 | ||
| 79 | 160 | describe("deriveLocalBranchNameFromRemoteRef", () => { |
| 80 | 161 | it("strips the remote prefix from a remote ref", () => { |
| 81 | 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 | export const EnvMode = Schema.Literals(["local", "worktree"]); |
| 16 | 16 | export type EnvMode = typeof EnvMode.Type; |
| 17 | 17 | |
| 18 | const GENERIC_LOCAL_ENVIRONMENT_LABELS = new Set(["local", "local environment"]); | |
| 19 | ||
| 20 | function normalizeDisplayLabel(value: string | null | undefined): string | null { | |
| 21 | const trimmed = value?.trim(); | |
| 22 | return trimmed && trimmed.length > 0 ? trimmed : null; | |
| 23 | } | |
| 24 | ||
| 25 | export 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 | ||
| 45 | export function resolveEnvModeLabel(mode: EnvMode): string { | |
| 46 | return mode === "worktree" ? "New worktree" : "Current checkout"; | |
| 47 | } | |
| 48 | ||
| 49 | export function resolveCurrentWorkspaceLabel(activeWorktreePath: string | null): string { | |
| 50 | return activeWorktreePath ? "Current worktree" : resolveEnvModeLabel("local"); | |
| 51 | } | |
| 52 | ||
| 18 | 53 | export function resolveEffectiveEnvMode(input: { |
| 19 | 54 | activeWorktreePath: string | null; |
| 20 | 55 | hasServerThread: boolean; |
| 21 | 56 | draftThreadEnvMode: EnvMode | undefined; |
| 22 | 57 | }): EnvMode { |
| 23 | 58 | const { activeWorktreePath, hasServerThread, draftThreadEnvMode } = input; |
| 24 | return activeWorktreePath || (!hasServerThread && draftThreadEnvMode === "worktree") | |
| 25 | ? "worktree" | |
| 26 | : "local"; | |
| 59 | if (!hasServerThread) { | |
| 60 | if (activeWorktreePath) { | |
| 61 | return "local"; | |
| 62 | } | |
| 63 | return draftThreadEnvMode === "worktree" ? "worktree" : "local"; | |
| 64 | } | |
| 65 | return activeWorktreePath ? "worktree" : "local"; | |
| 27 | 66 | } |
| 28 | 67 | |
| 29 | 68 | export function resolveDraftEnvModeAfterBranchChange(input: { |
apps/web/src/components/BranchToolbar.tsx+2-1| ... | ... | @@ -62,6 +62,7 @@ export function BranchToolbar({ |
| 62 | 62 | hasServerThread: serverThread !== undefined, |
| 63 | 63 | draftThreadEnvMode: draftThread?.envMode, |
| 64 | 64 | }); |
| 65 | const envModeLocked = envLocked || (serverThread !== undefined && activeWorktreePath !== null); | |
| 65 | 66 | |
| 66 | 67 | const showEnvironmentPicker = |
| 67 | 68 | availableEnvironments && availableEnvironments.length > 1 && onEnvironmentChange; |
| ... | ... | @@ -83,7 +84,7 @@ export function BranchToolbar({ |
| 83 | 84 | </> |
| 84 | 85 | )} |
| 85 | 86 | <BranchToolbarEnvModeSelector |
| 86 | envLocked={envLocked} | |
| 87 | envLocked={envModeLocked} | |
| 87 | 88 | effectiveEnvMode={effectiveEnvMode} |
| 88 | 89 | activeWorktreePath={activeWorktreePath} |
| 89 | 90 | onEnvModeChange={onEnvModeChange} |
apps/web/src/components/BranchToolbarEnvModeSelector.tsx+51-27| ... | ... | @@ -1,13 +1,20 @@ |
| 1 | import { FolderIcon, GitForkIcon } from "lucide-react"; | |
| 2 | import { memo } from "react"; | |
| 1 | import { FolderGit2Icon, FolderGitIcon, FolderIcon } from "lucide-react"; | |
| 2 | import { memo, useMemo } from "react"; | |
| 3 | 3 | |
| 4 | import type { EnvMode } from "./BranchToolbar.logic"; | |
| 5 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; | |
| 6 | ||
| 7 | const envModeItems = [ | |
| 8 | { value: "local", label: "Local" }, | |
| 9 | { value: "worktree", label: "New worktree" }, | |
| 10 | ] as const; | |
| 4 | import { | |
| 5 | resolveCurrentWorkspaceLabel, | |
| 6 | resolveEnvModeLabel, | |
| 7 | type EnvMode, | |
| 8 | } from "./BranchToolbar.logic"; | |
| 9 | import { | |
| 10 | Select, | |
| 11 | SelectGroup, | |
| 12 | SelectGroupLabel, | |
| 13 | SelectItem, | |
| 14 | SelectPopup, | |
| 15 | SelectTrigger, | |
| 16 | SelectValue, | |
| 17 | } from "./ui/select"; | |
| 11 | 18 | |
| 12 | 19 | interface BranchToolbarEnvModeSelectorProps { |
| 13 | 20 | envLocked: boolean; |
| ... | ... | @@ -22,18 +29,26 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe |
| 22 | 29 | activeWorktreePath, |
| 23 | 30 | onEnvModeChange, |
| 24 | 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 | 41 | return ( |
| 27 | 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 | 43 | {activeWorktreePath ? ( |
| 29 | 44 | <> |
| 30 | <GitForkIcon className="size-3" /> | |
| 31 | Worktree | |
| 45 | <FolderGitIcon className="size-3" /> | |
| 46 | {resolveCurrentWorkspaceLabel(activeWorktreePath)} | |
| 32 | 47 | </> |
| 33 | 48 | ) : ( |
| 34 | 49 | <> |
| 35 | 50 | <FolderIcon className="size-3" /> |
| 36 | Local | |
| 51 | {resolveCurrentWorkspaceLabel(activeWorktreePath)} | |
| 37 | 52 | </> |
| 38 | 53 | )} |
| 39 | 54 | </span> |
| ... | ... | @@ -46,27 +61,36 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe |
| 46 | 61 | onValueChange={(value) => onEnvModeChange(value as EnvMode)} |
| 47 | 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 | 65 | {effectiveEnvMode === "worktree" ? ( |
| 51 | <GitForkIcon className="size-3" /> | |
| 66 | <FolderGit2Icon className="size-3" /> | |
| 67 | ) : activeWorktreePath ? ( | |
| 68 | <FolderGitIcon className="size-3" /> | |
| 52 | 69 | ) : ( |
| 53 | 70 | <FolderIcon className="size-3" /> |
| 54 | 71 | )} |
| 55 | 72 | <SelectValue /> |
| 56 | 73 | </SelectTrigger> |
| 57 | 74 | <SelectPopup> |
| 58 | <SelectItem value="local"> | |
| 59 | <span className="inline-flex items-center gap-1.5"> | |
| 60 | <FolderIcon className="size-3" /> | |
| 61 | Local | |
| 62 | </span> | |
| 63 | </SelectItem> | |
| 64 | <SelectItem value="worktree"> | |
| 65 | <span className="inline-flex items-center gap-1.5"> | |
| 66 | <GitForkIcon className="size-3" /> | |
| 67 | New worktree | |
| 68 | </span> | |
| 69 | </SelectItem> | |
| 75 | <SelectGroup> | |
| 76 | <SelectGroupLabel>Workspace</SelectGroupLabel> | |
| 77 | <SelectItem value="local"> | |
| 78 | <span className="inline-flex items-center gap-1.5"> | |
| 79 | {activeWorktreePath ? ( | |
| 80 | <FolderGitIcon className="size-3" /> | |
| 81 | ) : ( | |
| 82 | <FolderIcon className="size-3" /> | |
| 83 | )} | |
| 84 | {resolveCurrentWorkspaceLabel(activeWorktreePath)} | |
| 85 | </span> | |
| 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 | 94 | </SelectPopup> |
| 71 | 95 | </Select> |
| 72 | 96 | ); |
apps/web/src/components/BranchToolbarEnvironmentSelector.tsx+39-16| ... | ... | @@ -1,9 +1,17 @@ |
| 1 | 1 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 2 | import { CloudIcon, FolderIcon, ServerIcon } from "lucide-react"; | |
| 2 | import { CloudIcon, MonitorIcon } from "lucide-react"; | |
| 3 | 3 | import { memo, useMemo } from "react"; |
| 4 | 4 | |
| 5 | 5 | import type { EnvironmentOption } from "./BranchToolbar.logic"; |
| 6 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; | |
| 6 | import { | |
| 7 | Select, | |
| 8 | SelectGroup, | |
| 9 | SelectGroupLabel, | |
| 10 | SelectItem, | |
| 11 | SelectPopup, | |
| 12 | SelectTrigger, | |
| 13 | SelectValue, | |
| 14 | } from "./ui/select"; | |
| 7 | 15 | |
| 8 | 16 | interface BranchToolbarEnvironmentSelectorProps { |
| 9 | 17 | envLocked: boolean; |
| ... | ... | @@ -18,8 +26,8 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir |
| 18 | 26 | availableEnvironments, |
| 19 | 27 | onEnvironmentChange, |
| 20 | 28 | }: BranchToolbarEnvironmentSelectorProps) { |
| 21 | const activeEnvironmentLabel = useMemo(() => { | |
| 22 | return availableEnvironments.find((env) => env.environmentId === environmentId)?.label ?? null; | |
| 29 | const activeEnvironment = useMemo(() => { | |
| 30 | return availableEnvironments.find((env) => env.environmentId === environmentId) ?? null; | |
| 23 | 31 | }, [availableEnvironments, environmentId]); |
| 24 | 32 | |
| 25 | 33 | const environmentItems = useMemo( |
| ... | ... | @@ -34,8 +42,12 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir |
| 34 | 42 | if (envLocked) { |
| 35 | 43 | return ( |
| 36 | 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" /> | |
| 38 | {activeEnvironmentLabel ?? "Environment"} | |
| 45 | {activeEnvironment?.isPrimary ? ( | |
| 46 | <MonitorIcon className="size-3" /> | |
| 47 | ) : ( | |
| 48 | <CloudIcon className="size-3" /> | |
| 49 | )} | |
| 50 | {activeEnvironment?.label ?? "Run on"} | |
| 39 | 51 | </span> |
| 40 | 52 | ); |
| 41 | 53 | } |
| ... | ... | @@ -46,19 +58,30 @@ export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvir |
| 46 | 58 | onValueChange={(value) => onEnvironmentChange(value as EnvironmentId)} |
| 47 | 59 | items={environmentItems} |
| 48 | 60 | > |
| 49 | <SelectTrigger variant="ghost" size="xs" className="font-medium"> | |
| 50 | <ServerIcon className="size-3" /> | |
| 61 | <SelectTrigger variant="ghost" size="xs" className="font-medium" aria-label="Run on"> | |
| 62 | {activeEnvironment?.isPrimary ? ( | |
| 63 | <MonitorIcon className="size-3" /> | |
| 64 | ) : ( | |
| 65 | <CloudIcon className="size-3" /> | |
| 66 | )} | |
| 51 | 67 | <SelectValue /> |
| 52 | 68 | </SelectTrigger> |
| 53 | 69 | <SelectPopup> |
| 54 | {availableEnvironments.map((env) => ( | |
| 55 | <SelectItem key={env.environmentId} value={env.environmentId}> | |
| 56 | <span className="inline-flex items-center gap-1.5"> | |
| 57 | {env.isPrimary ? <FolderIcon className="size-3" /> : <CloudIcon className="size-3" />} | |
| 58 | {env.label} | |
| 59 | </span> | |
| 60 | </SelectItem> | |
| 61 | ))} | |
| 70 | <SelectGroup> | |
| 71 | <SelectGroupLabel>Run on</SelectGroupLabel> | |
| 72 | {availableEnvironments.map((env) => ( | |
| 73 | <SelectItem key={env.environmentId} value={env.environmentId}> | |
| 74 | <span className="inline-flex items-center gap-1.5"> | |
| 75 | {env.isPrimary ? ( | |
| 76 | <MonitorIcon className="size-3" /> | |
| 77 | ) : ( | |
| 78 | <CloudIcon className="size-3" /> | |
| 79 | )} | |
| 80 | {env.label} | |
| 81 | </span> | |
| 82 | </SelectItem> | |
| 83 | ))} | |
| 84 | </SelectGroup> | |
| 62 | 85 | </SelectPopup> |
| 63 | 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 | 1993 | }); |
| 1994 | 1994 | |
| 1995 | 1995 | const mounted = await mountChatView({ |
| 1996 | viewport: DEFAULT_VIEWPORT, | |
| 1996 | viewport: WIDE_FOOTER_VIEWPORT, | |
| 1997 | 1997 | snapshot: withProjectScripts(createDraftOnlySnapshot(), [ |
| 1998 | 1998 | { |
| 1999 | 1999 | id: "setup", |
apps/web/src/components/ChatView.tsx+24-11| ... | ... | @@ -187,6 +187,7 @@ import { ComposerPrimaryActions } from "./chat/ComposerPrimaryActions"; |
| 187 | 187 | import { ComposerPendingApprovalPanel } from "./chat/ComposerPendingApprovalPanel"; |
| 188 | 188 | import { ComposerPendingUserInputPanel } from "./chat/ComposerPendingUserInputPanel"; |
| 189 | 189 | import { ComposerPlanFollowUpBanner } from "./chat/ComposerPlanFollowUpBanner"; |
| 190 | import { resolveEffectiveEnvMode, resolveEnvironmentOptionLabel } from "./BranchToolbar.logic"; | |
| 190 | 191 | import { |
| 191 | 192 | getComposerProviderState, |
| 192 | 193 | renderProviderTraitsMenuContent, |
| ... | ... | @@ -1065,9 +1066,12 @@ export default function ChatView(props: ChatViewProps) { |
| 1065 | 1066 | const isPrimary = p.environmentId === primaryEnvironmentId; |
| 1066 | 1067 | const savedRecord = savedEnvironmentRegistry[p.environmentId]; |
| 1067 | 1068 | const runtimeState = savedEnvironmentRuntimeById[p.environmentId]; |
| 1068 | const label = isPrimary | |
| 1069 | ? "Local" | |
| 1070 | : (runtimeState?.descriptor?.label ?? savedRecord?.label ?? p.environmentId); | |
| 1069 | const label = resolveEnvironmentOptionLabel({ | |
| 1070 | isPrimary, | |
| 1071 | environmentId: p.environmentId, | |
| 1072 | runtimeLabel: runtimeState?.descriptor?.label ?? null, | |
| 1073 | savedLabel: savedRecord?.label ?? null, | |
| 1074 | }); | |
| 1071 | 1075 | envs.push({ |
| 1072 | 1076 | environmentId: p.environmentId, |
| 1073 | 1077 | projectId: p.id, |
| ... | ... | @@ -2890,12 +2894,12 @@ export default function ChatView(props: ChatViewProps) { |
| 2890 | 2894 | return () => window.removeEventListener("keydown", onKeyDown); |
| 2891 | 2895 | }, [closeExpandedImage, expandedImage, navigateExpandedImage]); |
| 2892 | 2896 | |
| 2893 | const activeWorktreePath = activeThread?.worktreePath; | |
| 2894 | const envMode: DraftThreadEnvMode = activeWorktreePath | |
| 2895 | ? "worktree" | |
| 2896 | : isLocalDraftThread | |
| 2897 | ? (draftThread?.envMode ?? "local") | |
| 2898 | : "local"; | |
| 2897 | const activeWorktreePath = activeThread?.worktreePath ?? null; | |
| 2898 | const envMode: DraftThreadEnvMode = resolveEffectiveEnvMode({ | |
| 2899 | activeWorktreePath, | |
| 2900 | hasServerThread: isServerThread, | |
| 2901 | draftThreadEnvMode: isLocalDraftThread ? draftThread?.envMode : undefined, | |
| 2902 | }); | |
| 2899 | 2903 | |
| 2900 | 2904 | useEffect(() => { |
| 2901 | 2905 | if (!activeThreadId) { |
| ... | ... | @@ -4031,11 +4035,20 @@ export default function ChatView(props: ChatViewProps) { |
| 4031 | 4035 | const onEnvModeChange = useCallback( |
| 4032 | 4036 | (mode: DraftThreadEnvMode) => { |
| 4033 | 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 | 4043 | scheduleComposerFocus(); |
| 4037 | 4044 | }, |
| 4038 | [composerDraftTarget, isLocalDraftThread, scheduleComposerFocus, setDraftThreadContext], | |
| 4045 | [ | |
| 4046 | composerDraftTarget, | |
| 4047 | draftThread?.worktreePath, | |
| 4048 | isLocalDraftThread, | |
| 4049 | scheduleComposerFocus, | |
| 4050 | setDraftThreadContext, | |
| 4051 | ], | |
| 4039 | 4052 | ); |
| 4040 | 4053 | |
| 4041 | 4054 | const applyPromptReplacement = useCallback( |