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

275 lines
18 KiB
Markdown

# 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?