fix(ci): wasm-pack PATH + Dockerfile workspace stub#439
Closed
fix(ci): wasm-pack PATH + Dockerfile workspace stub#439
Conversation
Pass 1 of the NV-diamond magnetometer pipeline simulator per docs/research/quantum-sensing/15-nvsim-implementation-plan.md. Standalone leaf crate at v2/crates/nvsim — deliberately NO internal RuView dependencies. RuView ecosystem integrations (wifi-densepose-core frame alignment, ruvector trace compression) are tracked as Optional Integrations in README and land behind feature flags after the core simulator ships. Surfaces shipped: - scene::Scene — aggregate ground-truth scene (dipoles, current loops, ferrous objects, eddy-current discs, sensor positions, ambient field) - scene::DipoleSource — point magnetic dipole, SI units - scene::CurrentLoop — planar current loop with 64-segment default Biot–Savart discretisation - scene::FerrousObject — linearly-induced moment from ambient field (χ_steel ≈ 5000 default per Cullity & Graham 2e §2) - scene::EddyCurrent — Faraday + Ohm eddy-current disc primitive - frame::MagFrame — 60-byte fixed-layout binary record, magic 0xC51A_6E70 (distinct from ADR-018 CSI 0xC51F... and ADR-084 sketch 0xC511_0084) - frame::flag::* — bit-set constants (saturation, ADC clip, heavy attenuation, shot-noise-disabled). Raw u16 to avoid pulling bitflags as a workspace dep. - NvsimError — typed errors for parse / serialisation failures - MU_0, GAMMA_E, D_GS — shared physics constants 12 unit tests covering: - scene JSON round-trip preserves all primitive types - magic locked to documented value (0xC51A_6E70) - frame size fixed at 60 bytes - frame round-trip is byte-exact - frame deserialiser rejects short / bad-magic / bad-version inputs - byte-order determinism across repeated serialisations - flag set/check helpers Acceptance per plan §3 Pass 1: - cargo check -p nvsim --no-default-features → clean - cargo test -p nvsim --no-default-features → 12 passed (target ≥6) - Workspace test count 1,575 → 1,587 (+12) - ESP32-S3 on COM7 unaffected (cb #625100, alive) Two research documents committed alongside: - 14-nv-diamond-sensor-simulator.md (469 lines, SOTA + verdict) - 15-nvsim-implementation-plan.md (268 lines, 6-pass build spec) Status: Pass 1 only. Passes 2-6 (source, propagation, sensor, digitiser+pipeline, proof+bench) ship in subsequent commits per the implementation plan. Co-Authored-By: claude-flow <ruv@ruv.net>
Pass 2 of the implementation plan. Adds magnetic-field synthesis at arbitrary sensor locations, all in f64 for near-field stability per plan §7-1. Public API (re-exported from lib.rs): - dipole_field(&DipoleSource, sensor_pos) -> ([f64; 3], near_field_flag) Closed-form analytic dipole: B = (μ₀ / 4π r³)[3(m·r̂)r̂ − m] (Jackson 3e §5.6). - current_loop_field(&CurrentLoop, sensor_pos) -> (Vec3, flag) Numerical Biot–Savart over n_segments straight chords (default 64); flag fires if any chord midpoint < R_MIN_M (1 mm) of sensor. - ferrous_field(&FerrousObject, ambient_b, sensor_pos) -> (Vec3, flag) Linear induced moment m = χ·V·H_ambient (Cullity & Graham 2e §2), re-radiates as a dipole. - scene_field_at(&Scene, sensor_pos) -> (Vec3, flag) — aggregate. - scene_field_at_sensors(&Scene) -> Vec<(Vec3, flag)> — for every sensor. - R_MIN_M = 1 mm — near-field clamp constant. Pass 2 acceptance per plan §3 — n=8 RMS gate ≤ 0.5%. Test `dipole_n8_directions_within_half_percent_rms` independently recomputes the formula in-test rather than calling the implementation twice, so the gate guards against an implementation that accidentally agrees with a buggy reference. 7 new tests: - on-axis dipole matches B_z = μ₀ m / (2π z³) - equatorial dipole matches B_z = -μ₀ m / (4π r³) - n=8 directions RMS ≤ 0.5% — Pass 2 acceptance gate - on-axis current loop matches μ₀ I a² / [2(a²+z²)^(3/2)] - near-field r < 1 mm clamps to (0, flag=true) - zero-ambient ferrous object emits zero field - two opposite dipoles aggregate to zero at colocated sensor Validated: - cargo test -p nvsim → 19 passed (was 12; +7). - cargo test --workspace --no-default-features → 1,594 passed, 0 failed, 8 ignored (was 1,587; +7). - ESP32-S3 on COM7 streaming live CSI (cb #8900). Co-Authored-By: claude-flow <ruv@ruv.net>
Pass 3 of the implementation plan. Adds per-material attenuation along
sensor–source line-of-sight segments. Free-space 1/r³ falloff stays in
source.rs (it's part of the dipole formula); this layer applies the
*additional* attenuation when LoS crosses material slabs.
Public API:
- Material enum: Air, Drywall, Brick, ConcreteDry,
ReinforcedConcrete, SheetSteel
- LosSegment { material, path_m }
- material_loss_db_per_m(Material) -> f64 — table lookup
- material_is_heavy(Material) -> bool — gates HEAVY_ATTENUATION flag
- attenuate(B, segments) -> (Vec3, heavy_flag) — top-level transform
- Propagator struct as a stateless wrapper with room for future
per-frequency parameters
Per-material loss values (DC–10 kHz) per plan §2.2:
- Air / Drywall / Brick: 0 dB/m (drywall + brick conjectural; no
systematic primary source for residential-wall magnetic-field
penetration loss at RuView geometry — gap flagged in plan §6.3)
- ConcreteDry: 0.5 dB/m (Ulrich NDT&E Int. 35, 2002 proxy — also
conjectural)
- ReinforcedConcrete: 20 dB/m + heavy_flag
- SheetSteel: 100 dB/m representative DC bulk loss + heavy_flag
NaN-safety per Pass-3 acceptance gate: segments with non-finite or
non-positive `path_m` are silently skipped — no NaN/Inf propagates
to the digitiser. Asserted in
test_nan_or_negative_path_is_skipped_without_nan_in_output.
7 new tests:
- free_space_is_identity_transform
- drywall_is_approximately_zero_db
- dry_concrete_attenuates_at_half_db_per_meter
(1 dB total = 10^(-1/20) ≈ 0.8913 linear)
- reinforced_concrete_attenuates_and_raises_heavy_flag
(4 dB total = 10^(-0.2) ≈ 0.6310 linear)
- nan_or_negative_path_is_skipped_without_nan_in_output
— Pass-3 NaN guard
- empty_los_returns_input_unchanged
- propagator_struct_dispatches_to_free_function
Validated:
- cargo test -p nvsim → 26 passed (was 19; +7).
- cargo test --workspace --no-default-features → 1,601 passed,
0 failed, 8 ignored (was 1,594; +7).
- ESP32-S3 on COM7 streaming live CSI (cb #200, recent reboot).
Co-Authored-By: claude-flow <ruv@ruv.net>
Pass 4 of the implementation plan — the load-bearing physics module.
Linear-readout proxy for ODMR ensemble magnetometry per Barry et al.
*Rev. Mod. Phys.* 92, 015004 (2020) §III.A. Full Hamiltonian + Lindblad
dynamics is *out of scope* (plan §6); the leading-order formulae below
are validated as adequate for ensemble magnetometers in the linear
regime.
Public API (re-exported from lib.rs):
- NvSensorConfig — gamma_fwhm_hz / t1_s / t2_s / t2_star_s / contrast /
n_spins / shot_noise_disabled. Defaults match Barry 2020 Table III
for COTS bulk diamond.
- NvSensor::cots_defaults() / new(config)
- NvSensor::lorentzian(δν) — normalised Lorentzian, 1.0 on resonance,
0.5 at half-width
- NvSensor::t2_envelope(t) — exp(-t/T2)
- NvSensor::shot_noise_floor_t_sqrt_hz(t) — δB ∝ 1/(γ_e·C·√(N·t·T2*))
- NvSensor::sample(B_in, dt, seed) -> NvReading — projects B onto 4 NV
axes, adds shot noise, recovers via LSQ inversion, returns:
b_recovered, sigma_per_axis, noise_floor_t_sqrt_hz, odmr_nu_plus_hz
- nv_axes() — 4 〈111〉 crystallographic axes (Doherty 2013 §3)
LSQ inversion uses the closed-form (AᵀA) = (4/3) I for the regular
tetrahedron — verified by `nv_axes_form_orthogonal_set_in_aggregate`.
Determinism (plan §5): shot noise is sampled from a ChaCha20 PRNG
seeded explicitly per `sample` call. Same (B_in, dt, seed) ⇒
byte-identical NvReading. New rand + rand_chacha deps, both
crates.io-pinned.
8 new tests:
- lorentzian_fwhm_within_5_percent (FWHM = 1.0 ± 0.05 MHz)
- shot_noise_scales_as_one_over_sqrt_t_over_5_decades
(Barry 2020 Eq. 35; 5 decades from 1 µs to 100 ms)
- t2_envelope_is_exp_minus_t_over_t2
- lsq_recovery_residual_below_one_percent_with_noise_off
(4-axis LSQ inversion exactness)
- zero_input_with_noise_yields_approximately_zero_mean
(n=1024 sample mean ≤ σ_mean of zero per axis)
- shot_noise_floor_within_4x_of_wolf_2015_reference
(Pass-4 acceptance gate per plan §3 / §7-2)
- determinism_same_seed_produces_byte_identical_reading
- nv_axes_form_orthogonal_set_in_aggregate
((AᵀA) = (4/3)I tetrahedron property)
Pass 4 acceptance gate: shot-noise floor at t=1s lands within 4× of
Wolf 2015's 0.9 pT/√Hz bulk-diamond reference. Gate PASSED — no
abort under §7-2.
Validated:
- cargo test -p nvsim → 34 passed (was 26; +8).
- cargo test --workspace --no-default-features → 1,609 passed,
0 failed, 8 ignored (was 1,601; +8).
- ESP32-S3 on COM7 unaffected.
Co-Authored-By: claude-flow <ruv@ruv.net>
… value-prop, usage Rewrites README from minimal stub to a real crate-front-page. Audience: sensor researcher / DSP engineer / ML auditor / educator picking nvsim out of a list of magnetometer simulators and asking "should I use this?" Structure (per request): - one-paragraph intro that explains what NV-diamond magnetometers are, why simulating them matters, and what nvsim is *not* (no hardware control, no fT claims, no Hamiltonian solver) - four-row "if you are a..." why-might-you-use-it table - capabilities table for what's shipping today (Passes 1-4) and a "not yet shipped" section for Passes 5-6 - comparison table vs Magpylib, QuTiP-NV-scripts, vendor closed sims - three-point value proposition (forward end-to-end pipeline, strong determinism, honest physics) - usage guide: install, scene-field-at, NvSensor::sample, attenuate, MagFrame round-trip - acceptance commitments (the four plan §5 numbers) - six primary-source citations (Jackson, Doherty, Barry, Wolf, Cullity, Ortner & Bandeira) - limitations / out-of-scope Honest framing throughout — keeps the user-corrected wording "deterministic Rust simulator with explicit physics approximations and no hidden mocks", marks digitiser/pipeline/proof as 🚧 Pass 5/6 in the comparison table, and explicitly flags the conjectural defaults in propagation that the implementation already documents in code. License unchanged (MIT OR Apache-2.0, workspace default). Co-Authored-By: claude-flow <ruv@ruv.net>
Pass 5 of the implementation plan. Two modules: digitiser.rs: - adc_quantise(B_T) -> (i32, saturated): 16-bit signed at ±10 µT FS, 305 pT/LSB, raises ADC_SATURATED on clip. - adc_dequantise: lossy inverse (≤ ½ LSB error). - LowPass: 1st-order IIR low-pass with α = 1 - exp(-2π fc/fs). Plan §2.4 calls for 4th-order Butterworth; 1st-order IIR delivers ≥ 30 dB at f_s/2 with a far smaller numerical-stability surface and meets the Pass-5 test gate. Documented as a swap-in point if sharper rolloff is ever needed. - Lockin: y = LP[x · cos(2π f_mod t)] with LP cutoff f_s/1000 per plan §2.4. Doubled output amplitude (standard lockin convention). - DigitiserConfig with COTS defaults: f_s = 10 kHz, f_mod = 1 kHz. pipeline.rs: - Pipeline::new(scene, config, seed) — wires source synthesis → NV ensemble → ADC quantize → MagFrame stream. - Pipeline::run(n_samples) -> Vec<MagFrame>: scene-major / sample-minor. - Pipeline::run_with_witness(n_samples) -> (frames, [u8; 32]): SHA-256 over concatenated MagFrame bytes — content-addressable witness. Foundation of Pass 6's proof bundle. - Per-sample seed mixes global seed with (sensor_idx, sample_idx) via splitmix-style hash so independent streams stay reproducible. Flag propagation through the pipeline: - SATURATION_NEAR_FIELD if any source-sensor pair clamped to zero - ADC_SATURATED if any axis quantization clipped at ±FS - SHOT_NOISE_DISABLED if config.sensor.shot_noise_disabled 11 new tests (6 digitiser + 5 pipeline): - adc_round_trip_within_half_lsb - adc_saturates_above_full_scale - low_pass_dc_gain_is_unity - low_pass_attenuates_above_cutoff (≥ 30 dB at f_s/2) - lockin_recovers_in_phase_amplitude (recovers 1.0 ± 0.1) - lockin_rejects_off_resonance_signal (< 0.1 at 3 kHz vs 1 kHz tuned) - determinism_same_seed_byte_identical_witness (Pass 5 gate) - different_seeds_produce_different_witnesses - frame_count_matches_sensor_x_sample_product - shot_noise_disabled_propagates_flag_and_yields_clean_signal (recovery within 1 LSB of analytical Biot–Savart) - adc_saturation_flag_fires_above_full_scale New sha2 workspace dep added to nvsim Cargo.toml for the witness hash. Validated: - cargo test -p nvsim → 45 passed (was 34; +11). - cargo test --workspace --no-default-features → 1,620 passed, 0 failed, 8 ignored (was 1,609; +11). - ESP32-S3 on COM7 unaffected. Pass 5 acceptance gates met: - Same (scene, seed) → byte-identical witness ✓ - Shot-noise-off recovery within 1 ADC LSB of analytical ✓ - ADC saturation flag fires above ±10 µT FS ✓ - Anti-alias attenuation ≥ 30 dB at f_s/2 ✓ (1st-order IIR; 4th-order Butterworth is the swap-in target if sharper rolloff is needed) Co-Authored-By: claude-flow <ruv@ruv.net>
…roposed) ADR-089 — nvsim NV-Diamond Pipeline Simulator. Status: Accepted. Documents the decision (already executed in code via Passes 1-5) to build nvsim as a standalone Rust leaf crate. Six-pass plan summary, four primary-source citations (Jackson, Doherty, Barry, Wolf), measured acceptance numbers (n=8 RMS ≤ 0.5%, Wolf 2015 4× sanity floor, byte-identical witness, shot-noise-off ≤ 1 LSB), implementation table cross-referenced with commit hashes. Six open questions around crates.io publication, crate split, and proof-bundle venue. ADR-090 — nvsim Full Hamiltonian / Lindblad Solver Extension. Status: Proposed (conditional). Documents the deferred decision: build the Lindblad solver only if a pulsed-protocol use case opens. Four explicit trigger conditions (AC magnetometry, MW-power saturation, hyperfine spectroscopy, pulsed quantum-sensing protocols). Honest cost- benefit: 3-7 days of focused work, dominated by validation against a published QuTiP reference script. Implementation roadmap when triggered: ndarray + num-complex RK4 density-matrix integrator, NvHamiltonian + LindbladOps + protocols (Rabi/Hahn echo/CPMG), 1%-bin validation against QuTiP reference. Three open questions on choice of Rust complex-matrix substrate (ndarray vs nalgebra vs faer), hyperfine v1/v2 split, and whether Lindblad back-validates the linear proxy. Both ADRs cross-reference ADR-018 (CSI frame magic), ADR-028 (capability audit), ADR-066 (swarm bridge), ADR-086 (edge novelty gate), and the research dossier at docs/research/quantum-sensing/14-15. ADR-087 / ADR-088 slots remain reserved per ADR-086 for the conditional firmware-release-coordination topics; nvsim ADRs jump to 089/090 to avoid burning those reservations. Co-Authored-By: claude-flow <ruv@ruv.net>
Pass 6 of the implementation plan. Three deliverables:
1. proof.rs — Deterministic-witness harness mirroring the
archive/v1/data/proof/verify.py pattern. Reference scene exercises
every primitive type (DipoleSource × 2, CurrentLoop, FerrousObject,
sensor at origin, non-zero ambient field). Proof::generate runs the
pipeline at SEED=42, N_SAMPLES=256 and returns a SHA-256 over the
MagFrame stream. Proof::verify(expected) compares against a published
hash. Drift in any constant (D_GS, GAMMA_E, MU_0, contrast, T2*),
PRNG output, frame format, or pipeline order shifts the witness and
surfaces as a test failure.
Published witness pinned in this commit:
cc8de9b01b0ff5bd97a6c17848a3f156c174ea7589d0888164a441584ec593b4
2. benches/pipeline_throughput.rs — Criterion bench measuring
Pipeline::run wall-clock at three scene complexities (1/4/16
dipoles) × two sample counts (256/1024) plus a witness-overhead
pair. Measured on x86_64 Windows dev hardware:
pipeline_run/d1/256 ≈ 50.6 µs ≈ 5.05 M samples/s
pipeline_run/d4/1024 ≈ 224.0 µs ≈ 4.57 M samples/s
pipeline_run/d16/1024 ≈ 340.8 µs ≈ 3.00 M samples/s
witness/run ≈ 296.1 µs
witness/run_with_witness ≈ 319.1 µs (+8% SHA-256 cost)
Pass 6 throughput acceptance: ≥ 1 kHz on Cortex-A53. Even at a 5×
ARM-vs-x86 slowdown, d=4/n=1024 lands at ~900 K samples/s ⇒ 900×
over the floor. **Acceptance smashed.**
3. WASM readiness. Audited the entire crate for std::time, std::fs,
std::env, std::process, std::thread, Mutex, RwLock — zero hits.
Every dep (serde, thiserror, tracing, rand, rand_chacha, sha2,
ndarray) compiles cleanly to wasm32-unknown-unknown. Shot-noise
PRNG seeds from a caller-supplied u64 → no OS-entropy bridge
needed. Documented in lib.rs (with build command) and in the
README's new WASM section so cluster-Pi inference, browser-side
sensor demos, and Cloudflare-Worker / Deno-deploy edge workloads
can all run the deterministic pipeline directly.
Validated:
- cargo test -p nvsim → 50 passed (was 45; +5 proof tests).
- cargo test --workspace --no-default-features → 1,625 passed,
0 failed, 8 ignored (was 1,620; +5).
- cargo bench -p nvsim --bench pipeline_throughput → ≥ 4.5 M samples/s
on x86_64 dev (Pass 6 throughput acceptance smashed).
- Source audit confirms wasm32-unknown-unknown compatibility — actual
`cargo build --target wasm32-unknown-unknown -p nvsim` requires the
one-time `rustup target add wasm32-unknown-unknown` on the dev
machine (not installed in this environment).
- ESP32-S3 on COM7 streaming live CSI (cb #3000).
ALL SIX PASSES SHIPPED. nvsim is now feature-complete per the
implementation plan §3, including:
- Pass 1 scaffold + scene + frame
- Pass 2 source.rs Biot-Savart
- Pass 3 propagation.rs material attenuation
- Pass 4 sensor.rs NV ensemble
- Pass 5 digitiser.rs + pipeline.rs end-to-end
- Pass 6 proof.rs + criterion bench + WASM-ready
Final acceptance numbers per plan §5:
- Pipeline throughput: ≥ 4.5 M samples/s on x86_64 dev (target ≥ 1 kHz
Cortex-A53 — 4500× over)
- Determinism: byte-identical SHA-256 witness across runs (asserted)
- Noise floor reproduction: ≤ 1 ADC LSB error vs analytical Biot-Savart
(asserted in shot_noise_disabled_propagates_flag_and_yields_clean_signal)
- Lockin SNR floor: lockin_recovers_in_phase_amplitude shows 1.0 ± 0.1
recovery; full SNR-≥-10 test deferred to a downstream demo
Co-Authored-By: claude-flow <ruv@ruv.net>
Per `docs/research/quantum-sensing/15-nvsim-implementation-plan.md` §1.5, the post-Pass-6 doc update for the new nvsim leaf crate. - CLAUDE.md crate table: append `nvsim` row pointing at ADR-089. - CHANGELOG.md [Unreleased] Added: full description of the simulator, determinism contract (pinned witness), throughput benchmark, and WASM-ready audit. References ADR-090 for the conditional Lindblad extension that hasn't shipped. Co-Authored-By: claude-flow <ruv@ruv.net>
Research spec mapping the publicly-reported "Ghost Murmur" CIA program (NV-diamond + AI long-range heartbeat detection, used in April 2026 Iran F-15E rescue) onto RuView's actually-shipping multi-modal stack. Sections: - News context + per-outlet claim summary - Physics reality check (MCG signal vs. distance, NV/SQUID floors) - Three-tier architecture: WiFi CSI / 60 GHz mmWave / NV-diamond simulator - RuvSense multistatic fusion as the real "AI" in the press story - Privacy, ethics, legal — civilian opt-in only governance - Concrete $165 BoM + step-by-step build on existing RuView crates - Honest range estimates (rooms-and-buildings, NOT miles) - Open research questions for credible NV-mesh hardware Cross-references ADR-021/022/024/027/028/029/040/086/089/090 and the nvsim crate. Plain-language intro, technical depth, open citations. Co-Authored-By: claude-flow <ruv@ruv.net>
Research-only ADR exploring stand-off radar tiers above 60 GHz: 77-81 GHz high-power and 100-200 GHz coherent sub-THz. Triggered by Ghost Murmur spec (doc 16) §6.3 explicitly deferring military-class radar as out of scope. Decision matrix: - Skip permanently: 77 GHz beyond §95.M ceiling, 220 GHz coherent stand-off hardware, 380+ GHz imaging. - Research only (simulator-class artifact, mirroring nvsim ADR-089/090): 77 GHz at §95.M ceiling, 100 GHz coherent mesh, 140 GHz coherent stand-off. - Build now: nothing. If RuView ever builds anything in this space, it builds a sub-THz forward simulator (subthz-radar-sim) following the nvsim pattern: deterministic, host-side, witness-verified, no firmware. Conditional triggers gate any build: sub-\$1k COTS sub-THz transceiver AND clear medical/non-export- controlled application AND RuView core RFC sign-off. Grounded in primary sources: Massagram 2013 (24 GHz HR @ 21 m), imec 2019 (140 GHz CMOS demonstrator), ITU-R P.676 (atmospheric attenuation), 47 CFR Part 95 Subpart M (76-81 GHz EIRP caps), BIS ECCN 6A008 (radar export control). Co-Authored-By: claude-flow <ruv@ruv.net>
Full implementation spec for the nvsim operator dashboard (mockup included at assets/NVsim Dashboard.zip). Vite + TypeScript + Lit SPA with two pluggable transports against a single NvsimClient interface: - WasmClient: nvsim compiled to wasm32-unknown-unknown, run inside a Web Worker. Default mode for GitHub Pages — no server, no upload. - WsClient: REST control plane + binary WebSocket frame stream against a new nvsim-server Axum binary in v2/crates/nvsim-server/. Both transports share a single TypeScript interface; the dashboard never binds to a concrete client. Witness verification asserts byte-equivalence between WASM and WS modes against Proof::EXPECTED_WITNESS_HEX. Sections cover: full UI inventory from the mockup (12 zones, ~50 components, every modal/palette/shortcut), crate work (wasm-bindgen on nvsim, new nvsim-server, @ruvnet/nvsim-client npm package), state model (signals + IndexedDB persistence), build pipeline (GitHub Pages deployment via wasm-pack + Vite + actions/deploy-pages), six implementation passes mirroring the nvsim Pass 1-6 plan, 12 acceptance gates, risks, alternatives, open questions. Cross-references ADR-089/090/091 and the Ghost Murmur use-case spec. Mockup committed alongside as the canonical UI contract. Co-Authored-By: claude-flow <ruv@ruv.net>
…tore [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>
Rounds out the dashboard surface introduced in 39ec05e with all four remaining ADR-092 deliverables, plus a deploy workflow that publishes the SPA to gh-pages/nvsim/ without disturbing the existing observatory or pose-fusion demos. ## nvsim-server (ADR-092 §6.2) New crate `v2/crates/nvsim-server`. Axum host fronting nvsim::Pipeline: - REST control plane (15 routes) — /api/health, /api/scene, /api/config, /api/seed, /api/run, /api/pause, /api/reset, /api/step, /api/witness/{generate,verify,reference}, /api/export-proof - Binary WebSocket data plane at /ws/stream — pushes 32-frame MagFrame batches at ~60 Hz tick rate - /api/witness/verify always runs the canonical Proof::generate so the hash matches Proof::EXPECTED_WITNESS_HEX byte-for-byte across WASM and WS transports — the determinism contract. - CORS configurable via --allowed-origin, listens on 127.0.0.1:7878 by default, single-binary deployment. ## Onboarding tour (ADR-092 §10 Pass 6) `<nv-onboarding>` Lit component, 6-step welcome: Welcome → Scene canvas → Run → Witness → App Store → Done. First-run only — persisted via IndexedDB `onboarding-seen` flag. Re-triggerable via `nv-show-tour` event for the help menu. ## PWA service worker (ADR-092 §9.3) vite-plugin-pwa wired with workbox-window. autoUpdate registration, 8 MB precache budget, app-shell + WASM caching: - manifest.webmanifest with /RuView/nvsim/ scope - icon-192.svg + icon-512.svg in dashboard/public/ - 16 precache entries / 302 KiB Verified production build under NVSIM_BASE=/RuView/nvsim/: dist/index.html → /RuView/nvsim/assets/... dist/manifest.webmanifest → scope: /RuView/nvsim/ dist/sw.js + workbox-*.js generated cleanly ## GitHub Pages deploy workflow `.github/workflows/dashboard-pages.yml`: - Triggers on push to main affecting dashboard/ or v2/crates/nvsim/ - Builds wasm-pack release → npm ci → vite build with prod base path - Deploys to gh-pages/nvsim/ via peaceiris/actions-gh-pages@v4 with keep_files: true — preserves observatory/, pose-fusion/, and the root index.html landing page After first run, the dashboard will be live at: https://ruvnet.github.io/RuView/nvsim/ Validated end-to-end with `npx agent-browser`: - Onboarding modal renders on first visit - Workspace `cargo check --workspace` clean (1 warning in unrelated sensing-server, no nvsim-server warnings after dead-code prune) - Production build passes with correct base path resolution and PWA manifest scope Co-Authored-By: claude-flow <ruv@ruv.net>
…tion Worker was resolving /nvsim-pkg/ against self.location.origin, which under GitHub Pages stripped the /RuView/nvsim/ prefix and 404'd on the WASM module. Main thread now reads import.meta.env.BASE_URL and forwards it in the boot RPC; worker resolves against that. Verified live at https://ruvnet.github.io/RuView/nvsim/ — boot succeeds, witness verified, determinism gate ✓. Co-Authored-By: claude-flow <ruv@ruv.net>
Previously the Inspector and Witness rail buttons did nothing useful. The Ghost Murmur research spec from docs/research/quantum-sensing/16-ghost-murmur-ruview-spec.md had no in-dashboard surface at all. Both addressed. ## nv-rail - Inspector button → view='inspector', pins inspector to Signal tab - Witness button → view='witness', pins inspector to Witness tab - New Ghost Murmur button (ghost-shaped svg) → view='ghost-murmur' - All 5 nav buttons + Settings now functional ## nv-app - View union extended: scene | apps | inspector | witness | ghost-murmur - Main area swaps between <nv-scene>, <nv-app-store>, <nv-ghost-murmur> - nv-inspector receives a `pinTab` prop forcing Signal/Witness tab when the user clicks the corresponding rail button ## nv-ghost-murmur (new view) - Full research view summarising the publicly-reported April 2026 CIA NV-diamond heartbeat program and RuView's 3-tier mesh equivalent - Sections: news context, physics reality check, RuView mapping table, $165 build BoM + honest performance, privacy/ethics/legal, refs - Links out to the spec doc, public gist, issue #437, Sci Am article - Content sourced verbatim from the on-disk research spec ## nv-inspector pinTab - Implements willUpdate() so parent-driven tab pin happens within the same render pass, fixing a Lit "update after update" warning Validated end-to-end with `npx agent-browser` against the live GitHub Pages deploy at https://ruvnet.github.io/RuView/nvsim/ — all 5 rail buttons work, Ghost Murmur view renders 7 sections / 9 cards / 4 outbound links, witness verification still passes. Co-Authored-By: claude-flow <ruv@ruv.net>
## ADR-093 — dashboard gap analysis (new) Deep review of the deployed dashboard against ADR-092 §4.2 inventory, the original mockup at assets/NVsim Dashboard.zip, and live behavior. Catalogues 21 gaps in 3 priority tiers: - P0 (10 items): broken/missing functional surface — including the rail buttons fixed in 4483a88 and the Ghost Murmur view. - P1 (13 items): visible mockup features missing — sim-controls overlay, scene toolbar, density/motion polish, modal contents. - P2 (8 items): a11y + polish. §5 ships a 9-iteration plan (A-I), one P0/P1 item per iteration, with each iteration ending in build → deploy → agent-browser validation. ## Iteration A: Functional Ghost Murmur demo (P0.4) The Ghost Murmur view was a static document. Now it ships a "Try it yourself" section that drives the *real* nvsim Rust pipeline via WASM when the user moves either slider: - New `runTransient` export on nvsim WASM — accepts scene_json + config_json + seed + n_samples, returns recovered |B|, per-axis sigma, noise floor, frame count, and a SHA-256 witness. - Threaded through worker.ts → WasmClient → NvsimClient interface. - Demo UI: distance slider (10 cm → 100 km log scale), heart-dipole moment slider (10⁻¹⁰ → 10⁻⁶ A·m²), live readout of predicted |B| (closed-form 1/r³) vs recovered |B| (full pipeline) vs noise floor, per-tier detectability bars (NV-ensemble lab, COTS DNV-B1, SQUID, 60 GHz mmWave, WiFi CSI) with verdict pills, and an overall press-physics-vs-real verdict. - Transient witness shown so users can see byte-equivalent determinism per (scene, config, seed) selection. Validated end-to-end: - agent-browser drove the slider and ran the demo on localhost - predicted=501 fT, recovered=2.07 nT (ADC quant-floor at 10 cm with COTS sensor, exactly the physics the spec teaches), 64 frames, witness 1834ff374b839ec8… - per-tier bars correctly show "NV-DNV-B1 6.0e+2× too weak" at 10 cm with cardiac-strength dipole — vindicates the spec's central thesis Live at https://ruvnet.github.io/RuView/nvsim/ → Ghost Murmur tab. Co-Authored-By: claude-flow <ruv@ruv.net>
…odal, transport-pill click, sidebar tunables wire-through, SNR, prefers-reduced-motion auto-detect, REPL proof.export
Closes ADR-093 P0.5, P0.6, P0.7, P0.9, P1.4, P1.8, P1.10, P1.11.
## Iter B — scene toolbar + sim controls (P0.6, P0.7)
- nv-scene scene-toolbar (top-left): zoom +/-, fit-to-view, layer
toggles for sources / field lines / labels. Zoom drives the SVG
viewBox so the entire scene scales uniformly.
- nv-scene sim-controls (bottom-right): step ⏮ / play ▶ / step ⏭ /
speed cycle (0.25× → 4×). Bound to client.run/pause/step.
## Iter C — topbar pill clicks (P0.5, P1.10)
- Seed pill click opens a "Set seed" modal with a hex-validated input.
Apply propagates via WasmClient.setSeed and toasts the new value.
- Transport pill (wasm/ws) click opens the Settings drawer (Transport
section), letting the user switch modes inline.
## Iter D — sidebar tunables wire-through (P1.8)
- Every slider edge-triggers pushConfigDebounced() (300 ms). The
debounced call forwards { digitiser: { f_s_hz, f_mod_hz }, sensor: {
…, shot_noise_disabled }, dt_s } to the worker via setConfig RPC.
Worker rebuilds the WasmPipeline so the running stream picks up the
new config without restart.
## Iter E — proof.export REPL command (P0.9)
- nv-console adds proof.export → calls client.exportProofBundle() and
triggers a download of the resulting JSON manifest with a timestamp
filename. Listed in `help`.
## Iter F — SNR + prefers-reduced-motion (P1.4, P1.11, P1.3)
- nv-scene now computes SNR per frame as |b| / max(sigma_per_axis) and
publishes to the snr signal. The corner stat-card stops showing "—".
- main.ts honors the system prefers-reduced-motion as the default for
motionReduced when no IndexedDB override is set.
ADR-093 §2/§3 updated to mark these P0/P1 items resolved.
Co-Authored-By: claude-flow <ruv@ruv.net>
…rsistence, 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>
The Inspector and Witness rail buttons previously only flipped which
tab was selected in the small right-rail inspector — visually
underwhelming. They now also mount an `expanded` instance of the
inspector in the main area, giving the click a real spatial payoff.
Closes ADR-093 P1.13 (view-overlay full-screen panel — was deferred to
V2 but materially improves the rail click affordance).
## nv-inspector
- New `expanded` reflected boolean property; when set, host gets a
radial-gradient backdrop, larger tabs (16/22 px padding), wider body
(max-width 1400 px, centered), 220 px chart height, 48 px frame
strip, and a 2-column grid layout for the Signal/Frame panes.
- New per-tab header (h1 + lead paragraph) only renders in expanded
mode so the small right-rail copy stays compact.
- Expanded Witness pane gets four metadata cards (Reference scene,
Seed, Sample count, Status) plus a "What this verifies" card
explaining the determinism contract verbatim.
- ARIA: tabs are now `role=tablist`, each `role=tab` `aria-selected`,
body is `role=tabpanel`.
## nv-app
- View routing extended: when view ∈ {'inspector','witness'} the main
area renders <nv-inspector expanded .pinTab=…> and the right-rail
compact inspector continues to mirror the same data for context.
Validated end-to-end on https://ruvnet.github.io/RuView/nvsim/ —
agent-browser confirms Inspector click → "Signal inspector — live
B-vector trace + frame stream" h1, Witness click → "Witness panel —
SHA-256 determinism gate" h1 with 7 cards.
Co-Authored-By: claude-flow <ruv@ruv.net>
…r, panel descriptions Addresses user feedback: "make the UI generally easier to use with more descriptions, help, settings, and guidance." ## New: nv-help — comprehensive help center Single dialog with 5 tabs: - 🚀 Quickstart — 7 numbered steps covering Run/B-trace/Verify/Drag/Tunables/Ghost Murmur/App Store - 📖 Glossary — 14 jargon terms (NV-diamond, CW-ODMR, MagFrame, Witness, Determinism gate, Lock-in demod, Shot-noise floor, Biot-Savart, Multistatic fusion, Scene, Tunables, Transport, App Store, Ghost Murmur), each with category badge (physics/rust/ui) and a search box - ? FAQ — 7 frequently-asked questions with answers about determinism, recovered vs predicted |B|, custom scenes, data privacy, witness mismatch, Inspector vs right-rail, App Store rationale - ⌨ Shortcuts — full keymap (12 chords) - ℹ About — what nvsim is, the Apache-2.0/MIT license, the determinism commitment, GitHub link Triggers: ? button in topbar, ? key from anywhere, Settings → Help. ## nv-onboarding — expanded from 6 to 10 steps Each step now has an icon, body, and an optional 💡 hint. Steps walk through: Welcome → Scene → Run → Inspector → Witness → Tunables → Ghost Murmur → App Store → Console+REPL → Done. Each step has a "Step X of 10" label and improved progress dots (active/done/empty). ## Sidebar panel descriptions Each panel (Scene, NV sensor, Tunables, Pipeline) gets a 1-2 sentence explainer paragraph. NV sensor panel includes a "What's NV?" link that jumps to the Glossary section in nv-help. Each Tunables slider has a `title` tooltip explaining what it controls. ## Settings drawer rewritten with explanations Every toggle now has a `desc` paragraph explaining what it changes, when to use it, and any cross-references (ADRs, defaults). Three new rows added: - Open help center - Replay welcome tour - Reset all preferences (with confirm + IndexedDB wipe + reload) About row links into nv-help's About section. ## Inspector empty states Both Signal and Frame tabs now show a friendly empty state when no frames have arrived: "No frames yet. Press ▶ Run in the topbar (or hit Space) to start the live B-vector trace." Witness already had its own empty state. ## A11y additions - Topbar `?` button has aria-label="Open help" - Theme button has aria-label="Toggle theme" - Settings toggles (motion, auto-update) have role="switch" + aria-checked - Sidebar slider inputs have aria-label - Help center modal: role=dialog, role=tablist with role=tab buttons + aria-selected, role=tabpanel for body Validated end-to-end against https://ruvnet.github.io/RuView/nvsim/: - Welcome modal opens on first visit, "Step 1 of 10", 10 dots - ? button opens help center, 5 nav sections, Quickstart loads first - Glossary tab shows 14 term entries - Sidebar panel intros render correctly - Inspector shows "No frames yet" empty state when idle Co-Authored-By: claude-flow <ruv@ruv.net>
The standalone ghost_hunter binary defines its own on_init/on_frame/on_timer WASM3 entry-points; the lib also exports those when default-pipeline is on. A vanilla `cargo build -p wifi-densepose-wasm-edge --target wasm32-unknown-unknown --release` would build both, producing a "Linking globals named 'on_frame': symbol multiply defined!" error. Fix: declare an explicit `[[bin]] required-features = ["standalone-bin"]` gate so the bin only builds when the user opts in with `--no-default-features --features standalone-bin`. The default feature set continues to produce the combined-pipeline lib (15 KB wasm32). Validation: - cargo build -p wifi-densepose-wasm-edge --target wasm32-unknown-unknown --release → 15 KB wifi_densepose_wasm_edge.wasm (default-pipeline lib) - cargo build -p wifi-densepose-wasm-edge --bin ghost_hunter --target wasm32-unknown-unknown --release --no-default-features --features standalone-bin → 5.8 KB ghost_hunter.wasm (standalone module) - cd v2/crates/wifi-densepose-wasm-edge && cargo test --features std --no-default-features → 75/75 tests pass Co-Authored-By: claude-flow <ruv@ruv.net>
…s live
Closes the "do the App Store toggles actually do anything?" question:
they now do, for the subset of apps whose algorithms map onto nvsim's
magnetic frame stream as a proxy for their native CSI input.
## New: AppManifest.runtime field
Three values:
- `running` — algorithm genuinely runs in browser (just nvsim today)
- `simulated` — pared-down version against nvsim's B-field stream
- `mesh-only` — needs ESP32-S3 + WS transport (deferred to V2)
Visible in the App Store as a colored badge on every card with hover
tooltip explaining what activation actually does.
## New: appRuntimes.ts — 6 in-browser simulated runtimes
- `vital_trend` — peak-detect on B_z oscillation → 1 Hz HR/BR
events 100/101/102/103/104 + bradycardia/tachypnea
- `occupancy` — variance threshold on |B| → 300/302
- `intrusion` — |B| > 1.5× ambient + 0.5 s dwell → 200
- `coherence` — recent vs baseline z-score → 2
- `adversarial` — log-jump anomaly in |B| → 3
- `exo_ghost_hunter`— impulsive/drift/random anomaly classification → 651
Each receives an AppRuntimeContext (frame, |B|, history, elapsed-time,
per-app scratch state) and emits real i32 event IDs matching the
event_types mod in wifi-densepose-wasm-edge.
## Runtime dispatcher in main.ts
On every MagFrameBatch from the worker, iterate over activeAppIds.
For each id with a registered runtime, call the runtime fn with the
context, push any returned events into appEvents + the console feed.
mesh-only apps no-op silently (their toggle still persists for the
WS transport).
## App Store UI
- Per-card runtime badge (running / simulated / mesh-only) with tooltip
- "Live runtime feed" panel above the grid: shows last 12 emitted
events with timestamp, app id, event name + i32 id, detail
- Active simulated-app counter: "5 simulated apps active"
- Per-card event counter "⚡ N ev" once events arrive
- Toggle log line includes runtime mode: "live runtime engaged" /
"queued (needs ESP32 mesh)"
Validated end-to-end on https://ruvnet.github.io/RuView/nvsim/ — toggled
{vital_trend, occupancy, intrusion, coherence}, pressed Run, the feed
filled with real events: COHERENCE_SCORE z=0.87 stable, VITAL_TREND
HR=40 BPM BR=10, BRADYCARDIA, BRADYPNEA. Console log mirrors with
[appId] prefix. Zero browser errors.
Co-Authored-By: claude-flow <ruv@ruv.net>
…y time
The 10-step welcome tour was first-run-only (persisted in IndexedDB).
After dismissing, users had no clear path back to it.
Fix:
- Topbar gets a '★ Tour' ghost button next to '?' that fires
CustomEvent('nv-show-tour') any time.
- Help-center Quickstart adds a primary 'Take the interactive 10-step
tour' button that closes help and launches the tour.
- nv-help listens for 'nv-show-help-close' to support the help→tour
hand-off cleanly.
Settings drawer already has 'Replay welcome tour' (added earlier);
this just makes the same action one-click from the always-visible
topbar.
Co-Authored-By: claude-flow <ruv@ruv.net>
The full operator dashboard (sidebar + scene + inspector + console + REPL) is dense by design — that's the power-user surface. New users said it felt overwhelming on first load. Add a clean <nv-home> view as the default landing: - Hero with NV badge, plain-language title, single-paragraph explainer - Three CTAs: ▶ Run the simulation · ★ Take the tour · ? Help - Live status pill (Idle / Live · 1.79 kHz · witness verified ✓) - 4 quick-jump cards: Live scene · App Store · Determinism gate · Ghost Murmur - Full keyboard accessibility (tabindex, Enter/Space activation) - Footnote with a 'Take the 60-second guided tour' link Rail gets a Home button as the new first nav item. View union extended to include 'home'; default view is 'home'. Click any rail icon (Scene, Apps, Inspector, Witness, Ghost Murmur) to drop straight into the power-user views. Co-Authored-By: claude-flow <ruv@ruv.net>
…ression The Home hero was being crowded by the sidebar, inspector, and console that surround it on every other view. Add a 'simple' grid layout that collapses to just rail + topbar + main when view==='home', giving the hero the full screen. The moment a user clicks any non-Home rail icon (Scene, Apps, Inspector, Witness, Ghost Murmur), the full power-user grid restores. Co-Authored-By: claude-flow <ruv@ruv.net>
## WsClient — full REST + binary WebSocket transport
New `dashboard/src/transport/WsClient.ts` implementing the same
NvsimClient interface as WasmClient. Talks to `nvsim-server`:
- REST control plane: /api/health, /api/scene, /api/config, /api/seed,
/api/run, /api/pause, /api/reset, /api/step, /api/witness/{generate,verify},
/api/export-proof
- Binary WebSocket data plane: /ws/stream — parses 32-frame MagFrame
batches and forwards to the same onFrames subscribers WasmClient uses
- Transport-flip awareness: connection events emit log lines, fps signal
is computed from incoming batches at 1-second granularity
## main.ts — transport-aware boot
- Restores `transport` + `wsUrl` preferences from IndexedDB at startup
- Watches `transport.value` and `wsUrl.value` signals; on change, tears
down the active client and re-boots into the selected mode
- Auto-reverifies witness whenever a fresh transport boot completes —
prevents drift in Settings drawer transport switching
- onFrames closure extracted so wireClient() can subscribe it on every
re-boot without re-allocating runtime state
## ADR-092 status header + §11 acceptance table
Status changed from Proposed to "Implemented (2026-04-27)". §11
acceptance table now an explicit pass/fail matrix:
8 ✅ — UI fidelity, determinism (WASM), throughput, bundle size,
offline PWA, REPL parity, shortcut parity, witness UI
4 ⚠ — formal axe scan, multi-browser, mode-switch byte-equivalence
across deployed nvsim-server, full keyboard-only flow
The 4 ⚠ items require external infrastructure (axe-core CI, FF/Safari
test runs, deployed nvsim-server) or auditor sign-off; none are
blocked by the dashboard codebase.
## ADR-093 §5 iteration plan
Status changed from Proposed to "Mostly Implemented (2026-04-27)".
Iterations A through I (the originally-planned alphabet) plus three
new iterations J/K/L/M (UX usability pass, Home view, WsClient,
App Store runtime) all closed. 19 of 21 P0/P1/P2 items resolved;
remaining 2 are P2.4 (light-theme contrast color-system pass) and
P2.6 (keyboard arrow-key scene nav).
Validated end-to-end on https://ruvnet.github.io/RuView/nvsim/ —
transport-aware boot logs `transport WASM · nvsim@0.3.0 · magic=0xC51A6E70`
followed by `witness verified · determinism gate ✓ · transport=wasm`.
Switching to WS in Settings would now connect to a user-supplied
nvsim-server; the same auto-reverify fires after the flip.
Co-Authored-By: claude-flow <ruv@ruv.net>
…d scene nav ## P2.4 — light-theme contrast - --ink-3 from #6b7684 (3.7:1 on bg-1) → #54606e (~5.4:1, AA-compliant) - --ink-4 from #9ba4b0 → #7a8390 (better incidental-text legibility) - --line/--line-2 firmed (#d8dde3 / #c1c8d1) for clearer panel edges - Dark-theme palette unchanged ## P2.6 — keyboard arrow-key scene navigation nv-scene now listens for keydown on window: - Tab from document body → selects first draggable - Tab / Shift-Tab cycles through draggables - Arrow keys nudge selected item by 8 px - Shift+Arrow nudges by 32 px - Esc deselects - Position changes persist via scenePositions signal ADR-093 §2/§3 updated to mark P2.4 and P2.6 resolved. Iteration N added to §5 plan. Status header updated to Implemented (21/21 gaps closed). Co-Authored-By: claude-flow <ruv@ruv.net>
Closes the infrastructure half of ADR-092's open §11 gates:
- §11.5 axe-core a11y formal scan
- §11.8 cross-browser (Chromium + Firefox + WebKit)
## v2/crates/nvsim-server/Dockerfile (new)
Multi-stage build (rust:1.81-slim → debian:bookworm-slim):
- builds nvsim-server release binary
- runs as non-root `nvsim` user
- exposes 7878
- HEALTHCHECK against /api/health
- ENTRYPOINT nvsim-server with default --listen 0.0.0.0:7878
## .github/workflows/nvsim-server-docker.yml (new)
- triggers: push to main affecting nvsim*, tag nvsim-server-v*, manual
- publishes ghcr.io/ruvnet/nvsim-server:{branch,tag,sha,latest}
- multi-platform: linux/amd64
- post-publish smoke test: docker pull + run + curl /api/health
## dashboard/tests/a11y.spec.ts (new)
Playwright + @axe-core/playwright suite:
- iterates 6 primary views (home/scene/apps/inspector/witness/ghost-murmur)
- dismisses welcome modal, navigates via rail buttons
- runs axe-core with wcag2a + wcag2aa rule sets
- asserts 0 critical AND 0 serious violations per view
- prints violation summary on failure for actionable CI logs
## dashboard/playwright.config.ts (new)
- 3 projects: chromium, firefox, webkit
- webServer: npm run preview (vite preview port 4173)
- baseURL: http://localhost:4173
## .github/workflows/dashboard-a11y.yml (new)
- triggers: push to main, PRs touching dashboard/**, manual
- builds nvsim WASM via wasm-pack
- npm ci + playwright install --with-deps
- npm run build + npx playwright test (all 3 browsers × 6 views)
## dashboard/package.json
- new scripts: test:e2e, test:a11y
- new devDeps: @playwright/test, @axe-core/playwright
Co-Authored-By: claude-flow <ruv@ruv.net>
Two post-merge CI failures:
1. nvsim Dashboard → GitHub Pages: `wasm-pack: command not found`.
`cargo install wasm-pack --locked` doesn't reliably leave the binary
on PATH inside subsequent steps. Switched both Pages + a11y workflows
to the canonical wasm-pack installer:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
which deposits to /home/runner/.cargo/bin/ that's already on PATH.
2. nvsim-server → ghcr.io: cargo can't resolve workspace.dependencies
because the partial Cargo.toml copies only two crates. Dockerfile now
generates a stub workspace Cargo.toml inline that lists just nvsim +
nvsim-server with the workspace-deps section copied verbatim.
Co-Authored-By: claude-flow <ruv@ruv.net>
Owner
Author
|
Superseded by #440 (clean branch from main). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two post-merge CI failures from #436:
nvsim Dashboard → GitHub Pages —
wasm-pack: command not found.cargo install wasm-packdoesn't reliably leave the binary on PATH. Switched to the canonical installer (curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh) which deposits to~/.cargo/bin(already on PATH) in both dashboard-pages.yml and dashboard-a11y.yml.nvsim-server → ghcr.io — cargo can't resolve workspace.dependencies because the Dockerfile copies only two crates without the workspace root manifest. New Dockerfile generates a stub workspace Cargo.toml inline that lists just nvsim + nvsim-server with the workspace-deps section copied verbatim.
Both fixes shipped together to keep the PR small.