One click instead of four: after AddLaneModal creates a worktree lane, fire profile-init + agents-install + mcp-sync in parallel (Promise.allSettled, best-effort — none of the three blocks lane creation or each other). Needs one new route (POST /:id/profile/init, wrapping already-built lane-detect.js) since profile-init was CLI-only until now.
4.6 KiB
F5 — Auto-setup after Add Lane
Status: approved 2026-08-06.
Problem
F4 surfaced agents install/mcp sync/profile-init as per-lane manual buttons, but a user creating a lane through AddLaneModal (the /run page's "add lane" flow) still has to click each one separately afterward, or use the CLI. The user wants one click — pick a source repo, and setup happens automatically.
Scope
In scope:
- New route
POST /api/lanes/:id/profile/init— wrapsserver/lib/lane-detect.js'sdetectNode/scaffoldProfile(already built, CLI-only until now — same "extract to a route" shape F4 already established for skills-install). Body{force?: boolean}.lane.cwdis always the scaffold target (the CLI's<repo>argument becomes implicit — this is always "this lane's own directory" in the UI flow, never an arbitrary path).scaffoldProfile(repoPath, facts, {force})returns{written: string[], todos: string[]}(confirmed from its actual implementation,server/lib/lane-detect.js:229-) and throws.code === "EPROFILEEXISTS"if a profile is already there andforcewasn't set. Route returns{scaffolded: true, written: string[], todos: string[]}on success;{scaffolded: false, reason: string}(200, not an error) whendetectNodefinds nothing — matching the CLI's own "not an error, just nothing to scaffold" framing;400 {error:{code:"EPROFILEEXISTS",...}}when a profile already exists andforceis false. AddLaneModal.tsx: afterapi.lanes.worktree()succeeds, fireprofileInit,agentsInstall, andmcpSyncin parallel (Promise.allSettled— all three are independent of each other:agentsInstall/mcpSynconly need.git, not a profile). Each outcome (success/failure/skipped) is collected and shown as a short per-step result list before the modal closes, instead of closing silently the instant the lane record exists.
Explicitly out of scope:
ccam skills install— machine-wide, one-time-per-machine, not lane-specific. Running it every time someone adds a lane would be redundant (idempotent, but pointless) and conceptually wrong (it has nothing to do with THIS lane). Stays a manual Settings-page action.- Blocking lane creation on any of the three setup steps failing. A lane with a non-Node.js repo, no MCP servers configured, or a git quirk should still exist and be usable — setup is best-effort enrichment, not a precondition.
- Adopting an EXISTING folder (a second "add lane" mode) —
AddLaneModalonly does managed-worktree creation today; that's unchanged. This auto-setup applies to whatever lane the existing flow just created, regardless of how.
Design
Route
POST /api/lanes/:id/profile/init in server/routes/lanes.js, alongside the other per-lane setup routes (/agents/install, /mcp/sync) added in E3/F1:
router.post("/:id/profile/init", sameOriginGuard, (req, res) => {
const lane = laneOr404(req, res);
if (!lane) return;
const facts = detectNode(lane.cwd);
if (!facts) {
return res.json({ scaffolded: false, reason: "no detectable Node.js project" });
}
try {
const result = scaffoldProfile(lane.cwd, facts, { force: req.body?.force === true });
res.json({ scaffolded: true, written: result.written, todos: result.todos });
} catch (err) {
if (err.code === "EPROFILEEXISTS") {
return res.status(400).json({ error: { code: err.code, message: err.message } });
}
res.status(500).json({ error: { code: err.code || "ERUNTIME", message: err.message } });
}
});
AddLaneModal.tsx
After the existing api.lanes.worktree() call succeeds (currently: call onAdded, close modal), insert a "setting up…" phase:
const setup = await Promise.allSettled([
api.lanes.profileInit(result.lane.id),
api.lanes.agentsInstall(result.lane.id),
api.lanes.mcpSync(result.lane.id),
]);
Show a compact three-line result summary (profile / agents / MCP, each ✓/✗/skipped) for a couple seconds (or until the user dismisses it) before calling onAdded + closing — the user should see what happened, not have it vanish silently. A failure here is informational, never blocks proceeding to the new lane.
Client API
client/src/lib/api.ts's lanes object gains profileInit: (id: number, force = false) => request<{scaffolded: boolean, written?: string[], todos?: string[], reason?: string}>(...).
Verify
Backend: extend the manual-smoke-check convention (no automated HTTP harness in this repo) for the new route. Frontend: a test on AddLaneModal confirming all three setup calls fire after a successful worktree() call, and that a failure in one doesn't block the others or prevent onAdded from eventually firing.