moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
# Gap Card — S00 · AppShell + Information Architecture + Navigation
> Screen ID: **S00** · UX-refactor planning track · grounded in live `apps/web/src` + `packages/*`.
> Execution model is **LOCKED**: in-place incremental refactor of the existing shell, not a rebuild.
> PRD = `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`.
> Disposition: **rework** (the shell exists and is strong; it is reframed + extended, not recreated).
---
## 1. Screen & purpose
S00 is the **application chrome and navigation spine** that every other screen mounts inside — it is
not a "screen" the user opens, it is the frame they always see. PRD §1 names the spine: Home Cockpit,
Workspace Desktop, Command Center (Ctrl+K), visible Memory, Extend layer, Team. S00 is the layer that
makes that spine reachable.
Per the blueprint Developer Handoff (`_blueprint_extracted.txt:498`), **AppShell** = "Global route
layout, sidebar, active workspace, top status, command palette provider." Its responsibilities:
- Persistent top status bar (workspace, model, memory trophy, trial, search, notifications, clock).
- Primary navigation expressing the six IA layers (PRD §10: Global / Work / Intelligence / Extend /
Team / System) — blueprint `_blueprint_extracted.txt:68-81` + page-3 `06_board_all_in_one.png`.
- Global, always-available command layer (Ctrl+K) — PRD §12.3, §6.3.
- Active-workspace context held in a global store and threaded to every surface
(`_blueprint_extracted.txt:512`).
- The container for empty / loading / error / offline / permission-denied states (PRD §14.1).
**Purpose:** collapse the current 27-app "app-launcher" mental model into the IA spine
(PRD §3.2, §3.3, §20.4 "do not add more top-level apps without fitting IA"), without rebuilding
the windowed runtime that already works.
---
## 2. Required states (PRD / Blueprint)
S00 itself is mostly stateless chrome, but it **hosts** the global states and must render them:
- **Global states (PRD §14.1, blueprint AC `:604`):** Loading, Empty, Populated, Error,
Offline/local-only, Syncing, Permission denied, Partial data, Approval required — every screen
inside the shell must support these; the shell provides the offline indicator + error boundary +
approval modal mounts.
- **Navigation states:** active layer/route highlighted; active workspace shown in status; open vs
minimized window indicators (current dock dots).
- **Command Center states (PRD §12.3):** Idle, Query active, Grouped results, No results, Permission
prompt, Command success, Command failure.
- **Offline / local-only (PRD §6.7, §14.1; blueprint `:516`):** shell degrades connectors/MCPs
gracefully while keeping local workspace + memory reachable; offline badge in status bar.
- **First-run vs returning:** onboarding wizard replaces the shell entirely on first launch
(current behavior, see §3); returning users land in the populated shell.
- **Accessibility (PRD §19.3, blueprint `:489`):** keyboard-first, Ctrl+K + tab nav, visible focus,
non-color status, text labels on all badges.
Layout direction (blueprint `:483`, directional only): "Desktop-first, **left navigation**, central
workspace canvas, optional right context rail, Ctrl+K overlay." Note this conflicts with the current
**bottom-dock** shell — see Open Questions Q1 (the PRD acceptance criteria, not the mock, win:
PRD §24 risk row "Visual mocks overfit implementation").
---
## 3. Current state in repo (disposition: **rework**)
**There is no `AppShell` / `AppLayout` / `GlobalLayout` component** — grep over `apps/web/src` for
`AppShell|GlobalLayout|AppLayout` returns **zero matches**. The shell's responsibilities are spread
across these real files:
| File | What it does today |
|---|---|
| `apps/web/src/components/os/Desktop.tsx` | **The de-facto AppShell.** Root OS shell: wires all domain hooks, the window manager, `appConfig` (per-`AppId` title/icon/pos/size, `:77-102`), `renderAppContent` (the `switch(win.appId)` app router, `:276-363`), all overlays, theme observer, trial/tier fetch, onboarding early-return (`:262-273`), the `waggle:open-app` CustomEvent bus (`:162-169`). ~550 LOC. |
| `apps/web/src/components/os/StatusBar.tsx` | Top bar (PRD "top status"): logo, workspace name, focused-window label, model, memory-frame trophy, dev tokens/cost, trial badge, **Search button (Ctrl+K)**, notifications bell, offline indicator, clock. |
| `apps/web/src/components/os/Dock.tsx` + `lib/dock-tiers.ts` + `DockTray.tsx` | **The de-facto primary navigation.** Bottom dock, tier-filtered `DockEntry[]`, `zone-parent` flyouts (Ops, Extend) via portal-to-body `DockTray`, open/minimized dots, Spawn Agent, Waggle badge. `dock-tiers.ts` is the canonical `AppId` union + `getDockForTier()`. |
| `apps/web/src/components/os/AppWindow.tsx` | Draggable/resizable/snappable window chrome (one per open app). The "windowed runtime" — orthogonal to IA but the thing the IA must keep. |
| `apps/web/src/hooks/useWindowManager.ts` | Window state machine (`WindowState[]`, persisted to `localStorage waggle-window-state-v1`), `openApp(AppId)` / `openChatForWorkspace(...)`, focus/minimize/cycle. **Navigation == window ops, keyed by `AppId`, NOT by URL.** |
| `apps/web/src/hooks/useOverlayState.ts` | All overlay open/close flags (global search, switchers, notifications, etc.) — the global "command palette state / overlay state" store. |
| `apps/web/src/components/os/overlays/GlobalSearch.tsx` | **The de-facto Command Center (Ctrl+K).** Ctrl+K palette, 5 categories (`command\|workspace\|memory\|session\|skill`, `:15`), but built on a **static `COMMANDS[]` array (`:40-64`)** that is hand-synced to `appConfig` (drift warning in-file `:33-39`) — no Create/Run/Extend categories, no backend command index. |
| `apps/web/src/hooks/useKeyboardShortcuts.ts` | Binds global hotkeys (Ctrl+K search, persona/workspace switchers, new chat, window cycle). |
| `apps/web/src/lib/types.ts` | Holds the **stale** `AppView` union (8 ids, `:3-11`). |
| `apps/web/src/pages/Index.tsx` (route `/`) + `App.tsx` | Single-route app: `BrowserRouter``/``BootScreen``Desktop`. **No per-app routes exist** (frontend inventory `frontend.md:9-13`). |
**Key structural facts driving the rework (not a rebuild):**
1. **Single-route windowed desktop, not a navigable app.** "Navigation" is window management by
`AppId`. The blueprint's `/home,/workspaces,/memory,…` route groups (`:81`) and "left navigation"
(`:483`) are **directional**; the in-place model expresses the six IA layers through the existing
**dock-zone model** (`dock-tiers.ts` `zone-parent`) + window manager — NOT by introducing
react-router routes (PRD §20.2 "App surfaces -> Work/Intelligence/Extend categories";
§20.4 "Do not add more top-level apps without fitting IA").
2. **Dual app-id union drift (cleanup target).** `AppId` (27 ids, canonical, `dock-tiers.ts:7-13`)
vs `AppView` (8 ids, stale, `types.ts:3-11`). `AppView` is unused by the window manager. Dead ids
`terminal` / `calculator` / `notes` are declared in `AppId` but have **no component / no
`appConfig` entry** (Desktop `:77-102`) — dead.
3. **Three overlapping tier vocabularies** the IA gating must reconcile (frontend inventory
`frontend.md:376`): `UserTier` (UI density `simple\|professional\|power\|admin`, `dock-tiers.ts:15`),
`BillingTier` (`FREE\|TRIAL\|PRO\|TEAMS\|ENTERPRISE`, `:17`), and `PlanTier`
(`solo\|teams\|business\|enterprise` in `lib/feature-gates.ts`). Dock filters on
`UserTier` × `BillingTier` (`getDockForTier`, `:127`).
4. **Marketplace is doubly represented** (standalone `MarketplaceApp`, no dock entry, + a section
inside `CapabilitiesApp`) — an IA-consolidation point (frontend inventory `frontend.md:374`).
5. **IA→existing-app mapping already drafted** in `frontend.md:357-364` (Global/Work/Intelligence/
Extend/Team/System buckets → current apps). S00 hardens that mapping into dock zones + labels.
**Why rework, not create-new:** Desktop/Dock/StatusBar/AppWindow/window-manager are functional,
test-covered, and PRD §20.1 explicitly says keep+promote the substrate. The deliverable is an IA
**reframe** (zone labels, consolidation, union dedup, command-index wiring), not a new shell.
---
## 4. Frontend work
> Principle (CLAUDE.md §3.3 surgical changes): extend the existing shell. Do not introduce
> react-router app routes. Express IA through dock zones + a shared app catalog.
### 4a. Consolidate to one app catalog (kills the drift)
- **Rework `lib/dock-tiers.ts` into / beside a `lib/app-catalog.ts`** that is the single source for:
`AppId`, per-app metadata (title, icon, default pos/size — currently siloed in `Desktop.appConfig`
`:77-102`), IA-layer membership (`Work|Intelligence|Extend|Team|System|Global`), and tier gating.
Derive **both** `appConfig` (Desktop) and `COMMANDS[]` (GlobalSearch `:40-64`) and `DockEntry[]`
from it. Fixes FR #13 drift (called out in `GlobalSearch.tsx:33-39`).
- **Delete the stale `AppView` union** (`types.ts:3-11`) and the dead `terminal`/`calculator`/`notes`
ids from `AppId` (exhaustive grep first per CLAUDE.md §3.5 — they appear in `dock-tiers.ts`,
possibly tests).
### 4b. IA reframe of the dock (the "primary navigation")
- **Rework `TIER_DOCK_CONFIG`** (`dock-tiers.ts:82-105`) so the power-tier dock zones map 1:1 to the
PRD IA layers: today there are 2 zone-parents (`Ops`, `Extend`); the target is the six-layer IA
(`frontend.md:357-364` mapping). Likely outcome: rename/regroup into **Work** (Home, Chat, Memory,
Files/Artifacts, Sessions), **Intelligence** (Personas/Agents, Skills, Automations, Room/Mission
Control), **Extend** (Connectors, MCPs, Marketplace, AI Tools), **Team**, **System** (Settings,
Vault, Profile, Backup, Telemetry). Keep the existing `zone-parent` + `DockTray` mechanism.
- Resolve **Marketplace double-representation** — one canonical surface (Extend), remove the orphan.
- Keep tier filtering (`filterByBillingTier`, `:111`); reconcile the three tier vocabularies into one
gating helper (or document the mapping) as part of this card so downstream cards inherit it.
### 4c. Extract an explicit `AppShell` boundary (optional, low-risk)
- `Desktop.tsx` is ~550 LOC and conflates shell + router + state wiring (CLAUDE.md §3.2/§4 favor
smaller files). Optionally extract a thin `AppShell` that owns: status bar + dock + overlay mounts +
error boundary + offline state, leaving `Desktop` to own only the window manager + `renderAppContent`.
This is the component the blueprint names (`:498`). **Not required for behavior** — flag as a clean-up
the plan can sequence late.
### 4d. Command Center upgrade (Ctrl+K) — depends on backend §16.3 (see §5)
- **Rework `GlobalSearch.tsx`** from static-list + client fuzzy-match into a backend-fed command index:
add the PRD §12.3 category sections **Search / Launch / Create / Run / Navigate / Extend** (today only
command/workspace/memory/session/skill exist), federated over `/api/command/search` when present.
Reuse `fuzzyMatch` (`lib/fuzzy-match.ts`) as the offline fallback. Add the Command Center states
(§2). Keep the `onNavigate(type,id)``Desktop.handleSearchNavigate` (`Desktop.tsx:217-225`)
wiring; extend it for Create/Run/Extend dispatch.
### 4e. Global store for shell state (blueprint `:512`)
- Today shell state is scattered (`useWorkspaces`, `useOverlayState`, ad-hoc `Desktop` `useState` for
tier/trial/theme). Consolidate the **global** slice the blueprint names — `{ activeWorkspaceId,
command palette state, user profile, connection/offline status, feature flags }` — behind a single
provider/hook so Home/Workspace/Command surfaces read one source. Reuse `useOverlayState`,
`useWorkspaces`, `useOfflineStatus`, `useBilling`/`useFeatureGate` rather than replacing them.
**Reuse targets:** `Desktop.tsx`, `Dock.tsx`/`DockTray.tsx`/`dock-tiers.ts`, `StatusBar.tsx`,
`AppWindow.tsx`, `useWindowManager`, `useOverlayState`, `GlobalSearch.tsx`, `useKeyboardShortcuts`,
`ErrorBoundary.tsx`, `useOfflineStatus`, `useBilling`/`useFeatureGate`.
**Props/state:** `AppShell` (if extracted) takes no props (reads global store); the app catalog is a
pure module; `Dock` keeps its current props (`tier`, `billingTier`, `onOpenApp`, `openApps`,
`minimizedApps`, `onSpawnAgent`, `waggleBadgeCount`); `GlobalSearch` keeps `{open,onClose,onNavigate}`
plus internal async result groups.
**Adapter methods/hooks:** S00 chrome needs none beyond what exists. The Command Center upgrade
(4d) needs new adapter methods `commandSearch(q)` / `commandExecute(...)` / `commandRecent()` /
`commandSuggestions()` added to `lib/adapter.ts` (the one contract surface, `frontend.md:380`) once
the backend routes (§5) land.
---
## 5. Backend work
**S00 as pure shell/IA/navigation needs NO new backend** — it is frontend chrome + client-side
window routing. The dependency is the **Command Center (4d)**, which the IA spine requires
(PRD §6.3 "Ctrl+K always available"; blueprint `:515` "command index should unify workspaces, memory,
artifacts, sessions, agents, skills, connectors, MCPs, actions and recent commands").
PRD §16.3 Command Center endpoints (cross-ref `backend-routes.md:444-451`):
| PRD §16 endpoint | Status | EXTEND vs NET-NEW · substrate · note |
|---|---|---|
| `GET /api/command/search?q=` | **MISSING** | **NET-NEW** federating route. No `/api/command/*` exists; `commands.ts` is `/api/commands/execute` (slash-exec, different shape). Touches no new store — fans out over `/api/memory/search` (`memory.ts`), `WorkspaceManager.list()` (`workspaces.ts`), `/api/skills`, `/api/workspaces/:id/sessions/search` (`sessions.ts`). New `command.ts` route under `packages/server/src/local/routes/`. |
| `POST /api/command/execute` | **PARTIAL** | **EXTEND** `POST /api/commands/execute` (note **plural** `commands`, `commands.ts`). Current runs slash commands with a subset CommandContext; PRD's generic palette execute is broader (navigate/create/run/extend dispatch). Extend or add a thin singular alias. |
| `GET /api/command/recent` | **MISSING** | **NET-NEW** (or derive client-side from session/window history — defer to client first). |
| `GET /api/command/suggestions` | **MISSING** | **NET-NEW**. Closest analog `GET /api/skills/suggestions` (`skills.ts`, different domain) is a pattern to copy, not reuse. |
**No `.mind` migration for S00.** None of the Command Center routes add a data store
(`backend-routes.md:604-607`: every MISSING endpoint is buildable over existing substrates). The
status bar's memory trophy already uses `GET /api/memory/stats` (`StatusBar.tsx:40`), and trial/tier
uses `GET /api/tier` (`Desktop.tsx:136`) — both EXIST.
> Note: a command-index that searches "artifacts" (PRD §12.3) is blocked on the Artifacts domain
> (PRD §16.6, entirely MISSING — `backend-routes.md:477-484`). S00's Command Center can ship the
> existing object types first and add Artifacts/Agents/Automations groups as those screens land.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
S00 is mostly literal-union + nav typing. PRD §15.2 target unions — **none currently exist** in
`apps/web/src/lib/types.ts` (substrate-types `substrate-types.md:218-229`):
- **Reframe the IA into types:** introduce a `IALayer = 'global'|'work'|'intelligence'|'extend'|
'team'|'system'` union and attach it to the app-catalog entry type (new, in `lib/app-catalog.ts`
or `dock-tiers.ts`). Not a PRD §15 type but the type that makes the IA explicit.
- **Delete `AppView`** (`types.ts:3-11`) — superseded by `AppId` (§4a). Consolidate exports so
`AppId`/`UserTier`/`BillingTier`/`DockEntry` (currently in `dock-tiers.ts`) and the catalog type
live in one place.
- **Command Center types (for 4d):** extend `SearchCategory` (`GlobalSearch.tsx:15`) beyond
the 5 current values to PRD §12.3's Search/Launch/Create/Run/Navigate/Extend; add a `Command`
result type (PRD §11 glossary "Command" object). Add a `CommandResult` interface used by both the
adapter method return and the palette.
- **Tier reconciliation type:** a single helper type mapping `UserTier`↔`BillingTier`↔`PlanTier`
(currently three vocabularies across `dock-tiers.ts` + `feature-gates.ts`). Document or unify so
every downstream gap card inherits one gating contract.
PRD §15.2 entity unions (`WorkspaceType`, `Scope`, `Confidence`, `MemoryKind`, `ArtifactKind`,
`AgentType`, `AutonomyLevel`, `ExtensionType`) are **owned by their respective screens**, not S00 —
S00 only needs the nav/command/tier types above. Establishing the shared-types module location is an
S00 responsibility (blueprint Phase 0 "Define shared frontend types", PRD §8 Phase 0).
---
## 7. Dependencies (screens/phases first)
- **Blocks everything.** S00 is PRD Phase 0 / blueprint Phase 0 ("Freeze UX spine: route names,
screen inventory, navigation, naming, data scopes", `_blueprint_extracted.txt:581`) and PRD §21
Sprint 1 ("AppShell navigation, route map, shared types, command provider skeleton"). Every other
gap card (Home Cockpit S0x, Workspace Desktop, Memory Center, Extend, Team) mounts inside this shell
and consumes the app catalog + IA layering + global store defined here.
- **Internal ordering:** 4a (app catalog / union dedup) → 4b (dock IA reframe) → 4e (global store)
can all ship **frontend-only, no backend**. 4d (Command Center upgrade) is gated on backend §5
`/api/command/*` (PRD §21 Sprint 3) — ship the Ctrl+K **skeleton + static/offline fallback** in
Phase 0/Sprint 1 (matches "command provider skeleton", PRD §21 Sprint 1), then wire the backend
index in Sprint 3.
- **Depends on no other screen.** Consumes only existing substrate (`GET /api/tier`,
`GET /api/memory/stats`, `GET /api/workspaces`) which all EXIST.
---
## 8. Effort: **L**
Frontend-heavy reframe touching the highest-traffic, highest-blast-radius files in the app
(`Desktop.tsx` ~550 LOC, `Dock.tsx`, `dock-tiers.ts`, `GlobalSearch.tsx`, window manager) plus a
union/tier-vocabulary consolidation that requires exhaustive grep (CLAUDE.md §3.5) and the net-new
`/api/command/*` federating route. Not XL because it reuses the working windowed runtime wholesale
(no rebuild) and adds no data store / migration. The Command Center backend index pushes it from M→L.
---
## 9. Open questions
1. **Bottom dock vs left navigation.** Blueprint `:483` + mock `06_board_all_in_one.png` show
**left navigation**; the live shell is a **bottom dock** (`Dock.tsx`). Mocks are directional
(PRD §24); does the founder want the dock reframed-in-place (cheaper, keeps the OS feel) or
migrated to a left rail (closer to mock, larger blast radius)? Recommend in-place dock reframe
unless the left rail is a hard requirement.
2. **Routes vs window-IDs.** Blueprint `:81` asks for `/home,/workspaces,…` route groups; the app
is single-route windowed. Confirm we keep `AppId`-keyed window navigation (recommended, in-place)
rather than introducing react-router app routes (larger change, conflicts with multi-window).
Deep-linking/back-button behavior is the only thing real routes would buy.
3. **Tier-vocabulary unification.** Three tiers (`UserTier`/`BillingTier`/`PlanTier`) gate the dock,
billing, and features independently. Should S00 unify them into one model now (clean but
cross-cutting), or just document the mapping and defer? PRD §17 RBAC roles add a 4th axis.
4. **Command Center scope for v1 (PRD §23 Q-implied).** Which object types ship in the Ctrl+K index
first? Artifacts/Agents/Automations are blocked on their domains (§5 note). Propose:
workspaces + memory + sessions + skills + commands at launch, add the rest as screens land.
5. **AppShell extraction now or later?** Pull the shell out of `Desktop.tsx` (4c) up front for a
clean boundary, or defer to a polish pass to minimize churn during the high-velocity refactor?

View File

@@ -0,0 +1,197 @@
# Gap Card — S01 Home Cockpit
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extensions.
> PRD source of truth: §12.1 (lines 373-410), §15 (data model), §16.1 (API), §20.1 (keep/promote).
> Mockup `screen_01_home_cockpit.png` is **directional** — acceptance criteria win over pixels.
---
## 1. Screen & purpose
Home Cockpit is the **daily executive briefing and launch surface** — the first thing the user
sees after boot, before opening any workspace. PRD §12.1: "Give the user a useful daily briefing
and immediate next actions." Acceptance bar (§12.1): user understands the day in <30 s, continues a
workspace in one click, opens Ctrl+K from the keyboard, and captures a note/task/link/file from Home.
This is a **new top-level surface** in the IA spine (PRD §1: spine item #1). Today there is no Home
Cockpit; the closest things are the per-workspace `WorkspaceBriefing` (chat home screen) and the
modal `LoginBriefing` (one-shot catch-up overlay). The mockup shows six regions: greeting + date,
"You were working on" (recent workspaces with Continue), "Overnight" (consolidated / artifacts /
failures counters), "Up next" (events/tasks), "Suggested next actions", "Quick capture", and an
"Active models" tile.
---
## 2. Required states (PRD §12.1 + Blueprint p.265/447)
Functional requirements (§12.1):
- Greeting with user name + date/time.
- Active/recent workspaces ranked by recency and priority, each with one-click Continue.
- Overnight summary: memories consolidated, artifacts created, automations completed, failures.
- Upcoming meetings/events/tasks ("Up next").
- Suggested next actions (memory/session/task/schedule-derived).
- Quick capture for note/task/link/file.
- Active models + current mode, only if relevant (do not clutter).
- Ctrl+K hint/access.
States to implement (§12.1 "States" + Blueprint line 447):
`Loading` · `First-run empty` · `Normal populated` · `Attention required` · `Offline/local-only` ·
`Overnight failure` · `Permission denied for shared/team data`.
---
## 3. Current state in repo — disposition: **partial (keep-promote + create-new)**
PRD §20.1 explicitly: `WorkspaceBriefing.tsx` → "seed for Home Cockpit widgets" (keep and promote).
There is **no `HomeCockpit` component today** — it is a §20.3 "Create" item. So the work is: extract
reusable widget logic from the two existing briefing surfaces, build a new full-screen `HomeCockpit`,
and wire it to a new cross-workspace aggregation endpoint.
Existing files that feed this screen:
| File | What it does | Reuse role |
|---|---|---|
| `apps/web/src/components/os/WorkspaceBriefing.tsx` (283 LOC) | Per-workspace "home screen" inside ChatApp when a chat has no messages. Fetches `GET /api/workspaces/:id/context`; renders greeting, stats bar, pending tasks, recent decisions, "I Remember" memories, recent threads, cross-workspace hints, persona skill chips, suggested prompts, upcoming schedules. Collapse state persisted per-workspace via `lib/workspace-briefing-state`. | **Keep-promote.** Its section components (memory list, pending tasks, decisions, suggested-prompt chips, upcoming schedules) are the widget vocabulary the Cockpit reuses. It is **per-workspace**, so it can't be the Cockpit as-is. |
| `apps/web/src/components/os/overlays/LoginBriefing.tsx` (363 LOC) | Modal "I remember…" catch-up shown once after boot. Already does **cross-workspace aggregation** client-side: `getWorkspaces()` → per-workspace `getWorkspaceContext()` (N calls) + `searchMemory('…','global')` + `getMemoryStats()`. Builds workspace summaries (memoryCount/sessionCount/lastActive/summary/pendingTasks), ranked memory highlights (`lib/briefing-highlights`), brag header (`lib/login-briefing-brag`), time-aware greeting + identity name. Has first-run empty hook (3 demo bubbles). Filters E2E/test workspaces. | **Keep-promote (heaviest reuse).** Its data-gathering and ranking is exactly the Cockpit's "You were working on" + greeting + first-run-empty logic — but it does the N+1 fan-out **on the client**, which §20.4 forbids ("do not duplicate backend state calculation in frontend"). Promote this logic **into the new `GET /api/home/briefing`** server route. |
| `apps/web/src/components/os/apps/DashboardApp.tsx` | Dock app `home` — Workspaces grid (select/create/open chat), brain-health tier. Currently the thing the `home` dock key opens. | **Rework/keep.** Becomes the workspaces-grid sub-view; Home Cockpit becomes the new default `home` surface. Not the briefing itself. |
| `apps/web/src/components/os/apps/CockpitApp.tsx` (315 LOC) | "Cockpit" = system/ops dashboard (health, cost, crons, connectors, weaver, event stats, compliance). Dock id `cockpit`. **Name collision only** — this is the ops Command-Center surface, NOT the PRD Home Cockpit. | **Out of scope / do not conflate.** Maps to PRD §12.3-ish ops, not §12.1. Flag the naming clash. |
| `apps/web/src/components/os/cockpit/ComplianceDashboard.tsx` | EU AI Act compliance tiles, hosted inside `CockpitApp`. | Not relevant to S01. |
Supporting lib (frontend, reuse as-is):
`lib/briefing-highlights.ts` (`selectBriefingHighlights` — importance-then-recency ranking),
`lib/login-briefing-brag.ts` (`computeBragSummary`/`formatBragLine`/`timeAgo`),
`lib/workspace-briefing-state.ts` (collapse persistence), `lib/persona-display.ts`,
`lib/skill-recommendations.ts` (persona skill chips). **Note:** `lib/suggested-actions.ts` is for
**chat follow-up chips** (regex over the last assistant message) — it is NOT the Home "suggested next
actions" generator; do not reuse it for the Cockpit. Suggested actions for Home come from
`buildWorkspaceState().nextActions` (backend) aggregated cross-workspace.
Backend builder already present (keep-promote, §20.1): `packages/server/src/local/workspace-state.ts`
`buildWorkspaceState()` → typed `WorkspaceState` (active/openQuestions/pending/blocked/completed/stale/
recentDecisions/`nextActions`) with freshness classification. This is the per-workspace next-action
engine the Home briefing aggregates over.
---
## 4. Frontend work
**Create** `apps/web/src/components/os/apps/HomeCockpitApp.tsx` (new top-level surface; register in
`Desktop.tsx` `appConfig` + `renderAppContent`, and make the `home` dock key open it instead of
`DashboardApp``DashboardApp` demotes to a "Workspaces" grid reachable from a Cockpit tile/Ctrl+K).
Widget components (extract from the two briefing files so logic is shared, not copy-pasted — CLAUDE.md
"many small files"):
- `home/GreetingHeader.tsx` — greeting + name + date/time (promote from `LoginBriefing` lines 84-95 +
`WorkspaceBriefing` greeting). Reuse `buildTimeAwareGreeting` semantics from backend; client just renders.
- `home/RecentWorkspacesPanel.tsx` — "You were working on": ranked workspace cards w/ Continue button →
`openChatForWorkspace(id)` (via `useWindowManager`). Promote `LoginBriefing` summaries list (lines 277-334).
- `home/OvernightPanel.tsx` — counters (consolidated / artifacts / automations done / failures); failure
rows expandable → Automation Center. **New** (no current equivalent).
- `home/UpNextPanel.tsx` — upcoming events/tasks/schedules. Reuse `upcomingSchedules` + `pendingTasks`
shapes; aggregate across workspaces.
- `home/SuggestedActionsPanel.tsx` — top N `nextActions` aggregated from `buildWorkspaceState`, each
routing to its workspace. **Not** `lib/suggested-actions.ts`.
- `home/QuickCapturePanel.tsx` — note/task/link/file input → `POST /api/quick-capture`. **New.**
- `home/ActiveModelsTile.tsx` — promote `ModelPilotCard` / `useProviders`; render only when relevant.
Data layer:
- New hook `hooks/useHomeBriefing.ts``adapter.getHomeBriefing()` + `adapter.getOvernight()`, with
the §12.1 state machine (loading/empty/populated/attention/offline/overnight-failure/permission-denied).
- Extend `lib/adapter.ts` (the single sidecar gateway) with `getHomeBriefing()`, `getOvernight()`,
`quickCapture(payload)`. Per the frontend inventory, all PRD §16 endpoints get added here.
- First-run empty: reuse `LoginBriefing`'s demo-bubble hook (lines 222-247).
- Offline/local-only: reuse `useOfflineStatus`; degrade overnight/team tiles, keep local workspaces.
- Ctrl+K hint: surface the existing `GlobalSearch` (Ctrl+K) — already wired in `useKeyboardShortcuts`.
**Decision required (Open Q):** does `LoginBriefing` (modal) survive alongside Home Cockpit, or does the
Cockpit absorb it? They overlap ~80%. Recommend: collapse `LoginBriefing` into the Cockpit's first paint
and retire the modal (avoid two catch-up surfaces).
---
## 5. Backend work (PRD §16.1)
| PRD endpoint | Status | Plan |
|---|---|---|
| `GET /api/home/briefing` | **MISSING** | **NET-NEW** route (new `routes/home.ts` registered in `local/index.ts`). Server-side aggregation that promotes `LoginBriefing`'s client N+1 fan-out: iterate `workspaceManager` workspaces → reuse per-workspace logic already in `routes/workspaces.ts` `/context` handler (greeting/summary/recentMemories/pendingTasks/upcomingSchedules) + `buildWorkspaceState()` (`workspace-state.ts`) for `nextActions`, ranked by recency/priority. Reuse `briefing-highlights` ranking server-side. **Substrate touched:** `memory_frames` (per-workspace `.mind`), session JSONL, `awareness`, `cron-store`. No new store. **Refactor over EXTEND:** factor the `/context` body into a shared builder so `/context` and `/home/briefing` don't duplicate SQL. |
| `POST /api/quick-capture` | **PARTIAL** | **EXTEND** `routes/memory.ts` (or thin new handler delegating to it). Closest existing write is `POST /api/memory/frames`. Quick-capture = thin wrapper: default to **personal** mind, stamp `source: 'quick-capture'`, accept `kind ∈ note|task|link|file`. For `task` also write an `awareness` row so it surfaces in `nextActions`; for `file` route through `POST /api/ingest`. **Substrate:** `memory_frames` (personal `.mind`), `awareness`. No migration. |
| `GET /api/home/overnight` | **MISSING** | **NET-NEW** route (same `routes/home.ts`). Aggregate from existing substrates — **no new store**: `cron`/automation runs via `GET /api/cron/:id/history` (`notifications.ts`) + cron-store; consolidation/artifact counts from `events.ts` audit events (`GET /api/events?since=`) and `weaver` status (`/api/weaver/status`); failures from cron history error rows + notifications. Returns `{ consolidated, artifactsCreated, automationsCompleted, failures[] }`. The `Overnight failure` state is driven by `failures.length > 0`. |
Notes for the implementer:
- **Reuse, don't reinvent (§20.4):** the per-workspace catch-up math lives in `routes/workspaces.ts`
lines 311-592 and `workspace-state.ts`. Extract the shared body before adding the cross-workspace loop.
- **Privacy gate (CRITICAL — already bit us):** cross-workspace content aggregation was previously a
privacy leak. `routes/workspaces.ts` lines 553-564 show `crossWorkspaceHints` is **deliberately
DISABLED** (returns `[]`) because the original iterated every workspace MindDB and returned content
snippets with no grant check (security review: `cowork/Code-Review_MultiMind_April-2026.md` Critical #1).
`GET /api/home/briefing` is the SAME pattern (read every workspace) — but for Home it is the user's
**own** workspaces, so it is legitimate for personal scope. **Team/shared workspace rows must respect
`approvalGrantStore` / team RBAC** (PRD §12.1 `Permission denied for shared/team data` state). Do not
leak team-workspace content the caller can't access.
- Tier: Home briefing must work on FREE/TRIAL (it's the daily landing surface). Team-overnight rows are
TEAMS-gated; gate the team slice, not the whole endpoint.
**No `.mind` schema migration required.** All three endpoints read existing tables (`memory_frames`,
`awareness`, audit `events`) and write only via existing frame/awareness paths. (PRD §15.4 confidence/
provenance fields are a *separate, later* migration — not needed for S01.)
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
Today `lib/types.ts` has `WorkspaceContext` (lines 66-85) — per-workspace, already carries
greeting/recentMemories/pendingTasks/upcomingSchedules/crossWorkspaceHints. **Reuse its sub-shapes.**
Add new interfaces (in `lib/types.ts`, mirrored server-side):
- `HomeBriefing { greeting; date; recentWorkspaces: RecentWorkspaceCard[]; suggestedActions: SuggestedAction[]; upNext: UpNextItem[]; activeModels?; isFirstRun: boolean }`
- `RecentWorkspaceCard { id; name; group; summary?; lastActive; pendingCount; continueSessionId? }`
- `OvernightSummary { consolidated: number; artifactsCreated: number; automationsCompleted: number; failures: OvernightFailure[] }`
- `OvernightFailure { id; label; automationId?; error; at }`
- `QuickCaptureInput { kind: 'note'|'task'|'link'|'file'; content; workspaceId? }`
- `SuggestedAction { label; workspaceId; sessionId?; kind }`
Align kinds with PRD §15.2 (`MemoryKind`, `ArtifactKind`) where they overlap. These are net-new vs the
current `lib/types.ts` (which has no Home/Overnight/QuickCapture types).
---
## 7. Dependencies (screens/phases first)
- **AppShell / IA (Sprint 1, PRD §21):** Home Cockpit is the default landing surface — needs the dock
`home` key repointed and the §10 IA buckets settled first.
- **Command Center (Ctrl+K) (S03):** §12.1 acceptance "open Ctrl+K from the keyboard" — `GlobalSearch`
already exists, so this is a soft dep (hint only).
- **Workspace Desktop (S02):** "Continue" routes into the per-workspace runtime; needs `openChatForWorkspace`
(already in `useWindowManager`) — soft dep.
- **Automation Center (S11):** overnight failure rows link there; can ship Home with the count + a stub link.
- Belongs to **Sprint 2 ("Home and workspace state")** per PRD §21 — after shell, alongside the
workspace-context/state API extension.
---
## 8. Effort: **L**
Frontend is mostly **promotion** of two existing briefing surfaces into shared widgets (medium), but the
backend adds **two net-new aggregation routes** (`/home/briefing`, `/home/overnight`) that must (a)
refactor the per-workspace `/context` body into a shared builder to avoid duplication and (b) re-implement
the cross-workspace aggregation **safely** behind the grant/RBAC gate that previously caused a privacy
leak. The security-sensitive cross-workspace read is what pushes this from M to L. Not XL — no new data
store, no schema migration, and the ranking/greeting logic already exists.
---
## 9. Open questions
1. **LoginBriefing fate:** retire the modal and absorb its catch-up into Home Cockpit's first paint, or
keep both? (~80% overlap; two catch-up surfaces is confusing.)
2. **"Overnight" semantics:** time-window = since last app close? since midnight local? last 12 h? Affects
the `since=` query for events/cron history.
3. **Cross-workspace personal read:** confirm reading the user's *own* workspaces server-side (for the
briefing) is acceptable now that it's same-user (the prior leak was content snippets without grant
checks). Team/shared rows still gate through `approvalGrantStore`/RBAC — confirm the gate boundary.
4. **`home` vs `cockpit` naming:** `CockpitApp` already owns "Cockpit" (ops). PRD calls S01 "Home Cockpit".
Final dock/app naming to avoid the collision (proposal: S01 = `home`/"Home"; keep ops as `cockpit`).
5. **DashboardApp role:** does the Workspaces grid live as a Cockpit tab, a Ctrl+K destination, or stay a
separate dock app? (Affects whether `home` dock key fully repoints to HomeCockpit.)
6. **Quick-capture `file` flow:** does a file capture upload into a default/personal store, or prompt for a
target workspace? `POST /api/ingest` needs a destination.

View File

@@ -0,0 +1,219 @@
# Gap Card — S02 · Workspace Desktop
> Screen S02 of the Waggle OS UX-refactor. PRD §12.2 (lines 412-447). Execution model: **in-place
> incremental refactor** of `apps/web` + targeted sidecar extensions. Mockup
> (`screen_02_workspace_desktop.png`) is **directional**; PRD acceptance criteria win.
> Every claim grounded in repo source (paths cited).
---
## 1. Screen & purpose
**Purpose (PRD §12.2):** the primary runtime for a single bounded work context. It demotes chat
from "the whole product" to **one widget among many**, surfacing workspace state, artifacts, memory,
tasks, research, and activity on a single screen, with chat/agent interaction co-resident.
**Mockup reading** (`Waggle_OS_Handoff_Assets/screen_02_workspace_desktop.png`): a full-screen
(non-floating) layout for "Germany GTM Strategy" with:
- **Left nav rail** — Workspaces / Memory / Agents / Automations / Files / MCP Hub / Connectors + favourites + user tile.
- **Header** — workspace name + status pill ("Active"), team avatar stack, Share button, global search.
- **Tab bar** — Overview · Chat · Research · Artifacts · Memory · Tasks · Timeline.
- **Main canvas (Overview tab)** — multi-widget grid: AI workspace/chat preview, Key Artifacts list,
Tasks list, Memory highlights, Research overview (donut), Recent activity feed.
- **Right panel** — Workspace info, Members, Last activity, quick actions.
- (Implied) **status bar** — agents running / automations active / MCPs connected.
> The blueprint text page for "Screen 2 — Workspace Desktop" (`_blueprint_extracted.txt:675`) is an
> image-only placeholder; the only textual spec is PRD §12.2 + the §0/§9/§19 spine references
> (`_blueprint_extracted.txt:15,42,63,274,513,585`).
---
## 2. Required states (PRD/Blueprint)
**Functional requirements (PRD §12.2, lines 423-430):**
- Header: workspace name, **type**, **status**, team/avatar stack, share controls.
- Tabs: Overview, Chat, Research/Notes, Artifacts, Memory, Tasks, Timeline, Settings.
- Main canvas widgets: AI workspace/chat, key artifacts, tasks, memory highlights, research overview, recent activity.
- Right panel: workspace info, members, last activity, quick actions.
- Bottom/status bar: agents running, automations active, MCPs connected.
- Fixed default layout initial release; configurable widgets deferred to a later phase.
**States (PRD §12.2, lines 432-441):** No memory · Active work · Agent running · Artifact ready ·
Task blocked · Sync conflict · Permission denied · Offline.
**Acceptance criteria (PRD §12.2, lines 443-447):**
- Chat is one widget, not the whole product.
- Workspace state is always visible.
- User can reach memory, artifacts, agents, skills, tasks, automations, and settings from the workspace.
**Cross-cutting (Blueprint §state-rules):** server-derived state — "Home Cockpit and Workspace
Desktop must use server workspace-state/context APIs" (`_blueprint_extracted.txt:513`); cache
invalidation on memory import / artifact update / agent-run completion / connector sync / automation
completion / RBAC change (`:514`).
---
## 3. Current state in repo
**Disposition: `create-new` (the tabbed Workspace Desktop screen does not exist), reusing existing
substrate heavily.** There is **no** workspace-runtime surface today. The closest analog is a
chat-only floating window:
- **`apps/web/src/components/os/Desktop.tsx`** — root OS shell. `renderAppContent()` is a
`switch(win.appId)` (`:276-360`); a workspace "opens" only as `case 'chat'`
`<ChatWindowInstance>` (`:278-296`). There is **no `case 'workspace'`** and no tabbed runtime.
Apps are floating windows (`AppWindow` chrome), not a full-screen workspace surface. `appConfig`
(`:77`) has no workspace entry.
- **`apps/web/src/components/os/apps/ChatWindowInstance.tsx`** — per-window wrapper: owns model
fetch + per-window persona/autonomy, renders only `<ChatApp>` (`:221-248`). Chat **is** the whole
window — the exact inversion PRD §12.2 forbids.
- **`apps/web/src/components/os/WorkspaceBriefing.tsx`** — ChatApp "home screen" shown when a session
has no messages. Fetches `GET /api/workspaces/:id/context` via `adapter.getWorkspaceContext()`
(`:57`) and renders greeting / stats / pending tasks / recent decisions / "I Remember" memories /
recent threads / cross-workspace hints / suggested prompts / upcoming schedules. **This is the
single best reuse seed** — it already consumes the "Workspace Now" block the Overview tab needs,
but it lives *inside* chat and is read-only (no artifacts, no tasks CRUD, no tabs, no right panel).
- **`apps/web/src/components/os/apps/DashboardApp.tsx`** — Workspaces grid (select/create/open-chat).
This is the workspace **list** (S01-adjacent), not the per-workspace runtime.
- **`apps/web/src/components/os/apps/RoomApp.tsx`** — live sub-agent tiles via `useRoomState` SSE
(`:24`); the "agents running" status indicator can be derived from this per-workspace.
- **`apps/web/src/components/os/overlays/ContextRail.tsx`** — right-side rail for a clicked
frame/entity (`ContextRailTarget`); a **reuse target for the right-panel detail pattern**, but it
is an overlay keyed to a single frame, not a persistent workspace info/members/activity panel.
**Tabs that already have a host component** (to embed, not rebuild): Memory → `MemoryApp.tsx`,
Timeline → `TimelineApp.tsx`, Chat → `ChatWindowInstance`/`ChatApp`, Settings →
`SettingsApp.tsx`. **Tabs with NO host:** Overview (new), Research/Notes (new), Artifacts (no
backing entity at all — see §5), Tasks (store exists server-side, never wired to FE — see §4/§5).
**Adapter gap (confirmed by grep on `apps/web/src/lib/adapter.ts`):** `getWorkspaceContext` (`:272`),
`getWorkspaceFiles` (`:277`), `getPins`/`addPin` (`:1353/:1361`), `getDocuments` (`:1371`) exist;
**no `getTasks` / `getWorkspaceState` / `getWorkspaceActivity` methods** (0 matches). So the Tasks
list, the `/state` Overview source, and the activity feed have no client plumbing yet.
---
## 4. Frontend work
### Components to create
| Component | Role | Reuse / source |
|---|---|---|
| `apps/WorkspaceApp.tsx` (new) | Full-screen workspace runtime shell: header (name/type/status/avatars/Share) + `Tabs` + right panel + status bar. Owns `activeTab` state. | `components/ui/tabs.tsx`; header layout from `StatusBar.tsx` + `chat-header-layout.ts`; tab gating via `useFeatureGate`. |
| `workspace/OverviewTab.tsx` (new) | Default tab: widget grid (chat preview, key artifacts, tasks, memory highlights, research, recent activity). | **Port the read-only sections of `WorkspaceBriefing.tsx`** (greeting/decisions/memories/threads) into widget cards; add artifacts/tasks/activity widgets. |
| `workspace/WorkspaceInfoPanel.tsx` (new) | Right panel: info, members, last activity, quick actions. | Members from `adapter.getTeamMembers()`; "last activity" from new activity hook; quick actions raise `waggle:open-app`. ContextRail stays a separate frame-detail overlay. |
| `workspace/TasksTab.tsx` (new) | Task list/board for the workspace. | Net-new FE; backs onto existing `/api/workspaces/:id/tasks` (server store exists, FE plumbing missing). |
| `workspace/ResearchTab.tsx` (new) | Research/Notes surface. | Lightweight: notes-as-frames (memory) + wiki pages (`adapter.getWikiPages`). Lowest-fidelity tab; can ship as "notes" v1. |
| `workspace/ArtifactsTab.tsx` (new) | Artifacts grid (see S05 dependency). | Backs onto new `/api/artifacts` (net-new) or interim file-registry view via `getWorkspaceFiles`/`getDocuments`. |
### Components to rework / wire
- **`Desktop.tsx`** — add `case 'workspace'` to `renderAppContent` + an `appConfig.workspace`
entry; route `openChatForWorkspace` callers that should open the *desktop* (not a chat window) to a
new `openWorkspace(workspaceId)`. Decide window-vs-fullscreen (recommend full-bleed window using
existing maximize path in `AppWindow.tsx` to avoid a parallel layout system). **Surgical** — do
not refactor the window manager.
- **`useWindowManager.ts`** — add `workspace` to the `AppId` consumption; reuse `workspaceId` field
already on `WindowState`. No new state shape.
- **Embed existing apps as tab panels** — Memory/Timeline/Chat/Settings render their existing
components scoped by `workspaceId` (already accepted props on `TimelineApp`, `ChatWindowInstance`).
### Adapter methods / hooks to add (`lib/adapter.ts` — the one contract surface)
- `getWorkspaceState(id)``GET /api/workspaces/:id/state` (Overview; PARTIAL backend, §5).
- `getWorkspaceActivity(id, {limit})``GET /api/workspaces/:id/activity` (activity feed; PARTIAL, §5).
- `getTasks(workspaceId)` / `createTask` / `updateTask` / `deleteTask``/api/workspaces/:id/tasks*`
(routes EXIST, adapter methods MISSING).
- New hook `useWorkspaceDesktop(workspaceId)` composing state + activity + tasks + members +
fleet (agents-running) + cron (automations) + capabilities/status (MCPs-connected) for the status
bar. Reuse `useRoomState` for live agents.
---
## 5. Backend work
Per PRD §16.2 plus the substrate the Overview/widgets/status-bar need. Cross-referenced against
backend-routes inventory + backend-map `03c`.
| PRD §16 endpoint | Status | Extend vs net-new · substrate · migration |
|---|---|---|
| `GET /api/workspaces/:id` | **EXISTS** | `workspaces.ts`. Header name/team. But `type`+`status` fields are **MISSING** on `WorkspaceConfig` (`hive-mind-core/src/workspace-manager.ts:5-58`) — additive JSON fields, **no DB migration** (workspace.json file). Default `status:'active'`; derive `type` from `templateId`/`group`. |
| `PATCH /api/workspaces/:id` | **EXISTS** | `workspaces.ts`. Used for status change / Share controls. Stamp `updatedAt` in `update()` (`workspace-manager.ts:222`, currently unstamped). |
| `GET /api/workspaces/:id/context` | **EXISTS** | `workspaces.ts:311` — the "Workspace Now" block (`buildWorkspaceNowBlock()`, `workspace-context.ts:191-404`). Direct feed for Overview widgets (greeting/decisions/memories/threads/pending/schedules). |
| `GET /api/workspaces/:id/state` | **PARTIAL → EXTEND** | No `/state` route. `buildWorkspaceState()` (`workspace-state.ts:234-311`) already produces `active/openQuestions/pending/blocked/completed/stale/recentDecisions/nextActions` and is surfaced *inside* `/context` as `workspaceState`. Add a thin `/state` route returning that sub-object directly (Overview/Tasks consume `pending`+`blocked` as task seeds). Substrate: `memory_frames` + session JSONL + `awareness`. No migration. |
| `GET /api/workspaces/:id/activity` | **PARTIAL → EXTEND** | No per-workspace `/activity`. Closest: `GET /api/events?workspaceId=` (`events.ts`) and `GET /api/teams/:id/activity`. Add a thin `/activity` alias over the audit-event query (substrate: `ai_interactions`/`execution_traces`/events). No migration. |
| Tasks: `GET/POST /api/workspaces/:id/tasks`, `PATCH/DELETE …/:taskId` | **EXISTS (server)** | `tasks.ts` (backend-routes §1.7). Only FE plumbing missing — no net-new backend. |
| Status bar feeds | **EXISTS, no aggregate** | agents → `GET /api/fleet` (`fleet.ts`); automations → `GET /api/cron` (`cron.ts`); MCPs connected → `GET /api/capabilities/status` (`mcpServers[]`) / `install_audit`. Compose client-side in v1; an aggregate `/status` route is optional. |
| Members / Share | **PARTIAL** | Members → `GET /api/team/members` (EXISTS). **Share** → PRD §16.11 `POST /api/share` is **MISSING** (grep-confirmed, backend-routes §16.11). Header Share button is net-new backend; for non-team workspaces it can be a no-op/disabled in v1. |
| Artifacts widget/tab | **MISSING (largest gap)** | No `Artifact` entity, table, or `/api/artifacts*` route anywhere (substrate-types §e; backend-routes §16.6). Interim: render the **file registry** `GET /api/workspaces/:id/files` (`workspaces.ts`) + document versions `GET /api/workspaces/:id/documents` (`documents.ts`) as "artifacts". Full Artifact Center is **S05's** scope — S02 should depend on it, not build it. |
**Migration flag:** the only `.mind` SQLite migration *adjacent* to this screen is the optional
`memory_frames.metadata`/`confidence`/`kind` additions (substrate-types §c) needed for richer Memory
**filters****not required for S02's Overview/Tasks/Timeline tabs**; defer to S04 (Memory Center).
S02's own missing fields (`type`, `status`, `updatedAt`, `lastActiveAt`) are JSON-file additive — **no
DB migration**.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`WorkspaceType`, `Scope`, `Confidence` literal unions** (PRD §15.2) — **none exist** in
`apps/web/src/lib/types.ts` (substrate-types §e). Add `WorkspaceType` + `WorkspaceStatus`
(`active`/`paused`/`archived`) for header.
- **`Workspace``WorkspaceConfigV2` alignment** (PRD §15.3) — FE `Workspace` (`types.ts:22-40`)
lacks `type`, `status`, `description`, `updatedAt`, `lastActiveAt` and uses `persona` vs config's
`personaId`. Add the 5 missing fields (optional) for header/last-activity; keep DERIVED display
fields.
- **`WorkspaceState` type** — exists server-side (`workspace-state.ts:38-55`) but is **not mirrored**
in FE `types.ts`. Add a FE `WorkspaceState` mirror for the new `getWorkspaceState` method.
- **`Task` type** — `lib/types.ts` has no Task interface (server `tasks.ts` shape only). Add one for
the Tasks tab.
- **`Artifact` type** — **greenfield** (PRD §15.6). Owned by S05; S02 imports it once defined.
- `WorkspaceContext` already exists in `types.ts` (consumed by `WorkspaceBriefing`) — reuse for Overview.
---
## 7. Dependencies (screens / phases first)
- **Phase 1 (Shell + Ctrl+K)** — `_blueprint_extracted.txt:582` — must land first: this screen is
opened *from* the shell/route map and the left nav. S02 needs the new `route`/`activeWorkspaceId`
global state + the `case 'workspace'` shell wiring.
- **Phase 2 (Home + Workspace)** — `_blueprint_extracted.txt:585` — S02 ships **with** S01 (Home
Cockpit); both share `/api/workspaces/:id/context` + `/state`. S01's cross-workspace briefing
reuses S02's per-workspace builder.
- **S05 Artifact Center** — hard dependency for the Artifacts tab/widget (entity + `/api/artifacts*`
are entirely net-new there). S02 must ship the Artifacts tab as an **interim file-registry view**
if S05 is not ready.
- **S04 Memory Center** — the Memory tab embeds `MemoryApp`; richer confidence/kind filters (and the
`memory_frames` metadata migration) live there, not in S02.
- **S03 Command Center (Ctrl+K)** — the header search + quick actions route through the command provider.
---
## 8. Effort
**XL.** Net-new full-screen tabbed runtime that re-architects the product's primary surface (chat →
one-widget), needs 5-6 new FE components + several adapter methods/hooks, 2 thin backend routes
(`/state`, `/activity`) + workspace `type`/`status`/`updatedAt`/`lastActiveAt` additive fields, and
is gated on S05 (Artifacts) for one full tab. The read-only Overview seed exists
(`WorkspaceBriefing`), which keeps it from being 2×XL, but the shell-integration + tab embedding +
status-bar aggregation breadth dominate.
---
## 9. Open questions
1. **Window vs full-screen.** Should the Workspace Desktop be a maximized `AppWindow` (reuse existing
window manager + chrome) or a dedicated full-bleed route bypassing the floating-window system? The
mockup is full-screen; the current OS is windowed. Recommend maximized-window to avoid a parallel
layout system — needs founder/eng confirmation.
2. **Chat-as-widget vs Chat-tab.** PRD says "chat is one widget" (Overview) AND lists a "Chat" tab.
Is the Overview chat widget a live mini-composer or a read-only preview that deep-links to the Chat
tab? Affects whether `ChatApp` must run in two render modes.
3. **`workspace.type` taxonomy.** PRD §15.2 names `WorkspaceType` but no enum values are given. Derive
from the 15 existing `workspace-templates` categories, or define a new fixed set?
4. **`status` lifecycle.** Who/what sets `paused`/`archived` (manual header action vs automation)?
PRD §12.2 lists the status states but not the transitions.
5. **Share scope.** `POST /api/share` is net-new (PRD §16.11). Is Share in S02's MVP, or stubbed
until Team Workspace (S10)? For a solo/non-team workspace, what does Share do?
6. **Tasks store of record.** Tasks tab — back onto the existing `/api/workspaces/:id/tasks` store, or
model tasks as `pending`/`blocked` `StateItem`s from `WorkspaceState` (which are session/awareness-
derived, not first-class)? These are two different sources of truth to reconcile.

View File

@@ -0,0 +1,182 @@
# 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`).

View File

@@ -0,0 +1,267 @@
# Gap Card — S04 Memory Center
> Screen S04 of the Waggle OS UX Refactor. Execution model: **in-place incremental
> refactor** of `apps/web` + targeted backend extension over the existing `.mind`
> substrate. Mockup is directional; PRD §12.4 + §16.4 acceptance criteria win.
> Sources: PRD `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`
> (§12.4 lines 484-517, §16.4 lines 1086-1094, §15.4 lines 988-1013, §20.2 line 1276);
> backend-map `sections/03b-api-memory.md`, `sections/05b-subsystem-memory.md`;
> inventories under `docs/ux-refactor/_inventory/`.
---
## 1. Screen & purpose
Make memory **visible, trustworthy, searchable, and editable** (PRD §12.4). Every
memory item must answer: *what do I know, why do I know it, where did it come from,
how confident am I, can I edit it?* (PRD line 517).
Mockup (`screen_04_memory_center.png`, directional) shows a 3-pane layout:
- **Left rail** — search box, type/source/confidence/workspace/tag/date filter facets, scope tabs.
- **Center list** — memory cards (title, type icon, snippet, confidence/importance, source chip, date).
- **Right detail panel** — selected memory: content, type/source/evidence, confidence ring/score,
related memories, connected-graph mini-view, source-evidence chips, and an actions row
(edit / merge / archive / delete / share / add-to-workspace). A right-most column in the
mockup shows aggregate stats (frame count, distribution donut, top entities, recent activity).
This is the **Work-layer** "visible memory" pillar (PRD §1, §6 principle 2, §10.2).
---
## 2. Required states (PRD/Blueprint)
**Tabs (PRD §12.4):** `Active · Workspace · Team · Sources · Graph · Trash`.
**Filters (PRD §12.4):** type, source, confidence, importance, workspace, tag, date.
**Memory detail view fields (PRD §12.4 line 500):** content, type, source, evidence,
confidence, relevance, last used, tags, timeline, connected graph.
**Actions (PRD §12.4 line 502):** edit, merge, archive, delete, share, add to workspace/team;
show related memories + source evidence.
**Screen states (PRD §12.4 lines 504-513 + global §14.1/§14.4):** Empty memory · Importing ·
Consolidating · Active · Low confidence · Conflict · Deprecated · Source unavailable —
plus the global set Loading / Populated / Error / Offline-local-only / Permission-denied /
Partial-data / Approval-required, and the memory lifecycle states Raw/Imported/Working/
Consolidated/Active/Low-confidence/Conflicting/Deprecated/Archived/Deleted-tombstoned (§14.4).
**Acceptance (PRD line 517):** every memory exposes content + source + provenance + confidence
+ edit/delete. Privacy (§18.2): memory source and scope must be visible; user must be able
to archive/delete; team sharing requires explicit scope.
> Blueprint note: `_blueprint_extracted.txt` page 31 ("Screen 4 - Memory Center") is an
> image-only heading — no extra written spec beyond PRD. Cross-refs confirm the rework
> intent: line 571 "Turn `MemoryApp.tsx` into Memory Center with source/confidence/
> evidence/edit actions", line 296 "Visible memory with provenance and [confidence]".
---
## 3. Current state in repo
**Disposition: REWORK** (matches PRD §20.2 line 1276: `MemoryApp.tsx -> Memory Center`).
Substantial reuse of substrate + hook; the screen's tab axis, detail panel, and trust
surfacing are rebuilt.
**Primary component — `apps/web/src/components/os/apps/MemoryApp.tsx` (343 LOC).**
Today it is a 6-tab hub on a *different* axis than the PRD:
- `MEMORY_TABS` (line 46): `timeline | graph | harvest | weaver | wiki | evolution` — NOT the
PRD's `Active/Workspace/Team/Sources/Graph/Trash`. Only **Graph** overlaps 1:1.
- Left sidebar (line 119): search input (`onSearchChange`), a type-filter chip set
(`FRAME_TYPES`, line 21) + a min-importance range slider; flat chronological frame list.
- Detail pane (line 281): renders type icon, title, `type` chip, `importance: N/5`, timestamp,
markdown content, and a raw `metadata` JSON dump (line 310). **Edit button is a no-op
(line 289 — no handler); only Delete is wired.** No source, no confidence, no evidence,
no relevance, no related-memories, no tags, no timeline, no merge/archive/share.
- `readFrameProvenanceTool()` (line 32) already reads `metadata.tool|sourceTool|source` and
renders a small amber provenance badge (line 202) — the **only** trust signal present today.
- `ImportReminderBanner` (line 236) handles the "you have pending imports" nudge.
**Subcomponents — `apps/web/src/components/os/apps/memory/` (6 files):**
- `KnowledgeGraphViewer.tsx` → PRD **Graph** tab (keep-promote, reuse as-is).
- `HarvestTab.tsx` → maps to **Sources** tab inputs (keep; Sources tab wraps/extends it).
- `WeaverPanel.tsx`, `WikiTab.tsx`, `EvolutionTab.tsx`, `ImportReminderBanner.tsx` → these
are **out of the PRD Memory-Center tab set**. Per inventory `frontend.md` (f), Weaver/Wiki/
Evolution belong to the Intelligence layer (traces/distillation). The rework should **move
them off the Memory-Center tab bar** (relocate to their IA home or keep behind a secondary
surface) rather than delete — flag as IA cleanup, not in-scope deletion.
- Grep confirmed: **no file under `memory/` renders confidence/evidence/provenance** beyond the
inline badge in `MemoryApp.tsx`. The trust UI is greenfield.
**Hook — `apps/web/src/hooks/useMemory.ts` (81 LOC).** Provides `frames`, `selectedFrame`,
`filters{types,minImportance,searchQuery}`, `addFrame/editFrame/deleteFrame/incrementAccess/
refresh`, `stats{total,filtered,entities,relations}`. Calls `adapter.getMemoryFrames`,
`searchMemory`, `getMemoryStats`, `addMemoryFrame`, `updateMemoryFrame`, `deleteMemoryFrame`,
`incrementFrameAccess`. **Reuse and extend** (add merge/archive, scope/source/confidence/tag
filters, single-frame fetch).
**Wiring — `Desktop.tsx`:** `const memory = useMemory(activeWorkspaceId)` (line 107);
`<MemoryApp .../>` rendered for appId `memory` (lines 309-316) with `frames/selectedFrame/
searchQuery/stats/typeFilters/minImportance` + KG props (`knowledgeGraph`, `kgScope`). The
appId `memory` already exists in `AppId` (dock-tiers) — no new route needed (single-route
windowed desktop; inventory `frontend.md` §b).
**Frontend type — `apps/web/src/lib/types.ts:118-127` `MemoryFrame`:** `id,type,title,content,
importance:number,timestamp,workspaceId,metadata?`. Note inventory finding: this FE shape does
**not** match what `/api/memory/frames` returns (server `normalizeFrame` emits `source,
source_mind, frameType, accessCount, score, …`, `memory.ts:25-58`) — a real FE/BE contract
mismatch to reconcile in this rework.
---
## 4. Frontend work
**Rework `MemoryApp.tsx` → Memory Center** (keep file path per §20.2; do not create a parallel app).
Components to create/rework (small files, per house rules):
1. **`MemoryApp.tsx` (rework shell)** — replace the 6-tab bar with PRD tabs
`Active | Workspace | Team | Sources | Graph | Trash`. Keep the 3-pane layout
(rail / list / detail). State: `activeTab`, `filters`, `selectedFrameId`, `view`.
2. **`memory/MemoryFilterRail.tsx` (new)** — facet filters: type, source, confidence range,
importance, workspace, tag, date. Drives `useMemory().setFilters`.
3. **`memory/MemoryList.tsx` (new)** — extract the card list out of `MemoryApp`; each card shows
title, type icon, snippet, confidence badge, source chip, date. Loading/empty/error states.
4. **`memory/MemoryDetailPanel.tsx` (new)** — the PRD detail view: content + type + source +
`evidence[]` chips + `ConfidenceBadge` + relevance + last-used + tags + mini-timeline +
connected-graph snippet + related-memories list. Actions row: edit, merge, archive, delete,
share, add-to-workspace/team. Reuse design-system primitives from PRD §19.1
(`Confidence badges`, `Source/evidence chips`, `Status badges`, `Timeline`, `Detail drawer`,
`Approval prompt` for share/delete).
5. **`memory/MemoryEditDialog.tsx` (new)** — wire the currently-dead Edit button
(`MemoryApp.tsx:289`) to `useMemory().editFrame`.
6. **`memory/MemoryMergeDialog.tsx` (new)** — select 2+ frames → call merge adapter method.
7. **Reuse as-is:** `KnowledgeGraphViewer.tsx` (Graph tab), `ContextMenu.tsx`,
`HintTooltip`, `renderSimpleMarkdown`. **Relocate off this tab bar:** `WeaverPanel`,
`WikiTab`, `EvolutionTab`, `HarvestTab` (Harvest folds into the new **Sources** tab).
8. **`overlays/ContextRail.tsx`** is already the right-side full-context rail (`onContextRail`
prop, `MemoryApp.tsx:75,183`) — keep the integration.
**Hook/adapter work (`useMemory.ts` + `lib/adapter.ts`):**
- Extend `MemoryFilters` to `{ types, sources, minConfidence, minImportance, workspaceId,
tags, dateFrom, dateTo, scope, searchQuery }`; filter client-side first, push server-side
where the route supports it (`/api/memory/search` already accepts `since/until/workspace/scope`,
`memory.ts:117-120`).
- New adapter methods (thin wrappers; adapter is the single sidecar contract surface — inventory
`frontend.md` (c)): `getMemoryFrame(id)`, `archiveMemoryFrame(id)`, `mergeMemoryFrames(ids[])`,
`shareMemoryFrame(id, scope)` (Team tab). Team tab reuses existing `searchTeamMemory`
(`adapter.ts:489`).
- Reconcile `MemoryFrame` FE type vs server `normalizeFrame` output (add `source`, `sourceMind`,
`confidence`, `tags`, `evidence`, `status`, `relatedMemoryIds` — see §6).
**States to implement (all PRD-required):** Loading / Empty / Populated / Error / Offline /
Permission-denied / Importing / Consolidating / Low-confidence (badge) / Conflict (badge +
`conflictNote` from `CombinedRetrieval`, backend-map 05b §8) / Deprecated / Source-unavailable /
Trash.
---
## 5. Backend work (PRD §16.4 endpoints)
Status per inventory `backend-routes.md` Part 2 §16.4, re-verified against
`packages/server/src/local/routes/memory.ts` + `knowledge.ts`. Substrate = `memory_frames`
(+`_fts`/`_vec`) and `knowledge_entities/relations` in `packages/hive-mind-core/src/mind/`.
| PRD §16.4 endpoint | Status | Action — EXTEND existing vs NET-NEW · substrate |
|---|---|---|
| `GET /api/memory` | **PARTIAL** | EXTEND: alias/accept on existing `GET /api/memory/frames` (`memory.ts:188`). No new substrate. |
| `GET /api/memory/:id` | **MISSING** | NET-NEW thin route reading `FrameStore.getById(id)` (backend-map 05b §2). `memory_frames`. No migration. |
| `POST /api/memory` | **PARTIAL** | EXTEND: alias on `POST /api/memory/frames` (`memory.ts:237`). |
| `PATCH /api/memory/:id` | **PARTIAL** | EXTEND `PUT /api/memory/frames/:id` (`memory.ts:448`) to accept `PATCH` + bare `:id`. `FrameStore.update`. |
| `POST /api/memory/:id/archive` | **MISSING** | NET-NEW. Model archive as `FrameStore.update(id, importance:'deprecated')` (no hard delete) OR add `status` in the new `metadata` column (§6). `memory_frames`. |
| `DELETE /api/memory/:id` | **PARTIAL** | EXTEND: alias bare `:id` over `DELETE /api/memory/frames/:id` (`memory.ts:551`, → `FrameStore.delete`). |
| `POST /api/memory/merge` | **MISSING** | NET-NEW. Read N frames, synthesize merged content, write one frame, archive/delete the originals. Reuse `FrameStore` + dedup (`findDuplicate`, 05b §2). `memory_frames`. |
| `GET /api/memory/graph` | **EXISTS** | `knowledge.ts` `GET /api/memory/graph?scope=` — Graph tab. No work. |
**Adjacent reuse (no new endpoint):** Team tab → existing `GET /api/team/memory/search`
(`team.ts`, **Tier: TEAMS**, inventory backend-routes 1.8); Sources tab → existing
`GET /api/harvest/sources` (`harvest.ts`); stats column → existing `GET /api/memory/stats`
(`memory.ts:391`); conflict/relevance signals available from `CombinedRetrieval`
(backend-map 05b §8 `hasConflict`/`conflictNote`, `finalScore` relevance).
**.MIND MIGRATION FLAG (one, low-risk).** To back confidence / provenance-id / source-url /
tags / evidence / kind / title / status / relatedMemoryIds, `memory_frames` has **NO metadata
column today** (substrate-types.md (c): unlike `awareness.metadata` etc.). The migration runner
already does idempotent additive `ADD COLUMN` (precedent: it added `source`, `mind/db.ts:116-124`).
**Recommended:** ONE additive migration adding nullable
`metadata TEXT NOT NULL DEFAULT '{}'` to `memory_frames`, storing
`{kind,title,scope,sourceId,sourceUrl,confidence,tags,evidence,relatedMemoryIds,status}` as JSON.
This avoids touching the FTS5/vec0 virtual tables and the IPB scoring path. If `confidence`
becomes a primary filter/sort axis (PRD "filter by confidence" / "low-confidence surfaced"),
promote `confidence REAL` to a real indexable column in a later migration (same ADD-COLUMN
pattern). PRD §15.4 line 1013 explicitly endorses metadata-first.
> Latent-bug flag (not session-induced, surfaced by substrate-types.md (d)): `AuditRiskLevel`
> TS includes `'critical'` but the DDL CHECK allows only `low/medium/high` — irrelevant to S04
> but the same install-audit store backs Sources-tab provenance if surfaced; note for the plan.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
PRD §15.2 unions — **none exist in `apps/web/src/lib/types.ts`** (substrate-types.md (e)):
- Add `MemoryKind = 'fact'|'decision'|'task'|'preference'|'strategy'|'learning'|'goal'|'entity'`
(PRD line 949). Current `MemoryFrame.type` (`types.ts:120`) uses `fact/event/insight/decision/
task/entity` — OVERLAPS but mismatched (`event`/`insight` vs PRD `preference/strategy/learning/
goal`). Reconcile.
- Add `Scope = 'personal'|'workspace'|'team'|'organization'` (line 945) — drives the Active/
Workspace/Team tab partition. Today scope is IMPLICIT (which `.mind` file; surfaced as `_mind`
tag, `memory.ts:34`).
- Add `Confidence = number` (0-100, line 946).
Extend `MemoryFrame` (PRD §15.4, lines 990-1013) toward: `kind, title, content, scope,
workspaceId, teamId, source, sourceId, sourceUrl, confidence, importance, evidence[], tags[],
relatedMemoryIds[], relatedArtifactIds[], createdAt, updatedAt, lastAccessedAt, status`.
`relatedArtifactIds[]` stays **deferred** (no Artifact entity exists yet — that's S05;
substrate-types.md (e)). Also fix the FE-type ↔ `normalizeFrame` mismatch noted in §3/§4.
---
## 7. Dependencies (screens/phases first)
- **Phase placement:** PRD §8 Phase 2 / Sprint 4 (Memory + Artifacts). Depends on **Phase 0**
(shared frontend types + AppShell/IA freeze — the §15.2 unions live there) and on the
workspace-state/context contract from Phase 1.
- **Workspace scope** (`scope`/`workspaceId`) presupposes the S01/S02 workspace-type-and-status
fields (`WorkspaceConfigV2`, substrate-types.md (a)) for the Workspace/Team partition; usable
with the implicit `_mind` scope before that lands, so soft dependency only.
- **Team tab** depends on TEAMS tier + the team substrate (S10 Team Workspace); gate behind
`useFeatureGate`/tier and degrade to a "Teams feature" empty state otherwise.
- **Sources tab** reuses Harvest (already shipped); the broader Memory Import onboarding (S15/S16)
is independent.
- **No dependency on S05 Artifacts** for v1 (defer `relatedArtifactIds`).
- Open question O3 (PRD line 1418): Graph view in v1 or later — Graph already works, so keep.
---
## 8. Effort: **L**
Frontend is a multi-pane rework with a brand-new trust/detail surface (confidence, evidence,
related, merge, archive, share) replacing a thin detail pane, plus 4-5 new subcomponents and a
hook/adapter/type reconciliation. Backend is mostly EXTEND/alias over existing memory routes
(7 of 8 endpoints), but the trust fields require **one additive `.mind` migration** + a real
`merge` endpoint + a `:id` read + an archive path — net-new logic, low schema risk. The single
migration and the FE/BE `MemoryFrame` contract reconciliation push this past M into L.
---
## 9. Open questions
1. **Confidence source.** `memory_frames` has no confidence today; `knowledge_relations.confidence`
exists (edges only, schema.ts:93). Do we (a) store confidence in the new `metadata` JSON,
(b) promote to a `REAL` column now for indexable low-confidence filtering, or (c) derive a
proxy from `source` trust-class + `importance` until real confidence is computed? (PRD §12.4
wants "filter by confidence" + "low-confidence surfaced for review".)
2. **Conflict detection surfacing.** `CombinedRetrieval.detectConflict` (05b §8) yields
`hasConflict`/`conflictNote` at recall time, not as a stored per-frame state. Is the PRD
"Conflict" state (§14.4) a live recall-time signal or a persisted frame status?
3. **Archive vs deprecate vs tombstone.** PRD §14.4 distinguishes Deprecated / Archived /
Deleted-tombstoned. `importance:'deprecated'` exists; is "archive" a distinct status (needs
the `metadata.status` field) or an alias of deprecate? Delete = hard `FrameStore.delete` or
tombstone? (PRD Open Question O8, line 1423.)
4. **Merge semantics.** Does `POST /api/memory/merge` LLM-synthesize a combined frame, or just
concatenate + re-cognify? What happens to the originals (archive vs hard delete)?
5. **Tab-relocation scope.** Confirm Weaver/Wiki/Evolution move OUT of the Memory-Center tab bar
to their Intelligence-layer home (frontend.md (f)) is in-scope for S04, or deferred to the IA
pass so S04 only adds the new tabs and leaves the legacy ones temporarily.
6. **`MemoryKind` reconciliation.** Drop FE `event`/`insight` and add PRD `preference/strategy/
learning/goal`? This changes existing frame rendering + the type-filter chips.

View File

@@ -0,0 +1,243 @@
# Gap Card — S05 Artifact Center
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extensions.
> Mockups are directional; PRD acceptance criteria win over pixels (PRD §24).
> Sources: PRD §12.5 + §16.6, blueprint p.32 (`_blueprint_extracted.txt:306-317, 525, 547`),
> mockup `screen_05_artifact_center.png`, baseline inventories under `docs/ux-refactor/_inventory/`,
> backend-map `sections/04-feature-map.md`.
---
## 1. Screen & purpose
The **Artifact Center** is the **outcome layer**: it organizes *outputs* (documents, presentations,
spreadsheets, dashboards, research, code, media, designs, other) as first-class **relational objects**
not file attachments. PRD §12.5 purpose verbatim: "Organize outcomes, not just attachments." The
headline acceptance criterion (PRD line 532): **searching a topic (e.g. "Germany GTM") returns all
relevant outcome objects plus related memories, sessions, tasks, agents, and people — not just files.**
Mockup (directional): left filter rail (type / status / workspace / tag facets), a center **artifact
table** (icon, title, type, workspace, status badge, updated, owner) with a top search bar + view
toggle + pagination, and a right **detail panel** (preview thumbnail, metadata, related items, actions).
This is a **data-heavy table+detail screen**, the same shape the blueprint flags as acceptable in light
variant (`_blueprint_extracted.txt:484`).
The defining difference from today's Files app: an artifact is an **outcome with relations**
(`relatedMemoryIds / relatedSessionIds / relatedTaskIds / relatedAgentIds`,
`generatedByAgentId`, `status: draft|final|shared|generated`), addressable by a stable `id`, spanning
**all storage backends and all formats**. Today's Files app exposes only a raw filesystem tree scoped to
one workspace + one storage tab.
---
## 2. Required states (PRD / Blueprint)
PRD §12.5 functional requirements:
- Artifact **categories** (= `ArtifactKind`, PRD §15.2 line 950): `document | presentation |
spreadsheet | dashboard | research | code | media | design | other`.
- **Topic search returns artifacts + related memories/sessions/tasks/agents/people** (the §16.6
`GET /api/artifacts/search-related?q=` contract).
- **Detail panel**: preview, metadata, workspace, creator, updated time, access, status, tags, related
items, actions.
- **Actions**: open, share, duplicate, move, delete, relate to workspace/memory/session/task.
State model — PRD §14.1 global states (every major screen): Loading, Empty, Populated, Error,
Offline/local-only, Syncing, Permission denied, Partial data, Approval required.
Plus the **artifact-specific states** (PRD §14 line not enumerated but blueprint `:312-314, 458`):
**Draft, Final, Shared, Generated, External-missing (source unavailable), Permission denied.**
So the concrete states to build:
1. Loading (skeleton table + skeleton detail).
2. Empty ("no artifacts yet" — first-run / no outputs produced).
3. Populated (table + facets + detail).
4. Error (fetch failed).
5. Offline/local-only (sidecar unreachable — mirror FilesApp offline banner pattern).
6. Permission denied (team-scoped artifact the user can't view).
7. Per-row status badges: Draft / Final / Shared / Generated.
8. Source-unavailable (artifact row whose backing file/url is missing — show broken-link affordance).
---
## 3. Current state in repo (disposition: **create-new** for the screen; **keep-promote** the substrates)
**There is NO Artifact entity, type, route, or component anywhere.** Grep-confirmed:
- No `Artifact` type in `apps/web/src/lib/types.ts` (the only `Artifact` hit in `apps/web/src` is
`components/os/apps/memory/EvolutionTab.tsx`, referring to evolution `artifacts_json` — unrelated).
- No `artifacts` table in `packages/hive-mind-core/src/mind/schema.ts`.
- No `/api/artifacts/*` routes (grep over `packages/server/src/local/routes/*.ts`: 0 matches).
- Confirmed by `_inventory/substrate-types.md:259-266` ("**NO backing entity anywhere** … the single
largest entity gap") and `_inventory/backend-routes.md:476-484` (all 6 §16.6 rows PARTIAL/MISSING).
**The closest current surface is the Files app** (the current-component hint), which is **NOT an
artifact center** — disposition for it is **keep-as-is, do not retrofit**:
- `apps/web/src/components/os/apps/FilesAppTabs.tsx` — P16 three-tab (Virtual/Local/Team) wrapper that
remounts `FilesApp` per `storageType`. Storage-location switcher, not an outcome browser.
- `apps/web/src/components/os/apps/FilesApp.tsx` (735 LOC) — full file-manager: tree + list/grid +
preview + upload + rename/move/copy/delete + bulk ops + properties dialog + inline `VersionHistory`.
It is **path/workspace/storage-scoped** (`adapter.listFiles(workspaceId, currentPath)`), has no
cross-workspace aggregation, no type/status/relation model, no facet filtering by outcome kind.
- Sub-components `components/os/files/{FileTree,FilePreview,FileActions,FileUploadZone,SyntaxPreview,
WorkspaceRail}.tsx` — operate on `FileEntry` (`lib/types.ts:42-50`: `name/path/type/size/mimeType/
modifiedAt/createdAt`), a raw FS entry, not an outcome object.
**Three existing backend substrates the new Artifact layer must aggregate over (reuse, do not duplicate):**
1. **Workspace file registry** — `GET /api/workspaces/:id/files` (`workspaces.ts:594-606`) returns
`readFileRegistry(dataDir, id)` of `FileRegistryEntry { name, type, summary, sizeBytes, ingestedAt }`
(`routes/ingest.ts:125-130`). This is an **ingest log**, newest-first — closest thing to a
per-workspace "produced/ingested things" list, but no id, no status, no relations.
2. **Document version registry** — `GET /api/workspaces/:id/documents` +
`/documents/:name/versions` (`routes/documents.ts`, JSON at
`~/.waggle/workspaces/{id}/documents.json`, shapes `TrackedDocument`/`DocumentVersion`). Gives
versioning + size + createdAt keyed by name; already surfaced in FilesApp's `VersionHistory`
(`FilesApp.tsx:27-54`). No type/status/relations.
3. **Workspace storage files** — `GET /api/workspaces/:id/storage/files` + `/storage/read|write|delete`
(`workspaces.ts:885+`) — the actual byte store for virtual/local/team.
**Verdict:** the screen is **create-new** (`ArtifactCenter` is in PRD §20.3 "Create" list, line 1289).
The backend is **a thin net-new aggregation/normalization layer over the three existing stores** — no
new data store required (`_inventory/backend-routes.md:604-607`).
---
## 4. Frontend work
**New top-level app (dock id `artifacts`).** Register in `Desktop.tsx` `appConfig` + `renderAppContent`
switch, add `AppId` `'artifacts'` in `lib/dock-tiers.ts`, and a Work-bucket dock entry (the IA maps
Artifacts to the **Work** layer — `_inventory/frontend.md:360`). Do **not** route — this is a windowed
single-route desktop; opening is by `AppId` via `openApp` (`useWindowManager`).
Components to **create** (keep files small, ~200-400 LOC each per repo file-org rule):
- `components/os/apps/ArtifactCenterApp.tsx` — shell: search bar + view toggle + facet rail + table +
detail panel + pagination. Owns query/filter/selection state. Mirrors the FilesApp three-pane layout
idiom (rail / main / detail) so it feels native.
- `components/os/artifacts/ArtifactTable.tsx` (or `ArtifactRow.tsx` — blueprint names `ArtifactRow`,
`_blueprint_extracted.txt:487`) — list rows with icon/title/type/workspace/status badge/updated/owner.
- `components/os/artifacts/ArtifactFacetRail.tsx` — type/status/workspace/tag facet filters (left rail in mockup).
- `components/os/artifacts/ArtifactDetailPanel.tsx` — preview + metadata + related-items list + actions.
- `components/os/artifacts/ArtifactRelatedList.tsx` — renders related memories/sessions/tasks/agents;
clicking a related item should raise the relevant window via the existing `waggle:open-app`
CustomEvent (and/or `onContextRail` like FilesApp does, `FilesApp.tsx:206-208`).
**Reuse targets (do not rebuild):**
- Status/confidence badges, `Skeleton`, `Table`, view-toggle, `Pagination`, `HoverCard` — all exist in
`components/ui/*` (shadcn set, `_inventory/frontend.md:337-342`).
- Offline banner pattern + retry — copy from `FilesApp.tsx:384-389`.
- File preview for an artifact's backing file — reuse `components/os/files/FilePreview.tsx`.
- Empty-state idiom — `FilesApp.tsx:470-477`.
- Detail-panel metadata layout idiom — FilesApp Properties dialog (`FilesApp.tsx:667-729`).
- ContextRail for "show full context of this artifact" — `overlays/ContextRail.tsx` already exists
(extend `ContextRailTarget` with an `'artifact'` variant).
**New hook + adapter methods:**
- `hooks/useArtifacts.ts` — `{ artifacts, filters, setFilter, selected, select, search, refresh,
create, patch, remove, share }`; reads/writes through the adapter. Follow the `useMemory` shape
(`hooks/useMemory.ts`).
- Extend `lib/adapter.ts` (the single sidecar gateway, ~1930 LOC — new §16 methods land here per
`_inventory/frontend.md:380`) with: `getArtifacts`, `getArtifact`, `createArtifact`,
`patchArtifact`, `deleteArtifact`, `searchRelatedArtifacts`, `shareArtifact`.
**Props/state notes:** `ArtifactCenterApp` takes `{ workspaces?, activeWorkspaceId?, onSelectWorkspace?,
onContextRail? }` (same cross-workspace pattern FilesApp uses). It is **cross-workspace by default**
(the whole point vs FilesApp) — workspace becomes a *facet*, not a hard scope.
---
## 5. Backend work (PRD §16.6)
> No new data store. Every endpoint is a **net-new aggregation/normalization route** over the existing
> file registry + document versions + workspace storage. New file:
> `packages/server/src/local/routes/artifacts.ts`, registered in `local/index.ts`. The Artifact `id`
> can be a stable composite of `workspaceId + source-store + name/path` (or a registry-assigned id if a
> lightweight `artifacts.json` index is added per workspace, mirroring `documents.json`).
| PRD §16.6 endpoint | Status | Plan (EXTEND vs NET-NEW) + substrate |
|---|---|---|
| `GET /api/artifacts` | **PARTIAL → NET-NEW route** | No `/api/artifacts` domain. NET-NEW `GET /api/artifacts` in `artifacts.ts` that **fans out over `WorkspaceManager.list()`** and, per workspace, normalizes (a) file registry `GET /api/workspaces/:id/files` (`workspaces.ts:594`, `FileRegistryEntry`), (b) document versions `GET /api/workspaces/:id/documents` (`documents.ts`), into a unified `Artifact[]`. Supports `?workspaceId=&type=&status=&tag=&q=` facet filters. Cross-workspace = the differentiator. |
| `POST /api/artifacts` | **PARTIAL → NET-NEW route (thin)** | Closest writes that already persist bytes: `POST /api/ingest` (`ingest.ts`), `POST /api/workspaces/:id/files/upload` (`files.ts`), `POST /api/workspaces/:id/documents` (`documents.ts`), `POST /api/workspaces/:id/storage/write` (`workspaces.ts`). NET-NEW `POST /api/artifacts` records artifact metadata (kind/title/status/tags/relations + `generatedByAgentId`) in a per-workspace `artifacts.json` index and (optionally) writes the backing file via the storage route. |
| `GET /api/artifacts/:id` | **MISSING → NET-NEW** | Resolve composite id → normalized `Artifact` with relations + preview metadata. Reuse `documents.ts` version lookup for `relatedVersions`. |
| `PATCH /api/artifacts/:id` | **MISSING → NET-NEW** | Update title/status/tags/relations in the `artifacts.json` index (move = re-point `workspaceId`/`storagePath`; reuse `files/move`). |
| `DELETE /api/artifacts/:id` | **PARTIAL → NET-NEW route** | Closest: `POST /api/workspaces/:id/files/delete` (`files.ts`), `DELETE /api/workspaces/:id/storage/delete` (`workspaces.ts`). NET-NEW `DELETE /api/artifacts/:id` removes the index entry and (optionally) the backing file via those. |
| `GET /api/artifacts/search-related?q=` | **MISSING → NET-NEW (the headline endpoint)** | Federated search: query the normalized artifact index **plus** `GET /api/memory/search` (`memory.ts`), session search `GET /api/workspaces/:wid/sessions/search` (`sessions.ts`), tasks `GET /api/tasks` (`tasks.ts`), and fleet/agents (`GET /api/agents/active`/`/api/fleet`), returning grouped `{ artifacts, memories, sessions, tasks, agents }`. Internally can lean on existing FTS (`memory_frames_fts`, marketplace FTS5, wiki search). Delivers PRD line 532 acceptance. |
| `POST /api/artifacts/:id/share` (blueprint `:525`) | **MISSING → NET-NEW** | Blueprint adds a `/share` action not in PRD §16.6 list. Maps to the missing `POST /api/share` (`_inventory/backend-routes.md:551`) + team scope. Defer to the Team phase (see §7); gate behind TEAMS tier like `/api/team/*`. |
**Substrate touched:** workspace file registry (`ingest.ts` `FileRegistryEntry`), document versions
(`documents.ts` JSON), workspace storage (`workspaces.ts` storage routes), memory FTS
(`memory_frames_fts`), sessions JSONL, tasks store, fleet/orchestrator. **No `.mind` migration
required** for a metadata-first implementation: artifact metadata + relations live in a per-workspace
`artifacts.json` index (same pattern as `documents.json`). If artifacts must later be queryable in SQL
alongside frames, a future additive `artifacts` table in `schema.ts` follows the established
idempotent ADD-pattern (`mind/db.ts:116-124`) — flag, not now.
**Governance note:** if agent-generated artifacts (`generatedByAgentId`) need an audit trail, reuse
`InstallAuditStore`/`emitAuditEvent` (`workspaces.ts:631` already emits `workspace_update`) rather than
a parallel log.
---
## 6. Shared types needed (PRD §15.2 / §15.6 vs `lib/types.ts`)
**Net-new, none exist today** (`_inventory/substrate-types.md:226, 259-266`):
- `ArtifactKind` union (PRD §15.2 line 950) — add to `lib/types.ts` (and `packages/shared/src/types.ts`
if the sidecar route also imports it, to keep one contract).
- `ArtifactStatus = 'draft' | 'final' | 'shared' | 'generated'` (from blueprint states `:312`,
PRD §14 artifact states). Note blueprint also implies `external-missing`/`source-unavailable` —
model as a derived flag, not a status value.
- `Artifact` interface — PRD §15.6 (lines 1039-1056) blueprint `:547`: `id, title, kind, workspaceId,
teamId?, createdBy, generatedByAgentId?, source, status, mimeType?, storagePath?, previewUrl?,
summary?, tags[], relatedMemoryIds[], relatedSessionIds[], relatedTaskIds[], relatedAgentIds[],
createdAt, updatedAt`.
- `RelatedSearchResult` — the grouped `{ artifacts, memories, sessions, tasks, agents }` envelope for
`search-related`.
**Reconcile, don't fork:** define `Artifact`/`ArtifactKind`/`ArtifactStatus` **once** (shared package
preferred) so the sidecar route and the frontend hook share the contract — avoid the existing
FE↔BE `MemoryFrame` drift the inventory flags (`_inventory/substrate-types.md:246-247`). Optionally add
`relatedArtifactIds[]` to the memory side later (PRD §15.4) so the relation is bidirectional.
---
## 7. Dependencies (screens / phases first)
- **PRD Phase 2 — Work layer** (this screen's home; Memory Center is its sibling, Sprint 4). The
`search-related` federated endpoint is the binding dependency on Memory (FTS) + Sessions + Tasks.
- **AppShell / dock IA (Phase 0/1)** must exist first so `artifacts` registers as a Work-bucket dock
entry (consolidate on `AppId`, retire stale `AppView`).
- **Command Center (Ctrl+K) (Phase 1)** should index artifacts (`_blueprint_extracted.txt:515` — command
index unifies artifacts) — soft dependency; Artifact Center can ship before Ctrl+K wires it in.
- **`WorkspaceConfigV2` `type`/`status` fields** (S-workspace cards) help facet labels but are not
blocking.
- **Team Workspace / RBAC (Phase 5)** gates `POST /api/artifacts/:id/share` + permission-denied state.
Ship Artifact Center personal-scoped first; share/team-scope is a follow-on.
- **Agent run → artifact linkage** (PRD Journey 7, §15.6 `generatedByAgentId`) depends on the agent
runtime writing artifact records — a downstream integration, not a blocker for the read/browse screen.
---
## 8. Effort: **L**
Net-new full-stack surface: 5-6 new frontend components + a new hook + 7 new adapter methods, **plus**
a net-new backend aggregation domain (`artifacts.ts`, ~6 routes) that must normalize **three** existing
stores into one entity and a **federated** search across memory/sessions/tasks/agents. No new DB and
heavy component/route reuse keep it out of XL, but the cross-workspace aggregation + the
`search-related` federation + a brand-new shared `Artifact` contract make it clearly more than M.
---
## 9. Open questions
1. **Artifact identity & index.** Synthesize `id` as a composite (`workspaceId:store:name`), or add a
per-workspace `artifacts.json` index (mirroring `documents.json`) that assigns stable ids and holds
status/tags/relations? (PATCH/relations effectively require the latter.) → maps to PRD Open Q6
(storage: workspace FS vs virtual store vs external refs, PRD line 1421).
2. **What counts as an artifact in v1?** Only explicit outputs (generated docs/decks/etc.), or every
ingested file in the registry? The registry mixes ingested inputs with produced outputs — need a
classification rule for `kind`/`status`.
3. **`search-related` scope/cost.** Federating memory FTS + sessions + tasks + agents per query — cap
per-source result counts and run async result groups (PRD §24 perf mitigation), or a single indexed
provider? (Mirrors the Command Center search concern.)
4. **Share semantics (blueprint `/share` vs PRD `/api/share`).** Is `POST /api/artifacts/:id/share`
in-scope for the first Artifact Center cut, or deferred entirely to the Team phase?
5. **Preview generation.** `previewUrl`/thumbnails — generate server-side, reuse `FilePreview`
on-demand client-side, or skip thumbnails in v1 (icon + on-click preview only)?
6. **Bidirectional relations.** Do we add `relatedArtifactIds[]` to memory frames now (PRD §15.4) or
keep relations one-directional (artifact → others) in v1?

View File

@@ -0,0 +1,153 @@
# Gap Card — S06 Skills Hub (+ Skill Builder)
> Screen 06 of the Waggle OS UX-refactor. Sources: PRD §12.6 + §16.8 + §15 (`docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`),
> blueprint extract pp.33 + screen-table row 6 + states table (`_blueprint_extracted.txt`),
> mockup `Waggle_OS_Handoff_Assets/screen_06_skills_hub.png` (directional only),
> backend-map `sections/03d-api-marketplace-skills.md` + `sections/05g-subsystem-skills-marketplace-wiki.md`,
> and live repo grep/read. Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extension.
---
## 1. Screen & purpose
**Skills Hub** — the surface where reusable capabilities (a "skill" = a Markdown file in `~/.waggle/skills/{name}.md`, injected into the agent system prompt) are browsed, installed, authored, tested, and assigned to agents/workspaces. Blueprint row 6: *"Reusable capabilities across users, workspaces and agents. Install, create, test, assign to agent/workspace, archive."* Acceptance criterion (PRD §12.6 / blueprint): *"A user can understand what a skill does, where it is used, and what access it has."*
The mockup shows a left rail of category/state filters, a **My Skills** table (name / category / status / usage / last-used columns), a **Marketplace** panel, a **Workspace Skills** table, a **Custom Skills** panel, and a **Create Skill** CTA — i.e. a unified library + marketplace + builder entry, replacing the current "Skills & Apps" pack-grid.
Per the locked IA (frontend.md §f), this is the **Intelligence** layer, expressed through the existing dock/window-manager (`AppId='capabilities'`, dock key `skills`) — NOT a new route. The PRD also pairs this screen with the **Skill Builder** (PRD §12.6 5-step stepper; PRD §20.3 lists `SkillBuilder` under "Create").
---
## 2. Required states (PRD / Blueprint)
PRD §12.6 functional requirements:
- **Skills Hub tabs:** My Skills · Marketplace · Custom Skills · Workspace Skills.
- **Skill object fields:** name, description, category, instructions, inputs, outputs, tools/data, memory access, owner, status, usage, last used.
- **Skill Builder steps:** Basic Info · Instructions · Inputs & Outputs · Tools & Data · Review & Create.
- Support **test run before publishing**.
- Skills can be **assigned to agents, workspaces, automations, or used directly**.
Blueprint per-object states (states table) for **Skills**: `Loading, empty, populated, error` + item states `Installed, draft, custom, workspace, marketplace, update available`; actions `Test, install, publish, archive, rollback`. PRD §14.7 Extension states also apply: `Available / Installed / Update available / Installing / Failed install / Risk approval required / Disabled-revoked`.
Global states every screen must implement (PRD §14.1): Loading · Empty · Populated · Error · Offline/local-only · Syncing · Permission denied · Partial data · Approval required.
**Trust/tier state (load-bearing):** custom skills + marketplace install/publish are PRO-gated; FREE = built-in skills only (`tiers.ts`; 05g §19). A `403 → upgrade` state is required on create/install/publish.
---
## 3. Current state in repo
**Disposition: `partial`** — strong backend + a real (but mislabelled) hub component exist; the screen needs **rework** of the FE component to the PRD tab/table model, plus a **net-new Skill Builder** and **thin backend extensions** for `:id/test`, `:id/install`, and skill scope (workspace/custom).
### Frontend (exists, reworkable)
- `apps/web/src/components/os/apps/CapabilitiesApp.tsx` — the live Skills surface ("**Skills & Apps**" header, line 339). Tabs are `installed | starter | marketplace | tools | audit` (line 80, 358) — a **pack-centric grid**, NOT the PRD's My/Marketplace/Custom/Workspace tabs. Renders `PackCard`/`PackDetail` over `SkillPack` (pack groupings), has skill **test preview** (`handleTestSkill``POST /api/skills/test`, lines 88-102), pack install with `403→UpgradeModal` routing (`handleInstallError`, lines 149-162), a `tools` read-only catalog, and an `audit` tab (`GET /api/audit/installs`). Reachable via dock key `skills``appId 'capabilities'` (`lib/dock-tiers.ts:64`).
- `apps/web/src/components/os/apps/MarketplaceApp.tsx` — standalone marketplace browser (search/install/uninstall over `/api/marketplace/*`); registered as `appId 'marketplace'` but **no dock entry points at it** (frontend.md §a). Marketplace is doubly-represented (here + folded into CapabilitiesApp) — an IA-cleanup point for this screen.
- Adapter methods already wired (`lib/adapter.ts`): `getSkills`, `createSkill`, `getStarterPacks`, `getCapabilityPacks`, `installPack`, plus full marketplace set (frontend.md §c).
- Types: `SkillPack` (`lib/types.ts:210-218`: id/name/description/category(5-enum)/skills[]/installed/trust) — a **pack** shape, NOT the PRD per-skill `Skill` object. No `Skill`/`SkillScope`/`SkillStatus`/`ExtensionType` type in FE (substrate-types.md §e).
### What does NOT exist (gaps)
- **No `SkillBuilder` component** anywhere (grep confirmed — the only `SkillBuilder`/`Custom Skill` string matches are upgrade-modal copy in `TrialExpiredModal.tsx`/`UpgradeModal.tsx`). PRD §12.6 5-step builder is **net-new FE**.
- **No "Workspace Skills" / "Custom Skills" scoping at runtime.** Skills are flat files in `~/.waggle/skills/` (05g §8); the prompt loader ignores frontmatter (`loadSkills`, 05g §2 note). A `SkillScope` (`personal|workspace|team|enterprise`) exists only in `skill-frontmatter.ts` frontmatter + the `promote_skill` path — not persisted/queryable by the skills route (grep `scope|workspace` in `routes/skills.ts`**0 matches**). So the PRD's Workspace/Custom tabs have **no backing filter today**.
### Backend (mostly exists — see §5)
`packages/server/src/local/routes/skills.ts` already serves list/create/get/update/delete + `test` + `suggestions` + `hash-status` + starter/capability-pack catalogs + `/api/audit/installs`. Substrate detailed in 05g (skill-creator / -recommender / -usage / -retirement / -watcher) and 03d.
---
## 4. Frontend work
**Rework `CapabilitiesApp.tsx` → Skills Hub** (keep the file; this is the in-place refactor target), and **create `SkillBuilder`**. Reuse the existing window-manager surface (no new route); resolve the marketplace double-representation by making Skills Hub the canonical host and retiring/redirecting `MarketplaceApp`'s dead `appId`.
**Components to rework / create**
- **Rework** `CapabilitiesApp` tab model: `installed|starter|marketplace|tools|audit` → PRD **My Skills · Marketplace · Custom · Workspace** (keep `tools`/`audit` as secondary panels or fold `audit` into a detail-drawer "install history"). My Skills must render a **per-skill table** (name/category/status/usage/last-used per mockup), not just a pack grid.
- **Create** `SkillCard` / `SkillRow` (per-skill, distinct from `PackCard`) — design-system `SkillCard` is named in PRD §19.1 + blueprint components list. Show status badge (installed/draft/custom/workspace/update-available — PRD §14.7) and trust chip (reuse `lib/skill-pack-display.ts` `describeTrust`).
- **Create** `SkillBuilder.tsx` overlay (PRD §20.3 "Create") — `Builder stepper` (PRD §19.1) with the 5 steps. Pattern-match the existing builder/wizard idioms (`overlays/onboarding/*` step components, `CreateWorkspaceDialog`, `SpawnAgentDialog`). Step 5 "Review & Create" wires a **test run** before publish.
- **Create/extend** a `useSkills` hook (none exists today — `CapabilitiesApp` calls `adapter` directly via `Promise.allSettled`, lines 104-145). New hook returns `{ skills, packs, marketplace, install, create, test, refresh }`; keep `403→'waggle:tier-insufficient'` event routing (CapabilitiesApp lines 149-162) and `UpgradeModal` wiring intact.
**Reuse targets**
- `POST /api/skills/test` → already wired (`handleTestSkill`); reuse for builder Step 5 + per-skill test.
- `GET /api/skills/starter-pack/catalog` / `capability-packs/catalog` → already supply `state` per skill (`active|installed|available`); reuse for Marketplace/My-Skills state.
- `GET /api/skills/suggestions?context=` → recommended-skills strip.
- `dedupePacks`, `skill-pack-display`, `skill-recommendations`, `HintTooltip` — existing libs to keep.
**Props/state**
- `Skill` type (new, §6): `{ name, description, category, status, scope, usageCount, lastUsedAt, trust, instructions?, inputs?, outputs?, tools?, memoryAccess?, owner? }`.
- Builder form state mirrors `POST /api/skills/create` body (`{ name, description, steps[], tools?, category? }`) extended with inputs/outputs/memoryAccess once §5 lands.
**Adapter methods to add** (`lib/adapter.ts`): `testSkill(id, testInput?)` (path variant), `installSkill(id)` (unified dispatch), `updateSkill(id, patch)`, and scope-aware `getSkills({scope})` once the backend filter exists.
---
## 5. Backend work — PRD §16.8 endpoint-by-endpoint
> Cross-ref against backend-map 03d/05g + `routes/skills.ts`. Skills are flat files in `~/.waggle/skills/`; the marketplace is `~/.waggle/marketplace.db`. **No `.mind` migration is required for §16.8** — skills/marketplace are not in the `.mind` SQLite schema. (One optional migration is flagged below for the *install-audit critical column*, see "Cross-cutting".)
| PRD §16.8 endpoint | Status | Extend vs net-new | Substrate touched |
|---|---|---|---|
| `GET /api/skills` | **EXISTS** | as-is (`routes/skills.ts`, 05g §8) — returns `{ skills:[{name,length,preview}], count, directory }` | `~/.waggle/skills/*.md` |
| `POST /api/skills` | **EXISTS** | as-is — raw create `{name,content}`; structured create is `POST /api/skills/create` `{name,description,steps[],tools?,category?}` (the Builder's real target) | `~/.waggle/skills/*.md` + `redactSkillContent` + `skillHashStore` + install-audit |
| `PATCH /api/skills/:id` | **PARTIAL** | **EXTEND** existing `PUT /api/skills/:name` (keyed by **name**, method `PUT`). Add a `PATCH` alias and accept `:id``:name`. No new substrate. | `~/.waggle/skills/*.md` |
| `POST /api/skills/:id/test` | **PARTIAL** | **EXTEND** existing `POST /api/skills/test` (body-driven `{skillName,testInput?}`) — add the `:id` path variant routing to the same handler. Sandbox/dry-run only (parses frontmatter, shows `wouldInject`); no execution. | skill file + `parseSkillFrontmatter` |
| `POST /api/skills/:id/install` | **PARTIAL** | **NET-NEW thin dispatcher** over existing installs: `POST /api/skills/starter-pack/:id`, `POST /api/skills/capability-packs/:id`, `POST /api/marketplace/install` (PRO-gated, SecurityGate). `/skills/:id/install` resolves the source and delegates. Keep the `requireTier('PRO')` gate for marketplace-sourced skills. | marketplace.db + `MarketplaceInstaller` + `SecurityGate` + install-audit |
**Result: 0 net-new domains, 0 net-new stores.** 16.8 is `2 EXISTS / 3 PARTIAL` (matches `backend-routes.md` §16.8). All gaps are aliases/dispatchers over existing handlers.
**Backend gaps the PRD §12.6 tab model implies but §16.8 does NOT enumerate (flag for the plan):**
1. **Workspace/Custom scope filtering.** PRD tabs (Workspace Skills, Custom Skills) need a queryable `scope`. Today `SkillScope` lives only in optional frontmatter and is ignored by the prompt loader. **Extend** `GET /api/skills` to parse `parseSkillFrontmatter` and return `scope` + a `?scope=` filter (custom = user-authored vs starter/marketplace; workspace = `scope: workspace` frontmatter). No store change — derive at read time. Net-new query param, reusing the existing parser (05g §2). The "Custom" tab can also be derived as "not in starter/capability-pack catalogs."
2. **Assign-to-workspace/agent.** PRD "assign to agent/workspace" — workspace config already has a `skills?: string[]` field (`WorkspaceConfig`, substrate-types.md §a; matches `WorkspaceConfigV2.skills`). Assignment is a `PATCH /api/workspaces/:id { skills }` (EXISTS) — no new endpoint, but the FE must wire it.
3. **Archive/rollback** (blueprint actions). `retireStaleSkills` (05g §6) **moves** skills to `~/.waggle/skills-archive/` (recoverable) — an archive substrate exists but has **no HTTP route**. A manual "archive"/"restore" pair would be net-new thin routes over `skill-retirement.ts`. Defer unless in MVP scope (PRD §12.6 lists archive in blueprint, not in §16.8).
4. **Publish.** `POST /api/marketplace/publish` (PRO-gated) already exists for skill→catalog publish (03d/05g §13) — reuse for the Builder's "publish to marketplace" path.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
Currently the FE has only `SkillPack` (a pack grouping, `lib/types.ts:210-218`) — **no per-skill `Skill` type, no `SkillScope`/`SkillStatus`/`ExtensionType`** (substrate-types.md §e: all PRD §15.2 unions MISSING in FE).
Add to `apps/web/src/lib/types.ts` (FE), mirroring the existing backend `SkillFrontmatter`/`SkillTemplate` shapes (05g §2-3) so FE↔BE stay aligned:
```ts
type SkillScope = 'personal' | 'workspace' | 'team' | 'enterprise'; // matches skill-frontmatter.ts
type SkillStatus = 'installed' | 'draft' | 'custom' | 'workspace' | 'marketplace' | 'update-available';
type ExtensionType = 'skill' | 'connector' | 'mcp' | 'model' | 'template' | 'external_tool'; // PRD §15.2
interface Skill {
name: string; // file stem (id surrogate)
description: string;
category: string;
scope: SkillScope;
status: SkillStatus;
trust?: 'verified' | 'community' | 'experimental';
usageCount?: number; // from skill-usage.json (05g §5)
lastUsedAt?: string; // ISO
instructions?: string; // body
inputs?: string[]; outputs?: string[]; tools?: string[];
memoryAccess?: boolean; owner?: string;
}
```
Keep `SkillPack` for the pack-grid (starter/capability packs); `Skill` is the per-row object for My/Custom/Workspace tabs. Align `Skill.category` to the broader server `inferCategory` set (research/coding/knowledge/writing/planning/general — 05g §3), not the FE `SkillPack`'s 5-value enum.
---
## 7. Dependencies (screens/phases first)
- **PRD Phase 3 (Intelligence layer)** — Skills Hub + Skill Builder ship here (PRD §8, Sprint 6). Depends on **Phase 0/1 AppShell + IA + shared types** (`ExtensionType`, `Skill`) and the dock/zone IA being settled.
- **Soft dependency on S04 Memory** (memory-access declaration in skills — PRD field "memory access") and **S09 Agent Center** (skills assigned to agents — shared `skillIds` concept; `AgentDef` lacks `skillIds` today, substrate-types.md §e §15.5).
- **Shares the Extend-governance install-audit read surface** with S07 Connector / S08 MCP / Marketplace screens — the `GET /api/audit/installs` route exists (05g §8) and is already consumed by `CapabilitiesApp` AuditTab; the broader `GET /api/extend/audit` (substrate-types.md §d, gap 1) is shared across S06/S07/S08.
- **No dependency on Home/Command Center net-new domains.** Skills Hub can ship before those.
---
## 8. Effort
**M.** The backend is almost entirely present (2 EXISTS / 3 PARTIAL aliases-and-dispatchers, no new store, no `.mind` migration); the lift is FE: rework `CapabilitiesApp` from pack-grid to the PRD tab/table model, add a per-skill `Skill` type + `useSkills` hook, build the net-new 5-step `SkillBuilder`, and resolve the marketplace double-representation. Workspace/Custom scope filtering is a read-time derivation (cheap). Bumps toward L only if archive/rollback + full assign-to-agent wiring land in the same slice.
---
## 9. Open questions
1. **Tab semantics for "Custom" vs "Workspace":** is "Custom" = user-authored-not-from-catalog (derive by excluding starter/capability-pack ids) and "Workspace" = `scope: workspace` frontmatter? Confirm, since neither is a first-class persisted state today (skills are flat files; scope is optional frontmatter the loader ignores).
2. **Does the Builder publish to the local skills dir, the marketplace catalog, or both?** `POST /api/skills/create` (local) vs `POST /api/marketplace/publish` (PRO-gated catalog) are different targets — PRD §12.6 says "test run before publishing" but doesn't say which surface "publish" writes to.
3. **PRD §16.4 / §23 Q4 parity:** how much of the Marketplace tab is the live synced catalog (`/api/marketplace/search`, ~30 sources) vs seeded/mock entries in v1? Affects the Marketplace panel's empty/syncing states.
4. **Skill "inputs/outputs/memory access" persistence:** the current `SKILL.md` frontmatter (`skill-frontmatter.ts`) has `permissions` but **no structured inputs/outputs**. Builder Steps 3 ("Inputs & Outputs") + 4 ("Tools & Data") imply extending the frontmatter schema — confirm whether to extend `SkillFrontmatter` (and `generateSkillMarkdown`/`serializeFrontmatter`) or store these in skill body markdown only.
5. **Archive/rollback in MVP?** Blueprint lists `archive`/`rollback` as skill actions but PRD §16.8 omits them. `skill-retirement.ts` provides the move-to-archive substrate but no HTTP route — include now or defer to a later Extend-governance slice?
6. **Install-audit critical-column bug (cross-cutting, substrate-types.md §d gap 2):** `AuditRiskLevel` TS includes `'critical'` but both DDL CHECKs only allow `low/medium/high` — a `record()` with `'critical'` throws. Skill installs route through this audit path; confirm the plan picks up the one-line migration (or that the marketplace's CRITICAL→`'high'`+`approvalClass:'blocked'` mapping is the permanent contract).

View File

@@ -0,0 +1,190 @@
# Gap Card — S07 · Connector Hub
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extensions.
> PRD acceptance criteria win over the (directional) mockup. Every claim below is grounded in real files.
> PRD = `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`.
## 1. Screen & purpose
**Connector Hub** (PRD §12.7, blueprint Screen 7, dock app id `connectors`). Purpose: "Connect
external tools and data." It is the **Extend** layer surface for service connectors (distinct from
**S08 MCP Hub** — PRD §12.8 — which the current `ConnectorsApp` folds in as a second "MCP Servers" tab
via `McpCatalog`; the refactor should keep the catalog but the two are separate PRD screens).
PRD §12.7 acceptance criterion (the bar this card must hit):
> **"Users see exactly what tools are connected and whether data is flowing."**
The "whether data is flowing" half is the load-bearing gap — today there is no sync state, no last-sync
timestamp, and no recent-activity surface anywhere in the stack.
## 2. Required states (PRD/Blueprint)
PRD §12.7 functional requirements + blueprint Screen-7 state list (`_blueprint_extracted.txt:326-331`,
state list `:467`):
1. **Connected connectors** with **status** and **last sync**.
2. **Available connectors** grouped **by category**.
3. **Health surface**: health, recent sync activity, **token expiry**, and **errors**.
4. **Actions**: connect, **sync now**, manage, **revoke**, **reconnect**.
5. **Recommended** connectors based on onboarding + workspace needs.
6. State machine per blueprint: `connected · disconnected · expired token · syncing · failed · recommended`
(plus list-level `loading / empty / populated / error`).
7. RBAC/trust (PRD §17.3, §18.1): "Connectors require consent and revocation path" + "Connector/MCP
install audit" (append-only trust trail).
Mockup (`screen_07_connector_hub.png`, directional) adds visual chrome the PRD does not mandate but
implies: top tabs (Connected / Available / Recommended / Built by Waggle), a **Connector Summary** donut
(connected/syncing/error/disconnected counts), a **System Health** tile (API/sync/error-rate gauges), a
**Recent Activity** feed, and a "Need Help?" card. Treat these as direction, not a contract.
## 3. Current state in repo (disposition: **rework**)
**Frontend**`apps/web/src/components/os/apps/ConnectorsApp.tsx` (356 LOC):
- Two-tab sidebar: **Services** (native connectors) + **MCP Servers** (`McpCatalog`). Filter rail = `all / connected / available`.
- Renders connectors from `adapter.getConnectors()` grouped by a **hardcoded** `CATEGORIES` map (`:41-50`)
+ hardcoded `SETUP_HINTS` (`:52-60`). Per-row expand reveals capability chips, setup steps, and a
token/API-key input.
- Connect flow (`handleConnect`, `:113-135`): writes credential via `adapter.addVaultSecret('connector:<id>')`
**then** calls `adapter.connectConnector(id)` (note: the adapter's `connectConnector` POSTs **no body**
`adapter.ts:1161`; the credential is pre-seeded in vault). Disconnect = `adapter.disconnectConnector(id)`.
- `shouldResetCredentialInputs` (`:69-71`) is an exported, regression-tested pure guard
(`phase5b-connectors.test`) — **keep**, it prevents credential cross-submission.
- Subcomponents (`apps/web/src/components/os/apps/connectors/`): `BrandTile.tsx`, `brand-identity.ts`
(logo/colour identity — **keep, reuse**), `McpCatalog.tsx` + `McpServerCard.tsx` + `mcp-registry.ts`
(the MCP tab — belongs to **S08**, leave for that card).
- **What's MISSING in the UI vs PRD:** no last-sync, no health probe call, no "sync now", no "revoke"
(only disconnect), no recommended tab, no recent-activity feed, no summary/health tiles, no token-expiry
surfacing. `adapter.getConnectorHealth` **exists** (`adapter.ts:1156`) but is **never called** anywhere
(grep-confirmed) — the UI shows only the coarse list `status`.
**Backend**`packages/server/src/local/routes/connectors.ts` (121 LOC): 4 routes —
`GET /api/connectors`, `GET /api/connectors/:id/health`, `POST /api/connectors/:id/connect`,
`POST /api/connectors/:id/disconnect`. Backed by `fastify.connectorRegistry` (`ConnectorRegistry` in
`packages/agent/src/connectors/connector-registry.ts`; **30** connectors registered via
`setup-connectors.ts`; per-connector `healthCheck()` impls exist in every
`packages/agent/src/connectors/*-connector.ts`).
**Disposition: rework** — the shell, brand tiles, category grouping, and connect/disconnect flow are
solid and reusable, but the screen is missing the entire "is data flowing?" dimension (sync/health/
activity/recommended/audit). This is additive rework on a good base, not a rebuild.
## 4. Frontend work
**Rework `ConnectorsApp.tsx` into the PRD Connector Hub** (keep MCP tab in place for S08):
- **Add tabs** matching PRD: `Connected · Available · Recommended` (mockup also shows "Built by Waggle" —
optional). The existing `all/connected/available` filter rail collapses into these tabs.
- **Per-row health + last sync**: lazy-call `adapter.getConnectorHealth(id)` for connected rows (already in
adapter, currently dead) to render `status` (connected/expired/error), `tokenExpiresAt` (token-expiry
badge), `lastChecked`, and (new) `lastSyncAt`. Extract a **new `ConnectorCard.tsx`** component
(blueprint names `ConnectorCard`, `:488`) from the inline row JSX (`:262-340`) — status pill, last-sync,
health dot, actions menu (Connect / Sync now / Manage / Revoke / Reconnect).
- **Recommended tab**: reuse the existing `recommendConnectors(personaId)` from `@waggle/shared`
(`connector-recommendations.ts:146`) — today wired **only** into the MCP tab (`McpCatalog.tsx:69`),
not service connectors. Forward `personaId` (already a prop, `:28`) to a recommended-connectors section.
- **Summary + health + activity tiles** (mockup, directional): a small **`ConnectorSummary`** donut from
client-side status counts (no new endpoint), a **System Health** tile from the new
`GET /api/connectors/health` aggregate (§5), and a **Recent Activity** feed from the new
`GET /api/connectors/activity` (sync/connect/revoke events). The blueprint's reusable `Timeline` and
`EvidencePanel` (`:488`) can host the activity list.
- **Actions wiring**: add `syncConnector(id)``POST /api/connectors/:id/sync`; add `revokeConnector(id)`
`POST /api/connectors/:id/revoke` (PRD verb; aliases existing disconnect); "Reconnect" reuses the
existing connect flow. Surface a `syncing` row state + toasts on `failed`.
- **State**: extend the local `Connector` interface (`:31-38`) with optional `lastSyncAt`, `tokenExpiresAt`,
`health`, `category`, `recommended`. **Move category off the hardcoded map** onto the
`ConnectorDefinition.category` field that already exists in `@waggle/shared` (`types.ts:299`) but is not
yet emitted by the row data — reconcile so the UI stops carrying its own `CATEGORIES`.
**Adapter (`apps/web/src/lib/adapter.ts`) — add 2 methods, reuse 1:**
- `syncConnector(id): POST /api/connectors/:id/sync` (NEW).
- `revokeConnector(id): POST /api/connectors/:id/revoke` (NEW; or alias to existing `disconnectConnector`).
- `getConnectorHealth(id)` already exists (`:1156`) — start calling it; optionally add
`getConnectorsHealth(): GET /api/connectors/health` aggregate + `getConnectorActivity()`.
## 5. Backend work (PRD §16.9 connectors subset)
> §16.9 spans Connectors + MCPs + Marketplace. This card scopes the **Connectors** rows only; MCP rows
> (`/api/mcps*`) and `/api/marketplace*` belong to **S08 / S13**.
| PRD §16.9 endpoint | Status | Action |
|---|---|---|
| `GET /api/connectors` | **EXISTS** | `connectors.ts:6``registry.getDefinitions()`. **EXTEND** the definition payload to carry `category` (already on the type, `types.ts:299`) + `lastSyncAt` so the UI drops its hardcoded `CATEGORIES`/sync-shim. |
| `POST /api/connectors/:id/connect` | **EXISTS** | `connectors.ts:55`. **EXTEND**: write an `install-audit` entry (see audit gap below) on success. |
| `POST /api/connectors/:id/sync` | **MISSING** | **NET-NEW** route in `connectors.ts`. No sync substrate exists — `WaggleConnector` (`connector-sdk.ts`) has `connect`/`healthCheck`/`execute` but **no `sync()`**. Minimum viable: probe `healthCheck()` + record a `lastSyncAt` timestamp (persist in vault sub-key `connector:<id>:lastSync` or a small store) + emit an activity event. Full sync (re-pull data) is a larger connector-SDK addition — flag as phased. |
| `POST /api/connectors/:id/revoke` | **PARTIAL** | Closest is `POST /api/connectors/:id/disconnect` (`connectors.ts:107`, removes `connector:<id>` + sub-keys). **EXTEND**: add a `/revoke` alias (or rename) that additionally writes an `install-audit` `action:'rejected'`/revoke entry. Same intent, PRD verb. |
**Additional backend work the PRD requires but §16.9 does not enumerate (implied by §18.1 / §12.7):**
- **Install/consent audit on connect & revoke (MISSING write path).** `connectors.ts` does **NOT** call
`fastify.auditStore.record(...)` (grep-confirmed — only `marketplace.ts:224-319` writes audit today).
PRD §18.1 mandates "Connector/MCP install audit". The substrate exists: `InstallAuditStore`
(`packages/core/src/install-audit.ts`) with `AuditCapabilityType` including `'connector'` (`:22`),
actions `installed/rejected` (`:15`). **EXTEND** connect/revoke to record entries. ⚠ **Latent enum-drift
bug** to avoid: TS `AuditRiskLevel` allows `'critical'` (`install-audit.ts:16`) but both DDL CHECKs only
allow `low/medium/high` (`install-audit.ts:65`, `schema.ts:130`) — pass `'high'`, never `'critical'`.
- **Audit read route (MISSING).** No HTTP surface lists the audit trail (`getRecent`/`getByCapability`
have no route). The Connector Hub's "Recent Activity" + the Extend trust-trail view need a NEW read,
e.g. `GET /api/connectors/activity` (or a shared `GET /api/extend/audit` filtered to `type:'connector'`).
Net-new but reads existing `install_audit` rows — no migration.
- **System-health aggregate (optional, mockup).** A `GET /api/connectors/health` that fans
`registry.healthCheck()` over connected connectors for the System-Health tile. Net-new thin aggregator;
no new substrate.
**`.mind` migration:** **None required.** `install_audit` already exists in `schema.ts:119`; connector
credentials live in the **vault**, not `.mind`. `lastSyncAt` can live as a vault sub-key or a tiny store —
no schema change. (The audit table's pre-existing `risk_level` CHECK drift is a code-discipline note, not
a migration.)
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`ConnectorDefinition` / `ConnectorHealth` / `ConnectorStatus` already exist** in
`packages/shared/src/types.ts:266-312` and are the canonical contract — the FE should import these
instead of the ad-hoc local `Connector` interface in `ConnectorsApp.tsx:31-38` (a lossy duplicate).
- **`lib/types.ts` has a thin `Connector`** (frontend mirror, §(d) substrate inventory) — reconcile it to
the shared `ConnectorDefinition` shape (add `category`, `tokenExpiresAt`, `lastSyncAt`, optional `health`).
- **New optional fields:** `ConnectorDefinition.lastSyncAt?: string` (and surface `category` which is
already declared but not always populated). PRD §15.2 lists an `ExtensionType` union — connectors are one
member; not blocking for S07 but should be defined when S13 (Marketplace) lands.
- **`ConnectorRecommendation`** already exists (`connector-recommendations.ts:38`) — reuse as-is for the
Recommended tab.
## 7. Dependencies (screens/phases first)
- **Onboarding (S12, PRD §12.12 Tool Discovery / §16.1)** feeds the Recommended tab's "based on
onboarding" signal. `recommendConnectors(personaId)` works standalone today, so S07 is **not blocked**
but the richer "based on workspace needs" recommendation improves once onboarding tool-discovery answers
are persisted.
- **S08 MCP Hub** shares this app shell (the MCP tab). Coordinate the tab split so reworking S07 doesn't
regress the MCP catalog; ideally S07 and S08 land in the same phase or S07 first with the MCP tab
untouched.
- **Extend trust-trail / Marketplace (S13)** shares the install-audit read route — build the audit write +
read once, consume in both. Sequence the audit endpoint before/with S07's Recent Activity.
- No hard dependency on Home/Workspace screens.
## 8. Effort: **M**
Frontend rework on a solid existing shell (new tabs, ConnectorCard extraction, wire two dead/easy adapter
paths) is modest. The backend lift is small-but-real: `/sync` and `/revoke` routes + audit write on
connect/revoke + an audit read route + a health aggregate — all over existing substrate (registry, vault,
`install_audit`), zero migration. The one true unknown (real data-pull `sync` in the connector SDK) is
phaseable to a health-probe-only v1, which keeps this **M** rather than **L**.
## 9. Open questions
1. **Sync semantics.** Does "sync now" mean (a) re-probe health + stamp `lastSyncAt` (v1, cheap), or
(b) re-pull/refresh cached connector data (needs a new `sync()` on `WaggleConnector` + a cache)? PRD
§12.7 says "show recent sync activity" but connectors are currently **runtime tools** (`execute(action)`),
not background data-syncers. Recommend v1 = health-probe + timestamp.
2. **`lastSyncAt` storage.** Vault sub-key (`connector:<id>:lastSync`) vs a small dedicated store vs an
`install_audit` derived value? Vault sub-key is lowest-risk and matches the existing `connector:<id>:email`
pattern.
3. **revoke vs disconnect.** Add `/revoke` as a true alias of `disconnect`, or have `revoke` additionally
purge OAuth tokens (`<provider>_oauth_token`, `oauth.ts`) + write a stronger audit entry? PRD §17.3
("revocation path") implies the latter for OAuth connectors.
4. **Audit scope.** Connector-specific `GET /api/connectors/activity` vs a shared
`GET /api/extend/audit?type=connector`? The latter serves S07 + S08 + S13 with one route (preferred).
5. **Audit `.mind` location.** `install_audit` is per-`.mind` (per-workspace). Connectors are **global**
(vault-scoped, not workspace-scoped). Confirm the audit-store decorator binds to `personal.mind` so the
global Connector Hub trail is consistent (substrate inventory §(d) flags this exact ambiguity).
6. **Token-expiry source.** `ConnectorHealth.tokenExpiresAt` is populated only on the fallback path
(`connectors.ts:49`); confirm each connector's `healthCheck()` surfaces real expiry, else the expired-token
state will never show for SDK-backed connectors.

View File

@@ -0,0 +1,270 @@
# Gap Card — S08 · MCP Hub
> UX-refactor planning artifact. Execution model is the LOCKED **in-place incremental refactor** of
> `apps/web` + targeted backend extensions. Mockup is directional; PRD acceptance criteria win.
> Every claim below cites a real file. PRD = `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`.
---
## 1. Screen & purpose
**MCP Hub** — the power-user capability-extension surface for Model Context Protocol servers (PRD §12.8,
PRD line 566-579). Belongs to the **Extend** IA layer (PRD §10.4; blueprint line 74, 81: route group
`/mcps`). Explicit blueprint directive: **"Do not bury Connectors/MCPs inside Settings"** (blueprint line 79).
Purpose (PRD §12.8): *"Enable power-user extension through Model Context Protocol servers."* MCPs are
auditable capability providers for power users and agents (blueprint line 140). The governing acceptance
criterion: **"MCPs are powerful but always visible, scoped, auditable, and reversible."** (PRD line 579).
Primary user journey — **J11 / Journey 9 "Install MCP"** (PRD lines 736-743; blueprint line 206-208):
power user opens MCP Hub → selects e.g. Postgres MCP → reviews risk and scope → approves installation →
MCP becomes available to selected workspace/agent → **install audit is recorded**.
Mockup (directional only — `screen_08_mcp_hub.png`): top toolbar with tabs (Installed / Available /
Marketplace / Custom / Remote Registry), search, "Add MCP Server" CTA; left an "Installed MCPs" table
(name, status pill, connected-to, last-used, scope, actions) over an "Available MCPs" category-filtered
grid; right rail with an "MCP Overview" donut (counts), "MCP Health", "Recent Activity" (audit/log feed),
and a "Custom MCP" add affordance.
---
## 2. Required states (PRD / Blueprint)
**Tabs (PRD §12.8 line 572):** Installed · Available · Marketplace · Custom · Remote Registry.
**MCP object fields (PRD §12.8 line 573):** name, description, version, status, **connected to**,
**last used**, **locality** (local/remote), **risk**, **permissions**, **logs**. Target schema
`Extension/MCP` (blueprint line 554, PRD §15.2 `ExtensionType`): `id, type, name, source, version,
endpoint, capabilities, riskLevel, approvalClass, status, health, installedAt, approvedBy`.
**Actions (PRD §12.8 line 574):** install · start/stop · test · scope · view logs · revoke · add custom MCP.
Risky MCPs require approval + audit trail (PRD line 575; blueprint line 161 "elevated connectors/MCPs …
require approval").
**Extension lifecycle states (PRD §14.7 lines 906-914):** Available · Installed · Update available ·
Installing · Failed install · **Risk approval required** · Disabled/revoked. Plus per-server runtime
status (blueprint line 334-335): installed; available; **running**; **stopped**; **error**; risk-approval-needed.
**Connector/MCP UI states (PRD §14.5 / blueprint line 467):** Loading, empty, populated, error.
**Scope (acceptance-critical):** an MCP must be scopeable to workspace and/or agent (PRD line 742;
blueprint line 207 "scope to workspace/agent"; line 601 "Connector and MCP installs are visible,
permissioned, health-checked and auditable"; line 600 "a workspace agent cannot run with hidden
memory/tool/MCP access").
**Offline degradation (blueprint line 516):** offline mode degrades MCPs gracefully while keeping local
workspace/memory available.
---
## 3. Current state in repo
**There is NO dedicated MCP Hub app today.** MCPs surface in three thin, mostly-static places:
1. **Static catalog (discovery only) — `apps/web/src/components/os/apps/connectors/McpCatalog.tsx`** +
**`McpServerCard.tsx`** + **`mcp-registry.ts`** (a re-export shim of `@waggle/shared`). Rendered as the
"MCP Servers" tab inside **`ConnectorsApp.tsx`** (`apps/web/src/components/os/apps/ConnectorsApp.tsx:197-205,348-349`).
The catalog data is `MCP_CATALOG` / `MCP_CATEGORIES` / `CATEGORY_EMOJI` in
**`packages/shared/src/mcp-catalog.ts`** (`McpServer` interface at `:17`; `MCP_CATALOG` at `:53`; 14
categories at `:30`). Per the backend-map (`05f` §12, lines 391-401), this catalog is **static and not
connected at runtime** — a discovery/install-command directory.
- **The only "install" UX is copy-a-shell-command-then-restart** (`McpServerCard.tsx:30-37,98-138`
copies `server.installCmd`; tooltip text `:111-116`: "Copy the command … Run it in your terminal …
Restart Waggle"). There is **no in-app install, start/stop, test, scope, revoke, logs, risk, or
status** — none of the PRD §12.8 actions/fields exist in the UI.
2. **Live runtime status (read-only) — `GET /api/capabilities/status`**
(`packages/server/src/local/routes/capabilities.ts:35-53,103-110`) returns
`mcpServers:[{name,state,healthy,tools}]` from the real runtime. **But the runtime is empty by default**
(see #3 below), so this list is always `[]` on a stock install.
3. **The MCP runtime engine (rich, but unwired) — `packages/agent/src/mcp/mcp-runtime.ts`.**
`McpRuntime` has the full lifecycle the PRD needs: `addServer(config)` (`:308`), `removeServer(name)`
(`:327`), `startAll`/`stopAll`, per-instance `start()/stop()/callTool()` (`McpServerInstance` `:102,154,178`),
`getServerStates()` (`:362`), `getHealthy()` (`:370`), `isServerHealthy(name)` (`:399`), and **per-workspace
scoping** via `McpServerConfig.workspaceId` (`:13`) + `getToolsForWorkspace(workspaceId)` (`:385`).
**CRITICAL GAP:** the runtime is instantiated empty — `const mcpRuntime = new McpRuntime()` with comment
*"empty by default"* (`packages/server/src/local/index.ts:911`), and **nothing ever calls `addServer()`**
(grep `mcpRuntime.addServer` / `.mcp.json` / `loadMcpConfig` → 0 matches in `packages/server/src`). So
there is no persistence layer that loads installed MCP configs at boot, and no HTTP route to install/
start/stop/test/remove a server.
**Install-audit substrate is present** (`AuditCapabilityType` includes `'mcp'`
`packages/core/src/install-audit.ts:22`; `InstallAuditStore.record/getRecent/getByCapability` exist) but
**has no read HTTP route** (substrate-types inventory §d: writes only; `GET /api/audit/installs` exists in
`skills.ts` but is a generic recent-installs feed). MCP installs are not currently audited because there is
no MCP install path to audit.
**Adapter:** no `getMcps`/`installMcp`/`testMcp` methods in `apps/web/src/lib/adapter.ts` (grep → 0 matches).
**Disposition: `create-new`** (with substrate reuse). A net-new **MCPHub app** (`MCPHubApp.tsx`) promoted
out of ConnectorsApp's MCP tab, **plus net-new `/api/mcps/*` backend routes wiring the existing `McpRuntime`
+ a new persisted config store + install-audit**. The static catalog component (`McpCatalog`/`McpServerCard`)
is reused as the "Available"/"Marketplace" tab content. This is the single largest backend gap on the Extend
layer — the engine exists but is entirely unwired to UI or persistence.
---
## 4. Frontend work
**New IA placement:** add an `mcp-hub` (or reuse the dead `marketplace`-adjacent slot) `AppId` in
`apps/web/src/lib/dock-tiers.ts`, register chrome in `Desktop.tsx` `appConfig` + a `renderAppContent`
switch case (per frontend inventory §b lines 123-129), and add a dock entry under the **Extend** zone-parent
(`dock-tiers.ts` zone model). MCP Hub is power-user/PRO-tier-gated (PRD personas: power user/developer needs
MCPs — PRD line 151).
**Components to CREATE** (`apps/web/src/components/os/apps/`):
- `MCPHubApp.tsx` — shell with the 5 PRD tabs (Installed / Available / Marketplace / Custom / Remote
Registry). Mirror `ConnectorsApp.tsx`'s sidebar-tab + filter layout (`:183-216`) so it's visually
consistent with the Connector Hub it splits from.
- `mcp/InstalledMcpTable.tsx` — table of running/installed servers: name, status pill (running/stopped/
error/installing — maps `McpServerState` `mcp-runtime.ts:16`), connected-to, last-used, locality,
scope chip, row actions (start/stop, test, scope, view logs, revoke). Drives PRD §12.8 line 573-574.
- `mcp/AddCustomMcpForm.tsx` — Custom tab: `{ name, command, args[], env{}, workspaceId? }``POST /api/mcps`
(matches `McpServerConfig` `mcp-runtime.ts:8-14`). Reuse `injection-scanner` patterns server-side.
- `mcp/McpDetailPanel.tsx` (right rail) — MCP Overview counts, MCP Health, permissions/scope editor,
**logs viewer**, and the **risk/approval banner** ("Risk approval required" state). Reuse the approvals
surface pattern from `ApprovalsApp.tsx` / inline-chat approval (same backend `/api/approval/*`).
- `mcp/McpScopeDialog.tsx` — scope-to-workspace/agent picker (writes `workspaceId` onto the server config;
later, `mcpIds[]` onto workspace/agent per §6).
**Components to REUSE (keep-promote):**
- `McpCatalog.tsx` + `McpServerCard.tsx` + `mcp-registry.ts` → become the **Available** + **Marketplace** +
**Remote Registry** tab bodies. Today `McpServerCard` only shows a copy-command strip
(`McpServerCard.tsx:98-138`) — **rework** it to add a real **Install** button (calls the new
`installMcp` adapter method) while keeping copy-command as the offline fallback.
- `recommendConnectors` from `@waggle/shared` (already used `McpCatalog.tsx:15,69`) for "Recommended" tile.
- `LockedFeature.tsx` for tier gating; `ContextMenu.tsx` for row actions; shadcn `table`, `badge`, `dialog`,
`tabs`, `tooltip` primitives (`components/ui/*`, frontend inventory §e).
**Adapter methods to ADD** (`apps/web/src/lib/adapter.ts` — the single sidecar gateway, §c lines 162-265):
`getMcps()`, `installMcp(payload)`, `addCustomMcp(config)`, `startMcp(id)`, `stopMcp(id)`, `testMcp(id)`,
`scopeMcp(id, scope)`, `revokeMcp(id)`, `getMcpLogs(id)` — one per PRD §16.9 / §12.8 action.
**New hook:** `hooks/useMcps.ts` (mirrors the `useConnectors`-style pattern) returning
`{ installed, available, custom, install, start, stop, test, scope, revoke, refresh }`. Wire SSE/poll over
`/api/capabilities/status` or a new `/api/mcps` for live status (cache-invalidate on
"connector sync" / install completion per blueprint line 514).
---
## 5. Backend work (PRD §16.9 endpoints — all MCP rows)
> All net-new routes live in a new `packages/server/src/local/routes/mcps.ts`, registered in
> `local/index.ts`, wiring the already-built `McpRuntime` (`server.agentState.mcpRuntime`,
> decorated `local/index.ts:1361`). **No new SQLite table is strictly required** — but a persisted
> config source IS (see migration flag).
| PRD §16.9 endpoint | Status | Extend vs net-new + substrate it touches |
|---|---|---|
| `GET /api/mcps` | **PARTIAL → build net-new route over existing data** | No `/api/mcps` exists (grep-confirmed in backend-routes inventory, line 422 & §16.9 line 522). The data is **derivable today**: live runtime status from `McpRuntime.getServerStates()/getHealthy()/getAllTools()` (already surfaced in `capabilities.ts:35-53`), enriched with catalog metadata from `MCP_CATALOG` (`@waggle/shared`). **Net-new** thin route in `mcps.ts` that joins runtime state + persisted config + catalog into the `Extension/MCP` shape (§2). Touches: `McpRuntime` (agent), `MCP_CATALOG` (shared), new config store. |
| `POST /api/mcps/install` | **PARTIAL → net-new, route through existing install path** | No `/api/mcps/install` (inventory §16.9 line 523). Closest install paths are marketplace (`POST /api/marketplace/install` — SecurityGate + audit, `marketplace.ts:224-319`) and plugin install (`POST /api/plugins/install`). **Net-new** MCP install that: (a) persists an `McpServerConfig`, (b) calls `mcpRuntime.addServer(config)` + `start()` (`mcp-runtime.ts:308,102`), (c) runs the **SecurityGate + writes an `install_audit` row** with `capability_type:'mcp'` (`install-audit.ts:22`) — satisfying the "install audit is recorded" acceptance step (PRD line 743). Risky servers → return `risk approval required` state instead of starting. Tier: PRO (match marketplace). |
| `POST /api/mcps/:id/test` | **MISSING** | No MCP test/health route (inventory §16.9 line 524). Closest analog is `GET /api/connectors/:id/health`. **Net-new**: resolve the server, `start()` if needed, assert `isHealthy()` (`mcp-runtime.ts:94,399`) and/or do a `tools/list` round-trip, return health + discovered tools. Touches `McpRuntime`. |
| `POST /api/mcps/:id/revoke` | **MISSING** | No MCP revoke/uninstall by id (inventory §16.9 line 525). Closest: `DELETE /api/plugins/:name`. **Net-new**: `mcpRuntime.removeServer(name)` (`mcp-runtime.ts:327`) + delete persisted config + write `install_audit` `action:'rejected'`/`'revoked'`. Touches `McpRuntime` + config store + install-audit. |
| `GET /api/marketplace` | **PARTIAL** | Listing is `GET /api/marketplace/search` (`marketplace.ts`); the Marketplace tab reuses it. Alias of `/search` (inventory §16.9 line 526). Not MCP-specific work. |
| `POST /api/marketplace/install` | **EXISTS** | `marketplace.ts` (Tier PRO, SecurityGate) — reused by the Marketplace tab. |
**Additional routes implied by PRD §12.8 actions not in §16.9 list (net-new):**
- `POST /api/mcps` (add **custom** MCP) — blueprint API line 530 lists `GET/POST /mcps`. Persists config +
`addServer` + audit. **Net-new.**
- `POST /api/mcps/:id/start` and `POST /api/mcps/:id/stop` — PRD §12.8 "start/stop" action. Map to
`McpServerInstance.start()/stop()` (`mcp-runtime.ts:102,154`). **Net-new.**
- `PATCH /api/mcps/:id/permissions` (scope/permissions) — blueprint API line 530 (`PATCH /mcps/:id/permissions`).
Writes `workspaceId`/scope onto the persisted config (runtime honors it via `getToolsForWorkspace`,
`mcp-runtime.ts:385`). **Net-new.**
- `GET /api/mcps/:id/logs` — PRD §12.8 "view logs". No log capture exists in `McpServerInstance` today
(stderr is piped `:113` but not retained). **Net-new** + small runtime change to buffer stderr/state-change
events for retrieval.
- `GET /api/extend/audit` (or extend `/api/audit/installs`) — surface the install-audit trail for the MCP Hub
right-rail "Recent Activity". `InstallAuditStore.getRecent()` exists but has no MCP-scoped HTTP read
(substrate-types §d gap #1). **Net-new** (implied addition to §16.9, flagged in substrate inventory).
**Migration / persistence flag (IMPORTANT):**
- **No `.mind` SQLite migration is required for the MCP entity itself** — installed MCP configs can persist as
a JSON file (mirrors `workspace.json` / `.mcp.json` convention; note repo root already has an untracked
`.mcp.json` per git status) read at boot to populate `mcpRuntime.addServer()`. **This persistence layer is
entirely net-new** (today `mcpRuntime` is empty and never populated — `local/index.ts:911`). This is the
load-bearing backend gap: without it, "Installed" MCPs do not survive restart.
- **Install-audit DDL drift (latent, flag for the plan):** `AuditRiskLevel` TS includes `'critical'`
(`install-audit.ts:16`) but both CHECK constraints allow only `low|medium|high`
(`install-audit.ts:65`, `schema.ts:130`). Any MCP install recording `riskLevel:'critical'` would throw a
CHECK violation — map CRITICAL → `'high'` + `approvalClass:'blocked'` as marketplace.ts already does
(`marketplace.ts:224-319`), or fix the DDL.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`ExtensionType`** (PRD §15.2 line 953: `'skill'|'connector'|'mcp'|'model'|'template'|'external_tool'`)
**MISSING** in `apps/web/src/lib/types.ts` (substrate-types §e: none of the §15.2 unions exist in FE).
Add it.
- **`Extension` / `Mcp` interface** (blueprint line 554: `id, type, name, source, version, endpoint,
capabilities, riskLevel, approvalClass, status, health, installedAt, approvedBy`) — **MISSING**. No
`Extension`/`Mcp` type anywhere in FE. Add to `lib/types.ts` (and ideally `packages/shared/src/types.ts`
so the sidecar route and FE share it). The existing `McpServer` (`@waggle/shared` `mcp-catalog.ts:17`:
`id,name,description,author,category,url,installCmd,capabilities,official?,logo?`) is the **catalog/discovery**
shape — distinct from the **installed-instance** `Mcp` shape; keep both, the Hub joins them by `id`.
- **Runtime types already exist server-side** and should be the source of truth for the route contract:
`McpServerConfig`, `McpServerState`, `McpToolInfo` (`packages/agent/src/mcp/mcp-runtime.ts:8-22`). Mirror
`McpServerState` (`'starting'|'ready'|'error'|'stopped'`) into the FE status union (map to PRD §14.7 states).
- **Reuse from install-audit:** `AuditCapabilityType`, `AuditRiskLevel`, `AuditApprovalClass`
(`packages/core/src/install-audit.ts:15-22`) for the riskLevel/approvalClass/audit fields — do not invent
parallel enums.
- **`mcpIds[]` on Workspace/Agent** (PRD §15.3 `WorkspaceConfigV2.mcpIds`, §15.5 Agent `mcpIds`) —
**MISSING** from both `WorkspaceConfig` (`workspace-manager.ts`, substrate-types §a) and `AgentDef`
(`packages/shared/src/types.ts:36-47`, §e). Needed for the "scope to workspace/agent" action. Additive
optional field (no DB migration — `workspace.json` is JSON).
---
## 7. Dependencies (screens / phases first)
- **Connector Hub (S07)** — MCP Hub is split OUT of `ConnectorsApp.tsx`'s MCP tab; do the Connector Hub
promotion first (or jointly) so the shared sidebar-tab/filter/health/state patterns are settled and the
MCP tab can be cleanly extracted. Same `Extend`-layer dock placement work.
- **Extend layer / dock-zone IA** — requires the new `AppId` + dock-zone wiring (frontend inventory §b/§d:
consolidate on `AppId`, add Extend zone entries). Blocks any new Extend app from being reachable.
- **Install-audit read route (§d gap)** — shared with Skills (S?) and Connectors governance; build once,
reuse across the Extend layer.
- **Approvals surface** — risk-approval flow reuses existing `/api/approval/*` + `ApprovalsApp` patterns;
no new approval substrate, but the wiring depends on that surface staying stable.
- **Marketplace** — the Marketplace tab reuses `GET /api/marketplace/search` + `POST /api/marketplace/install`
(already exist); ensure marketplace MCP packages are tagged so they route to the MCP install path.
- **Phase hint:** later phase. Backend wiring (persisted config store + runtime population + `/api/mcps/*`)
is the prerequisite for any non-static UI; the static catalog already ships, so this is a depth upgrade,
not a day-1 blocker. Sequence after Home/Workspace/Memory core screens.
---
## 8. Effort: **XL**
Largest Extend-layer gap: the runtime engine exists but is **completely unwired** (empty `McpRuntime`, no
persistence, no HTTP surface, copy-command-only UI). Requires a net-new persisted MCP-config store + boot-time
runtime population + ~8 net-new sidecar routes (install/start/stop/test/scope/revoke/logs + custom add) +
SecurityGate/audit integration + a full new app shell with 5 tabs, installed-table, detail/logs panel, scope
dialog, risk-approval flow, and adapter/hook layer — while preserving the static catalog as the
Available/Marketplace tabs. (Frontend-only would be M; the backend wiring + persistence + governance is what
pushes it to XL.)
---
## 9. Open questions
1. **Persistence location/format for installed MCP configs** — JSON file (`.mcp.json` at dataDir, mirroring
`workspace.json`; root already has an untracked `.mcp.json`) vs a new `install_audit`-adjacent table?
Recommendation: JSON file (no migration), but confirm the dataDir path + multi-workspace scoping model.
2. **Scope model** — is an MCP scoped by writing `workspaceId` onto its single config (1 server : 1 workspace,
per current `McpServerConfig.workspaceId`), or by an `mcpIds[]` membership array on each workspace/agent
(N:N)? PRD §15.3/§15.5 imply `mcpIds[]` (N:N); the runtime today only supports the single-`workspaceId`
field (`mcp-runtime.ts:13,385`). N:N needs a runtime change.
3. **Logs capture** — `McpServerInstance` pipes stderr but does not retain it (`mcp-runtime.ts:113`). Add a
ring-buffer of stderr + stateChange events for `GET /api/mcps/:id/logs`, or defer logs to a later phase?
4. **"Remote Registry" tab semantics** — is this the Composio gateway (already referenced in
`McpCatalog.tsx:106,294-301`), the official `modelcontextprotocol/servers` registry, or a remote
(HTTP/SSE-transport) MCP class distinct from local stdio? The runtime today is **stdio-only**
(`mcp-runtime.ts:108-115`); remote-transport MCPs would need a new transport in `McpServerInstance`.
5. **Install execution surface** — does in-app "Install" run `npx …` (spawn a child process to install the
package) in the sidecar, or only register config + rely on a globally-installed binary? Security review
needed (spawning installers vs the current copy-to-terminal model). Tauri IPC/CSP implications.
6. **Tier gate** — confirm MCP Hub is PRO+ (matches marketplace install gate) vs power-user-density-tier
(`UserTier`) only. Three tier vocabularies to reconcile (frontend inventory §f).

View File

@@ -0,0 +1,144 @@
# Gap Card — S09 Agent Center
> UX-refactor planning artifact. Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extensions over the existing sidecar/substrate. Mockup is directional; PRD §12.9 + §16.7 acceptance criteria win.
> PRD: `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md` (§12.9 lines 581-595, §16.7 lines 1112-1120, §15.5 lines 1015-1035).
---
## 1. Screen & purpose
**Agent Center** — manage agents as explicit, governed work actors (PRD §12.9, blueprint Screen 9, page 36). Per blueprint p.8 "Agent Model" (lines 125-136), an Agent is a scoped worker carrying goal, model, autonomy, memory scopes, skills, tools, MCPs, permissions — distinct from a Persona (a prompt+tools template). The Center is the list/manage surface; the **Agent Builder** (S18, PRD §12.9 + blueprint p.45) is the 5-step create flow.
Mockup (`screen_09_agent_center.png`, directional): top category tab strip (All Agents / Personal / Workspace / Team / Autonomous / Archive) + Templates + Filters + **Create Agent**; a KPI metric row (active count, success-rate avg, run counts, hours-saved, cost); a left list/table of agent rows (avatar, name, goal sub-line, type chip, model, status pill, success-rate bar, owner avatars, last-run, sparkline); a right rail (Agents by Type donut, Top Capabilities, Recent Activity, Quick Actions). Search + sort + pagination on the list.
---
## 2. Required states (PRD/Blueprint)
PRD §12.9 functional requirements:
- **Agent categories:** Personal, Workspace, Team, Autonomous, Templates, Archive (tabs).
- **Agent card/list** shows: goal, status, owner, workspace, capabilities, model, success rate, last run.
- **Agent Builder** steps: Basic Info → Capabilities → Memory & Tools → Permissions → Review & Create.
- **Agent fields** (§15.5): name, goal, description, persona/avatar, model, autonomy, type, memoryScopes, skillIds, connectorIds, mcpIds, permissions, status, createdBy, lastRunAt, successRate.
- **Agent safety:** no hidden tool/memory access; all elevated access reviewed (blueprint p.8: "Agent Builder must require model, goal, memory scope, skills/tools/MCPs and autonomy level"; "run logs link to execution_traces and ai_interactions"; "Autonomous agents require explicit schedule/trigger and stop/pause").
PRD agent state model (§14.5): Draft · Idle · Running · Paused · Failed · Waiting for approval · Completed · Archived. Blueprint p.18 (line 342): Idle; running; paused; failed; needs approval; archived.
Global states (§14.1) every screen must implement: Loading · Empty · Populated · Error · Offline/local-only · Syncing · Permission denied · Partial data · Approval required.
Acceptance: "User can explain what an agent can see and do before enabling it." (PRD line 595.)
---
## 3. Current state in repo (exact files + what they do)
**Disposition: `rework`** (the AgentsApp surface is a Personas manager, not the PRD Agent Center; the new IA needs an agent-as-actor object the current code does not model — but it reuses persona/group/fleet/trace substrate, so it is a rework + create-new-entity, not a from-scratch replacement).
Live frontend (grep-confirmed under `apps/web/src/components/os/apps/`):
- `AgentsApp.tsx` (346 LOC) — titled **"Personas"**, NOT "Agents". Two tabs: `agents` (= persona catalog) + `groups` (= agent groups). Loads `adapter.getPersonas()` + `adapter.getCapabilityStatus()` (for tool list) + `adapter.getAgentGroups()`. CRUD over **personas** (`createPersona/updatePersona/deletePersona/generatePersona`) and **groups** (`create/update/delete/runAgentGroup`). No goal/status/owner/successRate/last-run/autonomy concept; no category tabs; no KPI row; no run/pause controls on a card.
- `agents/AgentCard.tsx` (48 LOC) — avatar + name + description + delete (custom only) + chevron. No status pill, success bar, model, owner, last-run.
- `agents/AgentDetail.tsx` (108 LOC) — persona detail: tools list, suggested commands, workspace affinity. No run/pause/traces, no permissions panel.
- `agents/CreateAgentForm.tsx`, `CreateGroupForm.tsx`, `GroupCard.tsx`, `GroupDetail.tsx`, `GroupExecutionPanel.tsx` — persona/group create+exec UI. `CreateAgentForm` is a single form (name/description/icon/tools/systemPrompt), NOT the PRD 5-step Builder.
- `agents/types.ts``BackendPersona` (id/name/description/icon/affinity/commands/tools/systemPrompt/custom), `AgentGroup`, `GroupExecState`. **No Agent type with goal/autonomy/status/successRate.**
- `overlays/SpawnAgentDialog.tsx` (447 LOC) — ad-hoc sub-agent run: task + persona-override + model + parent-workspace; 2-step config→confirm with cost estimate; calls `adapter.spawnAgent()``POST /api/fleet/spawn`. This is the real "run an agent" path today, but it is launch-only, not a managed agent.
- `apps/MissionControlApp.tsx` (227 LOC) — live **fleet** sessions (`adapter.getFleet()`), per-session pause/resume/stop (`fleetAction`), plus team/activity tabs and AI-tool inventory. This is the closest existing "running agents" view; it shows sessions, not agent definitions.
- `apps/RoomApp.tsx` + `apps/WaggleDanceApp.tsx` + `apps/EventsApp.tsx` — live sub-agent tiles / coordination signals / event stream (run-time visibility, not agent management).
Registration: `agents` is an `AppId` in `lib/dock-tiers.ts` and routed in `Desktop.tsx` `renderAppContent`; `mission-control` is a separate AppId. Frontend inventory confirms: `AgentsApp` = "Personas manager" mapped to Intelligence bucket (`_inventory/frontend.md:29,361`).
Live backend (grep-confirmed, `_inventory/backend-routes.md` §16.7):
- Persona catalog CRUD: `/api/personas` GET/POST/PATCH/DELETE + `/generate` (`personas.ts`; POST/generate are **Tier: PRO**). 22 built-in personas in `packages/agent/src/persona-data.ts` + custom from disk (`05a:220-261`).
- Agent groups: `/api/agent-groups` GET/POST/PATCH/DELETE + `/run`**`/run` is a placeholder stub, does NOT execute** (`03a:221`, JSON file `dataDir/agent-groups.json`).
- Sub-agent orchestrator state: `GET /api/agents/active``{ workers, active }` (`03a:136`).
- Fleet (real run/pause/resume/kill of sessions): `GET /api/fleet`, `POST /api/fleet/spawn`, `POST /api/fleet/:workspaceId/{pause,resume,kill}` (`backend-routes §1.13`).
- One-shot retrieval: `POST /api/agent/run` (SSE, separate path; `03a:228`).
- Trace store: `execution_traces` table (`mind/schema.ts:199`) with per-trace outcome/cost/duration; `TraceRecorder` auto-wires from agent loop (`05a:65`). **No HTTP listing route for traces** (`_inventory/backend-routes.md §16.7``/traces` PARTIAL).
**Key finding:** PRD's `/api/agents/*` (CRUD + run/pause/traces) is the **Cloud** server's Clerk-gated `routes/agents.ts`, NOT the sidecar. In the desktop (sidecar) context the entire §16.7 agent CRUD is **MISSING locally** (`backend-routes.md` §16.7 note, lines 488-492). There is **no persisted Agent entity** with PRD §15.5 fields anywhere local.
---
## 4. Frontend work
**Rework `AgentsApp.tsx` into the Agent Center** (keep the file; restructure). Concrete:
- **New `AgentCenter` shell** (rework `AgentsApp.tsx`): category tab strip (All / Personal / Workspace / Team / Autonomous / Templates / Archive) replacing the current Personas/Groups toggle. Keep Groups as a sub-view or fold groups under "Team/Autonomous". Add KPI metric row (active count, avg success rate, runs, est. hours saved, est. cost) — sourced from existing `getAgentStatus`/`getCostSummary`/`getFleet` + new agent list.
- **New `AgentRow`/rework `AgentCard.tsx`**: avatar, name, goal sub-line, type chip (`AgentType`), model, **status pill** (PRD §14.5 union), **success-rate bar**, owner avatar(s), last-run relative time, run/pause action buttons. Reuse `components/ui/badge` (status), `progress` (success bar), `avatar`. Status colors can follow `MissionControlApp` statusColors map.
- **Rework `AgentDetail.tsx`**: add Goal, Autonomy, Memory scope, Permissions panel, Run/Pause controls, and a **Traces** tab (links to `execution_traces` via new `/api/agents/:id/traces`).
- **New `AgentBuilder`** (PRD §20.3 "create"; blueprint S18): 5-step stepper (Basic Info → Capabilities → Memory & Tools → Permissions → Review & Create). Reuse the design-system **Builder stepper** (PRD §19.1) and the existing model picker `components/os/ModelSelector.tsx` + `lib/spawn-agent-helpers.ts` (model default selection) + persona/skill/connector/MCP multi-selects. `CreateAgentForm.tsx` (persona form) is a partial reuse target for the Basic Info + tools steps but must be extended to the agent contract.
- **Right rail**: Agents-by-Type donut (reuse `components/ui/chart`), Top Capabilities, Recent Activity (reuse fleet/events feed), Quick Actions.
- **Wire run/pause to fleet**: card "Run" → `/api/agents/:id/run` (which maps onto `fleet/spawn` per §5); "Pause" → `/api/agents/:id/pause` (maps onto `fleet/:wid/pause`). Keep `SpawnAgentDialog` as the quick-launch entry but have it also accept a saved agent id.
- **States**: implement all §14.1 globals — Loading (existing `Loader2`), Empty ("No agents — create one"), Error (existing error banner pattern in `AgentsApp`), Offline (`useOfflineStatus`), Permission denied (Team agents gated by `useBilling`/tier), Approval required (reuse `ApprovalsApp`/inline approval surface for elevated agent actions).
**Adapter methods/hooks (add to `lib/adapter.ts` — the single sidecar gateway):**
- `getAgents()`, `getAgent(id)`, `createAgent(body)`, `patchAgent(id, body)`, `runAgent(id, opts)`, `pauseAgent(id)`, `getAgentTraces(id)`.
- New `useAgents()` hook (mirror `useWorkspaces` shape: list/select/create/patch/run/pause/refresh). Reuse `useAgentStatus`, `useEvents`, `useRoomState`, `getFleet` for live run state.
**Shared types (FE):** add `Agent`, `AgentType`, `AutonomyLevel`, `AgentStatus`(state union) to `lib/types.ts` (see §6).
---
## 5. Backend work (PRD §16.7, against the local sidecar)
> Naming collision flagged: `/api/agents/*` CRUD currently exists ONLY on the Clerk-gated **Cloud** server (`packages/server/src/routes/agents.ts`), not the sidecar. The desktop talks only to the sidecar, so all of §16.7 is net-new **locally**. Add a new sidecar route file `packages/server/src/local/routes/agents.ts` registered in `local/index.ts`.
| PRD §16.7 endpoint | Status (sidecar) | Plan — EXTEND vs NET-NEW + substrate |
|---|---|---|
| `GET /api/agents` | **MISSING** | NET-NEW route. Backed by a **new agent store** (see migration note) — a persisted Agent entity. List can union saved agents + (optionally) derive read-only "agents" from `getPersonas()` for back-compat. Reuse `/api/agents/active` + `/api/fleet` to overlay live status. |
| `POST /api/agents` | **MISSING** | NET-NEW. Persist an Agent (§15.5 fields). Closest existing writes: `POST /api/personas` (custom persona) / `POST /api/agent-groups`. Record install/elevated-permission grant to `install_audit` if the agent claims elevated tools/MCPs. |
| `GET /api/agents/:id` | **MISSING** | NET-NEW read from the agent store (or map onto persona/group id for legacy). |
| `PATCH /api/agents/:id` | **MISSING** | NET-NEW. Closest: `PATCH /api/personas/:id` / `PATCH /api/agent-groups/:id`. |
| `POST /api/agents/:id/run` | **PARTIAL** | EXTEND: wire onto `POST /api/fleet/spawn` (`fleet.ts`, real execution — `{task, persona?, model?, parentWorkspaceId?}`). Resolve agent → persona+model+workspace, then spawn. Do NOT use `agent-groups/:id/run` (stub). Substrate: fleet/orchestrator + `execution_traces` via `TraceRecorder`. |
| `POST /api/agents/:id/pause` | **PARTIAL** | EXTEND: map agent→active session, reuse `POST /api/fleet/:workspaceId/pause`. |
| `GET /api/agents/:id/traces` | **PARTIAL** | EXTEND/NET-NEW thin route reading the `execution_traces` store (`mind/schema.ts:199`) filtered by agent/session; also expose `ai_interactions` links per blueprint p.8. No HTTP listing exists today — add one. Also reuse session timeline `GET /api/workspaces/:wid/sessions/:sid/timeline`. |
**Substrate touched:** persona catalog (`personas.ts` + `persona-data.ts`), agent-groups JSON store, fleet/sub-agent orchestrator (`05a`), `execution_traces` + `ai_interactions` tables, `install_audit` (for elevated-permission grants), `cron` (for Autonomous agents' schedule/trigger — blueprint p.8 line 136).
**.mind migration flag (REQUIRED for full §15.5 persistence):** there is **no Agent entity/table** in `mind/schema.ts` and personas live on disk (not as agents with goal/autonomy/status/successRate). Two options, lowest-risk first:
1. **JSON store (no DB migration):** persist agents to a `dataDir/agents.json` (mirroring the agent-groups JSON pattern, `03a:213`) — additive, reversible, no schema change. Recommended for v1.
2. **Mind table:** add an `agents` table to `mind/schema.ts` (bump `SCHEMA_VERSION`, use the established idempotent `ADD COLUMN`/`CREATE TABLE IF NOT EXISTS` migration pattern in `mind/db.ts`). Only if agents must be FTS/relation-queryable.
`successRate`/`lastRunAt` are **derivable** from `execution_traces.outcome` + `procedures.success_rate` (`schema.ts:147,206`) — compute at read time rather than store, per the §15.5 note that successRate "partially exists … derivable" (`substrate-types.md:255`).
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
None of the PRD §15.2 agent unions exist today (`substrate-types.md:218-229,249-257`). Add to `apps/web/src/lib/types.ts` (and mirror in `packages/shared/src/types.ts` if the cloud/agent runtime consumes them):
- `type AgentType = 'personal' | 'workspace' | 'team' | 'autonomous';`**MISSING** (PRD §15.2 line 951).
- `type AutonomyLevel = 'manual' | 'guided' | 'medium' | 'high';`**MISSING** (PRD §15.2 line 952).
- `type AgentRunState = 'draft' | 'idle' | 'running' | 'paused' | 'failed' | 'waiting_for_approval' | 'completed' | 'archived';`**MISSING** (PRD §14.5). Note the existing FE `AgentStatus` (`types.ts:249-254`) is the cost/model snapshot, NOT this lifecycle union — keep both, distinct names.
- `interface Agent { id; name; goal; description?; type: AgentType; personaId?; avatar?; model?; autonomyLevel: AutonomyLevel; workspaceIds?: string[]; teamId?; memoryScopes?: Scope[]; skillIds?: string[]; connectorIds?: string[]; mcpIds?: string[]; permissions?: ...; status: AgentRunState; createdBy?; lastRunAt?; successRate?; createdAt; updatedAt }`**MISSING entirely.** Closest is shared `AgentDef` (`packages/shared/src/types.ts:36-47`: id/userId/teamId/name/role/systemPrompt/model/tools/config/createdAt) which lacks goal/type/autonomy/status/scopes/successRate — extend or define `Agent` alongside it.
- `type Scope = 'personal' | 'workspace' | 'team' | 'organization';`**MISSING** (PRD §15.2 line 946), shared by S06 Memory; define once.
The existing `BackendPersona` (`agents/types.ts:3-13`) stays for the persona catalog; the new `Agent` is a distinct object that may *reference* a `personaId`.
---
## 7. Dependencies (screens/phases first)
- **Phase 0 (Shell/IA)** — `Agent` shared types + `AgentType`/`AutonomyLevel`/`AgentRunState`/`Scope` unions must land first (consumed here + by S06 Memory + S18 Builder).
- **S04 Workspace Desktop** — Workspace agents are scoped by workspace; Agent Center's Workspace/Team tabs depend on `WorkspaceConfigV2.agentIds` (a §15.3 MISSING field, `substrate-types.md:32`) and `type`/`status`. Coordinate the workspace-config additive change.
- **S18 Agent Builder** — the create flow is a sibling screen (PRD §12.9 same section); Agent Center's "Create Agent" opens it. Plan together.
- **S11 Automation Center** — "Autonomous" agents need a schedule/trigger; depends on cron substrate shared with Automations.
- **S08 MCP Hub / S07 Connector Hub / S05 Skills Hub** — the Capabilities/Memory&Tools builder steps pick from those catalogs (`skillIds`/`connectorIds`/`mcpIds`); their list endpoints feed the multi-selects.
- **Approval surface** — elevated agent actions reuse `ApprovalsApp` + `/api/approval/*` (already exists).
- Lands in **Phase 3 (Intelligence layer)** per PRD §8 / Sprint 6 (§21).
---
## 8. Effort: **L**
Rework of a 6-file persona surface into a category-tabbed Agent Center, a net-new persisted Agent entity (JSON store v1) + 4 net-new sidecar routes + 3 extend-over-fleet/traces routes, new shared types, plus a 5-step Agent Builder (sibling). Not XL because the run/pause/trace substrate (fleet, execution_traces, orchestrator) already exists and is wired — the work is a management/persistence layer + IA over it, not new agent-execution machinery. Builder may split into its own card to keep this at L.
---
## 9. Open questions
1. **Agent vs Persona boundary:** does v1 introduce a true persisted Agent object distinct from Persona, or does Agent Center initially just re-skin the persona catalog with derived status? (Recommend: real Agent entity, JSON store, referencing `personaId`.)
2. **`/api/agents/*` collision:** confirm the desktop should get its OWN sidecar `agents.ts` rather than proxying the Clerk-gated cloud route (the sidecar has no Clerk). (Backend-map says local is MISSING; assume net-new sidecar.)
3. **Persistence choice:** JSON store (`agents.json`, no migration) vs `agents` table in `mind/schema.ts` (SCHEMA_VERSION bump)? PRD §14.4 non-goal favors minimal backend; JSON recommended for v1.
4. **successRate/lastRun:** derive at read from `execution_traces`/`procedures`, or persist on the agent? (Derive recommended; storing risks staleness.)
5. **Autonomous agents:** are these modeled as Agent + linked cron automation (S11), or a distinct entity? Blueprint p.8 says "explicit schedule/trigger" — likely an Agent that owns a cron id.
6. **Categories taxonomy:** PRD lists Personal/Workspace/Team/Autonomous/Templates/Archive (6); mockup shows All/Personal/Workspace/Team/Autonomous/Archive + a separate Templates control. Confirm whether Templates is a tab or a side affordance.
7. **Run target resolution:** when an agent has multiple `workspaceIds`, which workspace does `/run` spawn into? Needs a picker or a default.

View File

@@ -0,0 +1,274 @@
# Gap Card — S10 · Team Workspace
> Screen S10 of the Waggle OS UX-refactor. Execution model is the LOCKED **in-place
> incremental refactor** of `apps/web` + targeted backend extensions. Mockups are
> directional; PRD acceptance criteria win. Every claim below is grounded in real files.
>
> Sources: PRD §12.11 (lines 613-626), §16.11 (1152-1158), §17 RBAC (1162-1187),
> §10.5 (329-338), §18 (1191-1218); blueprint `_blueprint_extracted.txt` lines 346-349,
> 470-473, 509, 514, 531-540, 542-545, J18/J19/J20/J21 (227-242); mockup
> `screen_10_team_workspace.png`.
---
## 1. Screen & purpose
**Purpose (PRD §12.11):** "Shared intelligence for teams." Team mode must feel like
**shared knowledge and shared outcomes**, not just a member list + chat (explicit
acceptance criterion, PRD line 626). It is the surface for the **Team layer** of the IA
(PRD §10.5): team workspace, members, roles/permissions, shared memory, shared artifacts,
shared skills, shared MCPs, and activity/audit.
This is a **Teams/Enterprise-tier** surface (PRD tier table; current placeholder gates it
to "Teams ($49/mo per seat) and Enterprise"). It is the UI home for RBAC (PRD §17) and the
team-governance API contract (PRD §16.11).
The mockup shows: workspace header with a team switcher + Invite/+ buttons; a metrics strip
(memory frames, agents, automations, tasks); a "Team Activity" feed; "Pinned" items;
"Team Members" panel with avatars/roles; a "Team Intelligence" summary card; "Upcoming"
events; and a "Team Goals" progress section. Treat as visual direction only.
---
## 2. Required states (PRD / Blueprint)
**Tabs (PRD §12.11, line 619):** Overview · Shared Memory · Shared Artifacts · Skills ·
Agents · MCPs · Automations · Settings.
**Overview content (PRD line 620):** team spaces, metrics, members, activity, pinned items,
team goals, upcoming events, team intelligence summary.
**Sharing (PRD line 621):** share memory / artifact / skill / MCP / automation **into team
scope subject to role**. Journeys: J19 share memory → choose scope → audit → appears in team
memory; J20 share artifact → team/workspace/member → permissions → activity feed (blueprint
234-239).
**Member management + RBAC (PRD line 622):** invite/member management (J18: invite → role
selected → accept → permissions applied, blueprint 227-228).
**RBAC roles (conflict to resolve — see §6):**
- PRD §17.2 table: **Owner / Admin / Contributor / Viewer**.
- Blueprint §RBAC model + role table: **Owner / Admin / Member / Viewer / Guest** (lines 156,
536-540, 603).
- Live substrate (`teams.db`): **owner / admin / member / viewer** (no contributor, no guest).
**Empty / loading / permission states (blueprint 470-473):** Loading, empty, populated,
**permission denied**; Invite pending, role conflict, shared/private, audit event. Member
actions: request access, change role, resend invite, export audit. J21: permission denied →
permission message → request access (**no silent failures or data leakage**, blueprint 240-242).
**Cache invalidation triggers (blueprint 514):** memory import, artifact update, agent run
completion, connector sync, automation completion, **RBAC change** — the Team Workspace must
re-fetch on any of these.
**Solo/offline behavior:** RBAC must be enforced at **both API and UI layers** (blueprint 162).
Local team CRUD already works in solo mode with a local userId (see §3).
---
## 3. Current state in repo (disposition: **rework**)
**Frontend — `apps/web/src/components/os/apps/TeamGovernanceApp.tsx` (45 lines):** a pure
**static placeholder**. It renders three hardcoded info cards (Role-Based Access / Tool
Governance / Audit Trail) + a tier-gate banner. **Zero data fetching, zero adapter calls, no
tabs, no members, no activity.** Registered as appId `governance` in `Desktop.tsx`/`Dock.tsx`
(see frontend inventory §a, line 47). The mockup's entire surface is unbuilt.
**Adapter — `apps/web/src/lib/adapter.ts`** has only the **remote-proxy** team methods:
`teamConnect` (`:1226`), `teamDisconnect`, `getTeamStatus` (`:1234`), `getTeamMembers`
(`:1239`), `getTeamActivity` (`:1247`), `getTeamMessages` (`:1255`), `searchTeamMemory`
(`:489`). **There are NO adapter methods for the local `/api/teams/*` CRUD** (create team,
get team detail, invite/add member, change role, remove member, team activity/audit). This is
the single biggest frontend wiring gap.
**Backend — substrate is strong and already live.** `packages/server/src/local/routes/team.ts`
exposes two prefixes:
- `/api/team/*` = **remote team-server proxy** with local fallbacks (connect/disconnect/status/
teams/members/presence/activity/messages/governance/memory-search). `/api/team/connect` is
`requireTier('TEAMS')` (`:110`); `/api/team/governance/permissions` is `requireTier('ENTERPRISE')`
(`:418`).
- `/api/teams/*` = **local SQLite CRUD on `teams.db`** (`teams` + `team_members` tables, DDL at
`team.ts:51-72`), works in **solo mode** via `getLocalUserId(dataDir)` (`:457`). Full CRUD:
`POST /api/teams` (`:460`, auto-adds creator as `owner`), `GET /api/teams` (`:492`),
`GET /api/teams/:id` (`:505`, returns team + members), `PUT /api/teams/:id` (`:523`),
`DELETE /api/teams/:id` (`:553`, owner-only), `POST /api/teams/:id/members` (`:581`,
owner/admin only — **this is invite/add**), `PUT`+`PATCH /api/teams/:id/members/:userId`
(`:615`/`:642`, role change), `DELETE /api/teams/:id/members/:userId` (`:665`),
`GET /api/teams/:id/activity` (`:690`, reads `audit_events` via `getAuditDb` from `events.js`,
`:713-722`).
**RBAC is already enforced server-side** with per-action checks: owner/admin gate add-member
(`:591`), owner-only gate role change in PUT (`:624`) but owner/admin in PATCH (`:649` — a
real inconsistency to fix, see §9), owner-only delete-team (`:558`), can't remove the owner
(`:682`), member self-removal allowed (`:677`). The local `team_members.role` CHECK constraint
is `('owner','admin','member','viewer')` (`team.ts:64`) — **no contributor, no guest**.
**Remote/cloud governance substrate (backend-map §02b):** the Postgres team layer
(`packages/server/src/db/schema.ts`, 20 tables) has `teams`, `team_members` (composite PK
`(team_id,user_id)`, role default `'member'`, 02b:93-101), `team_entities`/`team_relations`/
`team_resources` (shared KG + assets, 02b:181-228), and the three **team-capability governance**
tables added in migration `0001`: `team_capability_policies` (standing policy per role, 02b:232),
`team_capability_overrides` (one-off allow/deny, 02b:247), `team_capability_requests`
(request→decision queue, 02b:262). These back the per-role tool allow/deny + approval surfaces
the mockup/PRD imply, **but only on the cloud server** — the local `/api/team/governance/permissions`
proxy (`:418`) is the only sidecar window into them. **Note (02b:44): Postgres FKs are all
`ON DELETE no action` — no cascades; deleting a team/user is blocked if children reference it.**
**Disposition rationale — rework (not create-new):** the `governance` app slot, the dock entry,
the tier-gate, and (critically) the **entire local team CRUD + RBAC enforcement substrate already
exist**. The screen needs the placeholder component replaced with a real tabbed surface wired to
existing routes + new adapter methods — not a new app and not a new backend data store.
---
## 4. Frontend work
**Rework `TeamGovernanceApp.tsx`** (rename concept to "Team Workspace"; keep appId `governance`
to avoid touching `Desktop`/`Dock`/`dock-tiers` routing) into a tabbed shell. Reuse the existing
tabbed-app pattern (`MemoryApp.tsx` 6-tab model, `SettingsApp.tsx` 8-tab model, `ui/tabs.tsx`).
**Components to create** (keep files small, 200-400 LOC per CLAUDE.md §coding-style):
- `apps/team/TeamWorkspaceApp.tsx` (or in-place rework of `TeamGovernanceApp.tsx`) — tab shell:
Overview · Shared Memory · Shared Artifacts · Skills · Agents · MCPs · Automations · Settings.
Tier-gate stays (Teams/Enterprise) via existing `LockedFeature.tsx` / `useFeatureGate`.
- `apps/team/TeamOverviewTab.tsx` — metrics strip + activity feed + pinned + team goals +
upcoming + team-intelligence summary (mockup Overview).
- `apps/team/TeamMembersPanel.tsx` — member list with avatars + role badges + role-change
dropdown + remove (gated by current-user role). Reuse `ui/avatar`, `ui/badge`,
`ui/dropdown-menu`.
- `overlays/InviteMemberDialog.tsx` — invite by email/userId + role select (J18). Reuse
`ui/dialog`, `ui/select`. POSTs to `/api/teams/:id/members`.
- `apps/team/TeamActivityFeed.tsx` — audit/activity list (reuse `TimelineApp`/`EventsApp` row
styling). Export audit action (blueprint 472).
- `apps/team/ShareToTeamDialog.tsx` — scope picker (team/workspace/member) for sharing a
memory/artifact/skill/MCP/automation into team scope with role check (J19/J20). Surfaced
from Memory/Artifact screens too; lives here as the canonical component.
- `components/os/PermissionDenied.tsx` — shared "permission denied + request access" state
(J21) — reusable across Team tabs and elsewhere.
**Reuse targets:** `MemoryApp`'s frame list for the Shared Memory tab (filtered to team scope
via `searchTeamMemory`); `CapabilitiesApp`/`AgentsApp`/`ScheduledJobsApp` list rows for the
Skills/Agents/MCPs/Automations tabs (read-only team-scoped views first). `ContextMenu` for
row-level "Share to team".
**New hook — `hooks/useTeam.ts`** (mirror `useWorkspaces` shape): returns `{ team, members,
activity, currentUserRole, createTeam, invite, changeRole, removeMember, refresh }`. Drives
RBAC at the UI layer (blueprint 162): compute `currentUserRole` from `GET /api/teams/:id`
members + local userId, then hide/disable actions per PRD §17.2 capability matrix.
**New adapter methods** (add to `lib/adapter.ts` `LocalAdapter`, local `/api/teams/*` family —
this is where new PRD §16 endpoints land per frontend inventory §c): `getTeams`,
`getTeam(id)`, `createTeam`, `updateTeam`, `deleteTeam`, `inviteMember(id, {userId?, email?,
displayName?, role?})`, `changeMemberRole(id, userId, role)`, `removeMember(id, userId)`,
`getTeamAudit(id)`, and (for §16.11) `shareToTeam(...)`.
**State management (blueprint 511-514):** Team Workspace re-fetches on the RBAC-change cache
trigger + memory-import/artifact-update/automation-completion triggers. Use the existing
`QueryClient` invalidation pattern.
---
## 5. Backend work (PRD §16.11)
| PRD §16.11 endpoint | Status | Action |
|---|---|---|
| `GET /api/teams/:id` | **EXISTS** | `team.ts:505` — returns team + members. Wire adapter `getTeam`. No backend change. |
| `POST /api/teams/:id/invite` | **PARTIAL → EXTEND** | Invite is implemented as `POST /api/teams/:id/members` (`team.ts:581`, owner/admin gated). **Add a thin `/invite` alias** that forwards to the members handler (PRD uses `/invite`; substrate is `team_members` in `teams.db`). No new substrate. |
| `PATCH /api/teams/:id/members/:memberId` | **EXISTS** | `team.ts:642` (`:userId` == PRD `:memberId`); `PUT` variant at `:615`. **Fix the PUT/PATCH role-gate inconsistency** (PUT owner-only vs PATCH owner/admin — §9). Wire adapter `changeMemberRole`. |
| `GET /api/teams/:id/audit` | **PARTIAL → ALIAS** | Closest is `GET /api/teams/:id/activity` (`team.ts:690`, reads `audit_events` via `events.js` `getAuditDb`). **Alias `/audit` → the activity handler** (or add the audit-export shape). Substrate: `audit_events` table (events.ts). No new store. |
| `POST /api/share` | **MISSING → NET-NEW** | No `/api/share` route anywhere (grep-confirmed across `local/routes`). Sharing into team scope today is **implicit** (workspace `teamId` linkage at create + remote team-server frame sync). Net-new route accepting `{ kind: memory\|artifact\|skill\|mcp\|automation, id, scope: team\|workspace\|member, targetId }`, **role-checked** (PRD §17.2 "Can share"), writing into the team scope and emitting an audit event (reuse `emitAuditEvent`, already used in `team.ts:484`). Touches: `team_entities`/`team_resources` (cloud, 02b) for the synced object, `audit_events` (local) for the trail. For solo/local mode, share = tag the frame/artifact with team scope (note the FE/DB scope is implicit per-`.mind` today — see §6 + substrate-types §c). |
**Implied additional route (not in §16.11, flag for plan):** an **install-audit / governance read
route** for the Skills/Agents/MCPs tabs. `InstallAuditStore.getRecent/getByCapability` exist in
`packages/core/src/install-audit.ts` but **have no HTTP endpoint** (substrate-types §d). The
Team Workspace governance view (who installed what / risk / trust / approval) needs e.g.
`GET /api/extend/audit` or `GET /api/teams/:id/governance` surfacing `team_capability_policies/
overrides/requests`. The Enterprise proxy `GET /api/team/governance/permissions` (`team.ts:418`)
is the only existing window and is read-only + ENTERPRISE-gated.
**.mind migration:** **None required for the team CRUD/RBAC core**`teams.db` is a standalone
SQLite file with its own DDL (created on demand). The Postgres `team_capability_*` tables already
exist (migration `0001`). The only schema-adjacent work is if team-scope sharing needs a
structured `scope`/`teamId` on `memory_frames` (currently implicit per-`.mind`; substrate-types
§c flags `teamId`/`scope` as MISSING columns) — that would be a metadata-column migration on
`memory_frames`, deferrable behind the implicit-scope approach for v1.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **No `Team` / `TeamMember` / `TeamRole` types exist in `apps/web/src/lib/types.ts`.** Add
them (frontend inventory §d confirms the types.ts export list has no team entities). Source of
truth for the server shapes: `team.ts` `TeamRole = 'owner'|'admin'|'member'|'viewer'` (`:21`),
`MemberRow` (`team.ts:799-810` mapper).
- **RBAC role-union conflict — MUST be resolved before coding (decision needed, §9):**
- PRD §17.2 → `Owner | Admin | Contributor | Viewer`
- Blueprint → `Owner | Admin | Member | Viewer | Guest`
- Live `teams.db` CHECK → `owner | admin | member | viewer`
Recommendation: keep the live 4-role union (`owner/admin/member/viewer`) for v1 to avoid a DB
CHECK migration + RBAC-logic rewrite; treat PRD "Contributor" == "Member" and defer "Guest" as
a follow-up (it needs a new role + new deny-by-default capability rules). Surface this to the
founder per CLAUDE.md §3.1.
- **`Scope` union** (`personal/workspace/team/organization`) — MISSING in FE types
(substrate-types §e). Needed for `ShareToTeamDialog`. Add to `lib/types.ts` (PRD §15.2).
- **Capability/policy types** for the governance tabs (`team_capability_policies/overrides/
requests` shapes from 02b) — add FE types when that read route lands.
- Reuse existing `TierCapabilities` from `@waggle/shared` (`tiers.ts`) for the tier-gate; do not
invent a parallel gate.
---
## 7. Dependencies (screens / phases first)
- **RBAC role-model decision (§6/§9)** blocks the `TeamRole` type + all member-management UI —
resolve first.
- **`ShareToTeamDialog`** is cross-screen: it is invoked from S-Memory (J19) and S-Artifacts
(J20). Artifacts have **no backing entity at all** (substrate-types §e — the single largest
entity gap), so artifact-sharing depends on the Artifacts screen/entity landing first; memory-
sharing can ship independently against `memory_frames` + `searchTeamMemory`.
- **Permission-denied / request-access pattern (J21)** is shared with other screens — build the
reusable `PermissionDenied` component here, reuse elsewhere.
- Tier gating depends on `useFeatureGate`/`LockedFeature` (exist) — no new dependency.
- The remote/cloud governance tabs (per-role capability policies) depend on the **install-audit
read route** (§5 implied) which is itself a separate small backend task.
**Phase placement:** this is a **later-phase** screen. The foundational Work/Intelligence screens
(Home, Workspace Desktop, Memory, Agents) and the Artifacts entity should land first; Team
Workspace composes their list rows + shares into team scope.
---
## 8. Effort: **L**
The backend core is mostly EXISTS/PARTIAL (CRUD + RBAC enforcement + activity already live, only
`/invite` alias + `/audit` alias + the genuinely net-new `/api/share` + a governance read route),
but the **frontend is a from-zero rework of a 45-line placeholder into an 8-tab surface** with a
new hook, ~8 new components, ~10 new adapter methods, UI-layer RBAC gating, the shared
share/permission-denied components, and an unresolved role-model decision. Not XL because no new
data store and no `.mind` migration is required for v1; not M because of the tab/share/RBAC breadth
+ cross-screen coupling.
---
## 9. Open questions
1. **RBAC role model (blocking):** adopt PRD §17.2 (`Owner/Admin/Contributor/Viewer`), blueprint
(`+Member +Guest`), or keep the live `teams.db` 4-role union (`owner/admin/member/viewer`)?
Recommendation: keep live union for v1, map Contributor→Member, defer Guest. Needs founder sign-off.
2. **PUT vs PATCH role-gate inconsistency (real bug):** `PUT /api/teams/:id/members/:userId` is
**owner-only** (`team.ts:624`) but `PATCH` on the same path is **owner/admin** (`:649`). The PRD
uses PATCH. Which gate is correct — owner-only role changes, or owner+admin? Align both.
3. **`/api/share` scope semantics in solo/local mode:** memory frames have **no structured
`scope`/`teamId` column** (implicit per-`.mind`, substrate-types §c). For v1, model team-share as
(a) implicit via workspace `teamId` + remote sync, or (b) a `memory_frames` metadata migration?
Recommendation: (a) for v1, defer (b).
4. **Local vs remote team source of truth on this screen:** `/api/teams/*` (local `teams.db`, always
works) vs `/api/team/*` (remote proxy, TEAMS-gated). Does Team Workspace render local teams in solo
mode and switch to remote when connected, or remote-only? (Affects whether members/activity come
from `teams.db` or the team server.)
5. **Governance tabs depth for v1:** do the Skills/Agents/MCPs/Automations tabs render full
per-role capability policies (`team_capability_*` cloud tables) now, or ship read-only shared-item
lists first and defer the policy editor?
6. **Postgres no-cascade (02b:44):** deleting a team is blocked by FKs if `team_entities`/resources/
members reference it. The local `DELETE /api/teams/:id` deletes members first (`team.ts:563`) — does
the cloud delete path need an explicit child-cleanup order, and should the UI warn before delete?

View File

@@ -0,0 +1,249 @@
# Gap Card — S11 Automation Center
> UX-refactor implementation planning. Screen 11 of the 21-screen inventory.
> Execution model is LOCKED: in-place incremental refactor of `apps/web` + targeted
> backend extensions. The mockup is DIRECTIONAL; PRD acceptance criteria win over pixels.
> Every claim below is grounded in a real file (path + line where load-bearing).
>
> PRD: `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`
> §12.10 (lines 597-611), §16.10 (lines 1143-1150). Blueprint screen spec: `_blueprint_extracted.txt:354-359`.
---
## 1. Screen & purpose
**Automation Center + Automation Builder** — manage scheduled and event-driven work so
overnight/background runs are visible, reviewable, and stoppable (PRD §12.10; Definition of
Done #7 "automations… have coherent IA"; principle #9 "Automation with trust").
The PRD object is **Automation** (§11 glossary): "Scheduled/event-driven workflow using
triggers/actions/agents. Must be visible/stoppable." In the live repo this is **cron**:
`packages/core/src/cron-store.ts` (`CronStore`) + `packages/server/src/local/cron.ts`
(`LocalScheduler`) + `packages/server/src/local/routes/cron.ts` (`/api/cron/*`). The backend-map
is explicit: "Automations = a rename/extension of cron" (`_inventory/backend-routes.md:531`).
Two intelligence-layer surfaces overlap here and must NOT be double-built:
- **`ScheduledJobsApp.tsx`** (dock id `scheduled-jobs`) — the existing cron CRUD UI; this IS the
Automation Center seed.
- **`EventsApp.tsx`** (dock id `events`) — agent event/step stream + agent tree; this is the
closest existing analog for the Builder's "actions = agent task" and for History/Logs detail,
but it streams live `AgentStep`s, NOT cron execution rows. Different data source.
---
## 2. Required states (PRD / Blueprint)
**PRD §12.10 tabs:** Overview, Running, Scheduled, Triggers, History, Logs.
**Automation Builder steps:** Trigger → Condition → Actions → Review & Activate.
**Automation fields:** name, trigger, condition, actions, agent, notification, schedule, workspace, status.
**Actions:** run now, pause, edit, view logs, retry, disable.
**Cross-screen:** "Failed or risky automations surface in Home Cockpit attention required" (§12.10,
also Journey 16 / J17 `_blueprint_extracted.txt:224-226`, S01 Home Cockpit gap card dependency).
**States** (PRD §12.10 + §14.6 Automation states, lines 895-904; blueprint `:356-357`):
Draft, Scheduled, Running, Success, Failed, Paused, Awaiting approval, Disabled — plus the §14.1
global mandatory states: Loading, Empty, Populated, Error, Offline/local-only, Permission denied.
**Acceptance:** "Overnight work is visible, reviewable and stoppable" (PRD line 610; blueprint `:358-359`).
**Mockup (directional only):** header stat tiles (Active 8 / Scheduled 24 / Triggers 12 / Success
Rate 98.6% / Hours Saved 156h), an "Automation Health" donut (Healthy/Warning/Failed/Paused), a
"Running Automations" list with progress bars, a "Scheduled Automations" table (name / schedule /
next-run / workspace / status), a "Recent Activity" feed, and a "Popular Templates" rail. These map
to existing substrate (see §5) EXCEPT the analytics tiles (success-rate / hours-saved) and live
progress bars, which have no backing today — treat as derived/aspirational, not v1 blockers.
---
## 3. Current state in repo
**Disposition: `rework`** (promote + extend `ScheduledJobsApp` into Automation Center; the cron
substrate is solid and reused wholesale — this is the textbook in-place refactor case).
### Frontend (exists)
- **`apps/web/src/components/os/apps/ScheduledJobsApp.tsx`** — full cron CRUD UI. Single flat list +
inline create form. Calls `adapter.getCronJobs / createCronJob / updateCronJob / deleteCronJob /
triggerCronJob`. Create form already collects `jobType` (6 types via `CRON_JOB_TYPES`), schedule
(presets + custom cron via `CRON_SCHEDULE_PRESETS`), and an output channel (log/telegram). Per-row:
toggle enable/disable, Run-now (`triggerCronJob`, auto-enable handled), delete. **No tabs, no
history/logs view, no "Builder" stepper, no triggers concept.**
- **`apps/web/src/lib/cron-presets.ts`** — `CRON_JOB_TYPES` (6 job types w/ labels+descriptions),
`CRON_SCHEDULE_PRESETS` (6 cadences), `describeCronExpr`, `isPlausibleCronExpr`. Reuse as-is for
the Builder's schedule step.
- **`apps/web/src/components/os/apps/EventsApp.tsx`** — agent step stream (live/tree/replay tabs),
`buildAgentTree`. NOT cron-backed; relevant only as a visual pattern for History/Logs, not a data
source.
- **`apps/web/src/lib/types.ts:230-238`** — `CronJob { id, name, schedule, workspaceId, enabled,
lastRun?, nextRun? }`. Lossy projection (drops `jobType`, `jobConfig`, `nextRun` exists but
`lastRunAt`/`nextRunAt` are remapped to `lastRun`/`nextRun` by `normalizeCronJob` in `adapter.ts`).
- **`apps/web/src/lib/adapter.ts:811-851`** — cron methods. **Two defects to fix in the rework:**
1. `updateCronJob` (`:836`) issues **`PUT /api/cron/:id`**, but the server registers ONLY
`PATCH /api/cron/:id` (`routes/cron.ts:115-124`) — there is **no PUT cron route**
(grep-confirmed). The toggle in `ScheduledJobsApp.handleToggle` goes through this and would
404. Change to PATCH.
2. **No `getCronHistory` adapter method** exists, even though the route does (`/api/cron/:id/history`,
`notifications.ts:198-210`). History/Logs tabs need it added.
### Backend (exists, reused wholesale)
- **`packages/server/src/local/routes/cron.ts`** — POST/GET(list)/GET(:id)/PATCH/DELETE/POST(:id/trigger).
Trigger calls `server.scheduler.executeJob` and auto-enables disabled jobs (`:188-191`), emits a
`cron`-category notification (`:197-202`).
- **`packages/server/src/local/routes/notifications.ts:198-210`** — `GET /api/cron/:id/history`
→ `cronStore.getExecutionHistory(id, limit)` → `{ history, count }`.
- **`packages/core/src/cron-store.ts`** — `CronStore`: schedules table (`cron_schedules`), execution
history (`cron_execution_history` w/ `success`, `duration_ms`, `result_summary`, `error` — `:88-101`),
notifications. `recordExecution` (`:281`), `getExecutionHistory` (`:294`). 6 `CronJobType`s (`:15`):
`agent_task | memory_consolidation | workspace_health | proactive | prompt_optimization | monthly_assessment`.
- **`packages/server/src/local/cron.ts`** — `LocalScheduler`: tick loop, concurrency guard,
per-job failure tracking, **auto-disable after 5 consecutive failures** (`MAX_CONSECUTIVE_FAILURES`,
`:23,138-141`) — this maps directly to the §14.6 "Failed"/"Disabled" states.
- **`packages/server/src/local/setup-crons.ts`** + `executeJob` wiring (job-type dispatch) — confirm
which job types actually execute when planning the Builder's "Actions" step.
### What's MISSING entirely (net-new frontend, no backend store needed)
- **Trigger concept.** Cron is time-only (`cronExpr`). PRD §12.10 "Triggers" tab + Builder "Trigger"
step imply event-driven triggers (not just schedules). No event-trigger substrate exists; v1 should
scope Trigger = schedule-only, with event-trigger as an open question (see §9).
- **Condition concept.** No `condition` field on `CronSchedule`. Builder "Condition" step is net-new
(model as optional `jobConfig.condition`, no schema change).
- **Analytics** (success-rate %, hours-saved, health donut) — derivable from `cron_execution_history`
but no aggregation endpoint/UI today.
---
## 4. Frontend work
### Components to create / rework
| Component | Action | Notes |
|---|---|---|
| `ScheduledJobsApp.tsx` → **`AutomationCenterApp.tsx`** | **rework/rename** | Promote to tabbed shell: Overview / Running / Scheduled / Triggers / History / Logs (PRD §12.10). Keep the proven cron CRUD wiring; lift the existing flat list into the "Scheduled" tab. Add an Automation Health summary (counts by enabled/disabled/failed from existing data). Keep dock id `scheduled-jobs` OR add an alias (don't break `dock-tiers.ts` `AppId` union + `Desktop.renderAppContent` switch — `frontend.md:142-151`). Update `appConfig` title/icon in `Desktop.tsx`. |
| `AutomationBuilder.tsx` (overlay) | **create-new** | 4-step stepper (Trigger → Condition → Actions → Review & Activate) per PRD §12.10. Reuse the existing create-form fields (jobType, schedule presets, output channel) from `ScheduledJobsApp` + `cron-presets.ts`. Use the shadcn stepper-style pattern (PRD §19 "Builder stepper"); align with sibling builders (Agent/Skill builders, other S-cards). |
| `AutomationCard.tsx` | **create-new (small)** | Per-automation row: name, schedule summary (`describeCronExpr`), next-run, workspace, status badge (Running/Scheduled/Paused/Failed/Disabled), actions (run-now/pause/edit/logs/delete). Extract from current inline `ScheduledJobsApp` row JSX. |
| History/Logs tab content | **create-new** | List `cron_execution_history` rows (executed_at, duration, success/error, result_summary). Visual pattern can borrow `EventsApp` StepCard expand/collapse, but data = cron history, not `AgentStep`. |
### Reuse targets
- `cron-presets.ts` (schedule presets + job-type catalog + `describeCronExpr`) — as-is.
- `HintTooltip`, shadcn `tabs`/`table`/`badge`/`skeleton`/`dialog` (`components/ui/*`, `frontend.md:336-342`).
- Status-badge + skeleton/empty/error patterns already used across apps.
- `EventsApp` StepCard pattern (visual only) for the Logs detail rows.
### Adapter methods / hooks
- **Fix** `adapter.updateCronJob` → `PATCH` (currently `PUT`, 404s — `adapter.ts:837`).
- **Add** `adapter.getCronHistory(id, limit?)` → `GET /api/cron/:id/history` (route exists,
`notifications.ts:202`).
- **Add** `adapter.pauseCronJob(id)` thin helper → `PATCH /api/cron/:id { enabled:false }`
(PRD calls it "pause"; backend models it as `enabled:false` — `backend-routes.md:540`).
- **(Optional) Add** an `automations` alias namespace in the adapter so the new components read
PRD vocabulary while pointing at `/api/cron/*` — keeps the rename cosmetic, zero backend churn.
- **New hook `useAutomations`** (mirror `useWaggleDance`/`useNotifications` shape): wraps
list/create/update/delete/trigger/pause/history; optionally subscribes to the existing
`/api/notifications/stream` (`cron`-category events) for live status. No new SSE channel needed.
- Cross-screen: emit failed-automation count into the S01 Home Cockpit "attention required" feed
(PRD §12.10; consumes `cron_execution_history.success=0` + scheduler auto-disable signal).
### Props / state
- `AutomationCenterApp`: `activeTab`, `jobs: CronJob[]`, `loading`, `creating`, per-row `triggering`.
- `AutomationBuilder`: `step`, `{name, jobType, scheduleExpr, workspaceId, condition?, outputChannel}`,
`testing?` (Review step "test" before activate, per PRD §12.10 / Journey 12 step 5).
- Extend `CronJob` FE type with `jobType` + `jobConfig` (currently dropped by `normalizeCronJob`) so
the Builder can round-trip edits without re-deriving.
---
## 5. Backend work (PRD §16.10 endpoint-by-endpoint)
> Verdict for the whole section: **the cron substrate is complete; §16.10 is a rename/alias job, NOT
> net-new backend.** Backend-routes inventory: §16.10 = 0 EXISTS / 6 PARTIAL / 0 MISSING
> (`backend-routes.md:595`). All six "Automation" endpoints map onto existing `/api/cron/*` in
> `routes/cron.ts` + the history route in `notifications.ts`. Substrate touched: `cron_schedules` +
> `cron_execution_history` (in the personal/workspace `.mind` DB via `CronStore`). **No `.mind`
> migration required** (both tables already exist with lazy creation, `cron-store.ts:135-157`).
| PRD §16.10 endpoint | Status | EXTEND vs NET-NEW · substrate |
|---|---|---|
| `GET /api/automations` | **PARTIAL** | EXTEND: alias of `GET /api/cron` (`cron.ts:94`). Either register an `/api/automations` alias plugin that re-exports the cron handlers, or just point the new UI at `/api/cron`. Substrate: `cron_schedules`. |
| `POST /api/automations` | **PARTIAL** | EXTEND: alias of `POST /api/cron` (`cron.ts:67`). To support PRD `condition`, stash it in `jobConfig.condition` (no schema change — `job_config TEXT`). |
| `PATCH /api/automations/:id` | **PARTIAL** | EXTEND: alias of `PATCH /api/cron/:id` (`cron.ts:115`). |
| `POST /api/automations/:id/run` | **PARTIAL** | EXTEND: alias of `POST /api/cron/:id/trigger` (`cron.ts:172`; auto-enables + executes + emits notification). |
| `POST /api/automations/:id/pause` | **PARTIAL** | EXTEND: no dedicated `/pause`; equivalent is `PATCH /api/cron/:id { enabled:false }`. Add a thin `/pause` route OR have the adapter call PATCH (`backend-routes.md:540`). |
| `GET /api/automations/:id/logs` | **PARTIAL** | EXTEND: alias of `GET /api/cron/:id/history` (`notifications.ts:202` → `cronStore.getExecutionHistory`). Substrate: `cron_execution_history`. |
**Implied additions NOT in §16.10 (flag for plan):**
- **Overview/analytics aggregation** (mockup tiles: active/scheduled/success-rate/hours-saved + health
donut). No endpoint sums `cron_execution_history` today. Either compute client-side from per-job
history (cheap for small N) OR add a thin `GET /api/automations/summary`. Hours-saved has no source —
drop or stub. **Not a v1 blocker.**
- **Builder "test" before activate** (PRD §12.10 / Journey 12). `POST /api/cron/:id/trigger` runs a
real (auto-enabling) execution; there is no dry-run. Either reuse trigger as the "test run" (accepting
it really runs) OR scope test-run as an open question.
**.mind migration flag:** NONE. `cron_schedules` + `cron_execution_history` + `notifications` all
self-create (`cron-store.ts:135-157`). The only schema-adjacent change is storing `condition` inside
the existing `job_config` JSON blob.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
PRD §15 has **no dedicated Automation type** (the §15.2 unions cover Workspace/Memory/Artifact/Agent
but not Automation). So the type work is FE-local:
- **Extend `CronJob`** (`lib/types.ts:230-238`) with `jobType: CronJobType` and `jobConfig?:
Record<string, unknown>` (both currently dropped on the FE side by `normalizeCronJob` —
`adapter.ts:818`). Keep the legacy `schedule`/`lastRun`/`nextRun` remap for back-compat.
- **Add `CronExecutionRow`** FE mirror of the server type (`cron-store.ts:48-57`:
`id, schedule_id, schedule_name, executed_at, duration_ms, success, result_summary, error`) for the
History/Logs tab — camelCased in the adapter.
- **Add an `AutomationStatus` union** for badge rendering: derived (not stored) from
`enabled` + last-history-row success + scheduler disabled-set: `'scheduled' | 'running' | 'paused' |
'failed' | 'disabled'`. Maps to PRD §14.6.
- The 6-member `CronJobType` union is **duplicated** in two places (`cron-presets.ts:15` and
`core/cron-store.ts:15`) — reuse `cron-presets.ts`'s for FE; do NOT add a third copy.
- No PRD §15.2 union (`AutonomyLevel`, `ExtensionType`, etc.) is required for this screen.
---
## 7. Dependencies (screens / phases first)
- **Phase 3 — Intelligence layer** (PRD §8 / Roadmap Sprint 6, lines 236-240, 1344-1351). Same sprint
as Agent Center/Builder + Skills Hub/Builder; the Automation Builder's "Actions = run an agent task"
step benefits from Agent Center existing first, but cron's `agent_task` job type already works
standalone, so it is **not a hard block**.
- **S01 Home Cockpit** (downstream consumer): failed/risky automations must surface in Home "attention
required" (PRD §12.10, §12.1). Coordinate the failed-count signal contract with the S01 gap card.
- **AppShell / dock IA** (Phase 0): the rename/relocation of `scheduled-jobs` into the Intelligence
bucket touches `dock-tiers.ts` `AppId` + `Desktop.tsx` `appConfig`/`renderAppContent`
(`frontend.md:142-151`). Sequence after the Phase-0 IA freeze.
- Builder stepper pattern should be shared across Agent/Skill/Automation builders — coordinate the
common stepper primitive (PRD §19.1 "Builder stepper") rather than three bespoke steppers.
---
## 8. Effort
**M.** The backend is essentially free (6 PARTIAL endpoints = thin aliases over existing cron routes +
two real adapter fixes). Frontend is a moderate rework: promote the existing single-list
`ScheduledJobsApp` to a 6-tab Center, build a 4-step Builder overlay, add a History/Logs tab over an
existing route, and wire the Home-Cockpit failed-automation signal. No new data store, no `.mind`
migration. Pushes toward L only if event-driven Triggers (vs schedule-only) are pulled into v1.
---
## 9. Open questions
1. **Triggers tab scope.** PRD §12.10 lists "Triggers" as a tab and a Builder step, but cron is
schedule-only. Is v1 schedule-only (Trigger = cron cadence), or must event-driven triggers (e.g.
"on harvest complete", "on memory conflict") ship? No event-trigger substrate exists today.
2. **Condition step.** Is a real condition engine required, or is storing an advisory
`jobConfig.condition` string (no evaluation) acceptable for v1?
3. **Builder "test run".** Only `POST /api/cron/:id/trigger` exists and it really executes (and
auto-enables). Is reusing trigger as "test" acceptable, or do we need a dry-run path?
4. **Rename vs alias.** Register a true `/api/automations/*` alias plugin (PRD vocabulary in the
network tab) or keep `/api/cron/*` and rename only in the UI/adapter? (Affects the backend-map
contract surface.)
5. **Dock relocation.** Move `scheduled-jobs` into a new Intelligence dock zone, or keep its current
dock slot and just retitle? Confirm against the Phase-0 IA freeze.
6. **Analytics tiles.** Mockup shows success-rate % and "Hours Saved 156h". Success-rate is derivable
from `cron_execution_history`; hours-saved has no source. Drop hours-saved, or define a heuristic?
7. **Scope of automation = workspace vs global.** Cron supports `workspaceId:'global'` → `'*'`
(`cron.ts:73-74`). Does the Center show all-workspace automations, or filter to the active workspace?

View File

@@ -0,0 +1,225 @@
# Gap Card — S12 First Launch (Onboarding Step 1)
> Screen S12 of the Waggle OS UX-refactor. PRD source of truth: §12.12 (Onboarding Flow,
> step 1 "First Launch - promise and privacy"), §13 Journey 1 step 2, §14.1 global states.
> Blueprint screen spec: `_blueprint_extracted.txt:365-372` (PAGE 14, row 12). Mockup
> (directional only, PRD §24): `Waggle_OS_Handoff_Assets/screen_12_first_launch.png`.
> Execution model: **in-place incremental refactor** of `apps/web` — KEEP the wizard shell,
> redesign the welcome step per PRD §20.2.
---
## 1. Screen & purpose
The very first thing a fresh-install user sees: a minimal **promise + privacy reassurance**
splash before any setup. It is onboarding step 1 of the 7-step flow (PRD §12.12: First Launch →
Who Are You → Tool Discovery → Memory Import → Memory Review → Workspace Creation → Home Cockpit).
Purpose (blueprint S12): "Minimal promise and privacy reassurance." Acceptance (blueprint S12):
**"No infrastructure overload before user intent"** — i.e. no API-key/tier/model questions on
this screen; only the brand promise, a privacy note, a language affordance, and a single
Continue action.
Mockup content (directional): Waggle "W" logo + wordmark top-left; centered hero logo;
`Welcome to Waggle`; tagline `Your work. Your memory. Your agents.`; primary `Continue →`
button; a 4-dot progress indicator; footer-left privacy line ("Your data is private. Stored
locally."); footer-right language selector showing `English (US)`.
---
## 2. Required states (PRD / Blueprint)
Blueprint S12 names exactly four states (`_blueprint_extracted.txt:369-370`), plus the PRD §14.1
global-state baseline that "every major screen must implement":
| State | Source | What it means on S12 |
|---|---|---|
| **Fresh install** | Blueprint S12 | Default: brand promise + privacy + Continue. The only state the current code renders. |
| **Resumed setup** | Blueprint S12 | User dismissed/closed mid-onboarding and returns; wizard re-opens at the saved step (not necessarily step 0). Resume should land on the persisted step, and First Launch should communicate "picking up where you left off" rather than re-greeting cold. |
| **Offline** | Blueprint S12 + PRD §14.1 | Sidecar unreachable. Screen must still render (it is pre-network) and must not block; surface a non-alarming offline indicator and keep Continue usable (downstream steps degrade, not this one). |
| **Local-only** | Blueprint S12 + PRD §6/§18.1 (local-first default) | Privacy promise must be truthful and visible: "your data is private / stored locally." This is the trust hook the whole onboarding leans on (PRD §12.12 acceptance: "Nothing imports without explicit review/approval"). |
| Interaction: **Continue** | Blueprint S12 | Advance to step 2 (Who Are You). |
| Interaction: **Change language** | Blueprint S12 + mockup | A language affordance. **No i18n infra exists** (see §3) — scope decision required (§9). |
| Interaction: **View privacy note** | Blueprint S12 + mockup | A privacy note / link, inline or expandable. |
PRD §14.1 also lists Loading / Error / Permission-denied as universal — for a pre-network welcome
splash these collapse to: render immediately (no loading gate), and offline == the only "error-ish"
state that matters here.
---
## 3. Current state in repo (exact files + what they do)
**Disposition: `rework`** (KEEP the wizard shell per PRD §20.2 "Onboarding wizard → simplify";
redesign the step-0 component and add the missing affordances).
### The shell (KEEP)
- `apps/web/src/components/os/overlays/OnboardingWizard.tsx` — full-screen early-return wizard
(`OnboardingWizard.tsx:399-576`). Holds 8 steps (`STEP_NAMES`, `:35`):
`welcome / why-waggle / tier / memory-import / template / persona / api-key / ready`.
Step 0 auto-advances after **3 s** (`:136-141`), Escape → Skip (`:86-96`), a top progress bar +
step dots + Back + "Skip setup" chrome (`:421-493`). Rendered by `Desktop.tsx:262-272` when
`!onboardingState.completed`. This shell is the reuse target.
- `apps/web/src/hooks/useOnboarding.ts``OnboardingState` (`:10-19`: `completed, step, tier?,
workspaceId?, apiKeySet?, templateId?, personaId?, tooltipsDismissed?`), persisted to
`localStorage` key `waggle:onboarding` (`:21`). Auto-completes for returning users via Tauri
first-launch flag (`:102-131`) and a sidecar `getWorkspaces().length > 0` check (`:139-176`).
`?forceWizard=true` (DEV) forces step 0 (`:47-57`). **This is the "resumed setup" backbone** —
`state.step` already persists, so resume is half-built.
### The screen itself (REWORK)
- `apps/web/src/components/os/overlays/onboarding/WelcomeStep.tsx` — the current step-0 component
(54 lines). Renders the Waggle logo, eyebrow `Your AI Operating System`, `Welcome to the Hive`,
tagline `Persistent memory. Workspace-native. Built for knowledge work.`, a `Continue →` button
(`:39-45`) + "or click anywhere". **Gaps vs S12:** copy differs from mockup ("Welcome to the
Hive" vs "Welcome to Waggle"; tagline differs); **no privacy note**, **no language selector**,
**no offline/local-only awareness**. It is a pure presentational component (props
`WelcomeStepProps` = `goToStep` + `onClickAnywhere`, `onboarding/types.ts:10-12`).
- `apps/web/src/components/os/overlays/onboarding/WhyWaggleStep.tsx` (step 1, the current 2nd
screen) carries the 3 `VALUE_PROPS` (constants `:111-115`) + the "Skip — quick setup" escape
hatch. Mentioned because the mockup's tagline-style promise overlaps WelcomeStep/WhyWaggle; the
rework should not duplicate value-prop content across both.
- `apps/web/src/components/os/overlays/onboarding/constants.ts` — `VALUE_PROPS` (`:111-115`),
`fadeSlide` motion preset (`:118-123`), `STEP_NAMES`. Reuse `fadeSlide` for the rework.
- `apps/web/src/components/os/overlays/onboarding/types.ts` — `WelcomeStepProps` (`:10-12`).
### Supporting state already present (REUSE)
- `apps/web/src/hooks/useOfflineStatus.ts` — `useOfflineStatus()` returns `offline:boolean`
(`:36-99`, 2-consecutive-failure tolerance). Backs the **offline** state. Already consumed by
`Desktop.tsx:112` and `StatusBar.tsx`; thread it into the welcome step.
- Privacy/local-only copy: **no dedicated component exists**, but the claim is true per PRD §6
("Local-first by default") / §18.1. EraseDataDialog / data-erase surfaces exist elsewhere
(`overlays/EraseDataDialog.tsx`) but there is no reusable "privacy note" primitive — net-new
small UI.
- **No i18n infrastructure** — grep for `i18next | react-i18next | useTranslation |
LanguageSelector | changeLanguage | navigator.language` over `apps/web/src` returns **0 matches**.
All UI copy is hardcoded English. The mockup's `English (US)` selector has **no backing system**.
---
## 4. Frontend work
**Reuse the wizard shell; rework step 0 into a proper First-Launch screen.**
### Components to create / rework
1. **REWORK `WelcomeStep.tsx`** (or rename to `FirstLaunchStep.tsx` keeping the same step-0 slot)
- Align copy to mockup: `Welcome to Waggle` + tagline `Your work. Your memory. Your agents.`
(PRD §24: mockup is directional — keep current eyebrow/brand voice if it reads better, but
the privacy promise + language + Continue affordances are required by blueprint, not optional).
- Add **privacy note** (footer-left): short line ("Your data is private. Stored locally.") with
a "view privacy note" expand (inline `Popover`/`HoverCard` from `components/ui/`, or a small
details disclosure). Maps blueprint interaction "view privacy note" + state "local-only".
- Add **offline awareness**: consume `useOfflineStatus()`; when offline, show a subtle indicator
(reuse the StatusBar offline visual language) and keep Continue enabled (this step is pre-network).
- Add **language affordance** (footer-right): see §9 open question — recommended v1 = a static,
disabled-looking `English (US)` chip (honest: only English ships) OR a minimal selector wired to
a new `OnboardingState.locale` that only persists the choice. Do **not** build full i18n in this
card's scope.
- Keep the existing **Continue** (`goToStep(1)`) + **click-anywhere** + **autoFocus** +
keyboard-reachable button (WelcomeStep already satisfies WCAG 2.1.1/2.2.1 — preserve).
2. **(Optional, recommended) reconsider the 3 s auto-advance** (`OnboardingWizard.tsx:136-141`).
A privacy-reassurance screen that auto-dismisses in 3 s undercuts the "read the privacy note"
intent. Rework: keep auto-advance only when no interaction, or drop it for S12. (Surgical change,
one `useEffect`.)
3. **"Resumed setup" copy hook** — when `state.step > 0` on mount (returning mid-flow), the shell
already restores the step; ensure First Launch isn't re-shown cold. Minimal: the existing
`state.step` restore (`OnboardingWizard.tsx:39`) already handles navigation; add a one-line
"Welcome back — picking up where you left off" variant if `state.step` was persisted > 0. Low
priority; the resume mechanic exists.
### Reuse targets
- Shell: `OnboardingWizard.tsx` (progress bar, dots, Skip, Back, AnimatePresence step swap).
- Motion: `fadeSlide` (`constants.ts:118-123`).
- Offline: `useOfflineStatus()`.
- UI primitives: `components/ui/{button,popover,hover-card,badge}.tsx` (shadcn set already present).
- Brand assets: `assets/waggle-logo.{png,jpeg}` (already imported by WelcomeStep, theme-aware via
`useIsLightTheme`).
### Props / state
- `WelcomeStepProps` (extend): add `offline: boolean` (from `useOfflineStatus`), and — if a
language chip is wired — `locale?: string` + `onLocaleChange?: (l: string) => void`.
- `OnboardingState` (`useOnboarding.ts:10-19`): optionally add `locale?: string` (additive,
localStorage-only, no backend). Resume already covered by existing `step` field.
### Adapter methods / hooks
- **None new required.** This screen is pre-network. `useOfflineStatus` already wraps the health
probe; `adapter.trackTelemetry('onboarding_step', …)` already fires on step change
(`OnboardingWizard.tsx:31-33, :132`). No new adapter method.
---
## 5. Backend work
**This screen needs effectively NO backend.** It is the pre-intent splash; every interaction is
local UI + localStorage. Cross-referenced against backend-routes inventory and backend-map §03c —
nothing on S12 maps to a missing PRD §16 endpoint.
| Capability needed | PRD §16 endpoint | Status | Note / what to EXTEND vs NET-NEW | Substrate / migration |
|---|---|---|---|---|
| Continue / advance step | — (none) | **EXISTS (client-only)** | Step state is `OnboardingState.step` in `localStorage` (`useOnboarding.ts`). No server call. | none |
| Offline / local-only state | — (no PRD §16 row) | **EXISTS** | Reuse `GET /api/offline/status` (`offline.ts`) + the health probe already used by `useOfflineStatus`. No new route. | none |
| Privacy note / local-first claim | — (none) | **EXISTS (static)** | Truthful per PRD §6/§18.1; copy-only. No endpoint. | none |
| Language change | — (none) | **MISSING (no infra)** | No i18n/locale backend anywhere (grep-confirmed). If a real selector is wanted, locale persists client-side in `OnboardingState.locale` (localStorage) — **net-new client field, NOT a server route**. Could later piggy-back on `PUT /api/profile` (`profile.ts`, EXISTS) or `PATCH /api/settings` (`settings.ts`, EXISTS) if locale must sync, but that is out of scope for v1. | none (no `.mind` migration) |
| Returning-user / resume detection | — (none) | **EXISTS** | `useOnboarding` already calls `adapter.getWorkspaces()` (`GET /api/workspaces`, EXISTS) + Tauri first-launch flag to auto-complete returning users. No change. | none |
**No `.mind` migration. No net-new route. No substrate touch.** S12 is the single cleanest screen
in the refactor from a backend standpoint.
---
## 6. Shared types needed (PRD §15 vs lib/types.ts)
- **None of the PRD §15.2 unions are touched by S12** (no WorkspaceType/Scope/MemoryKind/etc. on a
welcome splash).
- The only candidate type change is local: add optional `locale?: string` to `OnboardingState`
(`hooks/useOnboarding.ts:10-19`) **if** the language affordance is wired beyond a static chip.
This is a frontend-only interface field, not a PRD §15 shared type, and needs no `packages/shared`
change.
- `WelcomeStepProps` (`onboarding/types.ts:10-12`) gains `offline` (+ optional locale props). Local
to the onboarding folder.
---
## 7. Dependencies (screens / phases first)
- **Phase placement:** PRD Sprint 5 / Release Phase 2 ("Onboarding flow: Welcome, Profile, Tool
Discovery, Memory Import, Memory Review"). S12 is the **entry** of that sequence.
- **Hard dependency:** the wizard shell (`OnboardingWizard.tsx`) must remain the host — already
exists, so no blocker. S12 can be reworked independently of the later steps.
- **Sibling screens that share the shell** (do these in the same sprint to keep the rework
coherent): S13 Who Are You, S14 Tool Discovery, S15 Memory Import, S16 Memory Review, S17 Create
Workspace. S12 should land first because it sets the privacy/local-first framing the rest rely on.
- **No dependency on Home Cockpit / Workspace Desktop / Command Center backend work** — S12 is
pre-workspace and pre-network.
- **Decision dependency:** the language-selector scope question (§9) should be answered before
implementation so the footer affordance isn't built twice.
---
## 8. Effort
**S** — Single presentational component rework inside an existing, working shell; reuse
`useOfflineStatus` + existing motion/UI primitives; **zero backend, zero migration, zero shared-type
churn**. The only thing that could push it toward **M** is electing to build a real (even minimal)
language selector + `locale` plumbing instead of a static `English (US)` chip.
---
## 9. Open questions
1. **Language selector — real or honest-stub?** No i18n exists (grep-confirmed: 0 matches for
i18next/useTranslation/etc.). Options: (a) static disabled `English (US)` chip (truthful, S
effort, recommended v1); (b) minimal selector that persists `OnboardingState.locale` but only
English is wired (cosmetic); (c) defer the affordance entirely. PRD §24 lets us treat the mockup
as directional, and PRD §4.4 lists native/i18n work as out-of-first-phase scope — so (a) or (c)
align with the PRD. **Recommend (a).** Confirm before building.
2. **Keep the 3 s auto-advance on a privacy screen?** Current step-0 auto-advances after 3 s
(`OnboardingWizard.tsx:136-141`), which fights the "read the privacy note" intent. Drop it for
S12, or keep-but-pause-on-interaction?
3. **Copy alignment:** mockup says "Welcome to Waggle" / "Your work. Your memory. Your agents.";
current ships "Welcome to the Hive" / "Persistent memory. Workspace-native…". Which brand voice
wins? (PRD §24: pixels don't, but the privacy promise must be present either way.)
4. **"Resumed setup" UX:** the step is restored from `localStorage` already — is a distinct
"welcome back" treatment on First Launch wanted, or is silent step-restore sufficient?
5. **Privacy note content:** inline expandable text vs link to a privacy doc/URL? No privacy-policy
route or doc is wired today — needs copy + destination decided.

View File

@@ -0,0 +1,231 @@
# Gap Card — S13 · Who Are You (Onboarding Profile)
> UX-refactor planning artifact. Execution model is **in-place incremental refactor** of `apps/web` +
> targeted backend extension. Every claim below is grounded in a real file. PRD = source of truth;
> mockup is directional (PRD §24). Screen index: **Onboarding Flow PRD §12.12 step 2** ("Who Are You").
---
## 1. Screen & purpose
The second onboarding step: capture the user's professional context so Waggle can personalize and
recommend. Per PRD §12.12 and the blueprint screen-spec table (PAGE 14, row 13): "Capture role,
industry, work type, team size and goals." Blueprint acceptance: **"Profile drives recommendations
but can be edited later."** Blueprint states: `Empty; partially complete; validation; saved`.
The mockup (`screen_13_who_are_you.png`) shows a single-step form inside the onboarding shell:
left rail step list (Welcome / Who are you / Tool discovery / Memory import / Review & confirm) + a
"Your data is private" reassurance card; center form with **Name, Role, Industry, Work type, Team
size, and a "What are your goals with Waggle?" multi-select chip group** (Build a second brain /
Improve team productivity / Automate recurring work / Make better decisions / Scale the business) +
free-text "Add other goal"; a right-hand live **"Your profile" preview panel** that mirrors the form
as the user types; Back / Continue footer. Mockup is directional — PRD/blueprint acceptance wins.
This profile drives downstream screens: Tool Discovery (S14) recommendations, Memory Import (S15)
source suggestions, Workspace Creation (S17) template/persona pre-fill, and the Home Cockpit greeting.
---
## 2. Required states (PRD/Blueprint)
From the blueprint screen-spec (PAGE 14, row 13) + PRD §14.1 global-state mandate:
- **Empty** — fresh form, nothing entered.
- **Partially complete** — some fields filled (Continue still allowed; profile is editable later).
- **Validation** — surface invalid/missing required input (blueprint lists "validation" as a state;
PRD acceptance says it must not block — soft validation, not a hard gate).
- **Saved** — profile persisted; advance to Tool Discovery.
- **Loading** (PRD §14.1) — restoring a resumed/partial profile on re-entry.
- **Offline / local-only** (PRD §14.1, §6.7) — sidecar unreachable; the step must still let the user
type and continue (write deferred / retried), mirroring the wizard's existing
"created locally, sync later" fallback (`OnboardingWizard.tsx:286-288`).
PRD §12.12 acceptance criteria that bind this screen: "Onboarding asks user questions, not
infrastructure questions"; "Profile drives recommendations." PRD §7.1 activation metric: "% of new
users who complete onboarding."
---
## 3. Current state in repo — disposition: **partial** (rework + create-new)
There is **no onboarding "Who Are You" profile step today.** The profile-capture *capability* exists
post-onboarding as a separate app, and the onboarding wizard does NOT collect it. Two surfaces are
relevant; both partially overlap the requirement:
**(a) `OnboardingWizard.tsx`** (`apps/web/src/components/os/overlays/OnboardingWizard.tsx`) — the
first-launch wizard. Its actual step order (`STEP_NAMES`, line 35) is:
`welcome → why-waggle → tier → memory-import → template → persona → api-key → ready` (8 steps,
0-indexed; rendered via the `step===N` switch at lines 499-571). **There is no profile/identity-capture
step at all.** Steps map to `onboarding/` components: `WelcomeStep, WhyWaggleStep, TierStep, ImportStep,
TemplateStep, PersonaStep, ModelTierStep, ReadyStep` (`onboarding/index.ts`). The wizard's data model
(`OnboardingState` in `hooks/useOnboarding.ts`) tracks `step/tier/templateId/personaId/workspaceId/
apiKeySet`**no profile fields.** Disposition for the wizard: **rework** — insert a new profile step
and a `WhoAreYouStep` component; widen `OnboardingState` minimally (or post directly to `/api/profile`).
> Note: the wizard's IA (tier picker, API-key step, model-tier step) is the OLD onboarding, not the
> PRD §12.12 flow (Welcome → Who Are You → Tool Discovery → Memory Import → Memory Review → Workspace
> Creation → Home). PRD §20.2 explicitly lists "Onboarding wizard → simplify to user-oriented 5-step
> setup plus workspace creation." S13 is one slice of that larger onboarding rework; this card scopes
> only the profile step, but the planner must sequence it inside the onboarding-IA rework (see §7).
**(b) `UserProfileApp.tsx`** (`apps/web/src/components/os/apps/UserProfileApp.tsx`, 533 LOC) — the
post-onboarding "My Profile" app (dock id `profile`). A 4-tab surface: **Identity** (Name/Role/Company/
Industry/Bio + "Research Me" + harvest-suggestion accept/dismiss banner, lines 242-343), Writing Style,
Brand & Templates, Interests (interests chips + language, lines 483-520). Its Identity tab already
renders the heading "**Who Are You?**" (line 245) and captures Name/Role/Company/Industry/Bio via
`adapter.getProfile()` / `adapter.updateProfile()` (lines 86-102, 120-133). This is the **reuse target**
for the form fields, validation pattern, and adapter wiring — but it is NOT an onboarding step, it has
NO Work type / Team size / Goals, and it has NO live preview panel. Disposition: **keep-promote** the
field/adapter patterns; extract a shared profile-form so the onboarding step and the app don't diverge.
**Net:** the requirement is **partial** — backend persistence (`/api/profile`) + a structurally similar
form (UserProfileApp Identity tab) exist, but the *onboarding step itself*, the *3 new fields*
(work type / team size / goals), and the *live preview panel* are net-new frontend.
---
## 4. Frontend work
**Create:**
- `apps/web/src/components/os/overlays/onboarding/WhoAreYouStep.tsx` — the new step component
(matches the `*Step.tsx` sibling convention; default-exported from `onboarding/index.ts`). Renders:
Name (text), Role (text), Industry (`<select>` reusing `UserProfileApp.tsx:37-41` `INDUSTRIES`),
**Work type** (select/segmented — e.g. Strategy & Operations / Engineering / Sales / Marketing /
Research / Other), **Team size** (select — Just me / 2-10 / 11-50 / 50+), **Goals** (multi-select
chip group, reusing the chip-toggle pattern from `UserProfileApp.tsx:488-498` interests), free-text
"other goal", and the **live "Your profile" preview panel** (right column; pure derived view of
current form state). Props: `{ profile, onChange, onContinue, onBack, saving }`. Local form state
mirrored to a single `Partial<UserProfile>` object (immutable updates per repo coding-style).
- `apps/web/src/lib/onboarding-profile.ts` (optional helper) — the goal/work-type/team-size option
constants + a `buildProfilePreview()` pure function (testable, co-located `.test.ts`), keeping the
step component thin (CLAUDE.md §3.2, file-org rules).
**Rework:**
- `OnboardingWizard.tsx` — insert the new step into the flow after `WelcomeStep` per PRD §12.12 order.
Touches: `STEP_NAMES` (line 35), the step switch (lines 499-571), step-index math (`progressPct`
line 396, dots lines 472), and a `handleProfileSave` that calls `adapter.updateProfile(...)` (same
call UserProfileApp uses, `UserProfileApp.tsx:123-128`). Keep the existing offline "created locally"
fallback semantics (lines 286-288) for the profile write. NOTE: the current wizard IA (tier/api-key/
model steps) is being reworked to the PRD §12.12 5-step flow in a sibling card — coordinate the
step-index churn with that card to avoid double-editing the switch.
- `UserProfileApp.tsx` Identity tab — **optional consolidation**: extract the shared field set into the
new `onboarding-profile.ts` form so onboarding and the app render the same Name/Role/Industry/Goals
controls (avoids the documented "two parallel systems" anti-pattern,
`feedback_grep_capability_not_feature_name.md`). At minimum, add the 3 new fields here too so a user
can edit Work type / Team size / Goals after onboarding (blueprint: "can be edited later").
**Reuse targets (do not recreate):**
- `INDUSTRIES` + `INTEREST_OPTIONS` chip-toggle pattern — `UserProfileApp.tsx:31-41, 488-498`.
- `adapter.getProfile()` / `adapter.updateProfile()``lib/adapter.ts` (profile method block;
inventory `frontend.md:232`). Already typed and used.
- `@/components/ui/{input,select-ish}` shadcn primitives (`components/ui/*`, inventory §(e)).
- Onboarding shell chrome (progress bar, Back/Skip, step dots) — already in `OnboardingWizard.tsx`.
**Adapter methods/hooks:** no NEW adapter method needed for the happy path —
`adapter.updateProfile(partial)` (PUT `/api/profile`, partial-merge) already accepts arbitrary profile
fields and the backend persists unknown-to-old-UI fields once the route is widened (see §5). The
profile is read via `adapter.getProfile()`. No new hook required; step holds local state and posts on
Continue.
---
## 5. Backend work
PRD §16 has **no dedicated onboarding-profile endpoints** — the profile domain is served by the
existing `/api/profile*` routes (`packages/server/src/local/routes/profile.ts`), which are NOT in the
PRD §16 list but already exist and are the correct substrate. So there is nothing "MISSING" in PRD §16
terms; the work is **EXTEND** of an existing route, not net-new routing.
| Capability | Status | Route to EXTEND vs NET-NEW · substrate |
|---|---|---|
| Read profile for the step | **EXISTS** | `GET /api/profile` (`profile.ts:162-164`) returns the full `UserProfile` (defaults-merged). Reusable as-is. |
| Persist profile from the step | **PARTIAL → EXTEND** | `PUT /api/profile` (`profile.ts:167-228`) partial-merges, but its allow-list of merged fields (lines 172-196) does **not** include the 3 new fields (`workType`, `teamSize`, `goals`). EXTEND the merge block + the `UserProfile` interface (`profile.ts:41-94`) + `DEFAULT_PROFILE` (`:96-135`) to carry them. Substrate: `profile.json` under `dataDir` (NOT SQLite) — **no DB migration.** |
| Mirror identity → memory | **EXISTS (reuse)** | `PUT /api/profile` already writes a `User identity:` P/I frame to personal memory on save (`profile.ts:201-224`). Optionally append role/industry/goals to that string so the agent picks up the new context — pure edit, no schema change. |
**Net-new fields (frontend + backend, additive):** `workType?: string`, `teamSize?: string`,
`goals?: string[]`. All three are pure additive optional fields on the JSON-file `UserProfile` shape.
The mockup's Industry/Name/Role already map 1:1 to existing fields.
**Substrate touched:** `profile.json` (file store, `getProfilePath()` `profile.ts:137-139`) and
(reused) personal `.mind` `memory_frames` via the existing identity-mirror path.
**.mind migration:** **NONE.** Profile lives in `profile.json`, not SQLite (`profile.ts:152-156`).
The identity-mirror writes frames through the existing append-only API — no schema change.
> Optional alignment (flag, not required for S13): a parallel structured identity record exists at
> `POST /api/identity` → `identity` table (`identity.ts:104-156`, fields name/role/department/
> personality/capabilities/system_prompt). The onboarding step writes to `/api/profile` (the richer,
> file-backed shape the UI already uses), NOT `/api/identity`. The planner should decide whether
> onboarding should ALSO seed `/api/identity` (it backs `adapter.getIdentity()` / the Home greeting
> name) or leave that to the existing profile→frame mirror. Out of S13 scope but worth a one-line
> decision to avoid two divergent identity stores.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
PRD §15.2 defines no profile/onboarding union (its unions are Workspace/Memory/Artifact/Agent-centric),
so there is no PRD §15 type to satisfy here. The relevant types are:
- **Frontend:** `apps/web/src/lib/types.ts` has **no `UserProfile` type** (the shape is declared
inline inside `UserProfileApp.tsx:45-52` and re-declared loosely in the new step). Recommend promoting
a shared `UserProfile` (with the 3 new fields) to `lib/types.ts` so the onboarding step, the app, and
the adapter return type share one contract (avoids the FE/BE drift the substrate inventory flags for
other entities). Low-cost, additive.
- **Backend:** `UserProfile` + `IdentitySuggestion` interfaces are exported from `profile.ts:31-94`.
Extend in place with the 3 new fields. (They are not in `packages/shared` today; keeping them in
`profile.ts` + mirroring an FE type is consistent with current layout.)
- New small unions for the option sets (`WorkType`, `TeamSize`, goal ids) can live in
`onboarding-profile.ts` as string-literal unions (repo coding-style prefers literal unions over enum).
---
## 7. Dependencies (screens / phases first)
- **Phase 2 (PRD §8)** — Onboarding flow is Phase 2 / Sprint 5 ("Who Are You" listed explicitly,
PRD §21 Sprint 5 + §8 Phase 2).
- **Onboarding-IA rework first.** S13 is one step inside the PRD §12.12 flow (Welcome → **Who Are You**
→ Tool Discovery → Memory Import → Memory Review → Workspace Creation → Home). The current wizard's
step order/IA differs from PRD §12.12; the step-index/switch churn in `OnboardingWizard.tsx` must be
coordinated with the sibling onboarding cards (S12 First Launch, S14 Tool Discovery, S15 Memory
Import, S16 Memory Review, S17 Workspace Creation) so the switch is rewired once, not per-card.
- **Downstream consumers of this profile:** S14 Tool Discovery (recommendations from work type/role —
PRD §12.12 acceptance "recommended from user selections"), S15 Memory Import (source suggestions),
S17 Workspace Creation (template/persona pre-fill), Home Cockpit greeting (name). S13 should land
before or with S14 since S14 consumes its output.
- **No backend prerequisite** — `/api/profile` GET/PUT already exist; the field extension is
self-contained and can ship independently of the Home/Workspace/Memory backend work.
---
## 8. Effort: **M**
One net-new step component + a live-preview panel + a thin backend field extension (3 additive JSON
fields, no migration) + onboarding wizard step insertion. The form fields, validation pattern, chip
toggles, and adapter wiring already exist in `UserProfileApp.tsx` to copy from, which keeps it out of
L. The "M" (not S) reflects: the live-preview panel is new UI, the wizard step-index/switch rewire is
fiddly and must be coordinated with the broader onboarding-IA rework, and a shared `UserProfile` type +
optional UserProfileApp consolidation add surface.
---
## 9. Open questions
1. **Identity store of record:** does onboarding write profile via `/api/profile` only (current UI
path), or ALSO seed `/api/identity` (the `identity` table that backs the Home greeting name /
`adapter.getIdentity()`)? Two identity stores exist; pick one to avoid drift. (§5 note.)
2. **Goal taxonomy:** are the 5 mockup goals (second brain / team productivity / automate recurring
work / better decisions / scale business) the canonical set, or directional? They likely feed S14
tool/connector recommendations — confirm the mapping owner.
3. **Work type vs Industry vs Persona/Template:** the existing onboarding already picks a template
(`TEMPLATE_PERSONA`, `constants.ts:28-44`) which implies a work domain. Does "Work type" duplicate
that signal, or is it a distinct axis used only for personalization? Resolve to avoid asking the
user the same thing twice (PRD §12.12: "asks user questions, not infrastructure").
4. **Validation hardness:** blueprint lists a "validation" state but PRD says profile is editable
later. Confirm Continue is never hard-blocked (soft-validate only) — assumed soft per PRD acceptance.
5. **Required vs optional fields:** which of Name/Role/Industry/Work type/Team size/Goals (if any) are
required to proceed? Assumed all optional (partial-complete is an allowed state).
6. **Pre-fill from harvest:** UserProfileApp already accepts harvest-extracted `identitySuggestions`
(`profile.ts:31-39`, `UserProfileApp.tsx:252-298`). Should the onboarding step pre-fill from those
if Memory Import (S15) ran first? In PRD §12.12 order, import comes AFTER Who Are You — so likely no
on first pass, but confirm whether a returning/resumed user sees suggestions here.

View File

@@ -0,0 +1,250 @@
# Gap Card — S14 Tool Discovery
> Screen 14 of the Waggle OS UX-refactor. Onboarding step 3 of 7 (PRD §12.12).
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend
> extension. Every claim below is grounded in repo source (paths cited).
> PRD = `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`.
---
## 1. Screen & purpose
"What tools do you use?" — the third onboarding step. The user selects the tools they
already use day-to-day (in plain product language, **not** an infra/connector setup
screen) so Waggle can recommend the right connectors, MCPs and skills downstream and
seed the Memory Import step (S15).
- **PRD §12.12** (step 3 of the 7-step onboarding flow): "Tool Discovery — ask which
tools are used." Acceptance: "Onboarding asks user questions, not infrastructure
questions"; "Connectors/MCPs/skills are **recommended from user selections**."
- **Blueprint** (`_blueprint_extracted.txt`):
- Screen index row 14 (`:381-384`): *"Ask what tools the user uses. Select tools, add
other, continue/back. States: No selection; selected; recommended; unsupported tool.
User-oriented language, not infra setup."*
- Mental model (`:54`): *"Tool discovery and harvest are core onboarding steps."*
- Journeys J01 (`:169`) and J02 (`:173`): Tool Discovery sits between *Who Are You*
(S13) and *Memory Import* (S15) on both the import and skip-import paths.
- **Mockup** (`screen_14_tool_discovery.png`, directional): two-column layout —
left = grouped selectable tool grid in **"Collaboration & Productivity"** (Gmail,
Calendar, Slack, GitHub, Notion, Jira, Salesforce) and **"AI & Developer Tools"**
(Claude, Claude Code, Cursor, Hermes, Codex, "Other tool"), each card with a
checkbox + "Select all" per group; right rail = **"You selected"** chip list +
**"What's next"** ("We'll suggest relevant connectors and MCPs", "Your data is
private"); left rail = the 7-step onboarding progress rail with "Tool discovery"
active; footer = Back / Continue.
---
## 2. Required states (PRD / Blueprint)
Blueprint S14 enumerates four states; the mockup adds layout affordances:
1. **No selection** — nothing chosen; "You selected" empty; Continue still allowed
(tool discovery is non-blocking, consistent with J02 skip-import).
2. **Selected** — one or more tools toggled; reflected in the "You selected" chip rail.
3. **Recommended** — "What's next" surfaces connectors/MCPs/skills derived from the
selections (PRD acceptance: recommendations come *from user selections*). This is a
silent default-ordering surface, not a friction step (per the project rule
`feedback_silent_recommendations_dont_ask.md`).
4. **Unsupported tool** — user adds a tool ("Other tool" / free-text) that Waggle has
no native connector for; captured as a profile signal, surfaced as "we'll watch for
this / available via MCP," never a hard error.
5. Plus: **Select-all per group**, **Back/Continue** nav, and per-group sections
("Collaboration & Productivity" vs "AI & Developer Tools").
Acceptance (PRD §12.12): user-oriented language; selections drive recommendations;
**nothing imports here** (import/consent is S15's job — this screen only records intent).
---
## 3. Current state in repo
**Disposition: `create-new`** (the step does not exist; substrate to feed it largely does).
### The onboarding wizard has NO Tool Discovery step today
`apps/web/src/components/os/overlays/OnboardingWizard.tsx` is an **8-step** wizard whose
`STEP_NAMES` (`:35`) are: `welcome, why-waggle, tier, memory-import, template, persona,
api-key, ready`. The step switch (`:498-572`) renders `WelcomeStep / WhyWaggleStep /
TierStep / ImportStep / TemplateStep / PersonaStep / ModelTierStep / ReadyStep`. **There
is no "what tools do you use?" step** anywhere in the flow. Step components live in
`apps/web/src/components/os/overlays/onboarding/` (`WelcomeStep, WhyWaggleStep, TierStep,
ImportStep, TemplateStep, PersonaStep, ModelTierStep, ReadyStep, constants.ts, types.ts,
index.ts`) — none is a tool picker.
> Note: the live wizard's step ordering (tier/template/persona/api-key) does **not** match
> the PRD's 7-step IA (Welcome → Who Are You → Tool Discovery → Memory Import → Memory
> Review → Workspace Creation → Home Cockpit). The refactor inserts Tool Discovery; the
> broader re-sequencing is a cross-screen concern (see §7).
### `OnboardingState` cannot persist a tool selection
`apps/web/src/hooks/useOnboarding.ts:10-19``OnboardingState` = `{ completed, step,
tier?, workspaceId?, apiKeySet?, templateId?, personaId?, tooltipsDismissed? }`. **No
`toolsUsed` / `selectedTools` field.** Persisted to `localStorage` key `waggle:onboarding`.
### Two SEPARATE substrates map to the mockup's two columns — neither is a "what tools do you use" survey
1. **SaaS / productivity column** → native **connector registry**.
`packages/agent/src/connector-registry.ts` `getDefinitions()` (`:59`) returns
`ConnectorDefinition[]` with live vault status. ~31 connectors are registered
(`packages/agent/src/connectors/index.ts`): GitHub, Slack, Jira, Gmail, Google
Calendar, Notion, Salesforce, HubSpot, Linear, Asana, Trello, Monday, Confluence,
Discord, Dropbox, etc. — a direct match for the mockup's Gmail/Calendar/Slack/GitHub/
Notion/Jira/Salesforce cards. `ConnectorDefinition` (`packages/shared/src/types.ts:276-302`)
already carries `id, name, displayName(via name), description, category` (`'productivity'
| 'development' | 'crm' | 'data' | 'communication' | 'storage' | 'integration'`),
`status`, `logoUrl`, `setupGuide` — exactly the fields a grouped, iconed tool grid needs.
Surfaced over HTTP at `GET /api/connectors` (`packages/server/src/local/routes/connectors.ts:6`).
2. **AI & Developer column****AI-OS tool detection** (DIFFERENT subsystem).
`packages/agent/src/tool-detection.ts` + `packages/shared/src/tool-detection.ts`:
`SUPPORTED_TOOLS` (`:23-31`) = `claude-code, claude-desktop, cursor, codex,
codex-desktop, hermes, openclaw`; `TOOL_DISPLAY_NAMES` (`:58`). Surfaced at
`GET /api/tools/detect` (`packages/server/src/local/routes/tools.ts`). This *detects
what is installed on the machine*; the mockup's AI/Developer column ("Claude, Claude
Code, Cursor, Hermes, Codex") aligns with this set but as a **self-report picker**, not
an install scan. The two can be merged: pre-check tools `tool-detection` already found.
### Recommendation engine — partial precedent, no connector recommender yet
`apps/web/src/lib/skill-recommendations.ts` is the existing template for "silent default
ordering from a user signal" (persona → 3-5 starter skills, `recommendSkills()` `:103`).
**There is no `connector-recommendations.ts`** (grep: only a *comment* in
skill-recommendations references the pattern; no file). The "What's next" rail needs a
new tool→connector/MCP/skill mapper following this same shape. `GET /api/skills/suggestions`
exists (`skills.ts`) but is context-driven, not tool-selection-driven.
**Reuse-not-rebuild verdict:** the *data* for both columns exists (`/api/connectors` +
`/api/tools/detect`); the *step UI*, the *state field*, and the *recommendation mapper*
are net-new. No new data store.
---
## 4. Frontend work
### Components to CREATE
| Component | Location | Role |
|---|---|---|
| `ToolDiscoveryStep.tsx` | `apps/web/src/components/os/overlays/onboarding/` | The step shell: grouped tool grid + "You selected" rail + "What's next" rail + Back/Continue. Mirrors the `*Step.tsx` prop contract (`goToStep`, controlled selection props). |
| `ToolGroup.tsx` (or inline) | same dir | One titled group ("Collaboration & Productivity" / "AI & Developer Tools") with a Select-all toggle and a grid of `ToolCard`s. |
| `ToolCard.tsx` | same dir | Single selectable tool tile (icon/logo + label + checkbox + selected ring). Reuse `components/ui/checkbox` + `card` + existing `connectors/BrandTile.tsx` icon pattern. |
### Reuse targets
- `components/os/apps/connectors/BrandTile.tsx` — already renders a branded connector
tile with logo; lift its icon/logo resolution into `ToolCard`.
- `components/ui/*` (`card`, `checkbox`, `badge`, `button`, `separator`) — DS primitives.
- `lib/skill-recommendations.ts` shape — model `connector-recommendations.ts` on it.
- Onboarding chrome (progress rail, Back/Continue/Skip) already exists in
`OnboardingWizard.tsx` (`:438-493`) — the step plugs into the existing AnimatePresence
switch; no new chrome.
### New lib helper
- `apps/web/src/lib/tool-recommendations.ts` — pure mapper
`recommendFromTools(selectedToolIds: string[]) => { connectors: string[]; mcps: string[];
skills: string[] }`, with a co-located `.test.ts` (matches `skill-recommendations.test.ts`).
Feeds the "What's next" rail. Tool ids reconciled across connector ids and `ToolId`.
### Wizard wiring (edits to existing files)
- `OnboardingWizard.tsx`: insert the step into `STEP_NAMES` (`:35`) and the step switch
(`:498-572`); thread `selectedTools` step-local state + `onUpdate({ toolsUsed })`; bump
the progress denominator (currently hard-coded `/7` `:396`, dots `[1..6]` `:472`,
`aria-valuemax={7}` `:426`) — these counts become inconsistent once a step is added and
the PRD re-sequence lands, so treat the step-count constants as a single thing to fix.
- `useOnboarding.ts`: add optional `toolsUsed?: string[]` (and optionally
`unsupportedTools?: string[]`) to `OnboardingState` (`:10-19`); additive, no migration
(localStorage).
### Data hooks
- Connector column: `adapter.getConnectors()` (`GET /api/connectors`) — already on the
adapter (`lib/adapter.ts`). No new hook strictly needed; a thin `useConnectors`-style
fetch in the step is fine, or reuse `ConnectorsApp`'s fetch pattern.
- AI/Dev column: `adapter.detectTools()` (`GET /api/tools/detect`) — already on the
adapter — to PRE-CHECK locally-installed AI tools.
---
## 5. Backend work (PRD §16 cross-reference)
S14 has **no dedicated PRD §16 endpoint** — it is an onboarding capture screen that reads
existing catalogs and writes the selection into onboarding/profile state. Required backend:
| Need | Status | Existing route/builder to EXTEND vs NET-NEW | Substrate |
|---|---|---|---|
| List SaaS/productivity tools (grid) | **EXISTS** | `GET /api/connectors` (`connectors.ts:6``connectorRegistry.getDefinitions()`). Already returns `category` + `logoUrl` for grouping/icons. No change. | connector registry (in-memory defs + vault status) |
| List AI/developer tools (+ pre-check installed) | **EXISTS** | `GET /api/tools/detect` (`tools.ts`, AI-OS). Returns per-tool `installed`. No change. | `tool-detection.ts` (FS/PATH probes) |
| Recommend connectors/MCPs/skills from selection ("What's next") | **PARTIAL** | Closest is `GET /api/skills/suggestions` (`skills.ts`, context-driven, wrong input). Recommended approach: do the mapping **client-side** in `lib/tool-recommendations.ts` (pure, testable, no round-trip) — no new endpoint. If server-side later: NET-NEW thin `GET /api/recommendations?tools=` over connector catalog + `mcp-catalog.ts` + starter-skills. | connector defs + `@waggle/shared` `mcp-catalog.ts` + starter skills |
| Persist the tool selection | **PARTIAL** | Two options: (a) **client-only** in `localStorage` via `OnboardingState.toolsUsed` (lowest risk, matches current onboarding persistence) — **recommended**; (b) write into the user profile via existing `PUT /api/profile` (`profile.ts:218`, partial-merge) so recommendations survive re-onboarding and feed S15. Prefer (a) for the step; optionally also (b) to thread selections into Memory Import. | `localStorage` and/or `profile` store |
**No net-new sidecar route is strictly required** for S14 itself, and **no `.mind`
migration** — both source catalogs already have HTTP routes, and the selection is small
UI state. (Contrast: S15 Memory Import and the Home/Artifacts screens DO need net-new
routes; S14 is the cheap one.)
> Adjacent (not S14-blocking): if the team later wants the "unsupported tool" free-text to
> become a real demand signal, the cheapest home is `PUT /api/profile` `unsupportedTools[]`
> — no new substrate.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`OnboardingState.toolsUsed?: string[]`** (+ optional `unsupportedTools?: string[]`) —
add to `apps/web/src/hooks/useOnboarding.ts:10-19`. Additive, optional.
- **`ToolRecommendation`** interface in the new `lib/tool-recommendations.ts` (mirrors
`SkillRecommendation` in `skill-recommendations.ts:20-27`).
- **Reuse, do not redefine:** `ConnectorDefinition` (`packages/shared/src/types.ts:276-302`)
for the SaaS column; `ToolId` / `DetectedTool` / `TOOL_DISPLAY_NAMES`
(`packages/shared/src/tool-detection.ts`) for the AI/Dev column.
- **PRD §15 note:** S14 does **not** introduce any of the missing §15.2 unions
(`WorkspaceType`, `Scope`, `MemoryKind`, `ArtifactKind`, …). It only needs the additive
onboarding field above, so it is **not** gated on the §15 type-alignment work that other
screens (Memory, Artifacts, Agents) require.
---
## 7. Dependencies (screens / phases first)
- **Onboarding re-sequence (cross-screen):** the live wizard order (tier/template/persona/
api-key) differs from the PRD 7-step IA. S14 slots between **S13 Who Are You** and
**S15 Memory Import** (J01/J02). Coordinate step insertion + progress-count fix once,
alongside the other onboarding screens (S12 First Launch, S13, S15, S16, S17) rather than
in isolation — they all touch `OnboardingWizard.tsx` + `useOnboarding.ts`.
- **Feeds S15 Memory Import:** selected tools should bias which import sources/connectors
S15 surfaces (PRD: recommendations from selections). S14 must land before/with S15.
- **Feeds Extend / Marketplace (S/connectors):** "What's next" recommendations point at
the same connector/MCP catalog those screens own.
- **No backend dependency** — `/api/connectors` and `/api/tools/detect` already exist, so
S14 frontend is **not blocked on any backend phase**.
- **Soft dependency:** `lib/tool-recommendations.ts` reuses the `skill-recommendations.ts`
pattern (already shipped).
---
## 8. Effort: **M**
Net-new step component + group/card subcomponents + a pure recommendation mapper + one
additive `OnboardingState` field + wizard-switch/progress-count wiring. **Zero net-new
backend routes and zero `.mind` migration** (both catalogs already have HTTP routes), which
keeps it out of L/XL. Larger than S (real new UI surface, two data sources to merge and
de-dupe, recommendation logic with tests, and the progress-count/step-sequence cleanup that
ripples through the wizard).
---
## 9. Open questions
1. **Catalog scope for the grid.** Mockup shows ~7 SaaS + ~5 AI tools, but the registry
has ~31 connectors. Show a **curated subset** (most common, matching the mockup) with a
"more" affordance, or the full grouped catalog? PRD says "user-oriented, not infra" →
leans curated. Needs a product call on the curated list.
2. **Two columns, two id-spaces.** SaaS column uses connector `id`s; AI/Dev column uses
`ToolId`. The "You selected" rail and `toolsUsed[]` need a unified id scheme (namespaced
e.g. `connector:gmail` / `tool:cursor`) so the recommender and S15 can disambiguate.
3. **Pre-check installed AI tools?** Should `GET /api/tools/detect` results pre-select the
AI/Dev cards (lower friction, "we already see Cursor") or stay unchecked until the user
opts in? J01 implies a populated, trusted start; lean pre-check + visible "detected" badge.
4. **Where do selections live long-term?** `localStorage` only (simplest), or also
`PUT /api/profile` so recommendations persist and feed Home/Extend after onboarding?
Profile-write is cheap and reusable but adds a server round-trip to the step.
5. **Recommendation placement: client vs server.** Recommend client-side
(`lib/tool-recommendations.ts`, pure + testable, no round-trip) per §5 — confirm the team
is fine deferring a `GET /api/recommendations` endpoint until a server-side consumer needs it.
6. **"Unsupported tool" handling.** Captured as a profile/demand signal only, or also
surfaced as "available via MCP" with a marketplace deep-link? Affects whether the free-text
needs any backend at all (recommend: capture-only for v1).

View File

@@ -0,0 +1,144 @@
# Gap Card — S15 Memory Import
> UX-refactor planning artifact. Execution model is **in-place incremental refactor** of `apps/web`
> + targeted backend extensions. Every claim is grounded in a real file (cited inline).
> Mockup is **directional** (PRD §24) — PRD acceptance criteria win over pixels.
---
## 1. Screen & purpose
**S15 = the Memory Import step of onboarding** (PRD §12.12 step 4; Blueprint screen 15, `_blueprint_extracted.txt:727`). It is the "Capture" surface where a new user connects/imports their existing AI history (Claude, Claude Code, Cursor, Hermes, Codex, ChatGPT, Gemini, Perplexity…) and work tools (Files, Notion, Google Drive, Slack) so Waggle's persistent memory starts non-empty.
PRD anchors:
- §12.12 step 4 "Memory Import — connect/import from AI tools, files, and work tools" (PRD:637).
- §16.5 Harvest API: `POST /api/harvest/preview`, `POST /api/harvest/commit`, `GET /api/harvest/sources`, `POST /api/harvest/sources/:id/sync` (PRD:1098-1101).
- Privacy gate: "No memory import without review/approval" (PRD:1207) and "Nothing imports without explicit review/approval" (PRD:646). **S15 produces the preview; the approval/commit decision is S16 Memory Review** (`_blueprint_extracted.txt:731`).
Mockup `screen_15_memory_import.png` (directional) shows a 3-region layout:
- **Left rail** — onboarding step list (Welcome / Why us / Tool discovery / Import memory [active] / Review & confirm) + a "Your data is private" footer card.
- **Center** — "Import your memory" with two grids: **AI Assistants & Coding Tools** (Claude, Claude Code, Cursor, Hermes, Codex, Other AI Tools — each a **Connect** action) and **Files & Workspace Tools** (Files, Notion, Google Drive, Slack — **Connect** actions). A primary **Continue** button bottom-right.
- **Right rail** — "What we import" (Conversations / Documents / Decisions / Artifacts / Code) and "You're in control" (review-before-import, never-shared, disconnect-anytime).
> Note the mockup is **Connect-centric** (OAuth/connector tiles) whereas the live onboarding step is **upload-centric** (file pickers). This is the core directional gap (see §2/§3).
---
## 2. Required states (PRD/Blueprint)
PRD §14.1 global states apply (Loading / Empty / Populated / Error / Offline-local-only / Syncing / Permission-denied / Partial / Approval-required). Concretely for S15:
| State | Trigger | Source of truth |
|---|---|---|
| Idle / source grid | step entered, no source picked | mockup center grids |
| Source auto-detected | sidecar finds local Claude Code at `~/.claude` | `POST /api/harvest/scan-claude-code``{found,itemCount,path}` |
| Preview / parsed | file uploaded or source connected; items parsed | `POST /api/harvest/preview``{itemCount, types, preview[]}` |
| Importing (live progress) | commit running | SSE `GET /api/harvest/progress` `{phase∈saving\|cognifying\|wiki-compile, current, total}` |
| Imported / done | commit returned | `POST /api/harvest/commit` `{saved, cognified, wikiCompiled, ...}` |
| No real embedder (degraded) | commit returns `cognifySkippedReason\|wikiSkippedReason = 'no_real_embedder'` | harvest commit response (05c §8) |
| Resume available | prior run interrupted | `GET /api/harvest/runs/latest-interrupted` |
| Connector consent / OAuth | "Connect" on Notion/Drive/Slack/Gmail | `GET /api/connectors`, `POST /api/connectors/:id/connect`, `GET /api/oauth/:provider/authorize` |
| Error | parse/commit/connect failure | per-call catch |
| Skipped | user declines (Journey 2, PRD:683-686) | navigates to next onboarding step |
| Privacy headline | always | mockup right rail; existing copy in `HarvestTab.tsx:362-365` |
**Approval boundary (load-bearing):** S15 ends at *preview*; the explicit approve-before-persist gate is **S16 Memory Review**. The live code today commits directly from S15 (no separate review screen) — a PRD-compliance gap, see §3.
---
## 3. Current state in repo (exact files + what they do)
Two existing surfaces already implement most of S15's *upload* path against the harvest substrate:
### 3a. Onboarding step (the real S15)
- `apps/web/src/components/os/overlays/OnboardingWizard.tsx` — step index **3** = `memory-import` (`STEP_NAMES` at `:35`). Holds import state (`importSource`/`importPreview`/`importing`/`importDone` `:64-72`), handlers `handleFileImport` (`:156-166``adapter.harvestPreview`), `handleImportCommit` (`:168-177``adapter.harvestCommit`, then `goToStep(4)`), Claude-Code auto-detect on mount (`:182-191``adapter.scanClaudeCode`) and `handleClaudeCodeHarvest` (`:193-202`). Renders `<ImportStep step===3>` at `:519-531`.
- `apps/web/src/components/os/overlays/onboarding/ImportStep.tsx` (192 LOC) — the actual S15 UI. 6 `SOURCE_TILES` (chatgpt/claude/gemini/perplexity/cursor/unknown, `:18-33`) as **file pickers** (`accept=".json,.txt,.md,.csv"` `:113`), Claude-Code detect banner (`:62-86`), preview list + "Import N items" (`:125-155`), done state (`:157-162`), and a text pointer to "Memory → Harvest for 14+ more sources" (`:167-173`).
- `apps/web/src/components/os/overlays/onboarding/types.ts:38-52``ImportStepProps`.
### 3b. Post-onboarding harvest hub (the "Memory → Harvest" surface)
- `apps/web/src/components/os/apps/MemoryApp.tsx`**Harvest tab**`apps/web/src/components/os/apps/memory/HarvestTab.tsx` (685 LOC). The full-featured sibling: connected-sources list with auto-sync toggle/remove (`:505-559`), 15-source selector (`SOURCE_ICONS :35-51`), upload **and** paste modes (`:582-639`), preview with type-count chips (`:642-679`), **live SSE progress bar** (`:420-447`), **resume/discard banner** (`:330-360`), **identity-suggestion nudge** post-commit (`:452-470`), dedup/enrichment summary (`:472-502`), `no_real_embedder` not yet surfaced as a distinct affordance.
- `apps/web/src/components/os/apps/memory/ImportReminderBanner.tsx` — nudge to revisit import.
### 3c. Adapter methods (the contract surface, `apps/web/src/lib/adapter.ts`)
`harvestPreview` (`:1684`), `harvestCommit` (`:1689`), `getHarvestSources` (`:1694`), `scanClaudeCode` (`:1699`), `extractHarvestIdentity` (`:1710`), plus (per frontend inventory) `subscribeHarvestProgress`, `getLatestInterruptedHarvestRun`, `resumeHarvestRun`, `abandonHarvestRun`, `removeHarvestSource`, `toggleHarvestAutoSync`. Connector side: `getConnectors`, `getConnectorHealth`, `connectConnector`, `disconnectConnector`.
### Disposition: **rework** (promote-and-extend, do NOT create-new)
The harvest *engine* and most of the *upload* UX already exist and are wired to the right endpoints. S15 needs to be **reworked** to (a) match the mockup's connect-grid IA, (b) **split commit out into S16 Memory Review** to satisfy the PRD "no import without review/approval" gate, and (c) reconcile the two near-duplicate surfaces (`ImportStep` vs `HarvestTab`) onto one shared component so onboarding and the standalone hub don't drift. This is squarely the locked in-place refactor model — reuse `harvest.ts` substrate, extend the frontend.
---
## 4. Frontend work
**Reuse targets (do not rebuild):** `HarvestTab.tsx` is the canonical, feature-complete harvest UI — its SSE progress, resume banner, identity nudge, and dedup summary should be the shared core. `ImportStep.tsx` is the lighter onboarding shell. The connector tiles in the mockup map to the existing `ConnectorsApp`/`connectors/BrandTile.tsx` patterns.
Concrete components:
1. **Extract a shared `MemorySourcePicker` + `HarvestPreview` + `HarvestProgress`** from `HarvestTab.tsx` (currently a 685-LOC monolith; CLAUDE.md §coding-style favors small files) into `components/os/apps/memory/` so both `HarvestTab` and the onboarding `ImportStep` consume one source of truth.
- Props: `sources: SourceTile[]`, `onPreview(data,source)`, `onConnect(connectorId)`, `preview`, `progress`, `result`, `mode: 'onboarding'|'hub'`.
2. **Rework `ImportStep.tsx`** to the mockup's two-grid layout: **AI Assistants & Coding Tools** (Claude, Claude Code, Cursor, Hermes, Codex, ChatGPT, Gemini, Perplexity, Other) + **Files & Workspace Tools** (Files=`/api/ingest` upload, Notion/Drive/Slack/Gmail = connector "Connect"). Add the right-rail "What we import" + "You're in control" panels (copy already exists in `HarvestTab.tsx:362-369`).
- Distinguish tile *kind*: `upload` (harvest file picker), `scan` (Claude Code local), `connect` (connector OAuth). Hermes/Codex are AI-tool launch/hook surfaces (`LauncherApp`/tool-detect) — for v1 they can be `upload`/"Other" or marked "coming soon" (open question OQ-1).
3. **Move the commit decision to S16 Memory Review.** S15's "Continue" should carry the parsed preview forward; the explicit **approve → `harvestCommit`** happens on S16. Update `OnboardingWizard.handleImportCommit` (`:168-177`) to defer commit, or have S15 stage previews and S16 commit them. (Satisfies PRD:646/1207.)
4. **Surface `no_real_embedder`** as a soft inline affordance ("semantic search/wiki won't update until you add an embedding key") using the `cognifySkippedReason`/`wikiSkippedReason` already in the commit response — currently unsurfaced in both UIs.
5. **Connector consent flow** for Files & Workspace Tools tiles: reuse `adapter.connectConnector` + OAuth redirect (`/api/oauth/:provider/authorize`); show "review permissions" before connect (PRD:1207, Journey 8).
6. **State coverage:** add explicit Loading/Error/Offline/Permission-denied/Approval-required renders per PRD §14.1 (HarvestTab has most; ImportStep is missing loading/offline).
Adapter: **no new methods required for the upload path** — all harvest + connector methods already exist (§3c). New methods needed only if backend adds `/api/harvest/sources/:id/sync` and a connector `/sync` (see §5).
---
## 5. Backend work (PRD §16.5 + adjacent)
Cross-referenced against `docs/backend-map/sections/05c-subsystem-harvest.md`, `03b-api-memory.md`, and the route inventory.
| PRD §16 endpoint | Status | Note / what to EXTEND vs NET-NEW | Substrate |
|---|---|---|---|
| `POST /api/harvest/preview` | **EXISTS** | `routes/harvest.ts``{data,source}``{itemCount,types,preview[]}` (05c §6). No change. | personal `.mind` |
| `POST /api/harvest/commit` | **EXISTS** | `routes/harvest.ts` — full ingest: parse → `FrameStore.createIFrame(gop='harvest',src='import')` → cognify → wiki recompile; SSE heartbeats; resumable run-store (05c §4). No change. | `memory_frames` (personal.mind) + KG + wiki |
| `GET /api/harvest/sources` | **EXISTS** | `routes/harvest.ts``{sources: HarvestSource[]}`. No change. | `harvest_sources` |
| `POST /api/harvest/sources/:id/sync` | **PARTIAL → EXTEND (net-new thin route)** | No per-source `/sync` action exists (route-inventory §16.5). Sources are registered via `POST /api/harvest/sources` and toggled via `PATCH /api/harvest/sources/:source`; actual re-sync runs through `POST /api/harvest/commit`. **Add a thin `POST /api/harvest/sources/:source/sync`** that resolves the source row and calls the existing commit path. **Path-key mismatch to resolve:** PRD uses `:id`; current sources are keyed by `:source` **name** (DELETE/PATCH both use `:source`, harvest.ts). Pick `:source` for consistency with siblings. | `harvest_sources` + commit pipeline |
**Adjacent endpoints S15 actually calls (not in §16.5 but load-bearing — all EXIST):**
- `POST /api/harvest/scan-claude-code` — local `~/.claude` dry-run scan (EXISTS, `harvest.ts`).
- `GET /api/harvest/progress`**SSE** progress (EXISTS).
- `GET /api/harvest/runs/latest-interrupted`, `POST /api/harvest/runs/:id/abandon`, resume via `commit {resumeFromRun}` (all EXIST).
- `POST /api/harvest/extract-identity` — post-commit identity suggestions (EXISTS) — feeds S16/Profile, not S15 proper.
- `POST /api/ingest` — for the "Files" tile (base64 file → text + frames, EXISTS, `ingest.ts`).
- `GET /api/connectors`, `POST /api/connectors/:id/connect`, `GET /api/oauth/:provider/authorize|callback` — for Notion/Drive/Slack/Gmail "Connect" tiles (all EXIST; connectors defined under `packages/agent/src/connectors/*` incl. `notion-connector.ts`, `gdrive-connector.ts`, `slack-connector.ts`, `gmail-connector.ts`). A connector `POST /api/connectors/:id/sync` is **MISSING** (route-inventory §16.9) — net-new if the workspace-tools tiles must pull data immediately, but for S15 a "Connected" state without immediate pull is acceptable for v1.
**`.mind` migration flag:** **None required for S15's import path.** Harvest writes plain `memory_frames` via the existing `createIFrame` signature — no schema change. (The broader Memory Center migration for explicit `confidence`/`provenance`/`kind` per PRD §15.4 — a single additive `metadata TEXT` column on `memory_frames` — is owned by the **Memory Center / S16 Review** cards, not S15. Provenance today is a text prefix `[Harvest:<source>]` in `content`, per 05c §4, which S16 can parse for the "source/confidence" review chips.)
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
S15's wire shapes are harvest-specific and live in `packages/hive-mind-core/src/harvest/types.ts` (`UniversalImportItem`, `HarvestSource`, `HarvestRun`, `ImportSourceType`, `ImportItemType` — 05c §1). The frontend currently re-declares lossy local interfaces (`HarvestSource`/`PreviewResult` in `HarvestTab.tsx:16-33`; `claudeCodeDetected` shape inline). Gaps vs `apps/web/src/lib/types.ts`:
- **No shared `HarvestSource`/`HarvestRun`/`PreviewResult`/`ImportSourceType` in `lib/types.ts`** — each surface hand-rolls them. Promote a canonical `HarvestSource`, `HarvestPreview`, `HarvestCommitResult`, `ImportSourceType` into `lib/types.ts` (mirror the hive-mind-core shapes) so `ImportStep` and `HarvestTab` share one contract.
- PRD §15.2 `Scope` / `MemoryKind` / `Confidence` unions are **not** S15's concern (they belong to S16/Memory Center) — S15 only needs the import-item/source/run types.
- `adapter.harvestPreview`/`harvestCommit` are typed `Promise<any>` (`adapter.ts:1684,1689`) — tighten to the new shared result types (CLAUDE.md / ts-rules forbid `any`).
---
## 7. Dependencies (screens/phases first)
- **Belongs to PRD Phase 2 "Work layer" → Onboarding flow** (PRD:233) / Roadmap **Sprint 5** (PRD:1335-1342). Sequenced after S12 First Launch, S13 Who Are You, S14 Tool Discovery.
- **Tightly coupled to S16 Memory Review** — S15 produces the preview; **S16 owns the approve→commit gate**. The commit-deferral rework (§4.3) cannot land without S16 existing. Plan S15 + S16 as a pair.
- **Feeds S17 Create Workspace** (Journey 18: Memory Review finds projects → pre-fill workspace, PRD:812-817) — downstream, not blocking.
- **Connector tiles depend on the Extend-layer Connector Hub** patterns (Phase 4) for OAuth/consent; for S15 v1 the existing `connectors.ts` + `oauth.ts` routes are sufficient (no Connector Hub UI dependency).
- Shared-component extraction (§4.1) should land **before** reworking `ImportStep` so both surfaces converge rather than diverge further.
---
## 8. Effort: **M**
Backend is near-zero (one thin `/sync` alias; everything else EXISTS). The weight is frontend: extract a shared harvest component from a 685-LOC monolith, rebuild `ImportStep` to the two-grid connect layout, wire connector-OAuth tiles, surface `no_real_embedder`, and re-sequence commit into S16. Not L because no new substrate/migration and the engine is done; not S because it touches two surfaces + the S15/S16 approval split + type promotion.
---
## 9. Open questions
1. **Hermes / Codex / Cursor tiles** (mockup) — these are AI-coding *tools* surfaced via the AI-OS launcher/tool-detect + hooks (`LauncherApp`, `/api/tools/detect`), not harvest export adapters (Cursor falls through `UniversalAdapter`; Hermes/Codex have no harvest adapter, 05c §1 union). For v1: render as `upload`/"Other" file pickers, or as "Connect via hooks" using the AI-OS launcher, or "coming soon"? PRD §12.12 says "connect/import from AI tools" without specifying the mechanism.
2. **S15/S16 commit split** — confirm the intended boundary: does S15 commit-and-S16-reviews-the-result, or S15-stages-previews-and-S16-commits? PRD:646/1207 ("nothing imports without review/approval") argues for the latter; current code commits at S15. (PRD Open Question — not listed in §23 but implied.)
3. **Connector "Connect" without immediate pull** — for Notion/Drive/Slack in S15, is a "Connected" state (creds stored) sufficient, or must data pull happen in-onboarding (requires net-new `POST /api/connectors/:id/sync`, §16.9 MISSING)? Maps to PRD §23 Q4 ("which connectors/MCPs are real in v1 vs seeded/mock").
4. **Two-surface reconciliation** — should the post-onboarding harvest surface stay in `MemoryApp → Harvest tab`, or be promoted to the PRD's Memory Center "Sources" tab (PRD §12.4 tabs include "Sources")? Affects where the shared component lives.
5. **`no_real_embedder` UX** — block import, warn-and-proceed, or prompt for an embedding key inline? (Cognify/wiki silently skip today; semantic recall degrades.)

View File

@@ -0,0 +1,278 @@
# Gap Card — S16 Memory Review
> Screen 16 in the UX-refactor blueprint deck. Onboarding step 5 ("Memory Review — approve
> before importing") AND the standing low-confidence review queue (Journey J08). Execution
> model is LOCKED: in-place incremental refactor of `apps/web` + targeted backend extension.
> Every claim below is grounded in repo source.
---
## 1. Screen & purpose
**Purpose (PRD §12.12 step 5; Blueprint screen 16, line 392-399):** the **trust gate before
memory becomes active**. After the user connects/imports sources (S15 Memory Import), Waggle has
parsed-but-not-committed items. S16 lets the user *review what was found, by category, with source
and confidence, and explicitly approve (or edit/skip) before anything is written to active memory.*
- Blueprint mental model (line 55-57): the **Understand** layer "classify, deduplicate, extract
entities, map relationships and **score confidence**" — "Memory Review shows categories, source
and confidence."
- Two entry points, same surface:
1. **Onboarding** (J01 First-time setup): `… → Memory Import → Memory Review → Workspace Creation`.
2. **Standing low-confidence queue** (J08): `Home alert → Memory Review queue → inspect evidence
→ approve/edit/reject` so "uncertain memory does not silently influence work."
- Mockup (`screen_16_memory_review.png`, directional only): header "Review before importing";
5 category stat tiles **Memories 342 / Decisions 56 / Tasks 32 / Artifacts 41 / Projects 17**;
a category-tabbed table (Memories | Decisions | Tasks | Artifacts | Projects) of rows with
Content / Type / Source / Confidence columns; search + Filters; right rail "Import summary"
(per-category counts), "Top sources" (Claude/ChatGPT/Google Docs/Notion with item counts), and
a "Confidence guide" legend; footer actions **Back / Skip for now / Edit selections /
Approved & import ABS items**.
**PRD acceptance criteria win over pixels (§24).** The load-bearing ACs:
- §12.12: "Nothing imports without explicit review/approval."
- Blueprint S16 acceptance: "Trust gate before memory becomes active."
- J08: "Uncertain memory does not silently influence work."
---
## 2. Required states (PRD / Blueprint)
Blueprint S16 states (line 396-397) + onboarding row (line 474-476):
| State | Meaning |
|---|---|
| **Empty** | No items found / nothing to review (source had no recognizable content). |
| **Preview found** | Parsed items shown, grouped by category, awaiting approval. |
| **Low confidence** | Subset surfaced as uncertain — must be inspectable (evidence) and individually approve/edit/reject (J08). |
| **Source error** | A source failed to parse/connect; offer retry-source. |
| **Approved** | User approved; commit runs (progress) → items become active memory. |
| **Import partial** | Onboarding state: some sources imported, some failed/skipped. |
Required **interactions** (line 394-395, 476): Filter, expand (inspect evidence), **edit selection**,
**approve import**, skip, **back**, **retry source**.
---
## 3. Current state in repo
**Disposition: `rework` (frontend) + `partial` (backend).** A trust-gate preview→commit flow EXISTS
in two places but neither delivers the categorized, per-item-confidence, per-item-selectable review
the screen requires. The existing surfaces are a *thin preview list + commit-all*, not a review gate.
### 3a. Onboarding ImportStep (the closest match to S16)
- `apps/web/src/components/os/overlays/OnboardingWizard.tsx` — step index 3 is "memory-import"
(`STEP_NAMES` `:35`). It calls `adapter.harvestPreview(data, source)` and stores
`result.preview` (`:163-164`), then `adapter.harvestCommit(data, source)` on approve
(`:168-177`), then auto-advances to the template step. **There is no separate "Memory Review"
step** — preview and commit are folded into the single ImportStep.
- `apps/web/src/components/os/overlays/onboarding/ImportStep.tsx` — renders a flat
`importPreview.slice(0,10)` list (`:125-155`) with one "Import N items" button. **No category
tiles, no per-item Type/Source/Confidence columns, no per-row selection, no evidence expand, no
source-error/low-confidence states.** Just "preview found → commit all → Memories imported!".
- `STEP_NAMES`/dots are hardwired to 8 steps (`:35`, dots `:472`). Inserting a dedicated Review step
shifts the step indices (template/persona/api-key/ready are `4/5/6/7`) — a refactor touch-point.
### 3b. MemoryApp HarvestTab (post-onboarding harvest surface)
- `apps/web/src/components/os/apps/memory/HarvestTab.tsx` — the richer harvest UI: connected-source
chips, Claude Code auto-detect, SSE progress, resume banner. Its `PreviewResult` interface
(`:28-33`) is `{ source, itemCount, types: Record<string,number>, preview: {id,title,type}[] }`.
`types` gives a **per-`type` count map** (the seed for category tiles) but `preview` is capped at
10 items, carries **no confidence**, and commit is **all-or-nothing** (no selection).
- Reached today via `MemoryApp.tsx:272` (`<HarvestTab />`, the "Harvest" tab).
### 3c. Backend preview/commit contract (what the UI can rely on)
- `packages/server/src/local/routes/harvest.ts`:
- `POST /api/harvest/preview` (`:221-236`) returns `{ source, itemCount, types: countByField(items,'type'),
preview: items.slice(0,10).map(i => ({id,title,type,source})) }`. **No confidence field; first-10 only.**
- `POST /api/harvest/commit` (`:243`+) parses with the adapter and writes **every** item as a raw
frame: `FrameStore.createIFrame('harvest', label+content, 'normal', 'import', ts)` (`:403-409`).
**No selection input, no confidence, no per-item categorization beyond the adapter's `type`.**
- `adapter.harvestPreview/harvestCommit` (`apps/web/src/lib/adapter.ts:1684-1692`) — both typed `any`,
signature `(data, source)`. No `selectedIds`/`approve` params exist.
- **The 4-pass `HarvestPipeline` (classify→extract→synthesize w/ `confidence` + dedup/contradiction
flags) EXISTS but is NOT called by the commit route** (backend-map `05c` §3, §4, line 7, 110,
117). `DistilledKnowledge.provenance.confidence` is produced only inside that unused pipeline.
So **the confidence + categorization data the mockup shows has no production producer today** —
this is the central backend gap (see §5).
**Net:** the *trust-gate intent* (preview before commit, nothing imports without consent) is partly
honored; the *categorized, confidence-scored, per-item-approvable review surface* the screen
specifies is **not built**.
---
## 4. Frontend work
Create a real Review surface usable from BOTH onboarding and the standing J08 queue.
### Components to create
- **`overlays/onboarding/MemoryReviewStep.tsx`** (NEW) — onboarding step 5. Replaces the inline
preview block currently embedded in `ImportStep.tsx`. Renders the full review layout.
- **`apps/web/src/components/os/apps/memory/MemoryReview.tsx`** (NEW, shared) — the reusable review
panel (category tiles + tabbed table + right rail + footer actions). Both the onboarding step and
a Memory-app "Review" surface mount this so there is ONE review implementation.
- **`components/os/apps/memory/review/`** sub-parts (small files per CLAUDE.md file-org rule):
`CategoryStatTiles.tsx`, `ReviewTable.tsx` (Content/Type/Source/Confidence cols + row checkbox +
expand-for-evidence), `ImportSummaryRail.tsx` (per-category counts + Top sources + Confidence
guide legend), `ReviewFooter.tsx` (Back / Skip / Edit selections / Approve & import N).
### Reuse targets (do not rebuild)
- `ContextMenu.tsx`, `components/ui/{table,tabs,checkbox,badge,input,scroll-area,skeleton}.tsx`
(shadcn set per frontend inventory §e) for the table/tabs/empty/loading states.
- `ContextRail.tsx` (`overlays/ContextRail.tsx`, exports `ContextRailTarget`) as the evidence/inspect
pattern for "expand → inspect evidence" (J08) rather than inventing a new evidence panel.
- HarvestTab's SSE progress wiring (`/api/harvest/progress`, phases saving/cognifying/wiki-compile)
for the "Approved → importing" state — lift the subscription into the shared panel.
- Color semantics from Blueprint design system (line 485): green=healthy/high-confidence,
orange=attention/low-confidence, red=risk/error — map to existing Hive DS semantic tokens
(no `hive-950` literals; tokens in `waggle-theme.css`).
### Props / state (shared `MemoryReview`)
```ts
interface ReviewItem {
id: string; title: string; content: string;
kind: MemoryKind; // mapped from ImportItemType (see §6)
source: ImportSourceType; // for the Source column + Top-sources rail
confidence?: number; // 0-100; undefined until backend produces it (see §5)
selected: boolean; // default true for high-confidence, false/uncertain otherwise
evidence?: string[]; // for the expand/inspect row
}
interface MemoryReviewProps {
mode: 'onboarding' | 'queue';
items: ReviewItem[]; loading: boolean; error?: string;
onApprove: (selectedIds: string[]) => Promise<void>; // → harvestCommit w/ selection
onSkip: () => void; onBack?: () => void;
onRetrySource?: (source: ImportSourceType) => void;
}
```
- Local state: per-category selection, search query, active category tab, expanded-row id.
- `selected` semantics enforce the AC: low-confidence rows start **unselected** so nothing uncertain
imports silently (J08).
### Adapter / hook changes
- Extend `adapter.harvestCommit` to accept an optional `{ selectedIds }` (or `approvedIds`) param;
type the preview response properly (drop `any`): `HarvestPreview { source; itemCount;
types: Record<MemoryKind, number>; items: ReviewItem[] }` — note the preview must return **all**
items (or paginate), not `slice(0,10)`, for a real review (see §5).
- NEW hook `useMemoryReview(source, data)` (or fold into a `useHarvest` hook) owning preview fetch +
selection state + commit; consumed by both mount points.
- Onboarding `STEP_NAMES`/dots/`progressPct` in `OnboardingWizard.tsx` must add the Review step
(8→9 steps OR split import/review and re-key 4-7). Update `displayStep`, dot array (`:472`),
Back-button range (`:444`), and the `goToStep(4)` auto-advances in `handleImportCommit`/
`handleClaudeCodeHarvest` to land on Review, not Template.
---
## 5. Backend work
### PRD §16.5 Harvest endpoints
| PRD §16 endpoint | Status | Extend vs net-new / substrate |
|---|---|---|
| `POST /api/harvest/preview` | **EXISTS (extend)** | `harvest.ts:221`. Today returns `types` + first-10 `preview` with **no confidence** and **no full item list**. EXTEND to (a) return **all** items (or `?limit/offset` paging) and (b) attach a per-item `confidence` + normalized `kind`. Substrate: `UniversalImportItem` (already carries `type`); confidence must come from a classifier (see below). No `.mind` migration for preview (in-memory parse). |
| `POST /api/harvest/commit` | **EXISTS (extend)** | `harvest.ts:243`. Today commits **all** parsed items. EXTEND body to accept `{ selectedIds?: string[] }`; when present, filter `items` before the `createIFrame` loop (`:382-409`). Honors the trust-gate AC ("nothing imports without approval"). Substrate: writes to `memory_frames` in personal.mind via `FrameStore.createIFrame` — unchanged shape, just a filtered set. |
| `GET /api/harvest/sources` | **EXISTS** | `harvest.ts` (`HarvestSourceStore`). Powers the right-rail "Top sources" + connected-source chips. |
| `POST /api/harvest/sources/:id/sync` | **PARTIAL** | No per-source `/sync` route (backend-routes.md §16.5; grep-confirmed absent). Sync today = `POST /api/harvest/commit`. Add a thin `/sources/:source/sync` that resolves the registered source + re-runs commit. NOTE PRD path uses `:id`; current sources are keyed by `:source` **name** (`DELETE/PATCH /api/harvest/sources/:source`) — keep the name key or alias. Net-new thin route, no new substrate. |
### Confidence + categorization (the real backend gap — implied addition to §16.5)
The mockup's **Confidence column / Confidence guide / low-confidence queue (J08)** and the **5
category tiles** have **no production producer**:
- `memory_frames` has **NO `confidence` column** (substrate-types §c, line 137: confidence exists
only on `knowledge_relations.confidence`). Frames are written `importance='normal', source='import'`.
- The classifier that yields `{categories, value, confidence}` is the unused `HarvestPipeline`
(Pass 1 Classify Haiku + Pass 3 Synthesize → `DistilledKnowledge.provenance.confidence`),
bypassed by the commit route (05c line 7).
- Category counts beyond the adapter's raw `type` (8 `ImportItemType` values) require a
classify step; the adapter `type` alone gives a coarse grouping but not the mockup's
Memories/Decisions/Tasks/Artifacts/Projects taxonomy 1:1.
**Decision needed (Open Q):** to deliver real confidence, the refactor must either
(a) wire `HarvestPipeline`'s classify/synthesize into a **preview-time** scoring pass (cost: Haiku/
Sonnet LLM calls per import — slow + paid; gated on a real embedder/key), or
(b) ship a **cheap heuristic confidence** at preview time (source-trust × adapter-type × dedup
signal) and reserve LLM scoring for an opt-in deep pass. Given onboarding latency budgets, (b) is
the pragmatic v1; (a) for the standing J08 queue.
**`.mind` migration flag:** if confidence becomes a queryable/filterable frame property (Filters in
the mockup; J08 "low-confidence review queue" off Home), add a nullable `metadata TEXT DEFAULT '{}'`
column to `memory_frames` (substrate-types §c line 156-164: the migration runner already does
idempotent `ADD COLUMN` on `memory_frames`; pattern at `mind/db.ts:116-124`) and store
`{kind, confidence, sourceId, status}` there, OR promote `confidence REAL` to a real column if it's
a primary filter axis. **This is the only potential schema migration on this screen.** Preview-only
confidence (not persisted) needs no migration.
### Standing low-confidence queue (J08) backend
- "Home alert → Memory Review queue" needs a way to *list already-imported low-confidence frames*.
No route exists. Closest: `GET /api/memory/frames` (`memory.ts:188`) + a confidence/status filter.
This depends on confidence being persisted (above). Implied addition to §16.4 Memory, not §16.5.
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`MemoryKind`** (PRD §15.2): MISSING in `apps/web/src/lib/types.ts` (substrate-types §e). FE
`MemoryFrame.type` exists but mismatches PRD (`event`/`insight` vs PRD `preference`/`strategy`/
`learning`/`goal`). The review needs a `MemoryKind` union AND a **mapping from the harvest
`ImportItemType`** (8 values: conversation/memory/instruction/preference/artifact/rule/decision/
document — 05c §1) to the review categories (Memories/Decisions/Tasks/Artifacts/Projects). That
map is a new pure helper (`lib/harvest-kind-map.ts`).
- **`Confidence`** (PRD §15.2, 0-100): MISSING. Add the type; add `confidence?: number` to the
review item shape (and later `MemoryFrame` once persisted).
- **`ReviewItem` / `HarvestPreview`** response types: NEW (replace the `any` on
`adapter.harvestPreview/harvestCommit`). Mirror the server contract; co-locate or add to
`lib/types.ts`.
- `ImportSourceType` already documented in 05c (24-value union) — import/re-declare for the Source
column + Top-sources rail.
---
## 7. Dependencies (screens / phases first)
- **Upstream (must precede):** S15 Memory Import — supplies the `{data, source}` (or connected
source) that S16 reviews. The `harvestPreview` call + source selection live there.
- **Shares substrate with:** S-Memory Center (§12.4) — the persisted-frame edit/archive/merge +
the J08 low-confidence queue read from the same `memory_frames` + confidence field. Build the
confidence-on-frames decision once and reuse.
- **Feeds:** S-Home Cockpit (§12.1) — the "low-confidence review" Home alert (J08) deep-links into
this surface; needs `/api/home/*` (separate gap) + the persisted-confidence read.
- **Onboarding flow (S-Onboarding):** inserting the Review step re-keys `OnboardingWizard` step
indices — coordinate with whoever owns the onboarding-flow gap card so step numbering is changed
once.
- **Phase hint:** core trust-gate (preview-with-selection + commit-selected + cheap heuristic
confidence) is an **early phase** (it gates onboarding J01, a day-0 flow). The persisted-confidence
column + LLM classify pass + standing J08 queue are a **later phase** (depend on the Memory Center
confidence decision).
---
## 8. Effort
**L.** The reusable review panel + onboarding step re-keying + adapter/preview type-tightening +
commit selection are M on their own; the confidence/categorization producer (heuristic v1 now,
optional LLM-classify + possible `memory_frames` metadata migration later) plus the J08 standing
queue push it to **L**. It is NOT XL because no new data store is required — everything writes to the
existing `memory_frames` substrate via `FrameStore`, and the preview/commit routes already exist to
extend rather than build net-new.
---
## 9. Open questions
1. **Confidence source (blocking design):** heuristic-at-preview (source-trust × type × dedup) vs
wiring the existing `HarvestPipeline` classify/synthesize for real LLM confidence? Latency/cost vs
fidelity. Recommendation: heuristic for onboarding v1, LLM for the J08 queue. (§5)
2. **Persist confidence?** If "Filters" + J08 queue need to query confidence on already-imported
frames, we need the `memory_frames` metadata/`confidence` migration. If review is preview-only
(pre-commit), no migration. Which scope is v1? (§5)
3. **Category taxonomy mapping:** mockup shows Memories/Decisions/Tasks/Artifacts/Projects, but the
adapter emits 8 `ImportItemType`s and PRD §15.2 `MemoryKind` is a different set. Which is canonical,
and what is the exact `ImportItemType → category` map (esp. "Projects" — Claude `projects[].docs`
land as `artifact` items per 05c §2)? (§6)
4. **Per-item edit:** the footer "Edit selections" + interaction "edit selection" — is this just
include/exclude (checkbox), or inline content/kind editing pre-commit? Pre-commit edit has no
route today (frames don't exist yet); would need to mutate the in-memory item set before commit.
5. **Onboarding step count:** split Import↔Review into two steps (8→9) or keep one screen with a
review sub-state? Affects `STEP_NAMES`, dots, progress, and the "Step N" label already softened to
avoid a fixed total (`OnboardingWizard.tsx:456-467`).
6. **Source-error / retry-source state:** S15 owns connect/parse; does S16 only *display* the partial
state and offer "retry source" (re-invoking S15's connect), or own retry itself?

View File

@@ -0,0 +1,223 @@
# Gap Card — S17 · Workspace Creation
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extension.
> Mockup is **directional only** (PRD §24); PRD acceptance criteria win over pixels.
> Every claim below cites a real file. PRD = `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`.
---
## 1. Screen & purpose
**Screen 17 — Workspace Creation.** "Create a workspace with suggested capabilities" — the screen that
turns onboarding (or a manual "+ New Workspace") into the user's **first useful context**.
- PRD §12.12 step 6 ("Workspace Creation — create first useful context") + Journeys 1/2 (PRD lines 678,
685: "Creates first workspace" / "Creates workspace manually" → lands in Home Cockpit).
- Blueprint screen-table row 17 (`_blueprint_extracted.txt:402-409`): Purpose "Create a workspace with
suggested capabilities"; Interactions "Enter name/type, add suggestions, review, create"; States
"Empty; recommended; validation error; created"; Acceptance "Workspace becomes first useful context
after onboarding."
- Mockup `screen_17_create_workspace.png` (directional): a **4-step wizard** (Basic info → Preferences →
Review & create) with a **left form column** (Description, Workspace type = Project/Client/Research/
Personal cards), a **center "Suggested capabilities" column** (Suggested Skills / Suggested Agents /
Suggested Connectors / Suggested MCPs, each with "Add all"), and a **right "Workspace summary" card**
(name, type, description, "Includes: Skills/Agents/Connectors/MCPs" counts). Footer: Cancel /
"Next: Preferences →".
**Net:** the mockup reframes the existing single-pane modal as a **stepped wizard whose center of gravity
is recommended capabilities + a live summary**, driven by workspace *type* and persona — not just a name +
template picker.
---
## 2. Required states (PRD / Blueprint)
From the blueprint state list + PRD acceptance criteria:
1. **Empty** — fresh form, no name/type, nothing suggested yet.
2. **Recommended** — once type/persona/template chosen, Suggested Skills/Agents/Connectors/MCPs populate
(silent default ordering, per `feedback_silent_recommendations_dont_ask.md` — recommend, don't interrogate).
3. **Validation error** — name required (PRD acceptance: validation surfaced inline); tier workspace-limit
reached (FREE = 5; existing 403 path); local storage-path missing.
4. **Created** — workspace persisted, becomes active, transitions to Home Cockpit (S?) / Workspace Desktop.
5. (Onboarding-embedded variant) — same flow but rendered as **step 6 of the wizard**, no modal chrome,
handing off to step 7 Home Cockpit (PRD §12.12).
PRD §12.12 acceptance that bears on this screen: "Connectors/MCPs/skills are **recommended from user
selections**" and "Onboarding asks user questions, not infrastructure questions."
---
## 3. Current state in repo
**Disposition: `rework`** (keep + heavily extend the existing component; do not create-new — the modal,
its template/persona/storage/agent-group machinery, and the live folder picker are all reusable substrate).
| File | What it does today |
|---|---|
| `apps/web/src/components/os/overlays/CreateWorkspaceDialog.tsx` (1129 LOC) | The live create-workspace modal. Single scrolling pane (NOT stepped). Fields: Template picker (category filter + search + 15 built-ins + custom CRUD via `TemplateCreatorModal`), Workspace **Name**, **Group** (`STANDARD_GROUPS`), **Storage Type** (virtual/local/team) + path + `FolderPickerModal` (live `adapter.browseLocal`/`browseLocalMkdir`), **Agent** (single persona grid from `PERSONAS` OR agent-group picker), **Share with team** toggle. Calls `onCreate({ name, group, persona?, agentGroupId?, shared?, storageType?, storagePath?, templateId? })`. |
| `apps/web/src/components/os/overlays/WorkspaceSwitcher.tsx` | Quick-switch list only (filters E2E/test names). **Not a creation surface** — adjacent, unchanged by S17. |
| `apps/web/src/hooks/useWorkspaces.ts:27-51` | `createWorkspace(data)``adapter.createWorkspace(data)`, optimistic add + select, local fallback on error. |
| `apps/web/src/lib/skill-recommendations.ts:103` | `recommendSkills(personaId)` → persona→skill-chip map w/ universal fallback. **Already the seed for "Suggested Skills".** |
| `apps/web/src/components/os/overlays/OnboardingWizard.tsx:278,314` | Onboarding's own inline `adapter.createWorkspace(...)` call (the embedded variant) — bypasses this dialog today. |
**Gaps vs mockup/PRD in the current component:**
- **No stepped wizard** — it's one scrolling modal, not Basic info → Preferences → Review.
- **No "Workspace type"** concept (Project/Client/Research/Personal) — only `Group` (free-string chips) +
`templateId`. (`WorkspaceType` is a missing schema field — see §5/§6.)
- **No Suggested Agents / Suggested Connectors / Suggested MCPs panels.** Suggested *skills* logic exists
(`recommendSkills`) but is **not wired into this dialog**. Connectors are fetched but only used for the
template editor's chip list, not surfaced as workspace-scoped recommendations.
- **No "Workspace summary" / Includes-counts card** (the right rail in the mockup).
- **No `description` field.**
**Two real FE bugs to fix in passing (surgical, in-scope):**
1. `useWorkspaces.createWorkspace` typed signature (`useWorkspaces.ts:27`) **omits `storageType`,
`storagePath`, `agentGroupId`** even though the dialog passes them — they're silently dropped at the
type boundary (the object still flows through to `adapter.createWorkspace`, but the local-fallback path
`:35-46` and the type contract lose them). Widen the param type.
2. `CreateWorkspaceDialog.onCreate` payload uses `persona`/`shared` (FE display names) while the backend
POST expects `personaId` and has **no `shared`/`group→team` mapping**`shared` is accepted by the
dialog but never reaches `POST /api/workspaces` (which has no `shared` field; team linkage is `teamId`).
Reconcile during rework.
---
## 4. Frontend work
**Rework `CreateWorkspaceDialog.tsx` into a 3-step wizard** (keep the file; restructure internals). Prefer
extracting step bodies into small co-located components to respect the 800-LOC ceiling (current file is
already 1129 LOC — this rework should *reduce* it by extracting).
Components to create (new, under `overlays/workspace-create/`):
- `WorkspaceTypePicker.tsx` — Project/Client/Research/Personal cards (drives type + default suggestions).
Maps to new `WorkspaceType` union (§6).
- `SuggestedCapabilities.tsx` — 4 sections (Skills/Agents/Connectors/MCPs) each with per-item add +
"Add all". Reuse `recommendSkills(personaId)` for Skills; derive Agents from `PERSONAS` +
`adapter.getAgentGroups()`; Connectors from `adapter.getConnectors()` (already fetched here); MCPs from
a new MCP list source (§5). Silent-default ordering (no "which tools?" prompt).
- `WorkspaceSummaryCard.tsx` — live right-rail: name, type, description, "Includes" counts (skills/agents/
connectors/mcps selected).
- `WizardStepper.tsx` — Basic info / Preferences / Review header (or reuse a stepper primitive if added).
Reuse targets (do NOT rebuild): `TemplateCreatorModal`, `FolderPickerModal`, `ChipPicker`, `Tooltip`,
`STORAGE_OPTIONS`, the persona grid, `STANDARD_GROUPS`, `LockedFeature` (tier gate), `useFeatureGate`
(`multi-workspace`), `useWorkspaces`.
State/props additions:
- Local state: `step` (0|1|2), `description`, `type` (`WorkspaceType`), `selectedSkills[]`,
`selectedAgentIds[]`, `selectedConnectorIds[]`, `selectedMcpIds[]`.
- Extend `onCreate(...)` payload to include `description, type, skills, agentIds, connectorIds, mcpIds`.
- Wire `recommendSkills` + a new `recommendCapabilities(type, personaId)` helper in
`apps/web/src/lib/` (extends existing skill-recommendations pattern) so suggestions react to
type/persona without a backend call where possible.
Adapter/hook:
- Widen `useWorkspaces.createWorkspace` param type (fix §3 bug 1) to carry the new fields + storage fields.
- `adapter.createWorkspace` already POSTs the body through (`lib/adapter.ts` `createWorkspace` → `POST
/api/workspaces`); new body fields ride along once the route accepts them (§5).
- For Suggested MCPs, add `adapter.getMcps()` if a `/api/mcps` (or capabilities-derived) source is built (§5).
Onboarding parity: route `OnboardingWizard` step 6 through the **same** step bodies (render without modal
chrome) instead of its bespoke `adapter.createWorkspace` calls (`OnboardingWizard.tsx:278,314`) — single
source of truth for the create flow.
---
## 5. Backend work (PRD §16.2 + suggestion sources)
§16.2 endpoints used by/adjacent to this screen:
| PRD endpoint | Status | Extend vs net-new / substrate |
|---|---|---|
| `POST /api/workspaces` | **EXISTS** | `workspaces.ts:116-135`. **EXTEND** the Body type + `WorkspaceManager.create` to accept `description`, `type` (`WorkspaceType`), `skills[]`, `agentIds[]`, `connectorIds[]`, `mcpIds[]`, `status`. These are additive fields on `WorkspaceConfig`/`CreateWorkspaceOptions` (`workspace-manager.ts:5-95`). Substrate: `workspace.json` (file, not SQLite). |
| `GET /api/workspaces` | **EXISTS** | `workspaces.ts:101`. Unchanged. |
| `GET /api/workspaces/:id` | **EXISTS** | `workspaces.ts`. Unchanged. |
| `PATCH /api/workspaces/:id` | **EXISTS** | Used post-create for edits; should accept the new fields too. |
| `GET /api/workspace-templates` | **EXISTS** (not in §16 but central here) | `workspace-templates.ts:33` (15 built-ins). Templates already carry `persona`, `connectors`, `suggestedCommands`, `starterMemory` — the seed for Suggested panels. May **EXTEND** the `WorkspaceTemplate` shape to add `skills[]`/`mcps[]`/`type` so a chosen template can pre-populate all 4 suggestion lists. |
Suggested-capabilities data sources (mockup center column):
- **Skills** — no new endpoint; FE `recommendSkills` + starter-pack catalog (`GET
/api/skills/starter-pack/catalog`, EXISTS). On create, the route already auto-installs starter skills
(`workspaces.ts:198-211`) and seeds template starter-memory frames (`:223-249`).
- **Agents** — `GET /api/personas` (EXISTS) + `GET /api/agent-groups` (EXISTS). No new endpoint.
- **Connectors** — `GET /api/connectors` (EXISTS). No new endpoint.
- **MCPs** — **PARTIAL/MISSING**: per backend-routes inventory §16.9, there is **no `GET /api/mcps`**.
MCP servers surface inside `GET /api/capabilities/status` (`mcpServers[]`) and the static catalog
`@waggle/shared` `mcp-catalog.ts`. **EXTEND** rather than net-new store: add a thin `GET /api/mcps`
read route that composes `capabilities/status` + `mcp-catalog`, OR have the FE read the existing catalog
+ capabilities/status directly for the Suggested-MCPs panel (lower-risk for S17; defer the dedicated
route to S20/MCP screen).
**No new data store needed.** Persisting `agentIds`/`connectorIds`/`mcpIds` on the workspace = additive
optional fields on the `workspace.json` config (substrate-types §a confirms: **no DB migration** — these
live in JSON, not SQLite). `WorkspaceManager.create/update` (`workspace-manager.ts:124-244`) writes the
JSON; `update()` should also stamp `updatedAt` (currently missing, substrate-types §a) — fold that in here
since we're extending the write path anyway.
**.mind migration:** **None required for S17.** (The memory-side `metadata`/`confidence` migration in
substrate-types §c belongs to the Memory screen, not workspace creation.)
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`WorkspaceType`** (PRD §15.2/§15.3) — **MISSING** everywhere. New literal union; mockup implies
`'project' | 'client' | 'research' | 'personal'` (confirm exact set — see Open Questions). Add to FE
`apps/web/src/lib/types.ts` AND backend `WorkspaceConfig` (`workspace-manager.ts`). Keep the existing
free-string `group` (don't drop — substrate-types §a "keep, do not drop").
- **`WorkspaceConfigV2` deltas** (PRD §15.3, lines 961-985) touched by this screen: add `description`,
`type`, `status` (`'active'|'paused'|'archived'`, default `'active'`), `agentIds[]`, `connectorIds[]`,
`mcpIds[]`, `updatedAt`. (`storageType`/`storagePath`/`teamId`/`teamRole`/`riskLevel`/`created` already
present.) Per substrate-types §a these are **pure additive optional fields, no DB migration**.
- FE `Workspace` (`lib/types.ts:22-40`) is a lossy projection (`persona` string vs config `personaId`;
derived `memoryCount`/`lastActive`/etc.). Add the same `type`/`description`/`status` + capability-id
arrays; reconcile `persona`→`personaId` mapping at the adapter boundary (don't break the switcher,
which reads `ws.persona`).
---
## 7. Dependencies (screens / phases first)
- **Schema additions are foundational** — `WorkspaceType` + the V2 config fields are shared by S?? Home
Cockpit, S?? Workspace Desktop (header shows type/status, substrate-types §b), and the Agent/Artifact
screens. Land the type + `POST/PATCH /api/workspaces` extension as a **shared early phase** before the
S17 UI rework so downstream screens consume the same fields.
- **Suggested capabilities** depends on the Skills (recommendSkills, exists), Personas/Agent-groups
(exist), Connectors (exists) sources — all available now. Only **Suggested MCPs** has a missing/partial
backend; gate that panel behind the MCP source decision (don't block S17 on the dedicated `/api/mcps`).
- **Onboarding (S?? §12.12)** consumes this flow as step 6 — unify after the standalone modal rework so
both paths share one implementation.
- PRD roadmap places this in **Sprint 5 — Onboarding and workspace creation** (PRD line 1335).
---
## 8. Effort
**M.** The modal, template/persona/storage/agent-group machinery, folder picker, and skill-recommendation
helper all exist — the work is restructuring into a 3-step wizard, adding 3-4 small components (type picker,
suggested-capabilities, summary card), wiring existing suggestion sources, and additive (no-migration)
backend field extension on the workspace config. Pushed toward the high end of M by the Suggested-MCPs
source ambiguity, the onboarding-parity unification, and the two FE bugs to reconcile; not L because there
is no new data store and no `.mind` migration.
---
## 9. Open questions
1. **Exact `WorkspaceType` values.** Mockup shows Project/Client/Research/Personal. PRD §15.2 names
`WorkspaceType` but does not enumerate. Confirm the canonical set (and whether `type` replaces or
coexists with the existing free-string `group`/`STANDARD_GROUPS`). Recommendation: coexist — `type` is
the new structured axis, `group` stays as the user's organizational label.
2. **Suggested MCPs source for S17.** Build the thin `GET /api/mcps` now, or read `capabilities/status` +
`mcp-catalog` directly from the FE and defer the dedicated route to the MCP screen? (Lower-risk =
defer.)
3. **`shared`/team handoff.** The dialog has a "Share with team" toggle that currently never reaches the
backend (no `shared` field on `POST /api/workspaces`). For S17, does "share" set `teamId`/`teamRole`
(requires a team to exist + TEAMS tier), or is it deferred to the Team screen? Today it's a dead toggle.
4. **Wizard vs modal in onboarding.** Confirm the embedded onboarding variant should render the *same*
step bodies (preferred, single source of truth) vs keeping `OnboardingWizard`'s bespoke create call.
5. **Does S17 install/connect on create, or only stage selections?** PRD says recommend; mockup "Add all"
implies staging. Confirm that selected connectors/MCPs are *recorded as workspace intent* (ids on the
config) vs *actually installed/connected* at create time (the latter pulls in install-audit + connector
OAuth flows — much larger scope; recommend record-only for S17).

View File

@@ -0,0 +1,205 @@
# Gap Card — S18 Agent Builder
> UX-refactor planning artifact. Execution model: **in-place incremental refactor** of
> `apps/web` + targeted backend extensions. Every claim is grounded in a real file.
> PRD = `docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`
> (§12.9 + §16.7). Mockup (directional only) =
> `Waggle_OS_Handoff_Assets/screens_18_21_builders_and_marketplace.png`.
---
## 1. Screen & purpose
Create an **agent as an explicit work actor** through a guided stepper, with no hidden
memory/tool access. PRD §12.9: "Manage agents as explicit work actors… User can explain
what an agent can see and do before enabling it." Blueprint Screen 18 (`_blueprint_extracted.txt:410-416`):
"Create an agent with goal, model, autonomy, memory, skills and permissions. Configure,
test, review, create. States: Draft; validation; approval needed; created. Acceptance:
No agent has hidden memory/tool access."
The Agent Builder is the create-flow companion to the **Agent Center** (Screen 9, a
separate gap card). S18 is one of four builder/marketplace screens grouped in the same
mockup (18 Agent Builder, 19 Skill Builder, 20 Automation Builder, 21 Marketplace).
PRD §16.7 names the agent CRUD/run/traces API surface this builder writes to.
---
## 2. Required states (PRD / Blueprint)
**Builder steps (PRD §12.9, line 589):** `Basic Info → Capabilities → Memory & Tools → Permissions → Review & Create` (5-step stepper).
**Agent fields the builder must collect (PRD §12.9 line 590 + §15.5 lines 1015-1036):**
name, goal, description, persona/avatar, model, autonomyLevel (`manual|guided|medium|high`,
PRD §15.2), type (`personal|workspace|team|autonomous`), memoryScopes, skillIds,
connectorIds, mcpIds, permissions, status, workspaceIds, createdBy, lastRunAt, successRate.
**Per-screen states (blueprint `:413-414` + `:461-463`):**
- `Draft` — partially-filled builder, save without activating.
- `Validation` — required-field gating (goal, model, memory scope, skills/tools/MCPs, autonomy per blueprint `:134`).
- `Approval needed` — elevated permission selections require an approval prompt before create (PRD §12.9 "all elevated access is reviewed"; §17.3 "elevated actions require human approval").
- `Created` — agent appears in Agent Center + target workspace (Journey 10, PRD lines 746-753).
- Plus global states (PRD §14.1): Loading, Error, Offline/local-only, Permission denied.
**Hard acceptance gate (blueprint `:415-416`, `:134`):** "No agent has hidden memory/tool access" and "Agent Builder must require model, goal, memory scope, skills/tools/MCPs and autonomy level." The Review step must render the full effective permission/memory/tool surface before the user confirms.
**Test affordance:** blueprint `:413` "Configure, **test**, review, create" — a dry-run before create (analogous to Skill Builder's test). PRD §12.9 does not list test as a functional requirement; treat as optional (open question §9).
---
## 3. Current state in repo — disposition: **create-new** (builder) + **partial** (substrate reuse)
There is **no Agent Builder and no first-class Agent entity** today. The closest existing
surface is a **persona/agent-group** model, which is a strict subset of the PRD Agent.
**Existing frontend (the seed):**
- `apps/web/src/components/os/overlays/SpawnAgentDialog.tsx` — a 2-step dialog (`config → confirm`, `:29`) that spawns a **transient sub-agent** (task + persona + model + parent workspace) via `adapter.spawnAgent``POST /api/fleet/spawn`. It collects only `task/persona/model/workspaceMode` (`:37-44`); it has model-fetch with 3-tier fallback (`:50-99`) and a cost-estimate review pane (`:388-421`). It does **not** persist an agent definition — it fires a one-shot fleet job. This is the seed named in the task, but it is a *spawn* dialog, not a *builder*.
- `apps/web/src/components/os/apps/AgentsApp.tsx` — titled **"Personas"** (`:175`), two tabs (`agents`/`groups`, `:16`). Lists personas via `adapter.getPersonas()` (`:36`) and agent-groups via `adapter.getAgentGroups()` (`:38`). "New Persona" → `CreateAgentForm`.
- `apps/web/src/components/os/apps/agents/CreateAgentForm.tsx` — a **single-page** form (not a stepper) collecting `name/description/icon/systemPrompt/tools[]` (`:9`), with an "Generate with AI" box (`:58-81`) → `adapter.generatePersona`. This is the closest existing "create-an-agent" UI, but it maps to a **persona** (no goal/autonomy/model/memoryScope/connectors/mcps/permissions).
- `apps/web/src/components/os/apps/agents/AgentDetail.tsx` — read view showing icon/name/description/tools/commands/affinity (`:49-103`). No goal/model/autonomy/permissions/successRate/status.
**Existing backend (the substrate to reuse, not rebuild):**
- `packages/server/src/local/routes/personas.ts``GET/POST/PATCH/DELETE /api/personas` + `POST /api/personas/generate`. POST persists a custom `AgentPersona` to disk (`saveCustomPersona`, `:57`); fields are `id/name/description/icon/systemPrompt/modelPreference/tools/workspaceAffinity/suggestedCommands/defaultWorkflow` (`:45-56`). **No** goal/autonomy/memoryScopes/connectorIds/mcpIds/permissions/status. PRO-tier gated (`:32`).
- `AgentPersona` interface (`packages/agent/src/personas.ts`; full field list in `docs/backend-map/sections/05a-subsystem-agent-runtime.md:198-218`) already carries `suggestedSkills?/suggestedConnectors?/suggestedMcpServers?/disallowedTools?/isReadOnly?` — useful seed columns for the richer Agent.
- `packages/server/src/local/routes/agent-groups.ts``GET/POST/PATCH/DELETE /api/agent-groups` + `POST /api/agent-groups/:id/run` (the run is a **placeholder stub**, `:105-127` — returns a queued jobId, does not execute). Groups persist to `{dataDir}/agent-groups.json` (`:29`). This is the closest "agent persistence on disk via JSON file" precedent.
- `packages/server/src/local/routes/fleet.ts``POST /api/fleet/spawn` is the **only real agent-execution path** (`:66-257`): creates a session, emits Waggle signals, and **fire-and-forgets `runAgentLoop`** in the background (`:142-245`). `POST /api/fleet/:workspaceId/pause|resume|kill` (`:260-290`). This is what `/api/agents/:id/run` and `/pause` must wire onto.
- `execution_traces` table (`packages/hive-mind-core/src/mind/schema.ts:199`) is written from `chat.ts` and `evolution.ts` (grep-confirmed) but has **no HTTP read route**`/api/agents/:id/traces` is net-new over this store.
**Why create-new, not rework:** the PRD Agent (§15.5) is a **superset** of persona — it
adds goal, autonomy, type, memoryScopes, connectorIds, mcpIds, permissions, status,
workspaceIds, lastRunAt, successRate. A persona is the *behavioral template*; a PRD Agent
is an *instantiated, scoped, governed actor*. Bending `CreateAgentForm`/persona-POST to
carry all of that would corrupt the persona contract (shared by the runtime prompt
composer). The clean model: **net-new Agent entity + 5-step builder**, with a persona
selected *as one field* of the agent. Heavy reuse of persona catalog, model picker,
tool catalog, fleet-spawn, and execution-traces underneath.
---
## 4. Frontend work
**Create new — `apps/web/src/components/os/overlays/AgentBuilder.tsx`** (or
`components/os/apps/agents/AgentBuilder.tsx` if hosted inside the Agents app shell).
A 5-step stepper matching PRD §12.9:
1. **Basic Info** — name, goal (required), description, persona/avatar picker, type (`AgentType`).
2. **Capabilities** — model (required), autonomyLevel (required), defaultWorkflow.
3. **Memory & Tools** — memoryScopes (required), skillIds, tools[] (required-ish per blueprint `:134`).
4. **Permissions** — connectorIds, mcpIds, explicit permissions; surface elevated-access warnings here (drives the "approval needed" state).
5. **Review & Create** — full effective surface (the "no hidden access" gate), then create.
**Reuse targets (do not rebuild):**
- **Stepper chrome:** there is no shared `BuilderStepper` primitive yet (PRD §19.1 lists "Builder stepper" as a design-system component to create). S18/S19/S20 all need it — build it **once** as `components/ui/builder-stepper.tsx` and share. (Cross-screen dependency — flag to the Skill Builder & Automation Builder cards.)
- **Model picker:** reuse `components/os/ModelSelector.tsx` (already used by Spawn/Settings/onboarding) instead of `SpawnAgentDialog`'s bespoke model-button grid (`SpawnAgentDialog.tsx:328-344`).
- **Persona picker:** reuse the persona grid pattern from `SpawnAgentDialog.tsx:258-276` + `PERSONAS` from `@/lib/personas`; or fetch live via `adapter.getPersonas()` (as `AgentsApp.tsx:36`).
- **Tool picker:** reuse the searchable checkbox list from `CreateAgentForm.tsx:104-145` (tools sourced from `adapter.getCapabilityStatus()` → flattened `ToolDef[]`, `AgentsApp.tsx:49-55`).
- **Skills/connectors/MCPs pickers:** `adapter.getSkills()`, `adapter.getConnectors()`, and MCP list (see §5 — MCP list endpoint is itself partial). Persona's `suggestedSkills/suggestedConnectors/suggestedMcpServers` (05a `:216-218`) can pre-seed recommendations.
- **Cost-estimate review pane:** lift from `SpawnAgentDialog.tsx:388-421` (`adapter.getModelPricing()`).
- **Approval prompt:** reuse the approvals pattern (`ApprovalsApp` / inline chat approvals) for the elevated-access gate; do not invent a new modal.
**Adapter methods to add (`apps/web/src/lib/adapter.ts` — the single sidecar gateway, ~150 methods):**
`getAgents()`, `createAgent(def)`, `getAgent(id)`, `patchAgent(id, partial)`, `runAgent(id, {task?})`,
`pauseAgent(id)`, `getAgentTraces(id)`. (Today the adapter has `getPersonas/createPersona/…`,
`spawnAgent`, `getFleet/fleetAction`, `getAgentGroups/…` — none of these is the PRD Agent CRUD.)
**Wiring:** register the builder open path through the existing `waggle:open-app` CustomEvent
/ overlay state (`useOverlayState`); add it as a "New Agent" entry in the Agents app (which
should be renamed/retitled from "Personas" to host both — Agent Center card decides final IA).
**Reuse SpawnAgentDialog** as the quick "run now without saving" path — keep it; it is a
different verb (ephemeral spawn) from the builder (persisted definition).
---
## 5. Backend work (PRD §16.7 endpoints)
> Naming collision (backend-routes inventory `:486-492`): the **Cloud** server has a
> Clerk-gated `routes/agents.ts` with `/api/agents` CRUD — but that is NOT the sidecar.
> The desktop frontend talks only to the **local sidecar**, where the sidecar agent surface
> is `/api/agent/*` (singular) + `/api/agents/active` + `/api/agent-groups/*` + `/api/fleet/*`.
> So in the desktop context every §16.7 row below is MISSING-or-PARTIAL **locally**.
| PRD §16.7 endpoint | Status (sidecar) | Extend vs net-new + substrate |
|---|---|---|
| `GET /api/agents` | **MISSING** | Net-new route. Reads a net-new agent-definition store. Closest existing "definitions": `GET /api/personas` (`personas.ts:15`) + `GET /api/agent-groups` (`agent-groups.ts:49`) — neither is the PRD Agent. |
| `POST /api/agents` | **MISSING** | Net-new. **Pattern to reuse:** agent-groups JSON-file persistence (`agent-groups.ts:29-43`, `{dataDir}/agent-groups.json`) → store agents at `{dataDir}/agents.json` (no SQLite, mirrors persona/group precedent). Validate goal/model/memoryScopes/autonomy/permissions. PRO-tier gate like `personas.ts:32`. |
| `GET /api/agents/:id` | **MISSING** | Net-new read over the new store. |
| `PATCH /api/agents/:id` | **MISSING** | Net-new partial-update (mirror `agent-groups.ts:74-90` PATCH shape). |
| `POST /api/agents/:id/run` | **PARTIAL** | **Extend, do not build new execution.** Resolve agent → call the real executor `POST /api/fleet/spawn` (`fleet.ts:66`, the only path that actually runs `runAgentLoop` `:185`). Map agent's persona/model/memoryScope/workspace onto the spawn body. (`/api/agent-groups/:id/run` is a stub — `agent-groups.ts:105` — do NOT reuse it as the model.) |
| `POST /api/agents/:id/pause` | **PARTIAL** | **Extend.** Map agent's running session → `POST /api/fleet/:workspaceId/pause` (`fleet.ts:260`). |
| `GET /api/agents/:id/traces` | **PARTIAL / net-new route** | No HTTP route reads traces today. The `execution_traces` table exists (`schema.ts:199`) and is written by `chat.ts`/`evolution.ts`. Add a thin read route over the execution-trace store, filtered by agent/session. Fallback: session timeline `GET /api/workspaces/:wid/sessions/:sid/timeline` (`sessions.ts`). |
**Substrate touched:** new `{dataDir}/agents.json` (file store — no DB migration);
`personas.ts` (read for persona field); `fleet.ts` (run/pause delegation);
`execution_traces` (read for traces); `install_audit` (write an audit entry on agent
create when elevated connectors/MCPs are attached — `InstallAuditStore`, see §d of the
substrate inventory; note the `risk_level='critical'` CHECK-constraint drift bug flagged
there `install-audit.ts:16` vs DDL `:65`).
**.mind migration:** **NONE required.** The agent definition lives in a JSON file (precedent:
persona disk store + `agent-groups.json`), not in SQLite. `lastRunAt`/`successRate` are
derived (successRate from `execution_traces.outcome` / `procedures.success_rate`
`schema.ts:147,206`), not new columns. This matches the LOCKED in-place model and the
backend-routes inventory verdict ("none require a new data store").
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
Per substrate inventory §(e): **none of the PRD §15.2 unions exist in `apps/web/src/lib/types.ts`.**
S18 needs:
- `AgentType = 'personal' | 'workspace' | 'team' | 'autonomous'` (PRD §15.2) — **MISSING**.
- `AutonomyLevel = 'manual' | 'guided' | 'medium' | 'high'` (PRD §15.2) — **MISSING** in FE (note: a *different* `AutonomyLevel` exists in `hooks/useWindowManager.ts` for chat windows — Normal/Trusted/YOLO; do **not** reuse, they are different vocabularies — flag the collision).
- `Scope = 'personal' | 'workspace' | 'team' | 'organization'` (for `memoryScopes`) — **MISSING**.
- A full **`Agent`** interface (PRD §15.5 fields) — **MISSING**. Today `packages/shared/src/types.ts` has a thin `AgentDef` (`:36-47`: id/userId/teamId/name/role/systemPrompt/model/tools/config/createdAt) lacking goal/type/autonomy/memoryScopes/skillIds/connectorIds/mcpIds/permissions/status/lastRunAt/successRate. Decide: extend `AgentDef` (shared, cloud-aligned) vs add a desktop-local `Agent` in `lib/types.ts`. Given the sidecar-only execution model, a **new `Agent` interface in `lib/types.ts` + a matching server-side type** is cleanest; keep `AgentDef` for the cloud path.
- `ExtensionType` (for the connector/mcp/skill pickers' provenance) — **MISSING** but shared with S19/S21.
Keep types consistent with the new `agents.json` shape (PRD §22.2 "Frontend types are
consistent with API contracts").
---
## 7. Dependencies (screens / phases first)
- **Phase 3 (Intelligence layer)** per PRD §8 / Sprint 6 — same phase as Agent Center (S9), Skill Builder (S19), Automation Builder (S20).
- **Agent Center (Screen 9)** — the builder's create result must land somewhere; the Agent Center list/card view + the new `Agent` type + `GET /api/agents` are a shared prerequisite. Sequence: define `Agent` type + agent store + `GET/POST /api/agents` **with** Agent Center, then layer the builder on top.
- **Shared `BuilderStepper` primitive** (PRD §19.1) — build once, shared by S18/S19/S20. Whichever builder ships first owns it.
- **Connector Hub (S?) / MCP Hub (S?)** — the Permissions step picks connectorIds/mcpIds; the MCP listing is itself PARTIAL (no `GET /api/mcps`; data lives in `capabilities/status.mcpServers[]` + `@waggle/shared mcp-catalog.ts`). The picker can read those existing surfaces without waiting for the full Hubs, but the canonical MCP list endpoint is a cross-card dependency.
- **Approval prompt** component (PRD §19.1) — reuse existing approvals plumbing; not a hard blocker.
- **AppShell / IA** (Phase 0/1) — the builder opens via the existing window-manager overlay path; no new routing (single-route windowed desktop, per frontend inventory §b).
---
## 8. Effort: **L**
Net-new 5-step builder UI + net-new `Agent` entity, store, and CRUD routes, plus
run/pause delegation onto fleet and a net-new traces read route — but **every backend
piece reuses an existing substrate** (persona catalog, agent-groups JSON-file precedent,
fleet-spawn executor, execution-traces store; no `.mind` migration). The heavy reuse and
absence of a DB migration keep it off XL; the breadth (4 new + 3 partial endpoints, a new
shared entity/type, a shared stepper primitive, and the elevated-access approval gate)
keeps it above M.
---
## 9. Open questions
1. **Agent vs persona boundary.** Confirm the model: persona = behavioral template (one
*field* of an agent) vs agent = scoped governed instance. This card assumes that split.
Should creating an agent ever auto-create a backing persona, or always reference one?
2. **Store location.** `{dataDir}/agents.json` (mirrors `agent-groups.json`) vs a new
`agents` row in personal `.mind`. This card recommends the JSON file (no migration,
matches precedent). Confirm.
3. **`/run` semantics.** Does Agent Builder's eventual run mean "fleet-spawn a one-shot in
a chosen workspace" (current real path) or "persistent agent that keeps running"? PRD
agent states include `Running/Paused/Completed` (§14.5) — confirm the lifecycle the
sidecar must support beyond fleet's ephemeral sessions.
4. **Test step.** Blueprint says "configure, **test**, review, create" (`:413`); PRD §12.9
omits test. Include a dry-run (like Skill Builder's `POST /api/skills/:id/test`) or defer?
5. **`successRate` source.** Derive from `execution_traces.outcome` vs `procedures.success_rate`
vs a new counter? (Both exist; pick one to avoid a third tally vocabulary.)
6. **Autonomy vocabulary collision.** PRD `AutonomyLevel` (`manual/guided/medium/high`) vs
the chat-window `AutonomyLevel` (Normal/Trusted/YOLO in `useWindowManager.ts`) vs the
agent-loop's tiered-autonomy. Which governs an agent's tool execution at run time?
7. **Cloud vs sidecar `/api/agents`.** The Cloud server already has Clerk-gated `/api/agents`
CRUD. Should the desktop sidecar's new routes share a contract/shape with it for future
sync, or stay independent?

View File

@@ -0,0 +1,242 @@
# Gap Card — S19 · Skill Builder
> UX-refactor planning artifact. Execution model is LOCKED **in-place incremental refactor**
> of `apps/web` + targeted sidecar extensions. Mockups are directional (PRD §24);
> PRD acceptance criteria win. Every claim below is grounded in a cited file.
>
> Sources: PRD §12.6 + §16.8 (`docs/.../Waggle_OS_UX_Refactor_PRD.md`), Blueprint Screen 19
> (`_blueprint_extracted.txt:417-421`, state model `:464`), mockup
> `Waggle_OS_Handoff_Assets/screens_18_21_builders_and_marketplace.png`, backend-map
> `sections/03d` + `05g`, inventories under `docs/ux-refactor/_inventory/`.
---
## 1. Screen & purpose
**Skill Builder** — the *create/edit* surface for a reusable capability (Skill). PRD §12.6:
a stepper Builder ("Basic Info → Instructions → Inputs & Outputs → Tools & Data → Review & Create"),
with **test-run before publishing** and assignment to agents/workspaces/automations or direct use.
Blueprint Screen 19 (`_blueprint_extracted.txt:417-421`): *"Create reusable capability. Define
prompt, inputs/outputs, tools, memory access, test."* States: *Draft; test pass/fail; published;
archived.* Acceptance: *"Skills are inspectable and reusable by agents/automations"* (PRD §12.6
acceptance: *"a user can understand what a skill does, where it is used, and what access it has"*).
This is the **Builder half of the Intelligence layer's Skills surface** (S06 Skills Hub is the
browse/library half). PRD Roadmap puts both in Sprint 6 / Phase 3 (`PRD:344-351`, `:236-240`).
The mockup tile labelled "Skill Builder" shows a left form column (name/description/category +
instructions textarea) and a right column (tools picker + memory access + a Test panel + a primary
"Create Skill" action) — directional confirmation of the §12.6 stepper.
---
## 2. Required states (PRD / Blueprint)
PRD §14.1 global states (every screen) + Blueprint Skills row (`_blueprint_extracted.txt:464`):
*Loading, empty, populated, error; Installed, draft, custom, workspace, marketplace, update available;
recovery: Test, install, publish, archive, rollback.*
Builder-specific lifecycle (Blueprint Screen 19 `:417-421`): **Draft → test pass/fail → published →
archived**, with PRD global add-ons: **validation error**, **approval required** (skills that request
elevated tool/secret access), **offline/local-only**.
Stepper steps the Builder must implement (PRD §12.6 `:542`):
1. **Basic Info** — name, description, category.
2. **Instructions** — the prompt/steps body.
3. **Inputs & Outputs** — declared input params + expected output shape.
4. **Tools & Data** — tools/data + **memory access** (read scope).
5. **Review & Create** — summary + **Test run** + publish-to-scope (personal/workspace/team).
Per-state behaviours required:
- **Validation error** — name normalises to kebab-case; empty name/description/steps blocked
(backend already 400s on these — §5).
- **Test pass/fail** — inline Test panel showing the injected-prompt preview + parsed metadata.
- **Published** — confirmation + the new skill appears in the Hub (Installed list).
- **Approval required** — when declared tools include elevated/secret/code-exec permissions
(frontmatter `permissions` block exists — `skill-frontmatter.ts:40-48`).
---
## 3. Current state in repo
**Disposition: `create-new`** (frontend Builder), **`keep-promote`** (backend structured-create
contract). There is **no Skill Builder component anywhere in `apps/web/src`** — grep for
`SkillBuilder|Skill Builder|skill-builder` over `apps/web/src` returns **0 matches**. Skill creation
today is only available to the *agent* (the `create_skill` tool, `skill-creator.ts:8`), not to the
*user* via UI.
What exists today:
- `apps/web/src/components/os/apps/CapabilitiesApp.tsx` — "Skills & Apps". A **browse/install/test**
surface only. Tabs: `installed | starter | marketplace | tools | audit` (`:80`, `:358`). It lists
packs (`PackCard` `:190`), shows a detail drawer (`PackDetail` `:249`), installs
(`handleInstall`/`handleMarketplaceInstall` `:164,:174`), and **tests** an existing skill
(`handleTestSkill``POST /api/skills/test` `:88-102`). **It has NO create/edit form.** This is
the natural host to add a "+ Create Skill" entry point that launches the new Builder.
- `apps/web/src/lib/adapter.ts:652``createSkill({ name, description })` exists but is **lossy**:
it POSTs to `/api/skills/create` with only `{name,description}` (no `steps`/`tools`/`category`),
while the backend requires a non-empty `steps[]` (`skills.ts:434`). So the one adapter method that
targets the create route is under-specified and **has no caller** in `apps/web/src` (grep:
`createSkill` appears only at its definition). Needs rework to the full template shape.
- `apps/web/src/lib/types.ts:210``SkillPack` (`id/name/description/category/skills[]/installed/trust`).
This is a **pack/catalog** shape, NOT a skill-authoring shape (no instructions/inputs/outputs/tools).
- `packages/agent/src/skill-creator.ts:11``SkillTemplate` (`name, description, triggerPatterns[],
steps[], tools[], category`) + `generateSkillMarkdown()` (`:26`). This is the canonical authored-skill
model the backend writes from.
- `packages/agent/src/skill-frontmatter.ts:53` — `ParsedSkill`/`SkillFrontmatter` (name, description,
`scope` personal→workspace→team→enterprise `:27`, `permissions` block `:40-48`). This is the read/parse
side; the Builder's "memory access / permissions" + "scope" map onto this.
Conclusion: backend authoring contract is solid and reusable; the **frontend Builder UI is greenfield**,
and the **adapter method must be widened** to the full template.
---
## 4. Frontend work
**Create (new):**
- `apps/web/src/components/os/overlays/SkillBuilder.tsx` — a 5-step stepper modal/drawer. Place it in
`overlays/` (consistent with `CreateWorkspaceDialog.tsx`, `SpawnAgentDialog.tsx`). Reuse the DS
Builder-stepper pattern (PRD §19.1 lists "Builder stepper" as a core component — none exists yet, so
this is the **first stepper**; AgentBuilder S18 + AutomationBuilder S20 should share it — see §7).
- Step 1 Basic Info: `name`, `description`, `category` (select from the `SKILL_FAMILIES` family set
in `skills.ts:13-31`: writing/research/decision/planning/communication/code/creative — surface
these as the category options, not the narrower `SkillPack.category` 5-union).
- Step 2 Instructions: ordered `steps[]` editor (add/remove/reorder) + optional `triggerPatterns[]`
(maps directly to `SkillTemplate.steps`/`.triggerPatterns`).
- Step 3 Inputs & Outputs: `inputs[]` / `outputs[]` param rows. **Backend does not persist these
structured today** (§5) — render them into the markdown body for v1, or block on the §5 backend
extension.
- Step 4 Tools & Data: `tools[]` multiselect (source the tool catalog already enumerated in
`CapabilitiesApp.tsx:422-434` — extract that hardcoded list to a shared module to avoid a third
copy; the agent tool registry is `packages/agent/src/tools.ts`) + **memory access / scope**
(`SkillScope` personal/workspace/team — `skill-frontmatter.ts:27`) + a `permissions` toggle group
(`skill-frontmatter.ts:40-48`).
- Step 5 Review & Create: summary card + **Test** button (calls existing test route) + Create.
- Optional `apps/web/src/components/os/overlays/skill-builder/` substeps if the file exceeds ~400 LOC
(CLAUDE.md file-org rule).
**Rework:**
- `apps/web/src/lib/adapter.ts` — widen `createSkill` (`:652`) to
`createSkill(t: { name; description; steps: string[]; tools?: string[]; category?: string;
triggerPatterns?: string[] })` (full `SkillTemplate` shape, matching `skills.ts:424-430`). Add
`updateSkill(name, content)` → `PUT /api/skills/:name` (`skills.ts:506`) for edit, and reuse the
existing test call (CapabilitiesApp inlines `fetch('/api/skills/test')` `:91` — promote it to an
`adapter.testSkill(skillName, testInput?)` method so the Builder and CapabilitiesApp share one path).
- `apps/web/src/components/os/apps/CapabilitiesApp.tsx` — add a "+ Create Skill" button (header,
next to the grid/list toggle `:340`) that dispatches `waggle:open-app` or opens the Builder overlay
via `useOverlayState` (the overlay open/close registry — `hooks/useOverlayState.ts`, per frontend
inventory §c). Optionally render an edit affordance on installed custom skills.
**Wiring:**
- Register the overlay in `Desktop.tsx` (overlays block) + add a flag to `useOverlayState`.
- Ctrl+K "Create → Skill" command should open it (PRD §12.3 Create category; `GlobalSearch.tsx`
already loads skills `:129`).
**Props/state:** local stepper state object `{ step, name, description, category, steps[],
triggerPatterns[], inputs[], outputs[], tools[], scope, permissions, testResult, status }`. On
Create → `adapter.createSkill(template)`; on Test → `adapter.testSkill(name)`. No new global store;
optimistic insert into the Skills Hub list on success (CapabilitiesApp already refetches via
`getSkills`).
---
## 5. Backend work (PRD §16.8)
| PRD §16.8 endpoint | Status | Note / what to EXTEND vs NET-NEW | Substrate |
|---|---|---|---|
| `GET /api/skills` | **EXISTS** | `skills.ts:164` — list installed skills. Hub/Builder list source. | `~/.waggle/skills/*.md` files |
| `POST /api/skills` | **EXISTS** | `skills.ts:167` raw `{name,content}`; **`POST /api/skills/create` (`skills.ts:431`) is the structured one** the Builder should use (`{name,description,steps[],tools?,category?}` → `generateSkillMarkdown` + `redactSkillContent` + audit + hash). | skill files + `auditStore` + `skillHashStore` |
| `PATCH /api/skills/:id` | **PARTIAL** | Update is `PUT /api/skills/:name` keyed by **name**, method **PUT**, body `{content}` (`skills.ts:506-510`). PRD uses `PATCH`+`:id`. **EXTEND**: accept `PATCH` alias + name↔id mapping (skills have no numeric id — name *is* the id). | skill files |
| `POST /api/skills/:id/test` | **PARTIAL** | Test exists as **`POST /api/skills/test`** (body `{skillName, testInput?}`, `skills.ts:570-575`) — not a per-id path. **EXTEND**: add `:id`-path variant (or keep body-driven; Builder can call either). Returns injected-prompt preview + metadata. | skill files + starter dir |
| `POST /api/skills/:id/install` | **PARTIAL** | No unified per-skill install-by-id. Closest: `POST /api/skills/starter-pack/:id` (`skills.ts:161`), `POST /api/skills/capability-packs/:id` (`:163`), marketplace `POST /api/marketplace/install` (`marketplace.ts`, **Tier: PRO**, SecurityGate). **EXTEND**: add a dispatcher `/skills/:id/install` that routes by source — OR leave install to the Hub (S06) and have the *Builder* only create (create == installed, since authored skills write straight to `~/.waggle/skills/`). | skill files / marketplace |
**Net-new backend gap surfaced by the PRD stepper (NOT in §16.8, but needed for §12.6 Step 3):**
the structured-create contract (`SkillTemplate`, `skill-creator.ts:11`) has **no `inputs`/`outputs`
and no `memoryScopes` fields**. PRD §15 skill object (and Blueprint §551 "Skill … inputs, outputs,
requiredTools, requiredMemoryScopes") expects them. Two options, both in-place:
- **v1 (no migration, recommended):** Builder serialises inputs/outputs/memory-scope into the
markdown body via an extended `generateSkillMarkdown` (add optional `inputs?`/`outputs?`/`scope?` to
`SkillTemplate`); they round-trip as `## Inputs` / `## Outputs` / frontmatter `scope:` sections.
`parseSkillFrontmatter` already reads `scope` (`skill-frontmatter.ts:27`) and `permissions`.
- **v2 (later):** promote to structured frontmatter if inputs/outputs become a query/filter axis.
**No `.mind` migration.** Skills are **filesystem markdown** (`~/.waggle/skills/*.md`), not SQLite —
confirmed `skills.ts:46-49`. The only DB touch is the **install-audit** record on create
(`skills.ts:476`, writes to `.mind` install_audit) + the skill-hash store. The audit `record()` call
already passes `riskLevel:'low'` so it sidesteps the latent `risk_level` CHECK drift noted in
substrate-types §(d) — no new exposure.
**Security:** create/update already pass content through `redactSkillContent()` (strips
secrets+user-paths, `skills.ts:464,:521`) and guard path-traversal in the name (`:447`). The Builder
must NOT bypass these — always go through `/api/skills/create` / `PUT /api/skills/:name`, never write
files another way. Elevated `permissions` (codeExecution/secrets/network) in a skill should trigger
the §2 "approval required" UX (PRD §12.6 / §17.3 elevated-action approval) — currently no approval
gate fires on skill authoring; flag as an open item (§9).
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
PRD §15.2 declares `ExtensionType = 'skill' | ...` — **MISSING** in `apps/web/src/lib/types.ts`
(substrate-types §e). For S19 specifically:
- **Add `SkillTemplate` (authoring) FE type** mirroring `packages/agent/src/skill-creator.ts:11`
(currently the FE only has the catalog-shaped `SkillPack` `types.ts:210`). Fields:
`name, description, category, steps[], triggerPatterns[], tools[]` + PRD additions
`inputs[]`, `outputs[]`, `scope` (`SkillScope`), `permissions`. Keep `SkillPack` (catalog) distinct
from `SkillTemplate` (authoring) — they are different objects.
- **Reuse `SkillScope`** (`personal|workspace|team|enterprise`) from `skill-frontmatter.ts:27` rather
than redeclaring (export it through the FE if needed; PRD §15.2 `Scope` is the broader
personal/workspace/team/organization — reconcile: skills use the `enterprise` variant).
- `SkillPack.category` is a narrow 5-union (`types.ts:213`) that does **not** match the backend
`SKILL_FAMILIES` 7-set (`skills.ts:13`). Widen the FE category union to the family set so the Builder
category picker and the Hub badges agree.
---
## 7. Dependencies (screens/phases first)
- **Phase/Sprint:** PRD Sprint 6 / Phase 3 — Intelligence layer (`PRD:344-351`, `:236-240`).
- **Hard prereq — DS Builder Stepper component** (PRD §19.1). None exists. S19 is the cheapest place
to introduce it, but **S18 Agent Builder** and **S20 Automation Builder** share the exact same
stepper pattern — build the stepper as a reusable primitive (`components/ui/` or
`components/os/overlays/builder-stepper/`) so all three Builders consume it.
- **Sibling — S06 Skills Hub** (the browse/library half, hosted today by `CapabilitiesApp.tsx`).
S19 launches *from* the Hub and writes *into* it (created skill → Installed list). They should ship
together or S06 first.
- **Soft — Command Center (Ctrl+K) (S03)** for the "Create → Skill" entry (PRD §12.3); not blocking.
- **Soft — Agent Builder (S18) / Automation Builder (S20)** consume skills via `skillIds`
(PRD §15.5) — they depend on skills *existing*, not on the Builder UI. Skill→agent assignment is a
downstream wiring concern.
---
## 8. Effort: **M**
Backend is mostly **EXISTS/PARTIAL** (structured-create, test, update all present — only thin aliases
`PATCH`/`:id`-path + an optional `inputs/outputs/scope` extension to `generateSkillMarkdown`, no DB
migration). The frontend is a net-new 5-step stepper + adapter widening + a reusable stepper primitive
shared with S18/S20 — real but bounded UI work. Not S (greenfield component + 3 endpoint extensions);
not L (no new substrate, no migration, backend authoring path already proven via the `create_skill`
tool).
---
## 9. Open questions
1. **Install vs create semantics.** Authored skills write straight to `~/.waggle/skills/` (create ==
installed). Does PRD `POST /api/skills/:id/install` (§16.8) apply to the *Builder* at all, or only
to the *Hub/Marketplace* install flow? Recommend: Builder = create-only; install-by-id lives in S06.
2. **Inputs/Outputs persistence.** v1 markdown-body serialisation vs structured frontmatter — confirm
whether inputs/outputs need to be *queryable* (drives the v1-vs-v2 backend choice in §5).
3. **Approval gate on elevated skills.** Should authoring a skill that declares
`permissions: {codeExecution|secrets|network}` trigger the PRD §17.3 approval prompt at *create*
time, or only at *run* time? No gate fires today on authoring.
4. **Scope vocabulary reconciliation.** `SkillScope` uses `enterprise`; PRD §15.2 `Scope` uses
`organization`. Pick one for the Builder's publish-to-scope picker.
5. **Test-run fidelity.** `POST /api/skills/test` returns only the *injected-prompt preview* + parsed
metadata (`skills.ts:570`), it does **not** execute the skill against an LLM. Is preview-only
sufficient for the PRD "test pass/fail" state, or is a live dry-run expected? (Live run would be
net-new and lean on `/api/agent/run`.)
6. **Category source of truth.** Reconcile `SkillPack.category` (5-union) vs `SKILL_FAMILIES` (7-set)
vs `SkillTemplate.category` (free string) — which is canonical for the picker?

View File

@@ -0,0 +1,224 @@
# Gap Card — S20 Automation Builder
> UX-refactor planning artifact. Execution model: **in-place incremental refactor** of
> `apps/web` + targeted backend extensions. Mockups are directional (PRD §24); PRD acceptance
> criteria win. Every claim is grounded in a real file path below.
>
> Sources: PRD §12.10 + §16.10 (`docs/Waggle_OS_UX_Refactor_Master_Handoff_Package/Waggle_OS_UX_Refactor_PRD.md`),
> Blueprint screen 20 (`_blueprint_extracted.txt:423-429`), mockup
> `Waggle_OS_Handoff_Assets/screens_18_21_builders_and_marketplace.png`, baseline inventories under
> `docs/ux-refactor/_inventory/`, backend-map `docs/backend-map/sections/03f-api-realtime-ops.md`.
---
## 1. Screen & purpose
A 4-step **stepper** builder that lets a user create a scheduled or event-driven workflow
("automation"). PRD §12.10 / Blueprint screen 20: *"Create scheduled or event-driven workflow.
Choose trigger/condition/actions/agent/notification, test, activate."*
PRD §12.10 fields (line 605): `name, trigger, condition, actions, agent, notification, schedule,
workspace, status`. Builder steps (PRD line 604): **Trigger → Condition → Actions → Review &
Activate**. This is the *create/edit* surface; the *list/run/pause/logs* surface is the sibling
**Automation Center** (screen 11, PRD §12.10 tabs Overview/Running/Scheduled/Triggers/History/Logs)
— scoped in its own card, but they share the same backend (cron) and types.
Mockup (directional, panel labelled "20. Automation Builder", top-right of
`screens_18_21_builders_and_marketplace.png`): left rail = step list (Trigger/Condition/Actions/
Review); center = config form with a **Trigger** block (schedule vs event), a **Condition** block,
an **Actions** list, an **Agent** selector and a **Notification** target; footer = Test +
Activate buttons.
---
## 2. Required states (PRD/Blueprint)
PRD §14.6 automation lifecycle states: `Draft, Scheduled, Running, Success, Failed, Paused,
Awaiting approval, Disabled`. Blueprint screen-20 states (`_blueprint_extracted.txt:428-429`):
**Draft; test fail; active; scheduled; approval needed.**
Builder-screen states (the create flow itself), composed with PRD §14.1 global states:
- **Draft / editing** — stepper in progress, per-step validation (e.g. invalid cron expr, no action chosen).
- **Test running / test pass / test fail** — dry-run a draft before activating (PRD line 604 "test").
- **Activating → Scheduled/Active** — on save the automation appears in Automation Center + Home overnight summary (PRD line 607, 611).
- **Approval required** — an automation whose actions touch an elevated/risky capability must surface an approval prompt (PRD §17.3 "Automations can only run actions allowed by the user/team role"; PRD line 607 "risky automations surface in Home").
- **Loading / Error / Offline / Permission-denied** — global states (PRD §14.1). Offline: degrade gracefully, keep draft local.
---
## 3. Current state in repo
**Disposition: `rework`** (promote the existing cron surface into the Trigger/Actions skeleton;
the Condition step + event-triggers + test/approval are net-new but build on cron — no new store).
Current implementation is the **cron** surface, not an "Automation Builder":
| Layer | File | What it does today |
|---|---|---|
| Frontend app | `apps/web/src/components/os/apps/ScheduledJobsApp.tsx` | Single-screen "Scheduled Jobs" manager (NOT a stepper). Inline create form with 4 fields: name, **jobType** (`<select>` over `CRON_JOB_TYPES`), **schedule** (preset `<select>` + custom cron), **outputChannel** (`log`/`telegram`). List rows: enable/disable toggle, run-now (`Play`), delete. `:55` create, `:93` toggle, `:102` delete, `:112` trigger. No condition, no agent picker, no test, no review step. |
| Form helpers | `apps/web/src/lib/cron-presets.ts` | `CRON_SCHEDULE_PRESETS` (6 cadences), `CRON_JOB_TYPES` (6 types), `describeCronExpr` / `isPlausibleCronExpr` cron→English. Reusable as-is for the Trigger step. |
| Adapter | `apps/web/src/lib/adapter.ts:815-845` | `getCronJobs`, `createCronJob`, `updateCronJob`, `deleteCronJob`, `triggerCronJob``/api/cron*`. |
| FE type | `apps/web/src/lib/types.ts:230-238` | `CronJob { id, name, schedule, workspaceId, enabled, lastRun?, nextRun? }`**lossy**: drops `jobType`, `jobConfig`, `createdAt`; renames `cronExpr``schedule`, `nextRunAt``nextRun`. No `trigger`/`condition`/`actions`/`agent`/`notification`/`status` (the PRD §15/§12.10 automation shape). |
| Route | `packages/server/src/local/routes/cron.ts` | Full CRUD + `POST /api/cron/:id/trigger` (auto-enables on run, `:188`). |
| Store | `packages/core/src/cron-store.ts` | `CronStore` over `.mind`: `cron_schedules` (`:71`), `cron_execution_history` (`:88`), `notifications` (`:104`). Schedule row: `name, cron_expr, job_type, job_config, workspace_id, enabled, last_run_at, next_run_at`. **No trigger-type, condition, or actions columns**`job_config` is a free JSON blob. |
| Scheduler | `packages/server/src/local/cron.ts` | `LocalScheduler` — 60 s tick, `getDue()` poll, `executeJob()` for manual trigger, auto-disable after 5 consecutive failures (`MAX_CONSECUTIVE_FAILURES`), `onJobComplete` notify callback. **Time-driven only.** |
| Executor (the real "actions") | `packages/server/src/local/index.ts:1379-1751` | `new LocalScheduler(cronStore, async (schedule) => switch(schedule.job_type){…})`. Cases: `memory_consolidation` (`:1381`, incl. `action:'index_reconcile'` marketplace sync), `workspace_health` (`:1504`), `proactive` (`:1525`, sub-actions morning_briefing/stale_workspace_check/task_reminder/capability_suggestion `:1561-1582`), `prompt_optimization` (`:1590`), `agent_task` (`:1722`, runs a `jobConfig.prompt` in a workspace), `monthly_assessment`. |
| History route | registered in `notifications.ts` | `GET /api/cron/:id/history` (backend-map `03f:147`) → execution rows. |
**Grep confirmation:** no `Automation`/`AutomationBuilder` component, no event-trigger code, no
`condition` field. The 13 frontend hits for "Automation|trigger|condition" are the word "trigger"
used in unrelated contexts (`triggerCronJob`, MCP triggers, marketplace) — confirmed none is an
automation builder. (`ScheduledJobsApp.tsx` is the only real match.)
**Mental model match:** what the user calls "trigger" today is **only a cron schedule** (time).
What the user calls "actions" today is **a single `jobType`** chosen from a fixed catalog (one
action per job). There is **no condition, no multi-action sequence, no per-automation agent
binding (except via `agent_task` prompt), and no event triggers.**
---
## 4. Frontend work
**New components (`apps/web/src/components/os/apps/automations/`):**
- `AutomationBuilder.tsx` — 4-step stepper (reuse the **Builder stepper** DS pattern from Agent/Skill
builders — PRD §19.1; align with `CreateAgentForm.tsx` in `apps/web/src/components/os/apps/agents/`).
Owns draft state `{ name, trigger, condition, actions[], agentId?, notification, schedule,
workspaceId, status:'draft' }`; submits via adapter (see §5).
- `TriggerStep.tsx` — radio: **Schedule** (reuse `cron-presets.ts` preset/custom `<select>` +
`describeCronExpr` preview — lift the schedule block straight out of `ScheduledJobsApp.tsx:195-223`)
vs **Event** (gated/"coming soon" if event triggers aren't built — see §5/§9). Workspace scope picker
(reuse `useWorkspaces`).
- `ConditionStep.tsx` — optional predicate (e.g. "only if N+ new frames", "only on weekdays"). **Net-new
concept** — no substrate; ship a minimal optional condition stored in `job_config.condition` (no DB
migration; see §5). Keep simple — PRD §3.2/§19.2 ("not forced during onboarding").
- `ActionsStep.tsx` — choose one or more actions. v1 maps to the existing `CRON_JOB_TYPES` catalog
(the only executable actions today, `index.ts` switch). For `agent_task`, surface an **Agent**
selector (reuse `useWorkspaces` + persona/agent picker from `agents/`) and a prompt field; for
`proactive`, surface the sub-action choice; plus a **Notification** target (reuse the
`log`/`telegram` `outputChannel` selector already in `ScheduledJobsApp.tsx:225-242`).
- `ReviewStep.tsx` — summary + **Test** button (dry-run, see §5) + **Activate** button.
- `AutomationCenter.tsx` (sibling, may be its own card) — promote `ScheduledJobsApp.tsx` into the
PRD §12.10 tabbed Center (Overview/Running/Scheduled/Triggers/History/Logs), wiring `GET
/api/cron/:id/history` for the Logs tab.
**Reuse targets:** `cron-presets.ts` (whole module), the schedule + output-channel JSX blocks in
`ScheduledJobsApp.tsx`, the stepper chrome from `agents/CreateAgentForm.tsx`, `useToast`,
`useWorkspaces`, `ui/select`/`ui/input`/`ui/dialog`.
**Adapter methods/hooks:** extend `lib/adapter.ts` cron methods to round-trip `jobType` +
`jobConfig` (currently `createCronJob` already accepts `jobConfig``:821`; the lossy FE `CronJob`
type is what drops it on read). Add `testAutomation(draft)` and (if added) `pauseCronJob(id)`,
`getCronHistory(id)`. New `useAutomations()` hook wrapping these (mirrors existing domain-hook
pattern). **Register the new app** in `Desktop.tsx` `appConfig` + `renderAppContent` switch and
the dock (`lib/dock-tiers.ts`), under the **Intelligence** IA bucket (PRD §10.3) — the existing
`scheduled-jobs` AppId can be renamed/aliased to `automations`.
---
## 5. Backend work (PRD §16.10)
> **Substrate verdict (matches the backend-routes inventory):** all six §16.10 endpoints are
> **PARTIAL** — the cron surface (`/api/cron*`) already provides full CRUD + trigger + history. The
> refactor **aliases/extends cron as "automations"**; no new store. `trigger`/`condition`/`actions`
> ride in the existing `job_config` JSON blob → **no `.mind` migration** for v1.
| PRD §16.10 endpoint | Status | Extend (existing) vs net-new + substrate it touches |
|---|---|---|
| `GET /api/automations` | **PARTIAL** | **Extend** `GET /api/cron` (`cron.ts:94`). Alias path; reshape `toResponse` to expose `trigger/condition/actions/status` derived from `job_type` + `job_config`. Substrate: `cron_schedules`. |
| `POST /api/automations` | **PARTIAL** | **Extend** `POST /api/cron` (`cron.ts:67`). Accept the richer automation body; persist `trigger`/`condition`/`actions` into `job_config` (and `cron_expr` for schedule triggers). Substrate: `cron_schedules`. |
| `PATCH /api/automations/:id` | **PARTIAL** | **Extend** `PATCH /api/cron/:id` (`cron.ts:124`). Substrate: `cron_schedules`. |
| `POST /api/automations/:id/run` | **PARTIAL** | **Extend** `POST /api/cron/:id/trigger` (`cron.ts:174`, runs `scheduler.executeJob`, auto-enables). Substrate: `cron_schedules` + executor switch (`index.ts:1379`). |
| `POST /api/automations/:id/pause` | **PARTIAL** | No `/pause` route. **Net-new thin route OR reuse** `PATCH /api/cron/:id {enabled:false}`. Add `/pause` alias for the PRD contract. Substrate: `cron_schedules.enabled` + scheduler `resetFailure`. |
| `GET /api/automations/:id/logs` | **PARTIAL** | **Extend/alias** `GET /api/cron/:id/history` (in `notifications.ts`; backend-map `03f:147`). Substrate: `cron_execution_history` (`cron-store.ts:88`). |
**Net-new beyond §16.10 (PRD requires, backend lacks):**
1. **Test / dry-run** (PRD line 604 "test"). No route today. **Net-new** `POST /api/automations/test`
that runs `scheduler.executeJob` against an unsaved draft (or a draft saved `enabled:false`) and
returns the result without scheduling. Touches the executor switch (`index.ts:1379-1751`) — must
make it callable with an ad-hoc schedule object, not only persisted rows.
2. **Condition evaluation** (PRD step 2). Net-new logic in the executor: before running actions, read
`job_config.condition` and short-circuit. No new table — `condition` is a JSON sub-field of
`job_config`. Keep predicate vocabulary minimal in v1.
3. **Event triggers** (PRD "event-driven", §14.6 "trigger fired", Blueprint "scheduled or
event-driven"). **Major net-new substrate gap**`LocalScheduler` is **time-poll only**
(`cron.ts` `getDue()` on `next_run_at`). True event triggers need an event→automation dispatch
path (could hang off `server.eventBus` audit/notification stream — `03f:321-347`). **Recommend
deferring event triggers to a later phase**; ship schedule-triggers only in v1 and gate the
"Event" option (see §9, §8).
4. **Approval gate for risky actions** (PRD §17.3, line 607). When an automation's actions touch an
elevated capability, route through the existing approval/install-audit machinery
(`/api/approval/*`, `install-audit.ts`). Wiring net-new; substrate exists.
**`.mind` migration flag:** **None required for v1** (trigger/condition/actions live in the existing
`job_config TEXT` blob — `cron-store.ts:77`). A later migration could promote `trigger_type` /
`status` to real columns (the migration runner already does idempotent additive `ADD COLUMN` on
`.mind` tables — see substrate-types `§(c)` precedent) **only if** they become query/filter axes for
Automation Center tabs (Running/Scheduled/Triggers).
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
PRD §15.2 unions `AutonomyLevel` / `AgentType` are **MISSING** from `apps/web/src/lib/types.ts`
(per substrate-types `§(e)`). Blueprint §17 names an **Automation** entity (`_blueprint_extracted.txt:553`):
`id, scope, trigger, condition, actions, agentId, schedule, notificationTargets, status, lastRunAt,
nextRunAt`. None of this exists in FE types today.
- **Add `Automation` interface** to `lib/types.ts` (Blueprint shape above) replacing the lossy
`CronJob` (`types.ts:230`) — or layer it as a richer superset and have the adapter map cron rows
into it. Add `AutomationTriggerType = 'schedule' | 'event'`, `AutomationStatus = 'draft' |
'scheduled' | 'active' | 'running' | 'paused' | 'failed' | 'awaiting_approval' | 'disabled'`
(PRD §14.6), and an `AutomationAction` discriminated union keyed on the existing `CronJobType`
catalog (`cron-presets.ts:15`).
- Reconcile FE↔BE: the FE `CronJob` already mismatches the `/api/cron` response shape
(`cronExpr``schedule`, drops `jobType`/`jobConfig`) — fix this drift as part of the type work.
- Keep the canonical `CronJobType` union single-sourced (currently duplicated in
`cron-presets.ts:15` AND `packages/core/src/cron-store.ts:15` — flag the dup; do not add a third).
---
## 7. Dependencies (screens/phases first)
- **PRD Phase 3 (Intelligence layer)** — same phase as Agent/Skill builders; reuse their stepper.
- **Agent Builder (S18/agents)** must define the agent picker the `ActionsStep` agent selector binds
to (PRD §12.9). Soft dependency — can fall back to persona/`useWorkspaces` until Agent entity lands.
- **Automation Center (screen 11)** is the list/run/logs sibling — share types + adapter; build the
Center's Logs tab on the same `/api/cron/:id/history`.
- **Home Cockpit (S?)** — overnight summary + "attention required" must surface failed/risky
automations (PRD line 607, 611). Soft consumer dependency (Home reads automation status).
- **Approval surface (`ApprovalsApp` / `/api/approval/*`)** — for the risky-action gate (§5.4).
- **DS:** Builder stepper + Approval prompt primitives (PRD §19.1) shared across all builders.
---
## 8. Effort: **L**
Rework + extend is cheap (the cron store/route/executor + `cron-presets` + output-channel UI all
exist and are reusable), but the gap between "cron job manager" and the PRD's
**Trigger/Condition/Actions/Agent/Notification stepper + Test + approval-gated activate** is wide:
net-new Condition step, dry-run test endpoint, FE type reconciliation, and an Agent binding. The
**Event-trigger half is XL on its own** (no event-dispatch substrate) and is recommended **out of
v1 scope** — if it stays in, this becomes XL.
---
## 9. Open questions
1. **Event triggers in v1 or schedule-only?** `LocalScheduler` is time-poll only; true event triggers
need a new event→automation dispatch path off `server.eventBus`. Recommend defer + gate the "Event"
radio option. (Ties to PRD §23 open-question spirit on scope.)
2. **Condition vocabulary** — what predicates does v1 support? (frame-count threshold, day-of-week,
workspace-state flag?) PRD names "condition" but specifies no predicate set.
3. **One action or multiple per automation?** Today one `job_type` == one action. PRD says "actions"
(plural). v1 = single action mapped to `CronJobType`, or true multi-action sequence (needs executor
rework + ordering semantics in `job_config`)?
4. **Does `pause` get a dedicated route or reuse `PATCH …{enabled:false}`?** Affects the §16.10
contract surface and Automation Center.
5. **Which `.mind`** do automations live in — personal vs per-workspace? `cron_schedules.workspace_id`
is nullable + `'global'→'*'` normalized (`cron.ts:74`); confirm scope model for the §16.10 list.
6. **Risky-action approval class** — which actions are "elevated" (e.g. `agent_task` writing files,
marketplace `index_reconcile`)? Needs an approval-class mapping before the §5.4 gate can ship.
7. **Naming/AppId** — rename `scheduled-jobs` AppId → `automations`, or keep both (alias)? Affects dock
+ window-manager + the dead-id cleanup already flagged in the frontend inventory.

View File

@@ -0,0 +1,137 @@
# Gap Card — S21 · Marketplace / Extend
> Execution model: **in-place incremental refactor** of `apps/web` + targeted backend extensions.
> Mockups directional only (PRD §24). PRD acceptance criteria win over pixels.
> Every claim grounded in real files (paths + lines below).
---
## 1. Screen & purpose
**Screen 21 — Marketplace / Extend Waggle** (PRD §12.13, lines 648-662; blueprint catalog row 21, `_blueprint_extracted.txt:432-439`).
The central, power-user extension surface. PRD §10.4 names the Extend layer as **Connectors, MCPs, Models, External tools, Marketplace** — S21 is the *Marketplace* node of that layer: a single place to discover, install, update, risk-approve, and audit **extensions of six kinds** (Skills, Agents, Connectors, MCPs, Models, Templates) without "hunting through settings" (PRD §12.13 acceptance criterion). It is the funnel for the PRO upgrade trigger (skills + connectors + marketplace are gated to PRO; `05g-subsystem-skills-marketplace-wiki.md:3`).
This card scopes the **Marketplace browse/install/audit** surface specifically. The sibling Extend surfaces — Connector Hub (S? / §12.7), MCP Hub (§12.8), Skills Hub (§12.6) — are separate screens that this one cross-links to; their per-domain CRUD lives in their own cards. S21 is the unified catalog + governance view across all extension types.
---
## 2. Required states (PRD / Blueprint)
**Functional requirements (PRD §12.13):**
- Categories: **Skills, Agents, Connectors, MCPs, Models, Templates** (6 facets).
- Search / filter / sort by **category, trust, popularity, source, risk**.
- Show per-item lifecycle: **installed, update available, risk approval (required), install failed**.
- Install actions **write to install audit**.
- Support **workspace / team scoping** of installs.
**Extension lifecycle states (PRD §14.7, lines 906-914):**
`Available · Installed · Update available · Installing · Failed install · Risk approval required · Disabled/revoked`.
**Acceptance criterion (PRD §12.13):** "Power users can extend Waggle without hunting through settings." Cross-cuts to §12.8 MCP acceptance ("powerful but always visible, scoped, auditable, and reversible") and the install-audit governance theme (`_blueprint_extracted.txt:43-45,95`: install audit is the "best basis for Extend governance").
**Journeys touching S21:** J8 install connector, J9 install MCP (audit recorded), J25 marketplace update → review changelog/risk (`_blueprint_extracted.txt:203,206,252`).
**Mockup (directional):** `screens_18_21_builders_and_marketplace.png` shows S21 as a card-grid catalog with a left category rail and per-card install affordances + trust badges — consistent with the spec; not a pixel target.
---
## 3. Current state in repo
**Disposition: `rework`** (the existing surfaces are reusable substrate but neither satisfies §12.13 — the catalog is hardwired to 3 of 6 categories and there is no unified Extend view with trust/risk/popularity/scope filters).
Marketplace is **doubly represented today** (frontend.md §f flags this as an IA cleanup point):
1. **`apps/web/src/components/os/apps/MarketplaceApp.tsx`** (258 lines, fully read) — standalone browser. Two tabs (`'search' | 'installed'`, `:26`). Calls `adapter.searchMarketplace` / `getMarketplaceInstalled` / `installMarketplacePackage` / `uninstallMarketplacePackage`. Renders a flat package list with a `scanBadge` (passed/failed Shield/AlertTriangle, `:141-145`), `installed` check, type/category/source chips (`:223-227`), and 403→`waggle:tier-insufficient` UpgradeModal routing (`:108-117`). **Registered in `Desktop.tsx`** appConfig `marketplace` (`Desktop.tsx:93`) + `renderAppContent` case (`:347`), but **NO dock entry points at it**`dock-tiers.ts:67` comment: "Marketplace already a tab inside Skills & Apps — no separate dock entry". So `MarketplaceApp` is currently reachable only via `waggle:open-app` events, effectively orphaned.
2. **`apps/web/src/components/os/apps/CapabilitiesApp.tsx`** (478 lines, fully read) — "Skills & Apps", the live Extend surface (dock key `skills` → appId `capabilities`, `dock-tiers.ts:64`). 5 tabs: `installed | starter | marketplace | tools | audit` (`:80,358`). The `marketplace` tab lists `SkillPack`s via `getMarketplacePacks`; the `audit` tab (`AuditTab`, `:15-58`) reads `GET /api/audit/installs` and renders name/source/outcome/timestamp rows. It has a pack-detail drawer (`PackDetail`, `:249-334`) with trust label + bundled-skills + install. This is *pack-oriented* (curated bundles), not the faceted *package catalog* of §12.13.
**Backend already present (more than the inventory claimed):**
- Full marketplace route family: `packages/server/src/local/routes/marketplace.ts``search` (FTS5 + faceted, `:56`), `packs`, `packs/:slug`, `enterprise-packs` (ENTERPRISE), `install` (PRO + SecurityGate, `:180`), `uninstall` (`:377`), `installed` (`:396`), `security-check`, `sources` GET/POST/DELETE (`:446,457,533`), `categories` (`:567``PACKAGE_CATEGORIES`), `sync`, `security-status`, `publish` (PRO, `:685`). Sort facet supported via `?sort=` (`:72`).
- **Install-audit READ route EXISTS** — `GET /api/audit/installs` at `packages/server/src/local/routes/skills.ts:685-703` (returns normalized `{ entries: [{ capabilityName, capabilityType, source, riskLevel, trustSource, approvalClass, action, initiator, detail }] }`). **This corrects substrate-types.md §d gap #1, which claimed "No HTTP endpoint surfaces the audit trail."** The write path is live too (`marketplace.ts:224-319` records every SecurityGate verdict; `skills.ts:212,315,475`). `adapter.getAuditInstalls` already wires it (frontend.md, adapter `Misc`).
- Install-audit store: `packages/core/src/install-audit.ts``AuditCapabilityType = native|skill|plugin|mcp|connector|marketplace` (`:22`), full enums for action/trust/approval/risk; read API `getRecent/getByCapability/getByAction/getAll`.
**Critical limitation (grounded):** the marketplace catalog **only supports 3 install kinds**`InstallationType = 'skill' | 'plugin' | 'mcp'` (`packages/marketplace/src/types.ts:185`), confirmed by `05g-subsystem-skills-marketplace-wiki.md:11` ("three installable package kinds — `skill`, `plugin`, `mcp`"). PRD §12.13 demands **six categories incl. Agents, Connectors, Models, Templates**, which have **no catalog representation today** (connectors are a separate `/api/connectors` registry with only `connect`/`disconnect`; templates are `/api/workspace-templates`; agents are personas/groups; models are `/api/litellm/models` + providers). So S21's "one catalog, six categories" is the central rework: a **federated catalog read** over the existing marketplace catalog + connector registry + template store + persona/agent catalog + model list, NOT a new package table.
---
## 4. Frontend work
**Strategy:** Consolidate the two marketplace surfaces into ONE Extend/Marketplace screen, retire the orphaned standalone, and give the new screen a federated faceted catalog. Keep `CapabilitiesApp`'s Skills-pack flow as the Skills facet; promote a real dock entry.
**Components to create:**
- `apps/web/src/components/os/apps/MarketplaceApp.tsx`**rework in place** (reuse the file/appId so Desktop wiring + tests survive). Expand from 2 tabs to the §12.13 model: a left **category rail** (Skills / Agents / Connectors / MCPs / Models / Templates / All) + a top **filter bar** (trust, popularity, source, risk dropdowns + search) + a **catalog grid**. Reuse the existing `scanBadge`, 403→UpgradeModal routing (`:108-117`), and install/uninstall handlers verbatim.
- `apps/web/src/components/os/apps/extend/ExtensionCard.tsx` (new) — one card type rendering the §14.7 lifecycle badge (Available/Installed/Update available/Installing/Failed/Risk-approval/Disabled), trust chip, risk chip, source, popularity. Props: `{ ext: Extension; onInstall; onUpdate; onRevoke; onOpenDetail }`.
- `apps/web/src/components/os/apps/extend/ExtensionDetail.tsx` (new, or generalize `CapabilitiesApp`'s `PackDetail` `:249-334`) — detail drawer with permissions/scope, changelog (for "update available", J25), risk approval CTA, install-audit trail for this item (via `getAuditInstalls` filtered client-side, or a new `?capability=` query — see §5).
- `apps/web/src/components/os/apps/extend/InstallAuditPanel.tsx` (new, or lift `CapabilitiesApp`'s `AuditTab` `:15-58`) — shared Extend governance trail; reused by S21 + Connector/MCP hubs.
- `apps/web/src/lib/extension-catalog.ts` (new pure module + co-located `.test.ts`) — client-side **federation/normalization**: merge marketplace packages (`searchMarketplace`), connectors (`getConnectors`), templates (`getWorkspaceTemplates`), personas/groups (`getPersonas`/`getAgentGroups`), models (`getModels`/`getProviders`) into one `Extension[]` with a unified `{ type: ExtensionType, trust, risk, installed, updateAvailable, source, popularity }` shape; derive lifecycle state. Keeps the fat federation logic out of the component (file-org rule: many small files).
**Reuse targets:** `MarketplaceApp` install/uninstall + 403 handler; `CapabilitiesApp` `PackCard`/`PackDetail`/`AuditTab` + `lib/skill-pack-display.ts` (`describeTrust`, `summariseSkills`) + `lib/dedupe-packs.ts`; `components/ui/*` (card/badge/tabs/input/select/tooltip); `HintTooltip`.
**Adapter methods/hooks:** existing — `searchMarketplace`, `getMarketplacePacks`, `getMarketplaceInstalled`, `installMarketplacePackage`, `uninstallMarketplacePackage`, `getAuditInstalls`, `getConnectors`, `getWorkspaceTemplates`, `getPersonas`, `getModels`, `getProviders` (all in `lib/adapter.ts`, per frontend.md §c). **New adapter methods** to add for the §16.9 gaps: `getMcps`, `installMcp`, `testMcp`, `revokeMcp`, `syncConnector`, `revokeConnector` (see §5). New hook `hooks/useExtensions.ts` wrapping `extension-catalog.ts` (catalog + filters + install/update/revoke actions + react-query caching).
**IA cleanup (frontend.md §f):** decide one home for marketplace. Recommended: keep `MarketplaceApp` as the dedicated S21 Extend surface, add a real dock entry under the `extend` zone-parent (`dock-tiers.ts:62-71`), and have `CapabilitiesApp`'s `marketplace` tab deep-link into it (or drop that tab) to remove the double representation.
---
## 5. Backend work (PRD §16.9 endpoint-by-endpoint)
| PRD §16.9 endpoint | Status | EXTEND vs NET-NEW + substrate / .mind |
|---|---|---|
| `GET /api/connectors` | **EXISTS** | `connectors.ts:6`. No change. |
| `POST /api/connectors/:id/connect` | **EXISTS** | `connectors.ts:55`. No change. |
| `POST /api/connectors/:id/sync` | **MISSING** | **NET-NEW** thin action in `connectors.ts` (re-init/re-fetch from connected service). Touches connector credential resolution (vault) + connector definition; record an audit entry (`auditStore.record`, type `connector`). No `.mind` migration. |
| `POST /api/connectors/:id/revoke` | **PARTIAL** | **EXTEND** — alias to existing `POST /api/connectors/:id/disconnect` (`connectors.ts:107`, removes vault creds + sub-keys). Same intent, add `/revoke` route delegating to disconnect + write a `revoked` audit entry. |
| `GET /api/mcps` | **PARTIAL** | **EXTEND/NET-NEW** — no `/api/mcps`. MCP catalog lives in `@waggle/shared mcp-catalog.ts`; installed MCPs surface inside `GET /api/capabilities/status` (`mcpServers[]`) and the marketplace catalog (`waggle_install_type:'mcp'`, `marketplace/src/mcp-registry.ts`). Add a dedicated `GET /api/mcps` route that joins catalog + installed-state + `.mcp.json`. Reads existing substrate; no migration. |
| `POST /api/mcps/install` | **PARTIAL** | **EXTEND** — route through the existing marketplace installer (`POST /api/marketplace/install`, which already handles `installType:'mcp'` → writes `.mcp.json`, `installer.ts:580`). Add an MCP-specific endpoint that resolves an MCP id → marketplace package → install, OR keep marketplace install as the single path and have the FE call it. Audit already recorded. |
| `POST /api/mcps/:id/test` | **MISSING** | **NET-NEW** — no MCP health/test route. Closest analog is `GET /api/connectors/:id/health` (`connectors.ts:16`). Add an MCP test route (spawn/handshake the MCP server, report ok/error). No `.mind`. |
| `POST /api/mcps/:id/revoke` | **MISSING** | **NET-NEW** — no MCP revoke-by-id. Closest: `DELETE /api/plugins/:name` (`skills.ts`). Add an MCP revoke route (remove from `.mcp.json` + record `revoked` audit). |
| `GET /api/marketplace` | **PARTIAL** | **EXTEND** — bare path = alias of `GET /api/marketplace/search` with default params (`marketplace.ts:56`). Add alias or accept both. |
| `POST /api/marketplace/install` | **EXISTS** | `marketplace.ts:180` (PRO + SecurityGate). No change. |
**Plus implied additions for §12.13 (not in §16.9's list but required by FRs):**
- **Six-category catalog.** The §12.13 categories (Agents/Models/Templates) have **no marketplace representation** (`InstallationType` is `skill|plugin|mcp` only, `types.ts:185`). Two options: (a) widen `InstallationType` + catalog schema to add `agent|model|template` (a marketplace-DB change in `packages/marketplace`, NOT a `.mind` migration — `marketplace.db` is a separate SQLite store) and seed registries; or (b) **federate at read time** (preferred for incremental scope) — the FE `extension-catalog.ts` merges marketplace (skill/plugin/mcp) + `/api/connectors` + `/api/workspace-templates` + `/api/personas`+`/api/agent-groups` + `/api/litellm/models`. Option (b) needs **no backend change** beyond §16.9; option (a) is a later phase if a true unified catalog with publish/install for all 6 kinds is wanted.
- **Install-audit read for Extend governance.** **EXISTS**`GET /api/audit/installs` (`skills.ts:685`). (Substrate inventory §d#1 said missing; it is present.) **Optional EXTEND:** add a `?capability=` / `?type=` filter param so the detail drawer can show per-item history without client-side filtering (currently only `?limit=`). The store already has `getByCapability()` (`install-audit.ts:125`) — just expose it.
- **Workspace/team scoping of installs (§12.13).** No per-workspace install scoping exists today (installs are global to `~/.waggle/`). This is net-new product surface; recommend deferring to Phase 4 polish (PRD §22 risk register flags "marketplace scope creep" — `PRD:1435` — "Start with catalog + install audit, postpone billing/public marketplace").
**`.mind` migration flag:** none required for S21. The audit table (`install_audit`) already exists in both DDL sites (`install-audit.ts:54` + `schema.ts:119`). **Latent bug to flag (substrate-types §d#2):** `AuditRiskLevel` TS includes `'critical'` (`install-audit.ts:16`) but both DDL CHECKs only allow `('low','medium','high')` (`install-audit.ts:65`, `schema.ts:130`) — a `record({riskLevel:'critical'})` throws. The marketplace route sidesteps it (maps CRITICAL→`high`+`blocked`, `marketplace.ts:228`). If S21 surfaces a true "critical risk approval" state that writes `critical`, this CHECK must be widened first (idempotent ADD/relax via the migration runner's ADD-COLUMN pattern, `mind/db.ts:116`).
---
## 6. Shared types needed (PRD §15 vs `lib/types.ts`)
- **`ExtensionType`** (PRD §15.2, line 953: `'skill' | 'connector' | 'mcp' | 'model' | 'template' | 'external_tool'`) — **MISSING** from `apps/web/src/lib/types.ts` (substrate-types §e). Add. Note PRD union has `external_tool` but §12.13 categories say "Agents" not "external_tool" — **reconcile**: §12.13 needs an `'agent'` category; PRD §15.2 `ExtensionType` lacks `agent` and adds `external_tool`. Flag as open question; likely the union should be `skill|agent|connector|mcp|model|template` (+ optional `external_tool`).
- **`Extension` interface** (new) — `{ id, name, description, type: ExtensionType, trust: AuditTrustSource-ish, risk: AuditRiskLevel, source, popularity?, version?, installed, updateAvailable, lifecycle: 'available'|'installed'|'update_available'|'installing'|'failed'|'risk_approval'|'disabled', scope?: Scope }`. No PRD §15 interface spells this out; derive from §12.13 + §14.7.
- **`Scope`** (PRD §15.2: `'personal'|'workspace'|'team'|'organization'`) — **MISSING**, needed for install scoping; add (shared with other Extend/Memory cards).
- Existing FE `MarketplacePackage` (local interface in `MarketplaceApp.tsx:8-18`) and `SkillPack` (`types.ts`) are narrower than `Extension`; `extension-catalog.ts` normalizes both into `Extension`.
- Reuse the canonical audit enums (`AuditTrustSource`, `AuditRiskLevel`, `AuditCapabilityType` from `packages/core/src/install-audit.ts:16-22`) rather than re-declaring trust/risk literals in the FE — export them or mirror minimally.
---
## 7. Dependencies (screens / phases first)
- **PRD phasing:** Extend layer = **Phase 4 / Sprint 7** (`PRD:241-245,1353-1357`). Comes after Work (Home/Workspace), Intelligence (Agents/Skills), so S21 should land in that phase.
- **Depends on / cross-links:**
- Connector Hub (§12.7) and MCP Hub (§12.8) screens — S21 federates their catalogs and shares the `InstallAuditPanel`; their `/sync`/`/revoke`/`/test` backend routes (§5) are shared deliverables.
- Skills Hub (§12.6) — S21's Skills facet reuses `CapabilitiesApp`'s pack flow.
- Agent Center (§12.9), Models (Settings→Models), Templates (workspace-templates) — needed as catalog sources for the 6-category federation.
- Dock IA refactor (`dock-tiers.ts` Extend zone) — S21 needs a real dock entry (currently none).
- UpgradeModal / tier gating (`waggle:tier-insufficient` event) — already wired; reuse.
- **Blocks:** nothing downstream; it is a leaf surface.
---
## 8. Effort
**L.** The frontend rework is moderate (consolidate 2 surfaces → 1 faceted catalog + extract shared `ExtensionCard`/`InstallAuditPanel` + new `extension-catalog.ts` federation). What pushes it to L: the **6-category federation** spans five different backend domains (marketplace, connectors, templates, personas/agents, models) each with a different shape, plus **6 net-new/extend §16.9 routes** (connector sync/revoke, mcps list/install/test/revoke) and the FE/BE type reconciliation. Not XL because no `.mind` migration, no new data store (federate-at-read), audit read route already exists, and install/uninstall + tier-gating + audit-trail plumbing are all already live.
---
## 9. Open questions
1. **`ExtensionType` reconciliation.** PRD §12.13 categories = Skills/Agents/Connectors/MCPs/Models/Templates (6, incl. **Agents**). PRD §15.2 `ExtensionType` = skill/connector/mcp/model/template/**external_tool** (6, incl. external_tool, **no agent**). Which is canonical — add `agent`, keep `external_tool`, or both (7)?
2. **Federate-at-read vs unified catalog table.** Ship S21 by federating existing domains client-side (no marketplace-DB change, fast), or invest in widening `InstallationType` + `marketplace.db` to natively catalog all 6 kinds (enables publish/install/version-tracking parity for agents/models/templates)? Recommend federate-first; revisit per §22 "postpone public marketplace".
3. **Install scoping (§12.13 "workspace/team scoping").** Installs are global to `~/.waggle/` today. Is per-workspace/per-team install scoping in scope for Phase 4, or deferred (it implies new persisted scope state on every installed capability)?
4. **"Update available" + changelog (J25).** No version/changelog tracking exists for installed packages beyond a `version` column on the marketplace package. Where does the "update available" signal come from — catalog sync diffing installed version vs latest, or a new field?
5. **MCP test semantics.** `POST /api/mcps/:id/test` — does "test" mean spawn-and-handshake the MCP server (live), or static manifest/permission validation? Affects whether it can run without launching a process.
6. **Should `MarketplaceApp` or `CapabilitiesApp` be the canonical S21 home** (IA double-representation, frontend.md §f)? Recommend `MarketplaceApp` reworked + real dock entry; `CapabilitiesApp` keeps Skills/Tools/Audit and drops its `marketplace` tab.