# Clawmates Frontend — Implementation & Wiring Handoff > **Audience:** the build agent (or engineer) integrating these screens into the Clawmates > platform (the Rust workspace + Next.js app) and feeding them live from the durable runner. > > **What this package is:** every Clawmates screen as a self-contained HTML file, plus a > dependency-free real-time bridge and client adapter that make them update live. Read this > file once, top to bottom, before wiring anything. Everything runs **offline in demo mode** > with synthetic data, so you can see each screen breathe before touching the backend. --- ## 0. Package layout ``` handoff/ ├── IMPLEMENTATION.md ← this file ├── README.md ← 60-second start ├── install.sh ← idempotent installer (Node check, static server, .env) ├── .env.example ← copy → .env for live mode ├── screens/ ← the UI. Open any .dc.html directly in a browser — no build step. │ ├── Clawmates World.dc.html │ ├── Clawmates Observe.dc.html │ ├── Clawmates Dashboard.dc.html │ ├── Clawmates Infrastructure.dc.html │ ├── Clawmates Auth.dc.html │ ├── Clawmates Landing.dc.html │ └── support.js ← the .dc runtime (MUST sit next to the .dc.html files) └── realtime/ ├── server.mjs ← demo + live SSE bridge (Node ≥20, zero deps) ├── clawmates-live.js ← browser client adapter → window.ClawmatesLive └── events.schema.json ← the Clawmates Event Taxonomy (the contract) ``` ### What a `.dc.html` file is Each screen is a **Design Component**: a single HTML file whose markup + a small logic class are rendered by the bundled `support.js` runtime. There is **no build step and no npm install to view them** — open the file in a browser, or serve the `screens/` folder statically. `support.js` must sit beside the `.dc.html` files (it does, in `screens/`). To edit a screen's behavior you change two regions inside the file: the `` template (markup, inline-styled) and the `class Component extends DCLogic { … }` block (state + a `renderVals()` that returns the values the template binds to). All live data flows into a screen by calling `this.setState(...)` from a `ClawmatesLive.on(...)` handler — see §3. --- ## 1. The screens | Screen | Purpose | Primary live inputs (see §3 taxonomy) | |---|---|---| | **World** | The "Large World" map in three formations: **Hierarchy** (org▸company▸team▸claw tree), **Flat** (peer mesh), **Live** (Gource-style convergence on a ``). | `topology.update`, `world.touch`, `node.activity`, `agent.status` | | **Observe** | Linked observation deck — **Agent** close-up (live reasoning + the agent's live computer screen) and **System** mission-control (both claws, comms, routines, telemetry). | `agent.task.update`, `agent.reasoning.delta`, `agent.computer.frame`, `agent.message`, `telemetry`, `routine.update`, `door.request` | | **Dashboard** | Tiered admin: org→company→team→claw with topology morph + the per-claw "anatomy" (brain compartments) and the agent's computer. | `agent.status`, `topology.update`, `routine.update` | | **Infrastructure** | Fleet management. **Local & Tailscale first** (host health cards, tailnet device list, 3-step Connect-a-host wizard), then **Cloud** (AWS/GCP/Apple silicon/Hetzner/DigitalOcean connect + provision). | `host.health`, `host.status`, `tailscale.devices`, `cloud.runner` (see §5) | | **Auth** | Clerk-style sign-in / sign-up with Google / Apple / GitHub OAuth + email. | n/a (wire to Clerk — see §6) | | **Landing** | Marketing page (deploy ladder, 12 topologies, §15 safety, self-host). | static | > **Canonical entry point for the product app:** World, Observe, Dashboard, and Infrastructure are > the in-product surfaces. Auth gates them; Landing is public. ### 1a. Per-screen integration seams (where synthetic becomes live) Each screen ships with a built-in synthetic animation so it looks alive offline. To make it *real*, register handlers in the logic class' `componentDidMount()` and feed `setState`. The exact seams: - **World → `_startLive()`** (the canvas engine). It holds `targets[]` (project/service/event nodes) and `agents[]` whose `.target` is currently chosen on a random timer. Replace the random retarget with **`world.touch`**: on each event, set the matching agent's `.target` to the index of `nodeId` and force a beam; the node-heat/glow already keys off "hot". Rebuild `targets[]` from `topology.update`. Hierarchy/Flat node lists also come from `topology.update`. - **Observe → Agent mode.** Bind the WORKING-ON-NOW card to `agent.task.update` (replace), the REASONING STREAM to `agent.reasoning.delta` (append), the live computer screen to `agent.computer.frame` (replace). The door toast binds to `door.request` / `door.resolve`. - **Observe → System mode & Dashboard.** Telemetry pills ← `telemetry`; comms list ← `agent.message`; routines/loops ← `routine.update`; status dots ← `agent.status`. - **Infrastructure.** Host health cards ← `host.health` + `host.status`; the Tailscale device list ← `tailscale.devices`; cloud runners ← `cloud.runner`. The Connect-a-host and Connect-a-provider wizards are driven by the daemon-pairing and provisioning APIs in §5. Every seam is a few lines: `ClawmatesLive.on('world.touch', e => engine.touch(e.agentId, e.nodeId))`. No markup changes — you are feeding values into components that already exist. --- ## 2. Run it (60 seconds, no backend) ```bash chmod +x install.sh && ./install.sh # Terminal 1 — synthetic event bridge (cd realtime && CLAWMATES_MODE=demo node server.mjs) # Terminal 2 — serve the screens caddy file-server --root ./screens --listen :8080 # or: npx serve ./screens -l 8080 ``` Open **http://localhost:8080/Clawmates%20World.dc.html?live=http://localhost:8420/live**, switch to the **Live** formation, and the agents converge on events streamed from the bridge. Without `?live=`, each screen runs its built-in synthetic animation (safe fallback). The `?live=` param (or `window.CLAWMATES_LIVE_URL`) is read by `clawmates-live.js`. --- ## 3. Real-time architecture & the Event Taxonomy The runner emits many internal events; the **bridge** (`realtime/server.mjs`) normalizes them into a small, stable taxonomy and fans them out as one SSE feed. The browser only ever sees these. Full JSON Schema: `realtime/events.schema.json`. Human summary: **Agent work** — `agent.status` `{agentId,status,role}` · `agent.task.update` `{agentId,taskId,title,elapsedMs,steps[]}` · `agent.reasoning.delta` `{agentId,text}` (append) · `agent.computer.frame` `{agentId,app,url?,lines[]}`. **Tools, doors & §15** — `agent.tool.call` `{agentId,tool,target,doorRequired}` · `door.request` `{doorId,agentId,action,target,summary}` · `door.resolve` `{doorId,decision,by}`. **Collaboration & world** — `agent.message` `{fromAgentId,toAgentId,text,ts}` · `world.touch` `{agentId,nodeId,kind}` (the Gource retarget) · `node.activity` `{nodeId,label,kind,heat}` · `topology.update` `{formation,nodes[],edges[]}`. **System** — `telemetry` `{tokensPerMin,costPerHr,loops,doorsPending}` · `routine.update` `{routineId,name,kind,owner,schedule?,progress?,state}`. **Infrastructure (new — see §5)** — `host.status` `{hostId,status}` · `host.health` `{hostId,cpu,ram,memPressure,disk,load,containers}` · `tailscale.devices` `{tailnet,devices[]}` · `cloud.runner` `{provider,id,region,size,status,cpu,ram,agents,costPerHr}`. ### The integration seam in the bridge `normalize(evt, payload)` in `server.mjs` translates **one raw runner event → zero+ taxonomy events**. The upstream event names there (`turn.started`, `tool.invoked`, `runner.telemetry`, …) are placeholders — **reconcile them with the actual cm-api SSE shapes.** Start by mapping just `world.touch`, `agent.status`, and `telemetry`; that alone brings World and the top bars alive. Add the rest incrementally. Unknown events are ignored, so the bridge is forward-compatible. ### The client adapter (`clawmates-live.js`) Include it in a screen (or inject via the Next proxy). It opens an `EventSource`, dispatches each event to `ClawmatesLive.on(type, fn)` handlers **and** a `window` `CustomEvent('clawmates:')`, and **replays the last value per stateful key** so a late-loading screen paints current state immediately (mirrors the runner's checkpoint-resume). If no live URL is set, it is inert. > **Security invariant — carry it through.** The bridge is **read-only** on the runner stream. It > must never hold or forward secret-broker credentials, and it must expose **no** door-approval > endpoint to the browser. Door *approvals* stay on the authenticated cm-api path; the bridge only > *renders* that a door is pending so the UI can badge it. --- ## 4. Wiring to cm-api [INTEGRATES WITH cm-api] Live mode points the bridge at the runner's SSE gateway and exchanges a session for a bearer token, exactly like the Next.js `/api` proxy. `.env`: ```ini CLAWMATES_MODE=live CM_API_SSE_URL=http://127.0.0.1:8080/v1/runs/stream # cm-api streaming gateway CM_API_TOKEN=sk-... # read-only, scoped to run events LIVE_PORT=8420 LIVE_ALLOWED_ORIGINS=http://localhost:8080 # the screens' origin (CORS) ``` **Deployment shapes:** - **A) Standalone:** serve `screens/` with Caddy; run the bridge as its own systemd service beside the product. Good for an ops/observability surface. - **B) In-app (tightest):** mount each screen in the Next.js app via `