diff --git a/docs/superpowers/specs/2026-06-02-void-v2-plan6-sacred-valley-design.md b/docs/superpowers/specs/2026-06-02-void-v2-plan6-sacred-valley-design.md new file mode 100644 index 0000000..310ea51 --- /dev/null +++ b/docs/superpowers/specs/2026-06-02-void-v2-plan6-sacred-valley-design.md @@ -0,0 +1,203 @@ +# Void 2.0 — Plan 6: Sacred Valley Widgets — Design Spec + +> Status: APPROVED 2026-06-02. Design-led phase; brainstormed with the +> `frontend-design` skill and the visual companion. Next step: `writing-plans`. +> Supersedes the prep brief `docs/plan-6-brainstorm-brief.md`. + +## 1. Purpose + +Bring the Sacred Valley homelab dashboard to Void 2.0, on v2's real backend. +The `#/sacred-valley` route currently holds a stub (`public/views/sacred_valley.js`); +this plan replaces it with a working dashboard composed of **two distinct bands**: + +1. **Data cards** — a draggable, reorderable set of widget cards. +2. **Little Blue's Health band** — a separate, fixed zone owned by the Little Blue + agent persona, showing homelab service health & uptime. + +The two bands are kept visually and structurally separate, by explicit request. + +## 2. Scope + +### In scope (ships as `2.0.0-alpha.8`) +- The two-band Sacred Valley view + grid framework + drag-to-reorder. +- Seven data cards: clock, weather, host-perf, speedtest, jobs, inbox, search. +- Little Blue **read-only** Health band: config-file service registry, a pg-boss + health-check engine, grouped status tiles with auto-pulled icons, links, and a + placeholder avatar/identity. +- Server-side, owner-scoped layout persistence. + +### Explicit non-goals (deferred) +- Little Blue as a **fix-it agent** — chat + repair/Proxmox tools. Belongs with the + later Yerin/Orthos agent-wiring phase. +- DB-backed and Proxmox-auto-discovery service registries (future upgrade path). +- Multi-host host-perf (Plan 6 monitors CT 311 only; "other hosts up?" is the + health band's job). +- Per-agent hue theming, `--rail-accent`, final Little Blue avatar art. +- Agent-output cards (Dross briefing / Yerin pulse / Orthos council). + +## 3. Decisions (from the brainstorm) + +| Decision | Choice | +|---|---| +| Grid engine | Custom CSS-grid auto-flow + hand-rolled drag-to-reorder. **No resize, zero deps.** Sizes are S/M/L presets (column spans 1/2/3). | +| Scope of dashboard | One **global** homelab dashboard (not per-Space). | +| Layout persistence | **Server-side**, owner-scoped, so desktop ↔ phone sync. localStorage mirrors for instant paint. | +| Card chrome | **"Refined B"** — dark panel, faint engraved texture, accent-underlined Cinzel title, low blackflame glow that **intensifies on hover**. | +| Real-time | **Per-card polling**, no new SSE. | +| Service registry (LB) | **Config file** now; DB-backed + Proxmox auto-discovery are noted future upgrades. | +| Little Blue fix-it tools | **Deferred** to a later agent phase. | +| Service icons | Auto-pulled from the walkxcode/dashboard-icons CDN, first-letter fallback (parity with v1). | + +## 4. Architecture + +### 4.1 View structure +`public/views/sacred_valley.js` (replaces stub) renders into `#main`: +- `#sv-cards` — the data-card band (draggable). +- `#sv-health` — Little Blue's health band (fixed). + +A small scheduler starts each card's refresh timer on view-mount and clears all +timers on unmount (route change), so polling never leaks across views. + +### 4.2 Card component contract +`public/components/sv_card.js` is a factory producing the refined-B chrome +(`.sv-card` + theme tokens). Each data card is a self-contained module in +`public/views/cards/` exporting a uniform interface: + +```js +export default { + id: 'host-perf', + title: 'Host Perf', + size: 'm', // 's' | 'm' | 'l' → grid column span 1 | 2 | 3 + mount(bodyEl) { /* build DOM via safe el()/mount() — never innerHTML from data */ }, + start() { /* begin refresh interval */ }, + stop() { /* clear interval */ } +}; +``` + +One module per card — each independently understandable and testable. All DOM is +built with the existing safe `el()/mount()`/`safeHref()` helpers (Plan 2 safe-DOM +invariant: no `innerHTML` from API data). + +### 4.3 Grid + reorder +CSS-grid auto-flow; S/M/L = column spans. A hand-rolled pointer-drag reorder on +`#sv-cards` (no resize). Reordering updates the in-memory order and persists it +(§4.4). The health band is not draggable. + +### 4.4 Layout persistence +- Migration: `dashboard_layout` table, owner-scoped, single logical row: + `{ order: text[]/jsonb, hidden: jsonb, sizes: jsonb }` + `updated_at`. +- API: `GET /api/dashboard/layout`, `PUT /api/dashboard/layout` (owner bearer). +- Client: localStorage mirror for first paint; server is source of truth. + +## 5. Data cards + +| Card | Size | Source | Backend | +|---|---|---|---| +| clock | S | Client only. Melbourne primary; optional secondary TZ. | none | +| weather | S | Open-Meteo, Melbourne, no key. | **new** `GET /api/weather` — server proxy, 15-min cache | +| host-perf | M | CT 311 `/proc` CPU/RAM/disk/net, 30s. | **new** `GET /api/host` — port v1 `lib/resources.js` → `lib/host/resources.js` | +| speedtest | M | Latest + ~30-bar history. | **new** `speedtest_results` migration + pg-boss recurring `speedtest` job (hourly `speedtest-cli`) + `GET /api/speedtest/history` + `POST /api/speedtest/run` (enqueue) | +| jobs | M | pg-boss states — counts by state + recent. | **reuse** Jobs API | +| inbox | S | Pending changes awaiting approval — count + recent; links `#/inbox`. | **reuse** pending_changes API | +| search | L | Spotlight: type → results → navigate. | **reuse** `GET /api/search` (FTS+vector RRF) | + +Notes: +- `speedtest-cli` (or `speedtest` Ookla CLI) must be present on the worker box; + `push-workers.sh` / deploy notes updated accordingly. +- `/api/weather` caches server-side (15 min) to avoid hammering Open-Meteo. + +## 6. Little Blue Health band + +### 6.1 Identity +- `public/components/littleblue_avatar.js` — **placeholder** blue humanoid + water-creature (inline SVG, cyan `--lb` glow). Marked placeholder; final art later. +- Header subtitle: "Health & Uptime of the lab". + +### 6.2 Service registry (config file) +`config/services.yaml`, authored fresh — **v1 tile titles are NOT inherited** +(they were mislabeled); every entry gets a correct title here. + +```yaml +- id: gitea + name: Gitea + category: infrastructure # agents | infrastructure | media + host: ct105 # display label on the tile + url: http://192.168.1.223:3000 # link target — "how to get there" + icon: gitea # optional; defaults to slugified name + check: + type: http # http | tcp + # url defaults to `url`; expect 2xx–3xx; short timeout +``` + +Group render order: **Agents → Infrastructure → Media → other**. Each group header +shows a "X/Y healthy" count. The build seeds the file with the real homelab +services for the user to correct. + +### 6.3 Health-check engine +- A **Node pg-boss recurring job** `health.check` (every ~60s) pings each service + (HTTP/TCP, short timeout) from void-server and writes a cache row to a + `service_status` table: `{ service_id, status, latency_ms, detail, checked_at }`. +- Status semantics: `ok` = check passed · `warn` = reachable but slow/degraded · + `down` = unreachable/error. +- Decoupled from page load → the band renders instantly from cache. +- The checker only fetches URLs from the operator-authored registry (no user + input) — pinned to the configured list (SSRF surface is operator-controlled). + +### 6.4 API +- `GET /api/health/services` → services grouped by category, each with cached + status + per-group healthy counts. +- `POST /api/health/check` → enqueue an immediate pass (owner-only). Backs the + "Run checks" button. + +### 6.5 Rendering +- `public/views/health_band.js` + `public/components/service_tile.js`. +- Tile: auto-icon (``, + slug = `icon` or slugified `name`, first-letter `
` fallback on `onerror`), + status dot (ok/warn/down), name, host, hover "open ↗" link → `url`. +- Grouped sections with header + "X/Y healthy" + divider, refreshed every 60s. + +## 7. Theme tokens +Add to `public/style.css :root`: +- Reuse `--accent` / `--accent-dim` / `--panel` / `--border` for refined-B chrome. +- Add `--lb: #7dd3d8` (Little Blue cyan) for the health band. +- A commented per-agent-hue block (Dross/Yerin/Orthos) reserved, unused this plan. + +## 8. Testing (TDD, real DB per v2 norms) +Backend (vitest): +- weather proxy cache behavior (mock fetch). +- `/api/host` response shape. +- speedtest repo + `/api/speedtest/history`. +- health engine status computation (ok/warn/down from mocked fetch/connect). +- registry loader: parse + group + ordering + icon-slug derivation. +- `dashboard_layout` repo: get/put + owner scoping. +- `/api/health/services` grouping + healthy counts. + +Frontend (pure-logic units): +- card module contract (each exports id/title/size/mount/start/stop). +- reorder ordering logic. +- category ordering (Agents→Infra→Media→other). +- safe-DOM: no `innerHTML` from data. + +Security: confirm the health checker rejects targets outside the registry. + +## 9. Build order (design risk → integration risk) +1. Grid framework + card contract + refined-B chrome + reorder + layout + persistence, proven on **clock / weather / host-perf**. +2. Reuse cards — **jobs / inbox / search**. +3. **speedtest** (worker + table + cron). +4. **Little Blue health band** (registry + engine + tiles + icons + avatar + placeholder). + +## 10. Release +- Version → `2.0.0-alpha.8` (bump `server.js` VERSION const + `package.json` + + CHANGELOG). +- New migrations: `dashboard_layout`, `service_status`, `speedtest_results`. +- Standard deploy: snapshot CT 310 + 311, `deploy/push.sh`, run `npm run migrate`, + refresh workers if `push-workers.sh` changed, verify `/health` = alpha-8. + +## 11. Future (out of this plan) +- Little Blue fix-it agent (chat + repair/Proxmox tools). +- DB-backed + Proxmox-auto service registry. +- Multi-host metrics; per-agent theming; final avatar art; agent-output cards. +- Optional self-hosted service-icon set (drop the CDN dependency).