feat(dashboard): iter G+H+I + P0.10 — modal forms, a11y pass, drag persistence, REPL history

Closes ADR-093 P0.10, P1.2, P1.6, P1.7, P2.1, P2.2, P2.3, P2.5.

## Iter G — modal contents (P1.6)
- nv-palette "New scene…" now opens a 5-field form (name, dipole
  moment, heart→sensor distance, ferrous toggle, 60 Hz mains toggle).
  On Apply: builds a real Scene JSON and pushes via client.loadScene().
- nv-palette "Export proof bundle…" now calls client.exportProofBundle()
  and triggers a real blob download with a timestamp filename.

## Iter H — a11y pass (P2.1, P2.2, P2.3, P2.5)
- Skip-to-main-content link at top of nv-app (focus-visible only).
- <main id="main-content" role="main"> wraps the central area; tabindex="-1"
  so the skip link can land focus there.
- nv-rail wraps its 5 view buttons in <nav role="navigation"
  aria-label="Primary"> with aria-current="page" on the active button
  and aria-label on every button. SVGs marked aria-hidden="true".
- nv-console body is now role="log" aria-live="polite"
  aria-label="Console output".
- nv-modal auto-focuses first interactive element on open and traps
  Tab cycling inside the dialog; nv-onboarding already had a dismiss
  affordance covered.

## Iter I — drag persistence (P1.7) + density visual (P1.2)
- scenePositions signal in appStore + IndexedDB key 'scene-positions'.
- nv-scene restores drag positions at connect; persists on pointerup.
- Density visual (CSS body.density-{comfy,default,compact}) confirmed
  active — was already wired but flagged as "doesn't change anything"
  in P1.2; verified during this iter.

## P0.10 — REPL history persistence
- replHistory + pushReplHistory in appStore, persisted to IndexedDB
  key 'repl-history'.
- nv-console arrow-up/down now read from the shared signal so command
  history survives view switches and reloads.

Validated end-to-end with `npx agent-browser` on
https://ruvnet.github.io/RuView/nvsim/ — skip-link, main role, console
log role, nav role, aria-current="page", New Scene modal with 5 form
fields all confirmed live. Console errors: zero.

ADR-093 §2/§3/§4 updated to mark these items resolved.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv
2026-04-26 22:08:49 -04:00
parent c9fbda12fd
commit 18c09d3305
9 changed files with 197 additions and 34 deletions
+9
View File
@@ -8,6 +8,7 @@ import {
setClient, transport, theme, density, motionReduced,
pushLog, expectedWitness, framesEmitted, fps, lastB, bMag,
pushTrace, pushStripBar, lastFrame, sceneJson, witnessHex,
replHistory, scenePositions, type SceneItemPos,
} from './store/appStore';
import { kvGet, kvSet } from './store/persistence';
@@ -37,6 +38,14 @@ function applyMotion(reduced: boolean): void {
effect(() => { applyDensity(density.value); kvSet('density', density.value); });
effect(() => { applyMotion(motionReduced.value); kvSet('motionReduced', motionReduced.value); });
// REPL history + scene drag positions persistence (P0.10, P1.7)
const histSaved = await kvGet<string[]>('repl-history');
if (histSaved && Array.isArray(histSaved)) replHistory.value = histSaved;
effect(() => { void kvSet('repl-history', replHistory.value); });
const positionsSaved = await kvGet<SceneItemPos[]>('scene-positions');
if (positionsSaved && Array.isArray(positionsSaved)) scenePositions.value = positionsSaved;
effect(() => { void kvSet('scene-positions', scenePositions.value); });
// Boot WASM client
const client = new WasmClient();
setClient(client);