Server-side icon cache (lib/health/icons.js + GET /api/icons/:slug.png) fetches each icon once from dashboard-icons into a persistent dir and serves it from the LAN. Browser never contacts the CDN; slug sanitized; first-letter fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
11 KiB
Void 2.0 — Plan 6: Sacred Valley Widgets — Design Spec
Status: APPROVED 2026-06-02. Design-led phase; brainstormed with the
frontend-designskill and the visual companion. Next step:writing-plans. Supersedes the prep briefdocs/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:
- Data cards — a draggable, reorderable set of widget cards.
- 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 | Cached locally, server-side. The server fetches each needed icon once from dashboard-icons into a persistent cache and serves it from the LAN. Browser never hits the CDN; no per-request slug leak. First-letter fallback on miss. |
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_layouttable, 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(orspeedtestOokla CLI) must be present on the worker box;push-workers.sh/ deploy notes updated accordingly./api/weathercaches 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--lbglow). 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 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 aservice_statustable:{ 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="/api/icons/<slug>.png">served from the local cache §6.6; slug =iconor slugifiedname; first-letter<div>fallback ononerror), status dot (ok/warn/down), name, host, hover "open ↗" link →url. - Grouped sections with header + "X/Y healthy" + divider, refreshed every 60s.
6.6 Local icon cache (no CDN leak)
lib/health/icons.js+ a persistent cache dirICON_CACHE(default/var/lib/void/icons, like the blob store — survives deploys).GET /api/icons/:slug.pngserves the cached file. On a cache miss, the server fetches<slug>.pngonce from dashboard-icons (cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/<slug>.png), validates it's a PNG, writes it to the cache, then serves it. Subsequent requests are local.- Slug is constrained to
[a-z0-9-](no path traversal, no arbitrary upstream). - On upstream 404/error, respond 404 → the tile falls back to the first-letter badge. Only the server ever contacts the CDN, once per distinct icon.
- "As needed": icons are fetched lazily on first reference, not bulk-synced.
7. Theme tokens
Add to public/style.css :root:
- Reuse
--accent/--accent-dim/--panel/--borderfor 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/hostresponse 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_layoutrepo: get/put + owner scoping./api/health/servicesgrouping + healthy counts.- icon cache: miss → fetch-once → cached file served; slug sanitization rejects traversal/invalid chars; upstream 404 → 404 (browser falls back).
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
innerHTMLfrom data.
Security: confirm the health checker rejects targets outside the registry.
9. Build order (design risk → integration risk)
- Grid framework + card contract + refined-B chrome + reorder + layout persistence, proven on clock / weather / host-perf.
- Reuse cards — jobs / inbox / search.
- speedtest (worker + table + cron).
- Little Blue health band (registry + engine + tiles + icons + avatar placeholder).
10. Release
- Version →
2.0.0-alpha.8(bumpserver.jsVERSION const +package.json+ CHANGELOG). - New migrations:
dashboard_layout,service_status,speedtest_results. - Standard deploy: snapshot CT 310 + 311,
deploy/push.sh, runnpm run migrate, refresh workers ifpush-workers.shchanged, 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: pre-seed/vendor the icon cache at deploy (fully offline, no first-use CDN fetch) and a periodic refresh.