diff --git a/web/src/fastForward.test.ts b/web/src/fastForward.test.ts index 4366c28..de74292 100644 --- a/web/src/fastForward.test.ts +++ b/web/src/fastForward.test.ts @@ -34,12 +34,14 @@ describe("resolveFastForward (#67)", () => { const res = resolveFastForward(projects, presets, "amber-falcon"); expect(res.project?.path).toBe("/repos/fresh"); expect(res.command).toBe("aider"); - expect(res.name).toBe("amber-falcon"); + expect(res.handle).toBe("amber-falcon"); const req = buildFastForwardRequest(res)!; expect(req.cwd).toBe("/repos/fresh"); expect(req.command).toBe("aider"); - expect(req.metadata).toEqual({ name: "amber-falcon" }); + // No name (#131): the daemon names the session after its first prompt, + // and a name here would collide with the branch handle. + expect(req.metadata).toBeUndefined(); expect(req.worktree).toEqual({ base_repo: "/repos/fresh", branch: "amber-falcon", @@ -88,9 +90,9 @@ describe("resolveFastForward (#67)", () => { expect(buildFastForwardRequest(res)).toBeNull(); }); - it("downgrades to a draft when the generated name is missing", () => { + it("downgrades to a draft when the generated handle is missing", () => { const res = resolveFastForward([project({ last_command: "claude" })], presets); - expect(res.name).toBeUndefined(); + expect(res.handle).toBeUndefined(); expect(buildFastForwardRequest(res)).toBeNull(); }); @@ -108,6 +110,17 @@ describe("resolveFastForward (#67)", () => { }); }); + it("seeds the handle as the branch and leaves the name field empty (#131)", () => { + const res = resolveFastForward( + [project({ path: "/repos/p", base_repo: "/repos/p", last_command: "aider" })], + presets, + "amber-falcon", + ); + const seed = fastForwardSeed(res); + expect(seed.name).toBe(""); + expect(seed.worktreeBranch).toBe("amber-falcon"); + }); + it("leaves the worktree default alone when not even a project resolved", () => { // Not `false`: the form defaults the toggle on and a false would stick, // so the fallback draft would disagree with a normally-opened draft and diff --git a/web/src/fastForward.ts b/web/src/fastForward.ts index 5f83611..0a140f1 100644 --- a/web/src/fastForward.ts +++ b/web/src/fastForward.ts @@ -10,19 +10,21 @@ import { sortProjects } from "./projectList.js"; export interface FastForwardResolution { project?: Project; command?: string; - name?: string; + /** A generated handle for the worktree branch (#131). Not a session name: + * the fast path names no session. */ + handle?: string; } /** The defaults: the currently focused session's project when it's still a * known project, else the most recent one (fresh-first, same ordering the * picker shows) — so `N` lands where the operator currently is, matching * what plain `n` seeds from `spawnSimilarProjectSeed`. Its last command - * falls back to the first non-custom preset, and the name is freshly - * generated when the daemon supplied one. */ + * falls back to the first non-custom preset, and the branch handle is + * freshly generated when the daemon supplied one. */ export function resolveFastForward( projects: Project[], presets: SpawnPreset[], - name?: string, + handle?: string, focusedCwd?: string, ): FastForwardResolution { const project = @@ -30,41 +32,45 @@ export function resolveFastForward( const lastCommand = project?.last_command?.trim(); const command = lastCommand || presets.find((p) => p.command !== "")?.command || undefined; - return { project, command, name: name || undefined }; + return { project, command, handle: handle || undefined }; } /** The immediate spawn request — worktree on, new branch named after the - * generated session name — or `null` when any default is missing. */ + * generated handle — or `null` when any default is missing. + * + * The request carries no name (#131): the daemon assigns a provisional one + * and replaces it once the session sees its first prompt. A name here would + * also collide with the branch handle's own session. */ export function buildFastForwardRequest( res: FastForwardResolution, ): CreateSessionRequest | null { - const { project, command, name } = res; - if (!project || !command?.trim() || !name) return null; + const { project, command, handle } = res; + if (!project || !command?.trim() || !handle) return null; return { cwd: project.path, command, - metadata: { name }, worktree: { base_repo: project.base_repo ?? project.path, - branch: name, + branch: handle, create_branch: true, }, }; } /** Seed for the fallback spawn form: whatever did resolve goes into the - * form; the rest stays with its own defaults. */ + * form; the rest stays with its own defaults. The name field stays empty + * (#131) — the handle seeds the branch, not the session name. */ export function fastForwardSeed(res: FastForwardResolution) { return { cwd: res.project?.path ?? "", command: res.command ?? "", - name: res.name ?? "", + name: "", // Left undefined rather than false when nothing resolved: the form // defaults the toggle on, and `false` would stick, so the fallback form // would disagree with both a normally-opened form and the TUI's fast path // (#67 review). worktreeEnabled: res.project ? true : undefined, worktreeBranch: - res.project && res.name ? res.name : undefined, + res.project && res.handle ? res.handle : undefined, }; } diff --git a/web/src/main.ts b/web/src/main.ts index 2154af1..8bc489f 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -356,14 +356,14 @@ async function spawnPresets(): Promise { } async function requestFastForward(): Promise { - const [discovered, name] = await Promise.all([ + const [discovered, handle] = await Promise.all([ fetchProjects().catch(() => [] as Project[]), fetchNames(1) .then((batch) => batch.names[0]) .catch(() => undefined), ]); const presets = await spawnPresets(); - const resolved = resolveFastForward(discovered, presets, name, focusedProjectCwd()); + const resolved = resolveFastForward(discovered, presets, handle, focusedProjectCwd()); const req = buildFastForwardRequest(resolved); if (req) { try { @@ -390,7 +390,7 @@ async function requestFastForward(): Promise { // one outside any repo spawns without a worktree, since there is no repo to cut // a branch from. Anything still unresolved opens the pre-filled form. async function requestSpawnInDirectory(path: string): Promise { - const [discovered, name] = await Promise.all([ + const [discovered, handle] = await Promise.all([ fetchProjects().catch(() => [] as Project[]), fetchNames(1) .then((batch) => batch.names[0]) @@ -399,7 +399,7 @@ async function requestSpawnInDirectory(path: string): Promise { const presets = await spawnPresets(); const known = discovered.find((p) => p.path === path); const project: Project = known ?? { path, name: path.split("/").filter(Boolean).pop() ?? path }; - const resolved = resolveFastForward([project], presets, name, path); + const resolved = resolveFastForward([project], presets, handle, path); const req = buildFastForwardRequest(resolved); if (req) { const inRepo =