Files
RuView/dashboard/src/app.css
ruv 39ec05edcb feat(dashboard): nvsim Vite+Lit dashboard with WASM transport + App Store [ADR-092]
End-to-end implementation of the operator dashboard for the nvsim
NV-diamond magnetometer simulator. Vite 5 + TypeScript strict + Lit 3,
~93 KB gzipped JS budget, runs the *real* nvsim Rust crate compiled to
wasm32-unknown-unknown inside a dedicated Web Worker.

Validated end-to-end with `npx agent-browser`:
- WASM module boots, build version + magic 0xC51A_6E70 reported
- Reference witness verifies byte-identical to Proof::EXPECTED_WITNESS_HEX
  cc8de9b01b0ff5bd97a6c17848a3f156c174ea7589d0888164a441584ec593b4
- Pipeline runs at ~1.88 kHz on x86_64 dev hardware (4500x over Cortex-A53)
- Zero browser console errors; only Lit dev-mode warning (expected)

## nvsim crate (additive)
- New `wasm` feature flag with wasm-bindgen 0.2 / serde-wasm-bindgen 0.6
- src/wasm.rs: WasmPipeline wrapper + referenceSceneJson +
  expectedReferenceWitnessHex + referenceWitness + hexWitness exports
- crate-type = ["cdylib", "rlib"] so native + wasm both build
- rand = { default-features = false } drops getrandom OS-entropy path,
  preserving the crate's WASM-ready posture
- Native: 50/50 tests still pass, witness unchanged

## dashboard/ (new package)
- Vite 5 + TypeScript strict, Lit 3 elements, signals-based store
- 12 Lit components mirroring the mockup zones (rail, topbar, sidebar,
  scene SVG with draggable sources + NV crystal, inspector tabs
  Signal/Frame/Witness, console with REPL + filter tabs, settings
  drawer, modals, ⌘K command palette, debug HUD, toast, app-store)
- IndexedDB persistence (theme, density, motion, app activations)
- WasmClient → Web Worker → wasm-pack-built nvsim WASM module
- NvsimClient TS interface — same shape covers future WsClient transport
- MagFrame parser (60-byte LE layout matching nvsim::frame)

## App Store (ADR-092 §14a — added during impl)
- Catalog of all 65 wifi-densepose-wasm-edge modules + nvsim
- 13 categories with event-ID-range labels
- Per-app metadata: id/name/category/crate/summary/events/budget/
  status/adr/tags
- Fuzzy search, category + status filters, IndexedDB-backed activation
- ADR-092 §14a documents the registry contract and per-app schema

## Build pipeline
- wasm-pack build crates/nvsim --target web outputs to
  dashboard/public/nvsim-pkg/ (60 KB pkg, 162 KB unoptimized .wasm)
- npm run build → 93 KB gzip JS, well under 300 KB budget
- ts.config strict, npx tsc --noEmit clean
- Vite worker correctly loads WASM via dynamic import resolving
  against worker origin

## E2E validation
- agent-browser open → 4-zone grid renders correctly in dark theme
- Run button → live B-vector trace, |B| readout updates, FPS counter
- App Store → all 66 apps listed with toggles, fuzzy search filters
  to "Ghost hunter" on "ghost" query
- Witness verify → green check, console logs "determinism gate ✓"
- Console errors: zero (only expected Lit dev-mode warning)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-26 19:22:04 -04:00

93 lines
2.6 KiB
CSS

/* nvsim dashboard — global styles
Ported from `assets/NVsim Dashboard.zip` per ADR-092 §7.1.
Per-component scoped styles live in each Lit element. */
:root {
--bg-0: #07090d;
--bg-1: #0d1117;
--bg-2: #131a23;
--bg-3: #1a232f;
--line: #1f2a38;
--line-2: #2a3848;
--ink: #e6edf3;
--ink-2: #b8c2cc;
--ink-3: #7c8694;
--ink-4: #4a5462;
--accent: oklch(0.78 0.14 70);
--accent-2: oklch(0.78 0.12 195);
--accent-3: oklch(0.72 0.18 330);
--accent-4: oklch(0.78 0.14 145);
--warn: oklch(0.7 0.18 35);
--ok: oklch(0.78 0.14 145);
--bad: oklch(0.65 0.22 25);
--grid: rgba(255, 255, 255, 0.04);
--shadow: 0 20px 60px -20px rgba(0, 0, 0, 0.6),
0 4px 12px -4px rgba(0, 0, 0, 0.4);
--radius: 12px;
--radius-sm: 8px;
--mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--sans: 'Inter', system-ui, -apple-system, sans-serif;
}
[data-theme="light"] {
--bg-0: #f4f5f7;
--bg-1: #fbfbfc;
--bg-2: #ffffff;
--bg-3: #f0f2f5;
--line: #e3e7ec;
--line-2: #d1d7de;
--ink: #11161d;
--ink-2: #38424f;
--ink-3: #6b7684;
--ink-4: #9ba4b0;
--grid: rgba(0, 0, 0, 0.05);
--shadow: 0 12px 40px -16px rgba(15, 30, 55, 0.18),
0 2px 8px -2px rgba(15, 30, 55, 0.08);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
font-family: var(--sans);
background: var(--bg-0);
color: var(--ink);
font-size: 14px;
line-height: 1.45;
overflow: hidden;
height: 100vh;
-webkit-font-smoothing: antialiased;
letter-spacing: -0.005em;
}
button { font-family: inherit; color: inherit; cursor: pointer; }
input, select { font-family: inherit; color: inherit; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }
@keyframes pulse { 50% { opacity: 0.5; } }
@keyframes dash { to { stroke-dashoffset: -200; } }
@keyframes float-up {
0% { opacity: 0; transform: translateY(8px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes diamond-spin {
0% { transform: rotateY(0) rotateX(8deg); }
100% { transform: rotateY(360deg) rotateX(8deg); }
}
@keyframes spin { to { transform: rotate(360deg); } }
body.reduce-motion *,
body.reduce-motion *::before,
body.reduce-motion *::after {
animation: none !important;
transition: none !important;
}
/* Density (set via class on <body> by setDensity()) */
body.density-comfy { font-size: 15px; }
body.density-default { font-size: 14px; }
body.density-compact { font-size: 13px; }