From 5f114d7c6ff97dfd08bd990f91b6cbe39a6fd1b7 Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Thu, 6 Aug 2026 09:09:30 +0700 Subject: [PATCH] =?UTF-8?q?docs(lanes):=20design=20F4=20=E2=80=94=20surfac?= =?UTF-8?q?e=20E1-F3c=20lane=20actions=20in=20the=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LaneCard gets agents-install/mcp-sync buttons, integration status badges, and a sync-base --check button (read-only preflight only — no merge button, that stays a session/skill action). Settings gets skills-install and housekeeping (gc) buttons, machine-wide. Two new routes needed (POST /api/skills/install, POST /api/lanes/gc) since those two primitives were CLI-only until now. --- .../2026-08-06-lane-actions-ui-design.md | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-06-lane-actions-ui-design.md diff --git a/docs/superpowers/specs/2026-08-06-lane-actions-ui-design.md b/docs/superpowers/specs/2026-08-06-lane-actions-ui-design.md new file mode 100644 index 0000000..45375cd --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-lane-actions-ui-design.md @@ -0,0 +1,86 @@ +# F4 — Surfacing E1-F3c's lane actions in the UI + +**Status:** approved 2026-08-06. + +## Problem + +E1 through F3c (this session) built six CLI-only primitives — `ccam lanes sync-base`, `ccam lanes agents install`, `ccam lanes mcp sync`, `ccam lanes integration `, `ccam lanes gc`, `ccam skills install` — with no UI surface. The user wants the lane-scoped, day-to-day-useful ones clickable from the dashboard instead of terminal-only. + +## Scope + +**In scope — LaneCard.tsx (per-lane, gated on `runtime?.available`, same condition the existing up/down button already uses since all four need a `.ccam/profile`):** +1. **"Install agents" button** → `POST /lanes/:id/agents/install` (exists). Shows the installed file list on success. +2. **"Sync MCP" button** → `POST /lanes/:id/mcp/sync` (exists). Shows synced servers + seeded profiles. +3. **Integration status badges** (tracker / dev_qc / ci_wait), read-only, on/off — reads `GET /lanes/:id/integrations/:name` (exists) for each of the three fixed names. +4. **"Check dev sync" button** → `POST /lanes/:id/sync-base` with `{mode: "check"}` (exists — the route already supports `mode`). Shows `DEV_DELTA`/`DEV_OVERLAP` or a collision list. **Read-only preflight only — no merge button.** Merging is a deliberate, evidence-recording action the driving skill/session takes (`ccam lanes sync-base` bare, or `--continue`); a UI click is the wrong place for a git merge with fix-loop implications. This matches the project's own boundary: "the console never writes a lane's stage" — extending that spirit, the console doesn't perform git merges either. + +**In scope — Settings.tsx (machine-wide, new "Lanes" section near the existing Hooks section — modeled on "Reinstall Hooks"/"Cleanup"):** +5. **"Install ship-feature-lane skill" button** → new route `POST /api/skills/install` (doesn't exist yet — needs building; wraps the same `fs.cpSync` logic `ccam skills install`'s CLI already has, factored into `server/lib/lane-agents.js` or a new tiny `server/lib/skills-install.js` so the CLI and route share one implementation instead of duplicating the copy logic). +6. **"Run housekeeping" button, with a "dry run" checkbox** → new route `POST /api/lanes/gc` (doesn't exist yet — wraps `server/lib/lane-gc.js`'s `reapOrphanMcp`/`capOversizedLogs`, already built and tested in F3c). Shows reaped pids + capped log paths. + +**Explicitly out of scope:** +- A merge button for `sync-base` (see #4's reasoning). +- Any UI for `ccam lanes sync-base --continue` (conflict resolution is a session/skill action with real judgment calls about which side to keep — not a button). +- Refactoring the CLI (`bin/ccam.js`) to call the new HTTP routes instead of its current direct `require()` of the lib modules — the CLI's local-only shape (no server round-trip needed when you're already on the machine) stays as-is; only the browser needs an HTTP path, so the routes are additive, not a replacement for the CLI's existing implementation. + +## Design + +### Two new backend routes + +- `POST /api/skills/install` — same `sameOriginGuard` as every other mutating route. Extracts the copy logic (`REPO_ROOT/.claude/skills/ship-feature-lane` → `~/.claude/skills/ship-feature-lane`, `fs.cpSync` with `force: true`) out of `bin/ccam.js`'s `cmdSkillsInstall` into a small exported function in `server/lib/skills-install.js`, so `bin/ccam.js` and the route both call the same code (no duplication). Returns `{installed: true, path: string}`. +- `POST /api/lanes/gc` — same guard. Body `{dryRun?: boolean}`. Calls `reapOrphanMcp`/`capOversizedLogs` from the already-built `server/lib/lane-gc.js` (no changes needed there — Task 2 of F3c already exports both). Returns `{reaped: number[], capped: {path: string, sizeBefore: number}[]}`. + +Both routes are machine-wide (no `:id`), registered at the router's top level (not under `/lanes/:id/...`) — `/api/skills/install` sits alongside other non-lane-scoped routes; `/api/lanes/gc` sits in the lanes router but before any `:id` param route (order matters: Express would otherwise try to resolve `gc` as a lane id on a route like `/lanes/:id`). + +### Client API additions (`client/src/lib/api.ts`) + +Inside the existing `lanes` object: +```typescript +agentsInstall: (id: number) => + request<{ installed: string[] }>(`/lanes/${id}/agents/install`, { method: "POST", body: "{}" }), +mcpSync: (id: number) => + request<{ servers: string[]; profilesSeeded: string[] }>(`/lanes/${id}/mcp/sync`, { method: "POST", body: "{}" }), +integration: (id: number, name: string) => + request<{ enabled: boolean }>(`/lanes/${id}/integrations/${encodeURIComponent(name)}`), +syncBaseCheck: (id: number, branch?: string) => + request<{ code: number; devDelta?: string[] | null; overlap?: string[] | null; collisions?: { file: string; collidesWith: string; suggestion: string }[] }>( + `/lanes/${id}/sync-base`, + { method: "POST", body: JSON.stringify({ mode: "check", branch }) } + ), +``` +A new top-level `settings` object addition (the existing `api.settings.reinstallHooks`/`cleanup` group): +```typescript +skillsInstall: () => request<{ installed: true; path: string }>("/skills/install", { method: "POST", body: "{}" }), +gc: (body: { dryRun?: boolean } = {}) => + request<{ reaped: number[]; capped: { path: string; sizeBefore: number }[] }>("/lanes/gc", { + method: "POST", + body: JSON.stringify(body), + }), +``` + +### LaneCard.tsx + +Four additions, all gated on `runtime?.available` (same condition the up/down button already uses — every one of these four needs a `.ccam/profile`): + +- **Install agents / Sync MCP buttons**: same visual pattern as the up/down toggle (`rounded px-2 py-1 text-fg-secondary transition-colors hover:bg-surface-2 disabled:opacity-50`), a `useState<"agents" | "mcp" | "sync-check" | null>` busy flag (one shared state covering all three new button actions plus check, mirroring `runtimeBusy`'s shape), result shown as a transient inline line below the button (reuse the `laneActionError`-style banner pattern, but for success text too — a new `actionResult: {label: string; message: string} | null` state scoped to LaneCard). +- **Integration badges**: a small row of three pill-shaped badges (`tracker` / `dev_qc` / `ci_wait`), fetched once when the card mounts and `runtime.available` is true (parallel `Promise.all` of the three `api.lanes.integration` calls, same effect-on-mount shape the existing `git`/`runtime` fetches already use), green/gray styling for on/off, no click handler — read-only. +- **Check dev sync button**: on click, calls `syncBaseCheck`; renders the result inline — `DEV_DELTA: N files` / `DEV_OVERLAP: none` or the file list, OR on `code: 5` the collision list with each suggested rename. No merge action anywhere in this UI path. + +### Settings.tsx + +New "Lanes" section (after the existing Hooks section, matching its heading/TOC-entry style), two buttons following the exact `runAction(key, asyncFn)` / `actionBanner([key])` pattern the Hooks/Cleanup buttons already use: +- "Install ship-feature-lane skill" → `api.settings.skillsInstall()`. +- "Run housekeeping" with a dry-run checkbox (state alongside the existing `abandonHours`/`purgeDays` inputs) → `api.settings.gc({dryRun})`. + +### i18n + +New keys in `client/src/i18n/locales/{en,vi}/lanes.json` (agents-install/mcp-sync/integration/sync-check labels + result messages) and `client/src/i18n/locales/{en,vi}/settings.json` (skills-install/housekeeping labels + result messages) — both locales updated together, matching every existing key's bilingual completeness. + +## Testing + +- Backend: unit tests for the two new routes' handler logic is thin (both just call already-tested lib functions) — a manual `curl` smoke check per this repo's established "no HTTP test harness" precedent is the verification, same as every other E/F route this session. +- Frontend: `LaneCard.test.tsx` gets new test cases for the four additions (button renders when `runtime.available`, hidden when not; click calls the right `api.lanes.*` method; busy state disables the button during the call). `screens.snapshot.test.tsx` will need its snapshot regenerated (`cd client && npx vitest run -u`) after the Settings/Workspace screens change — per this repo's own testing policy, review the diff before accepting it, don't blindly update. + +## Verify + +`npm run test:server` (route smoke-tested manually, but the full suite must stay green), `npm run test:client` including the regenerated snapshot, and a manual click-through in the dev UI: open Workspace, confirm the four LaneCard additions appear only for a profile-having lane and each button's result renders; open Settings, confirm both new buttons run and show a result banner.