# Chat Switchboard UI Rewrite — Design Document
**Version:** 0.0.1 (draft)
**Date:** 2026-03-20
**Scope:** Scorched earth rebuild of frontend on Preact+htm
---
## 1. Survey Findings
### What Exists
| Metric | Value |
|--------|-------|
| JS files | 55 |
| Total JS lines | ~29,350 |
| CSS files | 22 (~203KB) |
| Go templates | ~1,920 lines across 14 template files |
| `innerHTML =` assignments | 294+ (91 in ui-admin.js alone) |
| `querySelector` / `getElementById` calls | 600+ (130 in ui-admin.js) |
| Direct `fetch()` outside api.js | ~24 calls across 10 files |
| Vendor deps | marked, DOMPurify, mermaid, KaTeX, CM6 (esbuild bundle) |
### The SDK (switchboard-sdk.js)
- ~860 lines, version-tagged `v0.28.5` in code but `v0.30.2` in docs
- Thin wrapper over globals: `API`, `Events`, `Theme`, `UserMenu`
- Delegates to `API._get()`, `API._post()`, etc. — not its own fetch layer
- Has pipe/filter pipeline (pre-send, stream, render) — this is good and reusable
- Has event bus bridge — good, reusable
- Has theme control — good, reusable
- No RBAC. Only `sw.isAdmin` (boolean derived from `API.user.role`)
- No namespaced domain methods (e.g., `sw.api.channels.list()`)
### The Registry (sb.js)
- Action registry: `sb.register(name, fn)` + `sb.ns(name, obj)`
- Dual-write to `window[name]` for backward compat
- Go templates call `sb.call('action', args)` via onclick handlers
- 55 files all register into this flat global space
### Server Templates
- Go templates generate initial DOM with hardcoded IDs
- JS hydrates by ID lookups (`getElementById`, `querySelector`)
- Templates inject `window.__USER__`, `window.__PAGE_DATA__`, `window.__SURFACE__`
- This server-rendering + client-hydration pattern is deeply coupled
### What's Missing for RBAC
- **No user-facing permissions endpoint.** `GET /admin/users/:id/permissions`
is admin-only. There is no `GET /profile/permissions` or equivalent. The
login/refresh response includes only `role: "user"|"admin"`, not the
resolved permission set.
- The frontend has zero permission-level gating. Everything is gated by
`API.isAdmin` (binary) or by server-side template conditionals.
### Dependency Pipeline
- `Dockerfile.frontend`: Stage 1 `npm pack` pulls marked, DOMPurify,
mermaid, KaTeX. Stage 2 builds CM6 via esbuild. Stage 3 copies into
nginx image.
- Preact + htm would be added to Stage 1 as additional `npm pack` targets.
- No bundler for app code — scripts loaded sequentially via `
```
Application files continue to load as `