From eed5feeab284b20da76b1ca28e53c3a3dac91b6d Mon Sep 17 00:00:00 2001 From: ruv Date: Mon, 27 Apr 2026 10:27:19 -0400 Subject: [PATCH] feat(dashboard): full-screen Inspector + Witness views (P1.13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- dashboard/src/components/nv-app.ts | 6 +- dashboard/src/components/nv-inspector.ts | 186 ++++++++++++++++++++--- 2 files changed, 167 insertions(+), 25 deletions(-) diff --git a/dashboard/src/components/nv-app.ts b/dashboard/src/components/nv-app.ts index a1362f78..e4d825f8 100644 --- a/dashboard/src/components/nv-app.ts +++ b/dashboard/src/components/nv-app.ts @@ -102,7 +102,11 @@ export class NvApp extends LitElement { ? html`` : this.view === 'ghost-murmur' ? html`` - : html``} + : this.view === 'inspector' + ? html`` + : this.view === 'witness' + ? html`` + : html``} .card { margin-bottom: 0; } + @media (max-width: 1024px) { + :host([expanded]) .grid-2 { grid-template-columns: 1fr; } + } .tabs { display: flex; border-bottom: 1px solid var(--line); } @@ -160,6 +201,27 @@ export class NvInspector extends LitElement { } } + private renderHeader() { + if (!this.expanded) return ''; + const titles: Record = { + signal: 'Signal inspector — live B-vector trace + frame stream', + frame: 'Frame inspector — MagFrame v1 fields + raw bytes', + witness: 'Witness panel — SHA-256 determinism gate', + }; + return html` +

+ ${titles[this.tab]} +

+

+ ${this.tab === 'signal' + ? 'Real-time recovered field-vector and frame-stream sparkline. Both update at the running pipeline\'s frame rate. Use the Tunables panel in the sidebar to change f_s, f_mod, dt, and shot-noise behaviour.' + : this.tab === 'frame' + ? 'Decoded view of the most recent MagFrame: typed fields plus the raw 60-byte little-endian binary record (magic 0xC51A_6E70).' + : 'Re-derive the SHA-256 witness for the canonical reference scene (seed=42, N=256) right now in your browser and compare against Proof::EXPECTED_WITNESS_HEX. Same inputs → same hash, byte-for-byte, across every machine and transport.'} +

+ `; + } + private renderSignalTab() { const W = 320, H = 130, cy = 65, scale = 22; const cap = 200; @@ -173,27 +235,43 @@ export class NvInspector extends LitElement { return p; }; - return html` -
-
- B-vector trace - 3-axis · nT -
- - - ${svg``} - ${svg``} - ${svg``} - -
+ const b = lastB.value; + const bnT = [b[0] * 1e9, b[1] * 1e9, b[2] * 1e9]; -
-
- Frame stream - live + return html` +
+
+
+ B-vector trace + 3-axis · nT +
+ + + ${svg``} + ${svg``} + ${svg``} + + ${this.expanded ? html`
+ x: ${bnT[0].toFixed(3)} nT + y: ${bnT[1].toFixed(3)} nT + z: ${bnT[2].toFixed(3)} nT + |B| ${(bMag.value * 1e9).toFixed(3)} nT +
` : ''}
-
- ${stripBars.value.map((v) => html`
`)} + +
+
+ Frame stream + live +
+
+ ${stripBars.value.map((v) => html`
`)} +
+ ${this.expanded ? html` +
+ frames in window: ${stripBars.value.length} + noise floor: ${lastFrame.value ? lastFrame.value.noiseFloorPtSqrtHz.toFixed(2) + ' pT/√Hz' : '—'} +
` : ''}
`; @@ -208,6 +286,7 @@ export class NvInspector extends LitElement { hex = arr.slice(0, 60).join(' '); } return html` +
MagFrame v1 fields @@ -232,6 +311,11 @@ export class NvInspector extends LitElement { LE
${hex || '—'}
+ ${this.expanded ? html` +
+ Layout (little-endian): magic(u32) version(u16) flags(u16) sensor_id(u16) _reserved(u16) t_us(u64) b_pt[3](f32) sigma_pt[3](f32) noise_floor(f32) temp_K(f32). +
` : ''} +
`; } @@ -244,7 +328,34 @@ export class NvInspector extends LitElement { status === 'ok' ? '✓ Witness verified · determinism gate' : status === 'fail' ? '✗ Witness mismatch · audit required' : 'Verify witness'; + const match = expectedWitness.value && witnessHex.value && expectedWitness.value === witnessHex.value; return html` + ${this.expanded ? html` +
+
+
Reference scene
+
Proof::REFERENCE
+
2 dipoles · 1 loop · 1 ferrous · 1 sensor
+
+
+
Seed
+
0x0000002A
+
canonical Proof::SEED
+
+
+
Sample count
+
256
+
Proof::N_SAMPLES
+
+
+
Status
+
+ ${status === 'ok' ? '✓ matches' : status === 'fail' ? '✗ drift' : status === 'pending' ? '… running' : '— idle'} +
+
${match ? 'byte-equivalent' : 'not yet verified'}
+
+
+ ` : ''}
Expected (Proof::EXPECTED_WITNESS_HEX) @@ -260,17 +371,44 @@ export class NvInspector extends LitElement {
${witnessHex.value || '(not verified yet)'}
+ ${this.expanded ? html` +
+
+ What this verifies + ADR-089 §5 +
+
+

Pressing Verify runs the canonical reference pipeline + (Proof::generate) end-to-end inside this browser's WASM Worker: + scene → Biot-Savart synthesis → material attenuation → NV ensemble → ADC + lock-in → + concatenated MagFrame bytes → SHA-256.

+

If the resulting hash matches the constant pinned at build time + (cc8de9b01b0ff5bd…), every constant — γ_e, D_GS, μ₀, T₂*, contrast, the PRNG + stream, the frame layout, the pipeline ordering — is byte-identical to the published + reference. If it doesn't match, something drifted; the dashboard names which.

+

This is the same regression test that runs in + cargo test -p nvsim — running in your browser, against your own WASM build.

+
+
+ ` : ''} `; } override render() { return html` -
- - - +
+ + +
-
+
+ ${this.renderHeader()} ${this.tab === 'signal' ? this.renderSignalTab() : this.tab === 'frame' ? this.renderFrameTab() : this.renderWitnessTab()}