feat(maestro): bind N to fast-forward spawn in the web client (#67)
This commit is contained in:
@@ -196,6 +196,8 @@ export function renderTopbar(store: CatalogStore, onToggleSidebar?: () => void):
|
||||
spawnBtn.type = "button";
|
||||
spawnBtn.className = "btn btn-primary spawn-btn";
|
||||
spawnBtn.textContent = "+ Spawn";
|
||||
// The keyboard pair sits right here in the tooltip (#67).
|
||||
spawnBtn.title = "Draft pane: n — fast-forward spawn: N";
|
||||
spawnBtn.addEventListener("click", () => requestOpenSpawn());
|
||||
actions.appendChild(spawnBtn);
|
||||
return header;
|
||||
|
||||
+73
-1
@@ -3,13 +3,25 @@ import { ConnectionPool } from "./connections.js";
|
||||
import { installHudKeybind } from "./debug.js";
|
||||
import { CatalogStore } from "./events.js";
|
||||
import { shouldRetryClose } from "./reconnect.js";
|
||||
import {
|
||||
createSession,
|
||||
fetchNames,
|
||||
getDaemonInfo,
|
||||
projects as fetchProjects,
|
||||
} from "./api.js";
|
||||
import {
|
||||
buildFastForwardRequest,
|
||||
fastForwardSeed,
|
||||
resolveFastForward,
|
||||
} from "./fastForward.js";
|
||||
import { FALLBACK_PRESETS } from "./spawnForm.js";
|
||||
import { mountHud, type Hud } from "./hud.js";
|
||||
import { emptyLayout, type Layout, openSessions } from "./layout.js";
|
||||
import { createMultiView, type MultiView } from "./multiview.js";
|
||||
import { loadLayout, saveLayout } from "./persistence.js";
|
||||
import { OPEN_SPAWN_EVENT } from "./catalog.js";
|
||||
import { initTheme } from "./theme.js";
|
||||
import { sessionName, type Session } from "./types.js";
|
||||
import { sessionName, type Project, type Session, type SpawnPreset } from "./types.js";
|
||||
import { installViewportSizing } from "./viewport.js";
|
||||
import "./styles.css";
|
||||
|
||||
@@ -212,6 +224,7 @@ function renderWelcome(): HTMLElement {
|
||||
spawn.type = "button";
|
||||
spawn.className = "btn btn-primary";
|
||||
spawn.textContent = "+ Spawn session";
|
||||
spawn.title = "Draft pane: n — fast-forward spawn: N";
|
||||
spawn.addEventListener("click", () => requestOpenSpawn());
|
||||
(el.querySelector(".empty-actions") as HTMLElement).appendChild(spawn);
|
||||
return el;
|
||||
@@ -265,6 +278,65 @@ document.addEventListener("catalog-spawn-similar", (ev) => {
|
||||
mv.openDraft({ cwd: s.cwd, command: s.command });
|
||||
});
|
||||
|
||||
// Fast-forward spawn (#67): the draft pane's keystroke neighbour spawns
|
||||
// immediately with every default resolved — most recent project, its last
|
||||
// command (or the first non-custom preset), a fresh name, worktree on with
|
||||
// a new branch named after that name. Anything unresolvable (no projects
|
||||
// known yet, no command, no generated name) opens a draft pre-filled with
|
||||
// what did resolve instead.
|
||||
async function requestFastForward(): Promise<void> {
|
||||
showMultiView();
|
||||
const [discovered, name] = await Promise.all([
|
||||
fetchProjects().catch(() => [] as Project[]),
|
||||
fetchNames(1)
|
||||
.then((batch) => batch.names[0])
|
||||
.catch(() => undefined),
|
||||
]);
|
||||
let presets: SpawnPreset[] = FALLBACK_PRESETS;
|
||||
try {
|
||||
const info = await getDaemonInfo();
|
||||
if (info.presets?.length) presets = info.presets;
|
||||
} catch {
|
||||
/* keep fallbacks */
|
||||
}
|
||||
const resolved = resolveFastForward(discovered, presets, name);
|
||||
const req = buildFastForwardRequest(resolved);
|
||||
if (req) {
|
||||
try {
|
||||
await createSession(req);
|
||||
return;
|
||||
} catch {
|
||||
// A failed POST degrades to the same pre-filled draft as an
|
||||
// unresolvable default — never a silent dead keystroke.
|
||||
}
|
||||
}
|
||||
mv.openDraft(fastForwardSeed(resolved));
|
||||
}
|
||||
|
||||
// Keyboard pair (#67): `n` opens a draft pane, `N` fast-forwards a spawn.
|
||||
// Ignored while typing anywhere (form fields, the terminal's hidden
|
||||
// textarea, search) so an attached session's keys are never hijacked.
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.ctrlKey || e.altKey || e.metaKey) return;
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (
|
||||
target &&
|
||||
(target.tagName === "INPUT" ||
|
||||
target.tagName === "TEXTAREA" ||
|
||||
target.tagName === "SELECT" ||
|
||||
target.isContentEditable)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "n") {
|
||||
e.preventDefault();
|
||||
requestOpenSpawn();
|
||||
} else if (e.key === "N") {
|
||||
e.preventDefault();
|
||||
void requestFastForward();
|
||||
}
|
||||
});
|
||||
|
||||
type Route =
|
||||
| { kind: "catalog" }
|
||||
| { kind: "attach"; sessionId: string }
|
||||
|
||||
@@ -83,8 +83,9 @@ export function buildSpawnRequest(
|
||||
}
|
||||
|
||||
/** Built-ins used when the daemon's `/api/info` presets can't be fetched
|
||||
* (older daemon, transient error). Mirrors `maestro_config::builtin_presets`. */
|
||||
const FALLBACK_PRESETS: SpawnPreset[] = [
|
||||
* (older daemon, transient error). Mirrors `maestro_config::builtin_presets`.
|
||||
* Exported for the fast-forward spawn's preset fallback (#67). */
|
||||
export const FALLBACK_PRESETS: SpawnPreset[] = [
|
||||
{ id: "claude", label: "Claude", command: "claude" },
|
||||
{ id: "opencode", label: "opencode", command: "opencode" },
|
||||
{ id: "zsh", label: "Shell", command: "zsh" },
|
||||
|
||||
Reference in New Issue
Block a user