Skip to content

Commit ee3e3ab

Browse files
AyushKaithwasclaude
andcommitted
fix: keep worktree drafts unchanged until a base ref is configured
Switching a fresh draft from Current checkout to New worktree only drops the synced checkout branch when a worktree base ref is configured, and the Last used hint only shows in Last used mode. Users who never set the option keep the behaviour they had before. Web also no longer marks a draft's environment as manually chosen on every mode switch, which turned off load balancing, and falls back to the current branch when the ref list fails to load. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent 55d2f11 commit ee3e3ab

7 files changed

Lines changed: 74 additions & 19 deletions

File tree

‎apps/mobile/src/features/threads/new-task-context-presentation.test.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,33 @@ describe("resolveNewTaskBranchAfterWorkspaceModeChange", () => {
6363
previousMode: "local",
6464
branch: "feature/current",
6565
branchSelection: "auto",
66+
configuredBaseRef: "develop",
6667
}),
6768
).toBeNull();
6869
});
6970

71+
it("keeps the checkout branch when no base ref is configured", () => {
72+
for (const configuredBaseRef of [null, undefined]) {
73+
expect(
74+
resolveNewTaskBranchAfterWorkspaceModeChange({
75+
mode: "worktree",
76+
previousMode: "local",
77+
branch: "feature/current",
78+
branchSelection: "auto",
79+
configuredBaseRef,
80+
}),
81+
).toBe("feature/current");
82+
}
83+
});
84+
7085
it("keeps an explicit branch choice even when it matches the checkout", () => {
7186
expect(
7287
resolveNewTaskBranchAfterWorkspaceModeChange({
7388
mode: "worktree",
7489
previousMode: "local",
7590
branch: "feature/current",
7691
branchSelection: "manual",
92+
configuredBaseRef: { mode: "last-used" },
7793
}),
7894
).toBe("feature/current");
7995
});
@@ -85,6 +101,7 @@ describe("resolveNewTaskBranchAfterWorkspaceModeChange", () => {
85101
previousMode: "local",
86102
branch: "feature/current",
87103
branchSelection: undefined,
104+
configuredBaseRef: "develop",
88105
}),
89106
).toBe("feature/current");
90107
});

‎apps/mobile/src/features/threads/new-task-context-presentation.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { WorktreeBaseRef } from "@t3tools/contracts";
12
import { sanitizeNewRefName } from "@t3tools/shared/git";
23

34
type WorkspaceMode = "local" | "worktree";
@@ -64,8 +65,11 @@ export function resolveNewTaskBranchAfterWorkspaceModeChange(input: {
6465
readonly previousMode: WorkspaceMode;
6566
readonly branch: string | null;
6667
readonly branchSelection: "auto" | "manual" | undefined;
68+
readonly configuredBaseRef: WorktreeBaseRef | undefined;
6769
}): string | null {
70+
// Without a configured base, keep the checkout branch exactly as before the setting existed.
6871
if (
72+
input.configuredBaseRef != null &&
6973
input.previousMode === "local" &&
7074
input.mode === "worktree" &&
7175
input.branchSelection === "auto"

‎apps/mobile/src/features/threads/new-task-flow-provider.tsx‎

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ type NewTaskFlowContextValue = {
172172
/** False for threads without a project: their folder has no branch or worktree. */
173173
readonly canChooseWorkspace: boolean;
174174
readonly selectedBranchName: string | null;
175+
/** Set only when the base ref setting is Last used. */
175176
readonly lastWorktreeBaseBranch: string | null;
176177
readonly selectedWorktreePath: string | null;
177178
readonly startFromOrigin: boolean;
@@ -945,31 +946,40 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
945946
} else {
946947
pendingLocalBranchSyncDraftKeysRef.current.delete(selectedProjectDraftKey);
947948
}
949+
const branch =
950+
mode === "local"
951+
? localSelection.branch
952+
: resolveNewTaskBranchAfterWorkspaceModeChange({
953+
mode,
954+
previousMode: workspaceMode,
955+
branch: selectedBranchName,
956+
branchSelection: selectedBranchSelection,
957+
configuredBaseRef,
958+
});
948959
updateComposerDraftSettings(selectedProjectDraftKey, {
949960
workspaceSelection: {
950961
mode,
951-
branch:
962+
branch,
963+
worktreePath:
952964
mode === "local"
953-
? localSelection.branch
954-
: resolveNewTaskBranchAfterWorkspaceModeChange({
955-
mode,
956-
previousMode: workspaceMode,
957-
branch: selectedBranchName,
958-
branchSelection: selectedBranchSelection,
959-
}),
960-
worktreePath: mode === "local" ? localSelection.worktreePath : null,
965+
? localSelection.worktreePath
966+
: branch === selectedBranchName
967+
? selectedWorktreePath
968+
: null,
961969
branchSelection: mode === "local" ? "auto" : (selectedBranchSelection ?? "manual"),
962970
...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
963971
},
964972
});
965973
},
966974
[
967975
availableBranches,
976+
configuredBaseRef,
968977
draftStartFromOrigin,
969978
selectedBranchName,
970979
selectedBranchSelection,
971980
selectedProject,
972981
selectedProjectDraftKey,
982+
selectedWorktreePath,
973983
workspaceMode,
974984
],
975985
);
@@ -1394,7 +1404,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
13941404
workspaceMode,
13951405
canChooseWorkspace,
13961406
selectedBranchName,
1397-
lastWorktreeBaseBranch,
1407+
lastWorktreeBaseBranch: rememberedBranch,
13981408
selectedWorktreePath,
13991409
startFromOrigin,
14001410
draftKey: selectedProjectDraftKey,
@@ -1479,7 +1489,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
14791489
reset,
14801490
runtimeMode,
14811491
selectedBranchName,
1482-
lastWorktreeBaseBranch,
1492+
rememberedBranch,
14831493
hasMoreBranches,
14841494
selectedEnvironmentId,
14851495
selectedModel,

‎apps/web/src/components/BranchToolbarBranchSelector.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ export function BranchToolbarBranchSelector({
557557
const worktreeBaseBranchCandidate = resolveDefaultWorktreeBaseRef({
558558
configuredRef: configuredBaseRef,
559559
rememberedRef: rememberedBranchPending ? undefined : (rememberedRef?.name ?? null),
560-
refs: baseBranchesQuery.data?.refs ?? null,
560+
refs: baseBranchesQuery.error ? [] : (baseBranchesQuery.data?.refs ?? null),
561561
currentBranch: currentGitBranch,
562562
});
563563

@@ -704,7 +704,7 @@ export function BranchToolbarBranchSelector({
704704
value={itemValue}
705705
onClick={() => selectPickerItem(itemValue)}
706706
onContextMenu={(event) => handleBranchContextMenu(event, itemValue)}
707-
hint={isSelectingWorktreeBase && itemValue === lastWorktreeBaseBranch ? "Last used" : null}
707+
hint={isSelectingWorktreeBase && itemValue === rememberedBranch ? "Last used" : null}
708708
/>
709709
);
710710
}

‎apps/web/src/components/ChatView.tsx‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10427,11 +10427,16 @@ export default function ChatView(props: ChatViewProps) {
1042710427
if (isLocalDraftThread) {
1042810428
setDraftThreadContext(composerDraftTarget, {
1042910429
envMode: mode,
10430-
branch: resolveDraftBranchAfterEnvModeChange({
10430+
// Only send a cleared branch: a non-null branch marks the environment manual.
10431+
...(draftThread?.branch &&
10432+
resolveDraftBranchAfterEnvModeChange({
1043110433
mode,
10432-
branch: draftThread?.branch ?? null,
10433-
environmentSelection: draftThread?.environmentSelection,
10434-
}),
10434+
branch: draftThread.branch,
10435+
environmentSelection: draftThread.environmentSelection,
10436+
defaultWorktreeBaseRef: activeProjectSettings.settings.defaultWorktreeBaseRef,
10437+
}) === null
10438+
? { branch: null }
10439+
: {}),
1043510440
startFromOrigin: resolveNewDraftStartFromOrigin({
1043610441
envMode: mode,
1043710442
newWorktreesStartFromOrigin: activeProjectSettings.settings.newWorktreesStartFromOrigin,
@@ -10449,6 +10454,7 @@ export default function ChatView(props: ChatViewProps) {
1044910454
draftThread?.environmentSelection,
1045010455
isLocalDraftThread,
1045110456
multipleModelSelections,
10457+
activeProjectSettings.settings.defaultWorktreeBaseRef,
1045210458
activeProjectSettings.settings.newWorktreesStartFromOrigin,
1045310459
setPendingServerThreadEnvMode,
1045410460
scheduleComposerFocus,

‎apps/web/src/lib/chatThreadActions.test.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,31 @@ describe("chatThreadActions", () => {
109109
mode: "worktree",
110110
branch: "feature/current-checkout",
111111
environmentSelection: "auto",
112+
defaultWorktreeBaseRef: "develop",
112113
}),
113114
).toBeNull();
115+
expect(
116+
resolveDraftBranchAfterEnvModeChange({
117+
mode: "worktree",
118+
branch: "feature/current-checkout",
119+
environmentSelection: "auto",
120+
defaultWorktreeBaseRef: null,
121+
}),
122+
).toBe("feature/current-checkout");
114123
expect(
115124
resolveDraftBranchAfterEnvModeChange({
116125
mode: "worktree",
117126
branch: "feature/chosen-base",
118127
environmentSelection: "manual",
128+
defaultWorktreeBaseRef: "develop",
119129
}),
120130
).toBe("feature/chosen-base");
121131
expect(
122132
resolveDraftBranchAfterEnvModeChange({
123133
mode: "local",
124134
branch: "feature/current-checkout",
125135
environmentSelection: "auto",
136+
defaultWorktreeBaseRef: "develop",
126137
}),
127138
).toBe("feature/current-checkout");
128139
});

‎apps/web/src/lib/chatThreadActions.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
ModelSelection,
55
ProjectId,
66
ScopedProjectRef,
7+
WorktreeBaseRef,
78
} from "@t3tools/contracts";
89
import type {
910
ComposerThreadDraftState,
@@ -52,10 +53,16 @@ export function resolveDraftBranchAfterEnvModeChange(input: {
5253
mode: DraftThreadEnvMode;
5354
branch: string | null;
5455
environmentSelection: DraftThreadState["environmentSelection"];
56+
defaultWorktreeBaseRef: WorktreeBaseRef;
5557
}): string | null {
5658
// The local checkout branch is synced into a fresh draft automatically.
57-
// Let the configured worktree base win when that draft switches modes.
58-
return input.mode === "worktree" && input.environmentSelection === "auto" ? null : input.branch;
59+
// Let a configured worktree base win when that draft switches modes;
60+
// without one, keep the branch exactly as before the setting existed.
61+
return input.defaultWorktreeBaseRef !== null &&
62+
input.mode === "worktree" &&
63+
input.environmentSelection === "auto"
64+
? null
65+
: input.branch;
5966
}
6067

6168
export function resolveNewThreadModelSelectionOverride(input: {

0 commit comments

Comments
 (0)