Files
Void-Homelab/docs/superpowers/specs/2026-06-02-void-v2-plan6-sacred-valley-design.md
root f2f1ee4b10 docs: Plan 6 (Sacred Valley widgets) design spec
Two-band dashboard: draggable data cards (clock/weather/host-perf/speedtest/
jobs/inbox/search) + Little Blue read-only Health band (config registry +
pg-boss health engine + grouped service tiles w/ auto-icons). Refined-B chrome,
server-side layout persistence, polling refresh. Fix-it agent deferred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 22:03:11 +10:00

9.6 KiB
Raw Blame History

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:

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.jslib/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.jsplaceholder 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.

- 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 2xx3xx; 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 (<img loading="lazy" src="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/<slug>.png">, slug = icon or slugified name, first-letter <div> 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).