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

270 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gap Card — 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?