Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 4 additions & 12 deletions apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@ export function NewTaskBranchPickerRouteScreen() {
startFromOrigin: flow.startFromOrigin,
onChangeStartFromOrigin: flow.setStartFromOrigin,
createNewBranch: flow.createNewBranch,
onChangeCreateNewBranch: flow.setCreateNewBranch,
}
: undefined
}
Expand All @@ -371,9 +370,8 @@ export function BranchPickerScreen(props: {
readonly worktree?: {
readonly startFromOrigin: boolean;
readonly onChangeStartFromOrigin: (value: boolean) => void;
/** Scheduled tasks omit these: a recurring run would find its branch still checked out. */
/** Scheduled tasks omit this: a recurring run would find its branch still checked out. */
readonly createNewBranch?: boolean;
readonly onChangeCreateNewBranch?: ((value: boolean) => void) | null;
};
}) {
const searchRef = useRef<import("react-native").TextInputInstance>(null);
Expand Down Expand Up @@ -422,16 +420,10 @@ export function BranchPickerScreen(props: {
Platform.OS === "android" ? "rounded-[28px]" : "rounded-2xl",
)}
>
{worktree.onChangeCreateNewBranch ? (
<ToggleRow
onValueChange={worktree.onChangeCreateNewBranch}
title="Create new branch"
value={!checksOutExistingBranch}
/>
) : null}
{checksOutExistingBranch ? (
<Text className="bg-grouped-card px-4 pb-3 text-sm text-foreground-muted">
Select a local branch that is not already checked out.
<Text className="bg-grouped-card px-4 py-3 text-sm text-foreground-muted">
Select a branch that is not already checked out. A remote branch is checked out as a local
branch that tracks it.
</Text>
) : (
<ToggleRow
Expand Down
9 changes: 6 additions & 3 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import { resolveDraftProjectSelection } from "./new-task-project-selection";
import {
resolveNewTaskBranchLabel,
resolveNewTaskWorkspaceLabel,
resolveNextNewTaskWorkspace,
} from "./new-task-context-presentation";
import { incomingShareImports, useIncomingShare } from "../sharing/IncomingShareProvider";
import { idleShareFlagAtom, idleShareImportPhaseAtom } from "./incomingShareImport";
Expand Down Expand Up @@ -544,7 +545,9 @@ export function NewTaskDraftScreen(props: {
const workspaceLabel = resolveNewTaskWorkspaceLabel({
workspaceMode: flow.workspaceMode,
worktreePath: flow.selectedWorktreePath,
createNewBranch: flow.createNewBranch,
});
const nextWorkspace = resolveNextNewTaskWorkspace(flow);
const showBranchLoading =
environmentConnected && flow.branchesLoading && flow.availableBranches.length === 0;

Expand Down Expand Up @@ -822,7 +825,7 @@ export function NewTaskDraftScreen(props: {
const workspaceControls = (
<View className="flex-row items-center gap-1 px-2">
<ComposerInlineControl
accessibilityHint={`Switches to ${flow.workspaceMode === "local" ? "a new worktree" : "the current checkout"}`}
accessibilityHint={`Switches to ${resolveNewTaskWorkspaceLabel({ workspaceMode: nextWorkspace.mode, worktreePath: null, createNewBranch: nextWorkspace.createNewBranch !== false })}`}
accessibilityLabel={workspaceLabel}
disabled={isComposerInteractionLocked || !flow.isGitRepo}
renderIcon={(size) => (
Expand All @@ -834,13 +837,13 @@ export function NewTaskDraftScreen(props: {
)}
label={workspaceLabel}
maxWidth="100%"
onPress={() => flow.setWorkspaceMode(flow.workspaceMode === "local" ? "worktree" : "local")}
onPress={() => flow.setWorkspaceMode(nextWorkspace.mode, nextWorkspace.createNewBranch)}
showChevron={false}
/>

<View className="min-w-0 shrink">
<ComposerInlineControl
accessibilityLabel={`${flow.workspaceMode === "worktree" ? "Base branch" : "Branch"}: ${selectedBranchLabel}`}
accessibilityLabel={`${flow.workspaceMode === "worktree" && flow.createNewBranch ? "Base branch" : "Branch"}: ${selectedBranchLabel}`}
chevronDirection="right"
disabled={isComposerInteractionLocked || !flow.isGitRepo}
icon="arrow.triangle.branch"
Expand Down
21 changes: 19 additions & 2 deletions apps/mobile/src/features/threads/new-task-context-presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,30 @@ export function resolveNewTaskWorkspaceSelection(input: {
export function resolveNewTaskWorkspaceLabel(input: {
readonly workspaceMode: WorkspaceMode;
readonly worktreePath: string | null;
}): "Current checkout" | "Current worktree" | "New worktree" {
readonly createNewBranch?: boolean;
}): "Current checkout" | "Current worktree" | "New worktree" | "Check out in worktree" {
if (input.workspaceMode === "worktree") {
return "New worktree";
return input.createNewBranch === false ? "Check out in worktree" : "New worktree";
}
return input.worktreePath ? "Current worktree" : "Current checkout";
}

/** The workspace control cycles Current checkout, New worktree, then Check out in worktree. */
export function resolveNextNewTaskWorkspace(input: {
readonly workspaceMode: WorkspaceMode;
readonly createNewBranch: boolean;
readonly canCheckoutWorktree: boolean;
}): { readonly mode: WorkspaceMode; readonly createNewBranch?: boolean } {
if (input.workspaceMode === "local") {
return input.canCheckoutWorktree
? { mode: "worktree", createNewBranch: true }
: { mode: "worktree" };
}
return input.canCheckoutWorktree && input.createNewBranch
? { mode: "worktree", createNewBranch: false }
: { mode: "local" };
}

export function resolveNewTaskBranchWorktreePath(input: {
readonly workspaceMode: WorkspaceMode;
readonly projectCwd: string;
Expand Down
41 changes: 12 additions & 29 deletions apps/mobile/src/features/threads/new-task-flow-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ type NewTaskFlowContextValue = {
selection: ComposerDraftWorkspaceSelection | undefined,
) => ComposerDraftWorkspaceSelection;
readonly startFromOrigin: boolean;
/** False checks out the selected local branch itself in the new worktree. */
/** False checks out the selected branch itself in the new worktree. */
readonly createNewBranch: boolean;
/** Whether the server can check out an existing branch in a new worktree. */
readonly canCheckoutWorktree: boolean;
readonly pullRequestReference: string | undefined;
readonly draftKey: string | null;
readonly prompt: string;
Expand Down Expand Up @@ -191,11 +193,10 @@ type NewTaskFlowContextValue = {
key: string | null,
options?: ReadonlyArray<ProviderOptionSelection>,
) => void;
readonly setWorkspaceMode: (mode: WorkspaceMode) => void;
/** `createNewBranch` false checks out the selected branch in the new worktree. */
readonly setWorkspaceMode: (mode: WorkspaceMode, createNewBranch?: boolean) => void;
readonly selectBranch: (branch: VcsRef) => void;
readonly setStartFromOrigin: (value: boolean) => void;
/** Null when the server cannot check out an existing branch, which hides the toggle. */
readonly setCreateNewBranch: ((value: boolean) => void) | null;
readonly buildPendingTaskMessage: (
metadata: TurnCommandMetadata,
options?: {
Expand Down Expand Up @@ -648,7 +649,7 @@ export function NewTaskFlowProvider(
return false;
};

const setWorkspaceMode = (mode: WorkspaceMode) => {
const setWorkspaceMode = (mode: WorkspaceMode, nextCreateNewBranch?: boolean) => {
if (!selectedProjectDraftKey) {
return;
}
Expand All @@ -666,13 +667,15 @@ export function NewTaskFlowProvider(
mode === "local"
? localSelection.branch
: resolveNewTaskWorktreeBranch({
createNewBranch,
createNewBranch: nextCreateNewBranch ?? createNewBranch,
selectedBranchName,
branches: availableBranches,
branches: allBranchRefs,
}),
worktreePath: mode === "local" ? localSelection.worktreePath : selectedWorktreePath,
...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
...(draftCreateNewBranch !== undefined ? { createNewBranch: draftCreateNewBranch } : {}),
...((nextCreateNewBranch ?? draftCreateNewBranch) !== undefined
? { createNewBranch: nextCreateNewBranch ?? draftCreateNewBranch }
: {}),
// Refs have not named the current branch yet; the selection follows it.
...(mode === "local" && localSelection.awaitsCurrentBranch
? { followsCurrentCheckout: true }
Expand Down Expand Up @@ -718,26 +721,6 @@ export function NewTaskFlowProvider(
});
};

const updateCreateNewBranch = (value: boolean) => {
if (!selectedProjectDraftKey) {
return;
}
updateComposerDraftSettings(selectedProjectDraftKey, {
workspaceSelection: {
mode: workspaceMode,
branch: resolveNewTaskWorktreeBranch({
createNewBranch: value,
selectedBranchName,
branches: availableBranches,
}),
worktreePath: selectedWorktreePath,
...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
createNewBranch: value,
},
});
};
const setCreateNewBranch = supportsExistingBranchWorktree ? updateCreateNewBranch : null;

const refreshBranches = branchState.refresh;
const loadBranches = () => {
if (!selectedProject) {
Expand Down Expand Up @@ -915,6 +898,7 @@ export function NewTaskFlowProvider(
resolveWorkspaceSelection,
startFromOrigin,
createNewBranch,
canCheckoutWorktree: supportsExistingBranchWorktree,
draftKey: selectedProjectDraftKey,
pullRequestReference: selectedProjectDraft.workspaceSelection?.pullRequestReference,
prompt,
Expand Down Expand Up @@ -947,7 +931,6 @@ export function NewTaskFlowProvider(
setWorkspaceMode,
selectBranch,
setStartFromOrigin,
setCreateNewBranch,
buildPendingTaskMessage,
setPrompt,
replaceAttachments,
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/mcp/toolkits/project/handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ it.effect("reports a rejected existing-branch launch as an invalid request", ()
deletedAt: null,
} as OrchestrationV2ThreadShell;
const message =
'Branch "main" is already checked out at /repo. Select another branch, or enable Create new branch.';
'Branch "main" is already checked out at /repo. Select another branch, or choose New worktree.';
const dependencies = Layer.mergeAll(
Layer.mock(ProviderRegistry.ProviderRegistry)({ getProviders: Effect.succeed([]) }),
ServerSettings.layerTest(),
Expand Down
Loading
Loading