# PR3 Recon — SCREEN 03 · Workspace (Variation A: Overview + tabs)
Maps the **current** Workspace surface against the warm-Hive design (`workspace.html`,
`SCREENS.md` §03). Ship target = **Variation A "Overview + tabs"**, **Memory stays a
tab** (do NOT make the knowledge-graph the default — that's the alternate Variation B,
deferred).
Primary file: `apps/web/src/components/os/apps/WorkspaceDesktopApp.tsx` (1020 LOC).
Route wrapper: `apps/web/src/routes/WorkspaceRoute.tsx`.
Design ref: `docs/design_handoff_waggle_app/design-files/screens/workspace.html`.
---
## 1. Current structure (with line refs)
`WorkspaceDesktopApp.tsx` is a fixed-layout shell (no drag/resize grid, per its header
comment lines 9-21). Composition top-to-bottom:
- **Tab model** — 8 tabs, `WorkspaceTabId` union + `TABS` array
(`WorkspaceDesktopApp.tsx:58-77`): `overview · chat · research · artifacts · memory ·
tasks · timeline · settings`. Each tab is `{ id, label, icon }` (lucide icons). **No
per-tab counts.**
- **Header** (`:759-811`) — flat row: `
` workspace name (`:761`), a **type pill**
(`:762-766`), a **status pill** (`statusPillClass`, `:767-772` + helper `:117-126`),
an inline "N running" agent indicator (`:773-778`), a **members avatar stack**
(`:783-798`, initials, max 5 + overflow), and the **`WorkspaceActionsMenu`**
(`:800-809`, rename/archive/restore/export/delete). **No breadcrumb. No hex avatar.
No meta row** (memories/sources/updated). **No "Memory"/"Continue" header buttons.**
- **Tab bar** (`:814-839`) — horizontal scroll nav, `role=tablist`. Active tab =
`border-primary` bottom border + `text-foreground` (`:827-831`); honey comes via the
`--primary` token so the underline is already honey-tinted. Icon + label per tab, **no
count badges**.
- **Body = main canvas + right context panel** (`:842-969`):
- **Overview** (`OverviewTab`, `:518-545`) — a **widget grid**
(`grid-cols-1 md:grid-cols-2 xl:grid-cols-3`, fixed 16rem row height, `:530-533`):
5 `WidgetCard`s — `ChatPreviewWidget` (read-only thread preview, `:184-232`),
`ArtifactsWidget` (file registry, `:241-283`), `TasksWidget` (pending+blocked from
state, `:286-333`), `MemoryHighlightsWidget` (decisions + "I remember", `:335-377`),
`ActivityWidget` (audit feed, `:379-401`). Empty-state when everything empty
(`:850-868`).
- **Other tabs** — `chat` renders the `chatSlot` or a deep-link placeholder
(`:882-902`); `tasks` → `` (`:904`); `memory` →
`` (`:937-941`); `research`/`timeline`/`settings`
are `` stubs (`:906-957`); `artifacts` is an inline file-card grid
or placeholder (`:914-932`).
- **Right context panel** (`WorkspaceInfoPanel`, `:405-514`, `hidden lg:flex w-72`):
Workspace block (name/desc + memory/session counts, `:422-439`), **Team members**
(`:441-460`, global roster — see §2), **Last activity** + "N agents running"
(`:462-481`), **Quick actions** (Open chat / View memory / Review tasks, `:483-511`).
- **Whole-screen states** — loading (`:691-698`), `permission`/`notfound`/`offline`
error variants (`:700-754`) via `FullScreenState` (`:1000-1018`), empty-workspace
(`:850-868`).
- **Route wrapper** (`WorkspaceRoute.tsx`) — `/workspaces/:workspaceId/:tab?`; the URL
drives `activeTab` (controlled seam §5.2a, `:43-54`); `onTabChange` navigates
(overview = bare `/workspaces/:id`, `:52-53`); `chatSlot` portals the live chat
(`:57`); URL→shell sync effect (`:34-38`). The 8 tabs are pinned in `WS_TABS`
(`:20-22`).
- **`WorkspaceBriefing.tsx`** (284 LOC) — a SEPARATE component, shown **inside ChatApp**
when a thread has no messages (header comment `:1-5`). Reads the SAME
`getWorkspaceContext` (`:57`). Holds "I Remember" / "Recent Decisions" / "Recent
Conversations" / suggested-prompt chips / stats bar. **It is not part of the Workspace
Desktop shell** — it's the chat empty-state. Useful as a copy/data reference for the
Overview's "What Waggle knows" rows but is not the screen being rebuilt. Its
collapsed-state persistence is `lib/workspace-briefing-state.ts` (tested — see §3).
---
## 2. Data contract — what's REAL vs mocked
The Overview's three data feeds come from real, populated endpoints (verified in
`packages/server/src/local/routes/workspaces.ts`). The DESIGN, however, surfaces several
fields the contract does NOT yet provide. Table below; "✔ real" = endpoint returns it
today, "✖ mock" = design shows it but no field exists, "~ derivable" = computable from
existing data.
### Endpoints + adapter methods (all real, no envelope)
| Adapter (`lib/adapter.ts`) | Route (`workspaces.ts`) | Returns |
|---|---|---|
| `getWorkspaceContext(id)` `:582` | `GET /:id/context` `workspaces.ts:364-655` | summary, recentMemories, recentDecisions, recentThreads, suggestedPrompts, `stats{memoryCount,sessionCount,fileCount}`, greeting, pendingTasks, workspace{type,status,description} |
| `getWorkspaceState(id)` `:601` | `GET /:id/state` `:663-693` | active/openQuestions/**pending**/**blocked**/completed/stale/recentDecisions/nextActions (WorkspaceStateView) |
| `getWorkspaceActivity(id,limit)` `:606` | `GET /:id/activity` `:699-742` | `{events:[{id,ts,type,actor?,summary}]}` from audit_events |
| `getWorkspaceFiles(id)` `:587` | `GET /:id/files` `:744-756` | `{files:[…]}` from file registry (interim "artifacts") |
| `getTeamMembers()` `:2160` | (global team roster) | `[{id,name,status,avatar?}]` — **NOT workspace-scoped** (`WorkspaceDesktopApp.tsx:660-666` TODO) |
### Design field → reality
| Design element (workspace.html / SCREENS §03) | Source | Status |
|---|---|---|
| **Header meta: "142 memories"** | `ctx.stats.memoryCount` | ✔ real |
| **Header meta: "9 sources"** | — | ✖ mock (no `sourceCount`; harvest sources exist in mind but not exposed on context) |
| **Header meta: "1 agent live"** | `useRoomState().workspaceMap.get(id).live.length` (`:603-607`) | ✔ real (live SSE) |
| **Header meta: "updated 2h ago"** | `ctx.lastActive` (`:592,646`) | ~ derivable (have `lastActive`; relativeTime helper exists `:135-147`) |
| **Tab counts (Chat 3 · Memory 142 · Artifacts 7 · Files 12 · Team 4)** | memory=`stats.memoryCount` ✔; sessions=`stats.sessionCount` ✔; files=`stats.fileCount` ✔; team=`getTeamMembers().length` ✔(global); **artifacts** ✖ (no distinct artifact entity — interim = files) | ~ mostly derivable; artifacts count is the gap |
| **Left: summary card** | `ctx.summary` (`composeWorkspaceSummary` `workspaces.ts:31-75`) | ✔ real |
| **"What Waggle knows" fact rows** | `ctx.recentMemories` (content+importance+date) and/or `ctx.recentDecisions` | ✔ content real; **✖ per-fact provenance `⬡ source · when`** (frames have no `source` surfaced — design's "web · mem0.ai", "teardown.md" are mock; `date` IS present so "·when" is real) |
| **"Recent work" artifact rows (ext tile + name + provenance + time)** | `getWorkspaceFiles` → `normalizeArtifacts` (`:977-998`): name, mimeType/modifiedAt | ✔ name+time real; **✖ provenance source** ; ext-tile derivable from filename |
| **Right Status: agent live + name** | `useRoomState` live list — name not in the view-model today | ~ "live" real, agent NAME ✖ mock |
| **Right Status: model "auto · Claude Sonnet"** | `ctx.workspace.model` (`workspaces.ts:625`) | ✔ real (model id; "auto" + friendly name is display) |
| **Right Status: "+6 today"** | — | ✖ mock (no per-day memory delta on context; overnight delta exists on Home `OvernightSummary.consolidated` but not per-workspace) |
| **Right Status: "3 to review / Needs review"** | `HomeBriefing.needsReviewCount` exists at HOME scope (J08); per-workspace ✖ | ✖ mock at workspace scope |
| **Right "Up next" rows** | `state.pending` + `state.blocked` + `state.nextActions` + cron `upcomingSchedules` | ~ derivable (TasksWidget already uses pending/blocked; nextActions+schedules unused on this screen) |
| **Right Team avatar rows** | `getTeamMembers()` | ✔ real but **global roster, not per-workspace** (documented TODO `:660-662`) |
**Net:** the three load-bearing columns (summary, knowledge/memory facts, recent
work/artifacts, status counts, up-next, team) are all **backed by real endpoints**. The
**provenance `⬡ source · when` line** (a core trust pattern, README §6) is the single
biggest data gap — memory frames have a `source` column server-side
(`workspaces.ts:291` writes `source`, `:413` SELECTs around it) but it is **not
projected into `recentMemories`/`recentDecisions`** today, nor onto file rows. "9
sources", "+6 today", per-workspace "N to review", and a distinct **artifacts** entity
(vs files) are genuinely absent.
---
## 3. Test contract
What PR3 must NOT break (existing tests touching this surface):
- **`apps/web/src/test/p1a-workspace-route.test.tsx`** — pins the **route ↔ shell
contract**, NOT the visual layout. Asserts `WorkspaceRoute` calls
`selectWorkspace(routedId)` on deep-link / Back-Forward, skips when already active,
and **never** syncs the `local-default` placeholder (`:56-80`). It mocks
`WorkspaceDesktopApp` to a stub (`:30-32`) and `ChatHost`/`ChatSlot` (`:33-36`). The
route renders under `path="workspaces/:workspaceId/:tab?"` (`:44`). **Constraint:** the
`:tab?` param, the `selectWorkspace`-on-route effect, and the `local-default` guard
must survive any rewrite of `WorkspaceRoute`.
- **`apps/web/src/lib/workspace-briefing-state.test.ts`** — pins the per-workspace
briefing-collapsed localStorage helpers (key prefix `waggle:workspace-briefing-
collapsed:`, sanitisation, per-id isolation, no colon-boundary leak). Only relevant if
the briefing collapse behavior is carried into the new Overview; the helpers themselves
can be reused as-is.
- **Implicit `data-testid` contract** (consumed by live-smoke / Playwright + the empty
flows): `ws-desktop-root`, `ws-tab-bar`, `ws-tab-`, `ws-tab-panel`,
`ws-overview-grid`, `ws-widget-{chat,artifacts,tasks,memory,activity}`,
`ws-info-panel`, `ws-status-pill`, `ws-members-stack`, `ws-agents-running`,
`ws-desktop-{loading,permission-denied,notfound,offline,retry}`, `ws-memory-tab`.
Changing the tab set (8→6) removes `ws-tab-{research,tasks,timeline,settings}` — grep
for those test ids before deleting (none appear in the two test files above, so the
risk is in untracked Playwright smokes, not unit tests).
There is **no test that pins the 8-tab set, the widget-grid layout, or the right-panel
contents** — so the Overview re-layout (grid → 2-col 1.7fr/1fr) and the tab reduction
are free to change as long as the route/shell contract and the load-bearing test ids are
preserved.
---
## 4. Design spec — Variation A (exact copy + structure)
From `SCREENS.md` §03 + `workspace.html` Variation A markup:
**Header** (`workspace.html:138-155`):
- **Breadcrumb** (`.crumbs`, mono 11.5px, `--text-dim`): `Home › **Competitive
Intelligence**` (current workspace bold).
- **46px hex avatar** (`.wmark.hex`, 46×52, honey gradient `--honey-bright → --honey-deep`,
`#1a1407` glyph) showing the workspace initial.
- **H1** title (Hanken 650, 28px, `-0.02em`).
- **Meta row** (`.wmeta`, 13px `--text-muted`, gap 14px): `● 1 agent live` (live dot
`--healthy`) · `142 memories` · `9 sources` · `updated 2h ago`.
- **Header actions** (right): `Memory` (ghost button) + `Continue →` (honey primary).
**Tab bar** (`.tabs`, `workspace.html:157-164`): `Overview · Chat 3 · Memory 142 ·
Artifacts 7 · Files 12 · Team 4`. Active tab = `--text` + **2px honey bottom-border**
(`.tab.on`, `:44`). Each count is a mono 11px `--text-dim` `.cnt` span.
**Overview content** — 2-col grid `1.7fr / 1fr`, gap 22px (`.grid`, `:49`):
- **Left col:**
- **Summary card** (`.card`, `.summary` 15.5px/1.6, honey-bold keywords). Exact sample
copy: *"This workspace tracks the **persistent-memory competitor landscape** for the
Q2 board cycle. Waggle has mapped **9 rivals**, pulled current pricing, and drafted a
teardown — the live thread is mid-flight on turning the opening into a board brief."*
- **"What Waggle knows"** section (`.sec-h` mono uppercase label + brain icon, `:177`)
→ `.knows` list of `.fact` rows (`:61-67`): each = a **30px hex check tile**
(honey-gradient, `#1a1407` checkmark) + fact text (honey-bold spans) + a
**provenance line** `⬡ **source** · when` (`.prov`, mono 10.5px, `.src` =
`--intel`). Sample facts (`:279-284`): "Mem0 is cloud-only and raised prices ~15%…"
(`web · mem0.ai · 2h ago`), "Only 2 of 9 rivals ship local-first memory."
(`teardown.md · 2h ago`), "Mara wants the board brief to lead with the regulated-
industries opening." (`chat · Tue · 2d ago`), etc.
- **"Recent work"** section (file icon, `:182`) → `.arts` list of `.art` rows
(`:71-78`): **32px ext tile** (tinted by type color, mono ext label e.g. `MD`/`XLS`/
`PDF`) + name (``) + subtitle + right-aligned mono `.when` time. Samples (`:288-291`):
`teardown.md` "Q2 competitive teardown · 9 competitors" `2h ago`,
`pricing-landscape.xlsx`, `mem0-teardown.pdf`. Rows hover → `--honey-line` border.
- **Right col** (three `.card`s):
- **Status** (`:188-194`): `.stat-line` rows — `Agent` → `● Research-synth · live`
(`--healthy`); `Model` → `auto · Claude Sonnet`; `Memories` → `142 **+6 today**`
(delta `--healthy`); `Needs review` → `3 memories` (`--attention`).
- **Up next** (`:196-201`): `.agentrow`s — colored dot + label + mono status: `Board
brief from teardown` (draft, `--work`); `Export table → Salesforce` (awaiting you,
`--attention`); `Weekly digest` (`17:00`, `--healthy`).
- **Team** (`:203-206`): `.person` rows — 30px round avatar (initial, colored bg) +
name + role: `Mara K. · Owner`, `Research-synth · Agent · live`, `Deck-builder ·
Agent · idle`, `Jonas P. · Editor`.
**Non-Overview tabs** in this concept pass are a single empty placeholder (`.emptytab`,
`:211-216`): *"This tab is wired in the full prototype — Overview is the focus of this
concept pass."* — i.e. the design only fully specs Overview; the other tabs route to the
**real existing screens** (see §6).
**Memory stays a tab** (SCREENS §03 emphatic, README §4/§5 table). Variation B
(memory-forward knowledge-graph as the default view, `workspace.html:219-265`) is an
**alternate to defer**, NOT this PR.
---
## 5. Gap table (current → design)
| # | Area | Current | Design (Var A) | Severity |
|---|------|---------|----------------|----------|
| G1 | **Tab set** | 8 tabs: Overview/Chat/**Research**/Artifacts/Memory/**Tasks**/**Timeline**/**Settings** (`:68-77`) | 6 tabs: Overview/Chat/**Memory**/Artifacts/**Files**/**Team** | HIGH — drop Research/Tasks/Timeline/Settings from the bar; add Files + Team; reorder |
| G2 | **Tab counts** | none | per-tab mono count (Chat N·Memory N·Artifacts N·Files N·Team N) | MED — wire from `stats` + members; artifacts count gap |
| G3 | **Header avatar** | none | 46px hex avatar, honey gradient, workspace initial | MED — reuse `.hex` clip-path + initialsOf |
| G4 | **Breadcrumb** | none | `Home › Workspace` mono crumb | LOW |
| G5 | **Header meta row** | type pill + status pill + "N running" | `● agent live · N memories · N sources · updated Xago` | HIGH — restyle to meta row; "sources" is a data gap (§2) |
| G6 | **Header actions** | WorkspaceActionsMenu (kebab) | `Memory` ghost + `Continue →` honey | MED — add the two buttons; KEEP the actions menu (real feature, not in mock) |
| G7 | **Overview layout** | 3-col equal-height widget grid | 2-col 1.7fr/1fr: left summary+knows+recent-work, right status+upnext+team | HIGH — full re-layout |
| G8 | **"What Waggle knows" rows** | `MemoryHighlightsWidget` (plain list, no provenance, no hex tile) | `.fact` rows: hex check tile + honey-bold text + `⬡ source · when` provenance | HIGH — new row component; **provenance source is a data gap** |
| G9 | **"Recent work" rows** | `ArtifactsWidget` (file icon + name) | `.art` rows: ext tile + name + subtitle + mono time + hover border | MED — restyle; ext-tile + provenance |
| G10 | **Status card** | scattered in right panel (counts + last activity) | one Status card: agent/model/memories+delta/needs-review | MED — consolidate; "+6 today" & "needs review" are data gaps |
| G11 | **Up next card** | none (tasks live in a widget + Tasks tab) | `.agentrow` list (draft/awaiting-you/scheduled) | MED — derivable from pending/blocked/nextActions/schedules |
| G12 | **Team card** | right-panel "Team members" (global roster) | Team card with role labels (Owner/Agent·live/Editor) | LOW — restyle; still global-roster-backed |
| G13 | **Right context panel** | persistent `w-72` aside (info/team/last-activity/quick-actions) | folded INTO the Overview right column; no separate aside | MED — the aside's content moves into the grid's right col |
| G14 | **Provenance pattern** | absent on this screen | `⬡ source · when` on every fact + artifact (core trust pattern) | HIGH (trust) — needs `source` projected from frames (§2) |
| G15 | **Tokens/typography** | shadcn `text-foreground/muted-foreground`, `font-display`, 10-12px dense | warm tokens, Hanken H1 28/650, 13-16px body, mono labels | MED — apply warm-Hive tokens (already shipped, §6) |
| G16 | **Memory tab** | `` already embedded (`:937-941`) | Memory stays a tab | ✅ already correct — keep |
---
## 6. Reuse + build — tab bar → existing routes
The 6 design tabs map cleanly onto surfaces that **already exist**; PR3 rebuilds the
SHELL + Overview, and the other 5 tabs embed/deep-link the real screens:
| Design tab | Maps to | Exists? | How to wire |
|---|---|---|---|
| **Overview** | `WorkspaceDesktopApp` Overview canvas | ✔ (re-layout) | Rebuild as 2-col grid; this is the bulk of PR3 |
| **Chat** | `ChatSlot` (live per-workspace chat) | ✔ — `WorkspaceRoute.tsx:57` already portals `` into the `chat` tab via the `chatSlot` seam (`WorkspaceDesktopApp.tsx:112,885`) | reuse as-is |
| **Memory** | `MemoryCenterTab` | ✔ — already embedded `WorkspaceDesktopApp.tsx:937-941` (`mind="workspace"`, `consumeDeepLinks={false}`) | reuse as-is (✅ G16) |
| **Artifacts** | `ArtifactsRoute`/Artifact Center | ✔ route exists (`routes/index.ts:36`); workspace tab currently inlines a file-card grid (`:914-932`) | embed the Artifact Center component, or keep the file grid until the real artifact entity lands (§2 gap) |
| **Files** | `FilesRoute` | ✔ route exists (`routes/index.ts:37`) | NEW tab — embed the Files surface scoped to the workspace; data via `getWorkspaceFiles` (already used) |
| **Team** | `TeamRoute` | ✔ route exists (`routes/index.ts:48`) | NEW tab — embed Team; or a workspace-scoped panel. NB roster is global today (§2) |
**Drop from the bar** (no longer top-level per design): **Research** (placeholder stub
only `:906-912`), **Tasks** (still a real `` `:904` — relocate to the
Overview "Up next" card + keep reachable, don't delete the component), **Timeline**
(stub `:943-948` — lives at `/settings/timeline` per `routes/index.ts:29`), **Settings**
(stub `:951-957` — lives at `/settings`). Update `WS_TABS` in `WorkspaceRoute.tsx:20-22`
and the `WorkspaceTabId` union to the 6-tab set; unknown `:tab?` already falls back to
overview (`WorkspaceRoute.tsx:43-45`), so stale `/workspaces/:id/tasks` links degrade
gracefully.
**Reuse without change:**
- **Warm tokens already shipped** (PR1): `apps/web/src/index.css` defines `--honey`,
`--honey-wash`, `--honey-line`, `--honey-bright/-deep`, `--intel`, `--work`,
`--healthy`, `--bg`, `--surface*`, `--text*` (verified `:149-167`) + the shadcn HSL
core derives from them (`--primary` = honey `:29`). The Overview can use these
directly. `apps/web/src/waggle-theme.css` is the companion sheet.
- **Helpers in-file:** `initialsOf` (`:128-133`, for hex avatar + team), `relativeTime`
(`:135-147`, for "updated Xago" + artifact times), `normalizeArtifacts` (`:977-998`),
`humanizeActivitySummary` (`lib/activity-labels`).
- **Hex motif:** README §7 `.hex { clip-path: polygon(...) }` — add a Tailwind/utility
class for the 46px header avatar + the 30px fact check-tiles + 32px ext tiles.
- **Data plumbing:** the existing `useEffect` load (`:613-679`) already fetches context/
state/activity/members/files — **no new adapter calls needed** for Overview except
optionally projecting `source` onto memory rows (server change, §2/G14) and a per-
workspace artifacts/sources count (G2/G5).
- **`WorkspaceBriefing.tsx`** importance pills + "I Remember"/"Recent Decisions" copy
(`:181-199,164-178`) are a good reference for the "What Waggle knows" fact rows, but
the briefing itself stays the chat empty-state — don't fold it into the shell.
**Build new:** the 2-col Overview grid + `FactRow` (hex check tile + provenance),
`ArtRow` (ext tile + provenance + time), `StatusCard`, `UpNextCard`, `TeamCard`, the
46px hex header avatar + breadcrumb + meta row, and the `Memory`/`Continue →` header
buttons. Fold the current right `aside` content into the grid's right column (G13).