mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 08:02:54 +00:00
feat(worktree): add a "create multiple" toggle to the worktree composer
Add a checkbox to the New Workspace Composer ("Create worktree") modal.
When enabled, creating a worktree keeps the modal open and clears the
identity fields (name, prompt, note, attachments, linked GitHub/GitLab
issues and PRs, branch override, and the Start-from-derived compare base
and push target) while retaining context (repository, base branch, agent,
project group). This lets users spin up several worktrees from the same
repo in a row without reopening the dialog.
The toggle defaults off and, because the modal unmounts on close, always
reopens unchecked. It is shown only for git (worktree) targets;
folder-workspace creation keeps its create-and-close behavior.
This commit is contained in:
@@ -88,6 +88,10 @@ type NewWorkspaceComposerCardProps = {
|
||||
canReuseSelectedBranch: boolean
|
||||
reuseSelectedBranch: boolean
|
||||
onReuseSelectedBranchChange: (next: boolean) => void
|
||||
/** Shows the footer "create another after this" toggle — worktree targets only. */
|
||||
showCreateMultiple?: boolean
|
||||
createMultiple?: boolean
|
||||
onCreateMultipleChange?: (next: boolean) => void
|
||||
smartNameGitHubSourceContext?: TaskSourceContext | null
|
||||
/** Advisory shown under the name field when a fork PR can't accept maintainer pushes. */
|
||||
forkPushWarning: string | null
|
||||
@@ -324,6 +328,9 @@ export default function NewWorkspaceComposerCard({
|
||||
canReuseSelectedBranch,
|
||||
reuseSelectedBranch,
|
||||
onReuseSelectedBranchChange,
|
||||
showCreateMultiple = false,
|
||||
createMultiple = false,
|
||||
onCreateMultipleChange,
|
||||
smartNameGitHubSourceContext,
|
||||
forkPushWarning,
|
||||
detectedAgentIds,
|
||||
@@ -1002,7 +1009,43 @@ export default function NewWorkspaceComposerCard({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex justify-end">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-3',
|
||||
showCreateMultiple ? 'justify-between' : 'justify-end'
|
||||
)}
|
||||
>
|
||||
{showCreateMultiple ? (
|
||||
<label className="group flex w-fit cursor-pointer items-center gap-2 text-xs text-foreground">
|
||||
<span
|
||||
className={cn(
|
||||
'flex size-4 items-center justify-center rounded-[3px] border shadow-sm transition',
|
||||
createMultiple
|
||||
? 'border-emerald-500/60 bg-emerald-500 text-white'
|
||||
: 'border-foreground/20 bg-background dark:border-white/20 dark:bg-muted/10'
|
||||
)}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
'size-3 transition-opacity',
|
||||
createMultiple ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={createMultiple}
|
||||
onChange={(event) => onCreateMultipleChange?.(event.target.checked)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.NewWorkspaceComposerCard.createMultiple',
|
||||
'Create another after this'
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
) : null}
|
||||
<Button
|
||||
onClick={() => void onCreate()}
|
||||
disabled={createDisabled}
|
||||
|
||||
@@ -252,6 +252,13 @@ export type ComposerCardProps = {
|
||||
* rather than used as the base for a new branch. */
|
||||
reuseSelectedBranch: boolean
|
||||
onReuseSelectedBranchChange: (next: boolean) => void
|
||||
/** Whether the "create multiple" toggle is shown — worktree (git) targets
|
||||
* only; folder-workspace targets create-and-close as before. */
|
||||
showCreateMultiple: boolean
|
||||
/** When on, the modal stays open after each create and resets identity fields
|
||||
* so the user can create several worktrees in a row. */
|
||||
createMultiple: boolean
|
||||
onCreateMultipleChange: (next: boolean) => void
|
||||
agentPrompt: string
|
||||
onAgentPromptChange: (value: string) => void
|
||||
/** Rendered issueCommand template to preview inside the empty prompt
|
||||
@@ -890,6 +897,11 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
const [setupDecision, setSetupDecision] = useState<'run' | 'skip' | null>(null)
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [createError, setCreateError] = useState<WorkspaceCreateErrorDisplay | null>(null)
|
||||
// Why: when checked, a successful worktree create keeps the modal open and
|
||||
// resets identity fields so the user can queue another worktree without
|
||||
// reopening. Defaults off; the modal unmounts on close, so reopening always
|
||||
// starts unchecked.
|
||||
const [createMultiple, setCreateMultiple] = useState(false)
|
||||
const [advancedOpen, setAdvancedOpen] = useState(
|
||||
persistDraft ? Boolean((newWorkspaceDraft?.note ?? '').trim()) : false
|
||||
)
|
||||
@@ -3205,6 +3217,37 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
submitFolderTarget
|
||||
])
|
||||
|
||||
const resetForNextCreate = useCallback(() => {
|
||||
// Why: with "create multiple" on, clear identity fields after each create so
|
||||
// the next worktree starts clean. Context (repo, base branch, agent, project
|
||||
// group) is intentionally retained for fast sequential creation. The
|
||||
// PR-pick-derived refs (compare base, push target, branch override) are
|
||||
// identity, not durable context, so they reset too — leaving them while the
|
||||
// linked item is cleared would carry a half-set Start-from state (e.g. a
|
||||
// silent fork push target) into the next worktree.
|
||||
setName('')
|
||||
lastAutoNameRef.current = ''
|
||||
setAgentPrompt('')
|
||||
setNote('')
|
||||
setAttachmentPaths([])
|
||||
setLinkedWorkItem(null)
|
||||
setLinkedIssue('')
|
||||
setLinkedPR(null)
|
||||
setLinkedGitLabIssue(null)
|
||||
setLinkedGitLabMR(null)
|
||||
setBranchNameOverride(undefined)
|
||||
setBranchNameOverridePreservesNameEdits(false)
|
||||
setCompareBaseRef(undefined)
|
||||
setPushTarget(undefined)
|
||||
setReuseSelectedBranch(false)
|
||||
setStartFromResetHint(null)
|
||||
setForkPushWarning(null)
|
||||
setCreateError(null)
|
||||
// Refocus the name field on the next frame (after the reset re-render) so the
|
||||
// user can immediately type the next worktree name.
|
||||
requestAnimationFrame(() => nameInputRef.current?.focus())
|
||||
}, [])
|
||||
|
||||
const submitQuick = useCallback(
|
||||
async (requestedAgent: TuiAgent | null): Promise<void> => {
|
||||
if (isProjectGroupTarget) {
|
||||
@@ -3507,8 +3550,14 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
if (persistDraft) {
|
||||
clearNewWorkspaceDraft()
|
||||
}
|
||||
onCreated?.()
|
||||
runBackgroundWorktreeCreation(request)
|
||||
if (createMultiple) {
|
||||
// Why: keep the modal open and reset identity so the user can queue
|
||||
// another worktree right away; the creation above runs in the background.
|
||||
resetForNextCreate()
|
||||
} else {
|
||||
onCreated?.()
|
||||
}
|
||||
} catch (error) {
|
||||
const formattedError = formatWorkspaceCreateError(error)
|
||||
setCreateError(formattedError)
|
||||
@@ -3566,7 +3615,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
setupConfig,
|
||||
setupPolicy,
|
||||
isProjectGroupTarget,
|
||||
submitFolderTarget
|
||||
submitFolderTarget,
|
||||
createMultiple,
|
||||
resetForNextCreate
|
||||
]
|
||||
)
|
||||
|
||||
@@ -3616,6 +3667,11 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
|
||||
smartNameSelection?.kind === 'branch',
|
||||
reuseSelectedBranch,
|
||||
onReuseSelectedBranchChange: handleReuseSelectedBranchChange,
|
||||
// Why: the "create multiple" toggle only applies to worktree (git) targets;
|
||||
// folder-workspace targets keep the create-and-close behavior.
|
||||
showCreateMultiple: !isProjectGroupTarget,
|
||||
createMultiple,
|
||||
onCreateMultipleChange: setCreateMultiple,
|
||||
agentPrompt,
|
||||
onAgentPromptChange: setAgentPrompt,
|
||||
linkedOnlyTemplatePreview: shouldApplyLinkedOnlyTemplate ? linkedOnlyTemplatePrompt : null,
|
||||
|
||||
@@ -1026,6 +1026,7 @@
|
||||
"NewWorkspaceComposerCard": {
|
||||
"reuseExistingBranch": "Reuse branch",
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it.",
|
||||
"createMultiple": "Create another after this",
|
||||
"cbb47ee0dc": "Only available for local Git projects.",
|
||||
"d861de981b": "Sparse checkout",
|
||||
"090cfedeb4": "Write a note",
|
||||
|
||||
@@ -1078,7 +1078,8 @@
|
||||
"importingHostSetup": "Importing...",
|
||||
"importHostSetup": "Import",
|
||||
"reuseExistingBranch": "Reuse branch",
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it."
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it.",
|
||||
"createMultiple": "Create another after this"
|
||||
},
|
||||
"NewWorkspaceComposerModal": {
|
||||
"fa90f739a5": "Elija el proyecto, el nombre del espacio de trabajo y el agente antes de crear el espacio de trabajo."
|
||||
|
||||
@@ -1078,7 +1078,8 @@
|
||||
"importingHostSetup": "Importing...",
|
||||
"importHostSetup": "インポート",
|
||||
"reuseExistingBranch": "Reuse branch",
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it."
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it.",
|
||||
"createMultiple": "Create another after this"
|
||||
},
|
||||
"NewWorkspaceComposerModal": {
|
||||
"fa90f739a5": "ワークスペースを作成する前に、プロジェクト、ワークスペース名、および agent を選択します。"
|
||||
|
||||
@@ -1078,7 +1078,8 @@
|
||||
"importingHostSetup": "Importing...",
|
||||
"importHostSetup": "가져오기",
|
||||
"reuseExistingBranch": "Reuse branch",
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it."
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it.",
|
||||
"createMultiple": "Create another after this"
|
||||
},
|
||||
"NewWorkspaceComposerModal": {
|
||||
"fa90f739a5": "워크스페이스를 생성하기 전에 프로젝트, 워크스페이스 이름, agent를 선택하세요."
|
||||
|
||||
@@ -1078,7 +1078,8 @@
|
||||
"importingHostSetup": "导入中...",
|
||||
"importHostSetup": "导入",
|
||||
"reuseExistingBranch": "Reuse branch",
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it."
|
||||
"reuseExistingBranchHint": "Check out the existing branch instead of creating a new one from it.",
|
||||
"createMultiple": "Create another after this"
|
||||
},
|
||||
"NewWorkspaceComposerModal": {
|
||||
"fa90f739a5": "创建工作区之前选择项目、工作区名称和 Agent。"
|
||||
|
||||
Reference in New Issue
Block a user