diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8bcc8b82f..b3df5d8d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -98,6 +98,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
machine with no agent it opened a card that died on
`claude: command not found`. It now opens a shell — where the install command
from those links can be pasted — and says so before you press it.
+- **The new worktree can be named in plain words.** The name field only ever
+ took a branch name: anything with a space in it was refused as invalid, so
+ naming the work meant inventing the hyphens yourself, and leaving the field
+ blank handed the branch a name like `calm-glacier` — which is fine until it
+ is the name on a pull request, and renaming a branch after the PR is open
+ closes the PR. Type what the work is instead — `fix the auth redirect` — and
+ the branch becomes `fix-the-auth-redirect`, shown under the field before
+ anything is created. A name git already accepts is still used exactly as
+ typed, and a blank field still auto-generates one.
- **A project tab comes back to the screen you left it on.** Stepping over to
another project and back always dropped you onto the first project's
terminals, whatever you had been reading there: its Settings, the pull request
diff --git a/frontend/src/components/sidebar/WorktreeDialog.tsx b/frontend/src/components/sidebar/WorktreeDialog.tsx
index ec5867eac..e98005bed 100644
--- a/frontend/src/components/sidebar/WorktreeDialog.tsx
+++ b/frontend/src/components/sidebar/WorktreeDialog.tsx
@@ -17,7 +17,7 @@ import {
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
-import { isValidBranchName } from "@/lib/git/branch-name"
+import { toBranchName } from "@/lib/git/branch-name"
import { useSandboxChoice, type SandboxAnswer } from "@/lib/use-sandbox-choice"
import { cn, errorText } from "@/lib/utils"
@@ -110,10 +110,10 @@ function Group({ title, items, base, onSelect }: GroupProps) {
)
}
-// WorktreeDialog collects a worktree name (blank = random adjective-noun) and a
-// base picked from a searchable list — existing worktrees to resume, then local
-// and remote branches (remote bases are fetched and tracked). It stays open on
-// failure so git's error is readable in place.
+// WorktreeDialog collects a worktree name (blank = random adjective-noun, plain
+// words = a slug of them) and a base picked from a searchable list — existing
+// worktrees to resume, then local and remote branches (remote bases are fetched
+// and tracked). It stays open on failure so git's error is readable in place.
export function WorktreeDialog({
open,
onOpenChange,
@@ -138,8 +138,9 @@ export function WorktreeDialog({
const vis = filterBranches(branches, filter)
const flat = flatValues(vis)
const noMatches = branches !== null && flat.length === 0
- const trimmed = name.trim()
- const nameInvalid = trimmed !== "" && !isValidBranchName(trimmed)
+ // What the typed name creates: itself when git takes it, a slug of the first
+ // few words when it is a phrase, blank when nothing usable was typed.
+ const newBranch = toBranchName(name)
const isResume = base.startsWith("worktree:")
// Keep the selected base in view as it changes — the preselected current
@@ -208,7 +209,7 @@ export function WorktreeDialog({
move(-1)
} else if (event.key === "Enter") {
event.preventDefault()
- if (!nameInvalid && base && !submitting) {
+ if (base && !submitting) {
void submit()
}
}
@@ -228,7 +229,7 @@ export function WorktreeDialog({
setSubmitting(true)
setSubmitError("")
try {
- await onCreate(trimmed, id, group === "remote", sandbox.answer)
+ await onCreate(newBranch, id, group === "remote", sandbox.answer)
} catch (err) {
setSubmitError(errorText(err))
setSubmitting(false)
@@ -255,20 +256,15 @@ export function WorktreeDialog({
id="worktree-name"
value={name}
onChange={(e) => setName(e.target.value)}
- placeholder="Leave blank to auto-generate (e.g. swift-rabbit)"
+ placeholder="A branch name, or the task in plain words (e.g. fix the auth redirect)"
disabled={isResume}
- aria-invalid={nameInvalid || undefined}
autoFocus
/>
- {nameInvalid ? (
- Invalid branch name
- ) : (
-
- {isResume
- ? "Opens the selected worktree"
- : `Branch: ${trimmed || "