// Fast-forward spawn (#67): one keystroke, every default resolved, no form. // Pure resolution only — the caller (main.ts) owns the fetches and the POST, // and falls back to a seeded spawn form when anything is missing. import type { CreateSessionRequest, Project, SpawnPreset } from "./types.js"; import { sortProjects } from "./projectList.js"; /** What one fast-forward keystroke resolved. Anything absent means "open a * form pre-filled with the rest instead of spawning". */ export interface FastForwardResolution { project?: Project; command?: 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 branch handle is * freshly generated when the daemon supplied one. */ export function resolveFastForward( projects: Project[], presets: SpawnPreset[], handle?: string, focusedCwd?: string, ): FastForwardResolution { const project = projects.find((p) => p.path === focusedCwd) ?? sortProjects(projects, new Date())[0]; const lastCommand = project?.last_command?.trim(); const command = lastCommand || presets.find((p) => p.command !== "")?.command || undefined; return { project, command, handle: handle || undefined }; } /** The immediate spawn request — worktree on, new branch named after the * 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, handle } = res; if (!project || !command?.trim() || !handle) return null; return { cwd: project.path, command, worktree: { base_repo: project.base_repo ?? project.path, 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. 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: "", // 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.handle ? res.handle : undefined, }; }