Files
waggle-os/docs/ux-refactor/gap-cards/S03-command-center.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

183 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gap Card — S03 · Command Center (Ctrl+K)
> Execution model is the LOCKED **in-place incremental refactor** of `apps/web` + targeted
> backend extensions. Mockup (`Waggle_OS_Handoff_Assets/screen_03_win_k_command_center.png`) is
> DIRECTIONAL only; PRD §12.3 acceptance criteria win. Every claim below is grounded in a real file.
---
## 1. Screen & purpose
The universal command layer — opens from anywhere with Ctrl+K / Cmd+K — for **search, launch,
create, run, navigate, and extend** across every major object type. PRD §12.3 (lines 449-482),
blueprint screen 3 (`_blueprint_extracted.txt:284-290, 633, 679`). It is the product's "primary
interaction" per blueprint line 19 ("Primary interaction: Ctrl+K") and the IA spine item #3
(PRD line 19). Goal: "No user needs to know where a feature lives to use it" (PRD line 482).
The mockup shows a centered modal titled "What do you want to do?" with: a search input
("Search anything…"), pill tabs (Show my tasks / Open … STM Strategy / Find market analysis /
Summarize…), five category columns — **Launch · Create · Run · Navigate** plus a Search bucket —
each listing object/action rows, a "Suggested for you" card strip (review reports, draft proposals),
and a footer "Type a natural-language command…". A second "Open in new window" affordance is top-right.
---
## 2. Required states (PRD/Blueprint)
PRD §12.3 functional requirements (lines 461-467):
- Opens from anywhere via Ctrl+K / Cmd+K.
- Searches across **workspaces, memory, artifacts, sessions, people, agents, skills, commands, connectors, MCPs**.
- Category sections: **Search, Launch, Create, Run, Navigate, Extend**.
- Supports **natural-language command input**.
- Displays **recent and suggested** actions.
- **Permission-gated actions show an approval prompt before execution.**
- Reachable by both mouse and keyboard.
PRD §12.3 states (lines 469-477) + blueprint (`:289-290`):
Idle · Query active · Grouped results · No results · Permission prompt · Command success · Command failure.
Acceptance (lines 479-482): every major object and action is reachable; no user needs to know where a feature lives.
---
## 3. Current state in repo
**Disposition: `rework`** (keep the proven overlay shell + keyboard nav + adapter calls; restructure
categories to Search/Launch/Create/Run/Navigate/Extend; add execute + permission-prompt + recent/suggested;
back it with the net-new `/api/command/*` provider).
- **`apps/web/src/components/os/overlays/GlobalSearch.tsx`** (362 lines) — the existing Ctrl+K/Cmd+K
overlay. Today it does **client-side federated search only** across 5 categories
(`SearchCategory = 'command' | 'workspace' | 'memory' | 'session' | 'skill'`, line 15):
- `command` = a **hardcoded static `COMMANDS` array** of 23 app ids (lines 40-64) that must be
hand-kept in sync with `appConfig` in `Desktop.tsx` (the file's own comment flags this drift as
"the cause of FR #13", lines 33-39).
- `workspace`/`session`/`skill` pre-fetched on open via `adapter.getWorkspaces()`,
`adapter.getSessions(ws.id)` (first 5 ws × 3 sessions, lines 98-127), `adapter.getSkills()` (lines 129-138).
- `memory` = debounced (300 ms, min 2 chars) server call `adapter.searchMemory(query, globalScope?'global')`
(lines 142-168) — the **only** server-backed category.
- Matching is client-side `fuzzyMatch` (`lib/fuzzy-match`, line 11/177-187); keyboard nav (↑/↓/Enter/Esc),
selection clamp, scroll-into-view all already work (lines 224-239).
- There is **no execute path** — every Enter calls `onNavigate(category, id)` (lines 231-235, 320),
i.e. it only *opens a window*. No Create/Run/Extend, no recent, no suggested, no permission prompt.
- **`Desktop.tsx` wiring** — `<GlobalSearch open onClose onNavigate={handleSearchNavigate} />` (`:464`),
toggled by `ov.toggleGlobalSearch` (Ctrl/Cmd+K via `useKeyboardShortcuts`, `:207`) and the StatusBar
search button (`:424`). `handleSearchNavigate` (`Desktop.tsx:217-225`) handles only `command``wm.openApp`,
`workspace``selectWorkspace`+`openChatForWorkspace`, `memory``openApp('memory')`. **No `session`/`skill`
navigation, no execute.**
- **`apps/web/src/components/os/overlays/KeyboardShortcutsHelp.tsx`** (92 lines) — static cheat-sheet
modal (`shortcuts` array, lines 9-32). Lists "⌘K → Global Search" (line 21). **Disposition `keep`**
it is the separate `Cmd+?` help overlay, not the command center; only update its label if Ctrl+K is
rebranded "Command Center".
- **`adapter.executeCommand(command, workspaceId)`** (`lib/adapter.ts:1345-1350`) → `POST /api/commands/execute`
(note **plural** `commands`). Runs **slash commands only** (`/catchup`, `/status`, `/memory`, `/skills`);
workflow/spawn commands return "not available" (`packages/server/src/local/routes/commands.ts:1-87`).
Exists but is **not** the generic palette execute.
- **`adapter.searchSessions`** (`adapter.ts:424-427`), `adapter.searchMemory` (`:476-487`) exist and are
reusable as federation inputs. `lib/suggested-actions.ts` is **chat-message-scoped** (extracts follow-up
chips from the last assistant message) — NOT command-palette suggestions; do not reuse for §12.3 "suggested".
---
## 4. Frontend work
**Rework `GlobalSearch.tsx` → `CommandCenter.tsx`** (rename or keep filename; founder-directional).
Reuse 100% of the modal chrome, framer-motion animation, debounce, keyboard-nav, selection/scroll logic.
Concrete changes:
- **Expand `SearchCategory`** to the PRD §12.3 verbs: add `'create'`, `'run'`, `'navigate'`, `'extend'`
alongside `'search'` (today's `command/workspace/memory/session/skill` become **Search/Navigate** result
feeds). Category headers + ordering (`CATEGORY_LABELS`/`CATEGORY_ORDER`, lines 67-75) extend accordingly —
this matches the mockup's Launch/Create/Run/Navigate columns.
- **Replace client federation with a server provider.** Swap the multi-call client logic (lines 91-168)
for a single debounced `adapter.commandSearch(q, { scope })``GET /api/command/search?q=` (net-new
adapter method on `lib/adapter.ts`, the one contract surface). Keep the existing per-category calls only
as offline/fallback. Eliminates the hardcoded `COMMANDS`-vs-`appConfig` drift (FR #13).
- **Add an execute path.** New `onExecute(commandId|nlInput)``adapter.commandExecute()`
`POST /api/command/execute`. On Enter for a Run/Create/Extend item (vs Navigate items which keep calling
`onNavigate`). Render the §12.3 **permission prompt** state inline before executing gated actions
(reuse the approvals pattern — `useChat().pendingApproval`/`approveAction` and `ApprovalRequest` type,
`lib/types.ts`), plus **command success / command failure** toasts (`hooks/use-toast`).
- **Recent + Suggested.** On idle (empty query), render two strips matching the mockup: "Recent" from
`adapter.commandRecent()``GET /api/command/recent`, and "Suggested for you" from
`adapter.commandSuggestions()``GET /api/command/suggestions`.
- **Natural-language input** (PRD line 464): when the query doesn't match a structured result, surface a
"Run as command: '<query>'" row that posts the raw string to execute.
- **New adapter methods** (4): `commandSearch`, `commandExecute`, `commandRecent`, `commandSuggestions`.
- **State**: keep local `query/selected/sections`; add `recent`, `suggested`, `pendingPermission`,
`executing` states. Props: extend `GlobalSearchProps` (line 27) with `onExecute` and a workspace-id
for execute scoping. New optional hook `useCommandCenter()` (wraps the 4 adapter calls + debounce) is the
clean home for the provider, mirroring the existing domain-hook pattern.
---
## 5. Backend work (PRD §16.3)
All four are MISSING from the sidecar (grep-confirmed in `backend-routes.md:448-451`; `/api/command/*` =
0 matches in `packages/server/src/local/routes/*.ts`). None needs a new data store — all federate over
existing substrate, consistent with the in-place model.
| PRD §16.3 endpoint | Status | Plan |
|---|---|---|
| `GET /api/command/search?q=` | **MISSING** | **NET-NEW** route file `packages/server/src/local/routes/command.ts`. Federates over existing reads: workspaces (`WorkspaceManager.list()`), memory (`MindDB` full-text, reuse `memory.ts` search), sessions (reuse `sessions.ts` `/sessions/search`), skills (`skills.ts`), connectors (`connectors.ts`), MCPs (from `capabilities/status` `mcpServers[]` + `@waggle/shared` `mcp-catalog.ts`), agents/personas (`personas.ts`+`agent-groups.ts`), and the app/command catalog (extract from a shared catalog to kill the `COMMANDS` drift). Substrate touched: read-only across `memory_frames`, sessions JSONL, install-audit/marketplace, workspace configs. **No `.mind` migration.** |
| `POST /api/command/execute` | **PARTIAL** | EXTEND, do **not** duplicate. `POST /api/commands/execute` (plural, `commands.ts`) already runs slash commands with a real `CommandContext`. Either (a) add a singular `/api/command/execute` alias that broadens the context to also dispatch Navigate/Create/Run/Extend intents (open app, create object, fleet-spawn, install), or (b) widen the existing plural route. Reuses `commandRegistry.execute`, `fleet/spawn`, `marketplace/install`. Substrate: same as the underlying action; add an **install-audit** write for gated executes (`InstallAuditStore.record`, `core/install-audit.ts`). **No migration.** |
| `GET /api/command/recent` | **MISSING** | **NET-NEW** (or derive client-side). Cheapest server path: record executed commands to `ai_interactions` / a small recents list and read back; or derive from session/event history (`events.ts`). Substrate: `ai_interactions` table (read) — no schema change required. |
| `GET /api/command/suggestions` | **MISSING** | **NET-NEW**. Reuse the **workspace-state next-actions** seed: `deriveNextActions` in `packages/server/src/local/workspace-state.ts:182-218` + `buildWorkspaceNowBlock` (`workspace-context.ts`) give cross-workspace suggestion candidates; `skills.ts` `/skills/suggestions` is the skill-domain analog to fold in. Substrate: read-only over memory/awareness/cron. **No migration.** |
Permission gating: the execute route should run gated actions through the existing approval/SecurityGate
path so the FE permission-prompt state has a real backend (reuse `approval.ts` + marketplace SecurityGate).
---
## 6. Shared types needed (PRD §15 vs lib/types.ts)
PRD §15 defines no dedicated Command type, but the palette needs a result/command union. Add to
`apps/web/src/lib/types.ts` (and mirror server-side in the route):
- `CommandResult { id; kind: 'search'|'launch'|'create'|'run'|'navigate'|'extend'; objectType: 'workspace'|'memory'|'artifact'|'session'|'person'|'agent'|'skill'|'connector'|'mcp'|'command'|'app'; title; subtitle?; icon?; score; requiresApproval?: boolean; payload? }` — supersedes the local `SearchResult` interface (`GlobalSearch.tsx:17-25`).
- `CommandExecuteRequest`/`CommandExecuteResult` (success/failure + optional permission descriptor).
- Reuse existing `ApprovalRequest` (`lib/types.ts`) for the permission-prompt state rather than inventing a new one.
- The `objectType` union overlaps PRD §15.2's missing unions (`ArtifactKind`, `AgentType`) and the absent
Artifact entity (substrate-types §e) — Artifact/Agent results are blocked until those screens land (see §7).
---
## 7. Dependencies (screens/phases first)
- **PRD Sprint 3 = Command Center** (PRD lines 1321-1326: indexed search provider, result groups,
command execution, recent/suggested) — this card IS Sprint 3. Depends on **Sprint 1 (Shell + Ctrl+K**,
blueprint `:582`) being the home of the command provider.
- **Search breadth is gated by other screens' substrate.** "artifacts" and "agents/people" facets need:
Artifacts (S05, PRD §16.6 — entirely net-new, substrate-types §e: no Artifact entity exists) and
Agents (S09, PRD §16.7 — sidecar agent CRUD MISSING). Ship Command Center with the **available** facets
(workspaces/memory/sessions/skills/commands/connectors/MCPs) and add artifact/agent facets when those
screens land. Do not block the whole screen on them.
- Execute's Create/Run/Extend intents lean on existing fleet-spawn / marketplace-install / cron — already present.
---
## 8. Effort
**L.** The FE overlay is largely reusable (shell, keyboard nav, debounce all done — that caps it below XL),
but the work spans: 4 net-new/extended backend routes federating across ~8 substrates, an execute +
permission-prompt path with audit writes, recent/suggested providers, a shared `CommandResult` type, and
killing the `COMMANDS`/`appConfig` drift — full-stack across multiple existing route files.
---
## 9. Open questions
1. **Route naming:** add singular `/api/command/*` (PRD-literal) as the new surface, or rename the existing
plural `/api/commands/execute`? Plural is already referenced by `adapter.executeCommand` + `commands.ts`.
Recommend: new singular `command.ts` + alias execute to the existing registry to avoid a breaking rename.
2. **"Open in new window"** affordance in the mockup (top-right) — is the Command Center expected to also
open as a persistent windowed app (an `AppId`), or is it modal-only? Affects whether it needs an
`appConfig`/dock entry vs staying an overlay.
3. **Suggested/recent scope:** cross-workspace blended, or scoped to the active workspace? PRD says
"recent and suggested" without scope; `deriveNextActions` is per-workspace today.
4. **Natural-language commands** (PRD line 464): heuristic intent-parse (cheap, deterministic) vs an LLM
round-trip? The existing `commands.ts` is registry-keyed; NL needs an intent resolver — confirm budget.
5. **Permission prompt reuse:** is the chat approvals pipeline (`approval.ts` + `useChat.pendingApproval`)
the intended mechanism for palette-initiated gated actions, or a lighter inline confirm?
6. The `COMMANDS`-vs-`appConfig` drift (FR #13) — confirm the shared app-catalog refactor is in-scope here
vs a separate cleanup (it is the right place to consolidate `AppId` and retire stale `AppView`).