feat(maestro): port spawn dialog to React (#248)
Ports the vanilla spawn form/modal to src/components/spawn/: SpawnModal, SpawnForm, DirAutocomplete, BranchPicker, and a useFastForward hook for the one-keystroke spawn. Pure logic modules (spawnForm.ts's buildSpawnRequest, fastForward.ts, spawnSimilar.ts, projectList.ts, toolIcon.ts) are reused as-is rather than re-derived. Exports supportsStructuredView from spawnForm.ts so the React form can share the structured-view-eligibility check with the vanilla one.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// React port of main.ts's `requestFastForward` (#67): one keystroke, every
|
||||
// spawn default resolved — most recent project (or the one the operator is
|
||||
// on), its last command, a fresh generated branch handle, worktree on with a
|
||||
// new branch. A failed POST, or any missing default, falls back to the same
|
||||
// pre-filled form `onFallback` opens, rather than a dead keystroke.
|
||||
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { createSession, fetchNames, getDaemonInfo, projects as fetchProjects } from "../../api.js";
|
||||
import { buildFastForwardRequest, fastForwardSeed, resolveFastForward } from "../../fastForward.js";
|
||||
import { FALLBACK_PRESETS, type SpawnFormSeed } from "../../spawnForm.js";
|
||||
import type { Project, Session, SpawnPreset } from "../../types.js";
|
||||
|
||||
export interface UseFastForwardOptions {
|
||||
/** The project of the session the operator is currently on, if any — its
|
||||
* base repo for a worktree session, else its cwd. Same rule the plain
|
||||
* spawn modal's seed uses, so `N` lands where the operator currently is. */
|
||||
focusedCwd?: string;
|
||||
onSpawned(session: Session): void;
|
||||
onFallback(seed: SpawnFormSeed): void;
|
||||
}
|
||||
|
||||
async function resolvePresets(): Promise<SpawnPreset[]> {
|
||||
try {
|
||||
const info = await getDaemonInfo();
|
||||
if (info.presets?.length) return info.presets;
|
||||
} catch {
|
||||
// keep fallbacks
|
||||
}
|
||||
return FALLBACK_PRESETS;
|
||||
}
|
||||
|
||||
/** Returns a function to call on the fast-forward keystroke. */
|
||||
export function useFastForward(opts: UseFastForwardOptions): () => Promise<void> {
|
||||
const { focusedCwd, onSpawned, onFallback } = opts;
|
||||
|
||||
return useCallback(async () => {
|
||||
const [discovered, handle] = await Promise.all([
|
||||
fetchProjects().catch(() => [] as Project[]),
|
||||
fetchNames(1)
|
||||
.then((batch) => batch.names[0])
|
||||
.catch(() => undefined),
|
||||
]);
|
||||
const presets = await resolvePresets();
|
||||
const resolved = resolveFastForward(discovered, presets, handle, focusedCwd);
|
||||
const req = buildFastForwardRequest(resolved);
|
||||
if (req) {
|
||||
try {
|
||||
onSpawned(await createSession(req));
|
||||
return;
|
||||
} catch {
|
||||
// degrades to the pre-filled form below, never a silent dead key
|
||||
}
|
||||
}
|
||||
onFallback(fastForwardSeed(resolved));
|
||||
}, [focusedCwd, onSpawned, onFallback]);
|
||||
}
|
||||
Reference in New Issue
Block a user