* feat(server): cross-node RSSI-weighted feature fusion + benchmarks
Adds fuse_multi_node_features() that combines CSI features across all
active ESP32 nodes using RSSI-based weighting (closer node = higher weight).
Benchmark results (2 ESP32 nodes, 30s, ~1500 frames):
Metric | Baseline | Fusion | Improvement
---------------------|----------|---------|------------
Variance mean | 109.4 | 77.6 | -29% noise
Variance std | 154.1 | 105.4 | -32% stability
Confidence | 0.643 | 0.686 | +7%
Keypoint spread std | 4.5 | 1.3 | -72% jitter
Presence ratio | 93.4% | 94.6% | +1.3pp
Person count still fluctuates near threshold — tracked as known issue.
Verified on real hardware: COM6 (node 1) + COM9 (node 2) on ruv.net.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ui): add client-side lerp smoothing to pose renderer
Keypoints now interpolate between frames (alpha=0.25) instead of
jumping directly to new positions. This eliminates visual jitter
that persists even with server-side EMA smoothing, because the
renderer was drawing every WebSocket frame at full rate.
Applied to skeleton, keypoints, and dense body rendering paths.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat: DynamicMinCut person separation + UI lerp smoothing
- Added ruvector-mincut dependency to sensing server
- Replaced variance-based person scoring with actual graph min-cut on
subcarrier temporal correlation matrix (Pearson correlation edges,
DynamicMinCut exact max-flow)
- Recalibrated feature scaling for real ESP32 data ranges
- UI: client-side lerp interpolation (alpha=0.25) on keypoint positions
- Dampened procedural animation (noise, stride, extremity jitter)
- Person count thresholds retuned for mincut ratio
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs: update CHANGELOG with v0.5.1-v0.5.3 releases
Co-Authored-By: claude-flow <ruv@ruv.net>
* chore: update vendored ruvector to latest main (v2.1.0-40)
Was at v2.0.5-172 (f8f2c600a), now at v2.1.0-40 (050c3fe6f).
316 commits with new crates: ruvector-coherence, sona, ruvector-core,
ruvector-gnn improvements, and security hardening.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat: RuVector Phases 2+3 — temporal smoothing, kinematic constraints, coherence gating
Phase 2 (sensing server):
- Temporal keypoint smoothing via EMA (alpha=0.3) with coherence-adaptive blending
- Coherence scoring: running variance of motion_energy over 20 frames
- Low coherence → reduce alpha to 0.1 (trust measurements less)
- Per-node prev_keypoints for frame-to-frame smoothing
- Bone length clamping (±20%) in derive_single_person_pose
Phase 3 (signal crate):
- SkeletonConstraints: Jakobsen relaxation (3 iterations) on 12-bone
COCO-17 kinematic tree — prevents impossible skeletons
- CompressedPoseHistory: two-tier storage (hot f32 + warm i16 quantized)
for trajectory matching and re-ID
- 8 new tests for constraints + history
Vendored ruvector updated to v2.1.0-40 (latest main, 316 commits).
Workspace deps remain at v2.0.4 (crates.io) until v2.1.0 is published.
647 tests pass across both crates (0 failures).
Refs #296
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(server): use max instead of sum for multi-node person aggregation
With nodes in the same room, each node sees the same people. Summing
per-node counts double-counted (2 nodes × 1 person = 2 persons).
Now uses max() so 2 nodes × 1 person = 1 person.
Verified on real hardware: COM6 (node 1) + COM9 (node 2) on ruv.net,
estimated_persons=1 with 1 person in room.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(server): reduce skeleton jitter + raise person count thresholds
- EMA alpha 0.3→0.15, low-coherence 0.1→0.05
- Remove tick-based noise (main jitter source)
- Breathing 5x slower, extremity jitter 3x smaller, stride 2x smaller
- Person count 1→2 threshold 0.65→0.80
- Aggregation sum→max for same-room nodes
Verified on COM6+COM9: 1 person stable.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(signal): subcarrier importance weighting via mincut partition (Phase 1)
Adds subcarrier_importance_weights() to ruvector signal crate — converts
mincut partition into per-subcarrier float weights (>1.0 for sensitive,
0.5 for insensitive subcarriers).
Sensing server now uses weighted mean/variance in extract_features_from_frame
instead of treating all 56 subcarriers equally. This emphasizes body-motion-
sensitive subcarriers and reduces noise from static multipath.
Expected: ~26% reduction in keypoint jitter (±15cm → ±11cm RMS).
284 tests pass (191 trainer + 51 lib + 18 vital_signs + 16 dataset + 8 multi_node).
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(firmware): stack overflow risk + tick-rate independence (review findings)
Critical fixes from deep review:
1. **Stack overflow prevention**: Moved BPM scratch buffers (br_buf, hr_buf)
from stack to static storage in both process_frame() and
update_multi_person_vitals(). Combined stack was ~6.5-7.5 KB of 8 KB
limit — now reduced by ~4 KB to safe margins.
2. **Tick-rate independence**: Post-batch yield now uses
pdMS_TO_TICKS(20) with min-1 guard instead of raw vTaskDelay(2).
Previously assumed 100Hz tick rate.
3. **EDGE_BATCH_LIMIT to header**: Moved from local const to
edge_processing.h #define for configurability.
Firmware builds clean at 843 KB.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(server): stale node eviction, remove unsafe pointer (review findings)
Critical fixes from deep review:
1. **Stale node eviction**: node_states HashMap now evicts nodes with no
frame for >60 seconds, every 100 ticks. Prevents unbounded memory
growth and stale smoothing data when nodes are replaced.
2. **Remove unsafe raw pointer**: Replaced the unsafe raw pointer to
adaptive_model (used to break borrow checker deadlock with
node_states) with a safe .clone() before the mutable borrow.
AdaptiveModel derives Clone so this is a clean copy.
284 tests pass, zero failures.
Co-Authored-By: claude-flow <ruv@ruv.net>
The server parsed rssi from buf[14] and noise_floor from buf[15], but
the firmware (csi_collector.c) packs them at buf[16] and buf[17]:
Firmware: n_subcarriers=u16(6-7) freq=u32(8-11) seq=u32(12-15) rssi=i8(16)
Server: n_subcarriers=u8(6) freq=u16(8-9) seq=u32(10-13) rssi=i8(14) ← WRONG
This caused RSSI to read the high byte of the sequence counter instead
of the actual signed RSSI value, producing positive values (e.g., +9)
instead of the correct negative values (e.g., -46 dBm).
Added inline documentation of the frame layout matching csi_collector.c.
Closes#332
* docs(adr): ADR-068 per-node state pipeline for multi-node sensing (#249)
Documents the architectural change from single shared state to per-node
HashMap<u8, NodeState> in the sensing server. Includes scaling analysis
(256 nodes < 13 MB), QEMU validation plan, and aggregation strategy.
Also links README hero image to the explainer video.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat(server): per-node state pipeline for multi-node sensing (ADR-068, #249)
Replaces the single shared state pipeline with per-node HashMap<u8, NodeState>.
Each ESP32 node now gets independent:
- frame_history (temporal analysis)
- smoothed_person_score / prev_person_count
- smoothed_motion / baseline / debounce state
- vital sign detector + smoothing buffers
- RSSI history
Multi-node aggregation:
- Person count = sum of per-node counts for active nodes (seen <10s)
- SensingUpdate.nodes includes all active nodes
- estimated_persons reflects cross-node aggregate
Single-node deployments behave identically (HashMap has one entry).
Simulated data path unchanged for backward compatibility.
Closes#249
Refs #237, #276, #282
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(firmware,server): watchdog crash on busy LANs + no detection from edge vitals (#321, #323)
**Firmware (#321):** edge_dsp task now batch-limits frame processing to 4
frames before a 10ms yield. On corporate LANs with high CSI frame rates,
the previous 1-tick-per-frame yield wasn't enough to prevent IDLE1
starvation and task watchdog triggers.
**Sensing server (#323):** When ESP32 runs the edge DSP pipeline (Tier 2+),
it sends vitals packets (magic 0xC5110002) instead of raw CSI frames.
Previously, the server broadcast these as raw edge_vitals but never
generated a sensing_update, so the UI showed "connected" but "0 persons".
Now synthesizes a full sensing_update from vitals data including
classification, person count, and pose generation.
Closes#321Closes#323
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(firmware): address review findings — idle busy-spin and observability
- Fix pdMS_TO_TICKS(5)==0 at 100Hz causing busy-spin in idle path (use
vTaskDelay(1) instead)
- Post-batch yield now 2 ticks (20ms) for genuinely longer pause
- Add s_ring_drops counter to ring_push for diagnosing frame drops
- Expose drop count in periodic vitals log line
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(server): set breathing_band_power for skeleton animation from vitals
When presence is detected via edge vitals, set breathing_band_power to
0.5 so the UI's torso breathing animation works. Previously hardcoded
to 0.0 which made the skeleton appear static even when breathing rate
was being reported.
Co-Authored-By: claude-flow <ruv@ruv.net>
The source field was set to "esp32" on the first UDP frame but never
reverted when frames stopped arriving. This caused the UI to show
"Real hardware connected" indefinitely after powering off all nodes.
Changes:
- Add last_esp32_frame timestamp to AppStateInner
- Add effective_source() method with 5-second timeout
- Source becomes "esp32:offline" when no frames received within 5s
- Health endpoint shows "degraded" instead of "healthy" when offline
- All 6 status/health/info API endpoints use effective_source()
Fixes#297
Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
The person-count heuristic was causing widespread flickering (#237, #249,
#280, #292) because:
1. Threshold 0.50 for 2-persons was too low — multipath reflections in
small rooms easily exceeded it
2. No actual hysteresis despite the comment claiming asymmetric thresholds
3. EMA smoothing (α=0.15) was too responsive to transient spikes
Changes:
- Raise up-thresholds: 1→2 persons at 0.65 (was 0.50), 2→3 at 0.85 (was 0.80)
- Add true hysteresis with asymmetric down-thresholds: 2→1 at 0.45, 3→2 at 0.70
- Track prev_person_count in SensingState for state-aware transitions
- Increase EMA smoothing to α=0.10 (~2s time constant at 20 Hz)
- Update all 4 call sites (ESP32, Windows WiFi, multi-BSSID, simulated)
Fixes#292, #280, #237
Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
Remove from index: daemon.pid, vectors.db, memory.db,
pending-insights.jsonl, session state, node_modules.
These are machine-specific runtime artifacts that should
never have been committed.
Co-Authored-By: claude-flow <ruv@ruv.net>
The sensing-server binary was referenced in tauri.conf.json but doesn't
exist in CI environment. Removed the resources section to fix the build.
Co-Authored-By: claude-flow <ruv@ruv.net>
## New Features
- WiFi Configuration Modal: Configure ESP32 WiFi credentials directly from the desktop app
- Serial port WiFi commands: Sends wifi_config/wifi/set ssid commands via serial
- Improved feedback UI with status indicators (Success/Commands Sent/Error)
## API Improvements
- New Tauri command: configure_esp32_wifi(port, ssid, password)
- 21 new integration tests covering all API functionality
- ESP32 VID/PID detection for CP210x, CH340, FTDI, and native USB
## UI Enhancements
- WiFi button in Serial Ports table for ESP32-compatible devices
- Modal with SSID/password inputs and clear status feedback
- "Done" button after configuration with "Try Again" option
## Testing
- 18 unit tests + 21 integration tests = 39 total tests passing
- Tests cover: discovery, settings, server, flash, OTA, provision, WASM, state, domain models
Co-Authored-By: claude-flow <ruv@ruv.net>
## Changes
- Auto-scan serial ports on Discovery page load (not just Serial tab)
- Show USB device hint when no network nodes found but USB devices detected
- Add "Flash →" button in Serial Ports table for quick navigation
- Fix server stop: proper SIGTERM/SIGKILL with process group handling
- Add data source selector on Sensing page (simulate/auto/wifi/esp32)
- Fix log viewer scroll (use containerRef.scrollTop instead of scrollIntoView)
- Add fallback serial port scanning for macOS when tokio_serial fails
## Fixes
- ESP32 USB devices now visible immediately on Discovery page
- Server processes properly terminated on stop
- Log viewer no longer scrolls entire page
Co-Authored-By: claude-flow <ruv@ruv.net>
- Bundle sensing-server binary in app resources (bin/sensing-server)
- Add find_server_binary() for multi-path binary discovery
- Connect Sensing page to real WebSocket endpoint (ws://localhost:8765/ws/sensing)
- Add DataSource type and source config for data source selection
- Default to simulate mode when no ESP32 hardware present
- Add ADR-055: Integrated Sensing Server architecture
- Add ADR-056: Complete RuView Desktop Capabilities Reference
Closes integration of sensing server as single-package distribution.
Co-Authored-By: claude-flow <ruv@ruv.net>
- Add navigation to Quick Actions (Flash, OTA, WASM buttons now work)
- Add error feedback for Scan Network failures
- Create version.ts as single source of truth for version
- Switch reqwest from rustls-tls to native-tls for Windows compatibility
- Version bump to 0.4.1
Co-Authored-By: claude-flow <ruv@ruv.net>
- Switch from rustls-tls to native-tls for better Windows support
- Fix Cargo.toml formatting (remove duplicate sections)
Co-Authored-By: claude-flow <ruv@ruv.net>
- Add desktop-release.yml workflow for automated Windows/macOS builds
- Fix frontendDist path in tauri.conf.json for production builds
- Builds macOS (arm64 + x64) and Windows (MSI + NSIS) on native runners
- Creates GitHub Release with all artifacts on tag push or manual dispatch
To trigger a release:
git tag desktop-v0.3.0 && git push origin desktop-v0.3.0
Or use workflow_dispatch from GitHub Actions UI
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs: add ADR-052 Tauri desktop frontend with DDD bounded contexts
Proposes a Tauri v2 desktop application as the primary UI for RuView,
replacing 6+ CLI tools with a single cross-platform app. Covers hardware
discovery, firmware flashing (espflash), OTA updates, WASM module
management, sensing server control, and live visualization.
Includes DDD domain model with 6 bounded contexts, aggregate definitions,
domain events, and anti-corruption layers for ESP32 firmware APIs.
Closes#177
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs: add persistent node registry, OTA safety gate, plugin architecture to ADR-052
Incorporates engineering review feedback:
- Persistent node registry (~/.ruview/nodes.db) — discovery becomes reconciliation
- BatchOtaSession aggregate with TdmSafe rolling update strategy
- Plugin architecture section — control plane extensibility trajectory
- Renumbered sections for new content (9-12 added, impl phases now section 13)
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs: add ADR-053 UI design system — Foundation Book + Unity-inspired interface
- Dark professional theme with rUv purple accent (#7c3aed)
- Foundation Book typographic hierarchy (heading-xl through body-sm)
- Unity Editor-inspired panel layout (sidebar + list/detail split + inspector)
- 6 component specs: NodeCard, FlashProgress, MeshGraph, PropertyGrid, StatusBadge, LogViewer
- Color system with status indicators (online/warning/error/info)
- 4px base grid spacing system
- Branding: splash screen, status bar, about dialog
Refs #177
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix: rewrite ADR-053 UI design system with practical terminology
Replace sci-fi themed language (Asimov Foundation references, Prime Radiant,
Encyclopedia Galactica, Terminus, Seldon Crisis) with clear, practical
terminology that engineers and operators can immediately understand.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix: specify Three.js for mesh topology visualization in ADR-053
Use Three.js for the mesh topology view, consistent with existing
visualization patterns in ui/observatory/js/ and ui/components/.
Includes implementation details: MeshPhongMaterial for node status,
BufferGeometry for dynamic updates, OrbitControls, raycasting.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat: add Tauri v2 desktop crate with React frontend (Phase 1 skeleton)
Rust backend (wifi-densepose-desktop):
- 14 Tauri commands across 6 groups: discovery, flash, OTA, WASM, server, provision
- Domain types: Node, NodeRegistry, FlashSession, OtaSession, BatchOtaSession
- AppState with DiscoveryState and ServerState behind Mutex
- Workspace Cargo.toml updated with new member
- cargo check passes cleanly
React/TypeScript frontend:
- TypeScript types matching Rust domain model
- Hooks: useNodes (discovery polling), useServer (start/stop/status)
- Components: StatusBadge, NodeCard, Sidebar
- Pages: Dashboard, Nodes (table + expandable details), FlashFirmware
(3-step wizard with progress bar), Settings (server/security/discovery)
- App.tsx with sidebar navigation routing
- Vite 6 + React 18 + @tauri-apps/api v2
Implements ADR-052 Phase 1 skeleton. All commands return stub data.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat: implement ADR-053 design system across all frontend components
Create design-system.css with all ADR-053 tokens:
- CSS custom properties: colors, spacing, fonts, panel dimensions
- Typography scale classes (heading-xl through data-lg)
- Form control and button base styles
- Custom scrollbar, selection highlight, animations
Update all components to use design system tokens:
- Replace hardcoded colors with var(--bg-surface), var(--border), etc.
- Replace generic monospace with var(--font-mono) (JetBrains Mono)
- Replace system font stack with var(--font-sans) (Inter)
- Replace spacing values with var(--space-N) tokens
- StatusBadge: use var(--status-online/warning/error/info)
- Dashboard: add stat cards with data-lg class, use StatusBadge
- FlashFirmware: pulse animation on progress bar during writes
- Settings: default bind_address 127.0.0.1 (matches ADR-050)
Add status bar footer with "Powered by rUv", node count, server status.
Load Inter + JetBrains Mono from Google Fonts in index.html.
Update ADR-053 status from Proposed to Accepted.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix: add missing @tauri-apps/plugin-dialog and plugin-shell dependencies
Required for firmware file picker in FlashFirmware page and
shell sidecar support. Fixes Vite build failure.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix: add defensive optional chaining for node.chip access
Rust DiscoveredNode stub doesn't include chip field yet.
Use optional chaining (node.chip?.toUpperCase()) to prevent
TypeError at runtime.
Co-Authored-By: claude-flow <ruv@ruv.net>
* feat: add OTA, Edge Modules, Sensing, Mesh View pages with enhanced design system
Implement all 4 remaining pages (OtaUpdate, EdgeModules, Sensing, MeshView)
and enhance the design system with glassmorphism cards, count-up animations,
page transitions, gradient accents, live status bar, and consistent status
dot glows across the UI.
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs: add desktop crate README and link from main README
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs: add download/run instructions to desktop README
Co-Authored-By: claude-flow <ruv@ruv.net>
Add environment-tuned activity classification that learns from labeled
ESP32 CSI recordings, replacing brittle static thresholds.
- Adaptive classifier: 15-feature logistic regression trained from JSONL
recordings (variance, motion band, subcarrier stats: skew, kurtosis,
entropy, IQR). Trains in <1s, persists as JSON, auto-loads on restart.
- Three-stage signal smoothing: adaptive baseline subtraction (α=0.003),
EMA + trimmed-mean median filter (21-frame window), hysteresis debounce
(4 frames). Motion classification now stable across seconds, not frames.
- Vital signs stabilization: outlier rejection (±8 BPM HR, ±2 BPM BR),
trimmed mean, dead-band (±2 BPM HR), EMA α=0.02. HR holds steady for
10+ seconds instead of jumping 50 BPM every frame.
- Observatory auto-detect: always probes /health on startup, connects
WebSocket to live ESP32 data automatically.
- New API endpoints: POST /api/v1/adaptive/train, GET /adaptive/status,
POST /adaptive/unload for runtime model management.
- Updated user guide with Observatory, adaptive classifier tutorial,
signal smoothing docs, and new troubleshooting entries.
The web UI had persistent 404 errors on model, recording, and training
endpoints, and the sensing WebSocket never connected on Dashboard/Live
Demo tabs because sensingService.start() was only called lazily on
Sensing tab visit.
Server (main.rs):
- Add 14 fully-functional Axum handlers: model CRUD (7), recording
lifecycle (4), training control (3)
- Scan data/models/ and data/recordings/ at startup
- Recording writes CSI frames to .jsonl via tokio background task
- Model load/unload lifecycle with state tracking
Web UI (app.js):
- Import and start sensingService early in initializeServices() so
Dashboard and Live Demo tabs connect to /ws/sensing immediately
Mobile (ws.service.ts):
- Fix WebSocket URL builder to use same-origin port instead of
hardcoded port 3001
Mobile (jest.config.js):
- Fix testPathIgnorePatterns that was ignoring the entire test directory
Mobile (25 test files):
- Replace all it.todo() placeholder tests with real implementations
covering components, services, stores, hooks, screens, and utils
ADR-043 documents all changes.
* feat: RVF training pipeline & UI integration (ADR-036)
Implement full model training, management, and inference pipeline:
Backend (Rust):
- recording.rs: CSI recording API (start/stop/list/download/delete)
- model_manager.rs: RVF model loading, LoRA profile switching, model library
- training_api.rs: Training API with WebSocket progress streaming, simulated
training mode with realistic loss curves, auto-RVF export on completion
- main.rs: Wire new modules, recording hooks in all CSI paths, data dirs
UI (new components):
- ModelPanel.js: Dark-mode model library with load/unload, LoRA dropdown
- TrainingPanel.js: Recording controls, training config, live Canvas charts
- model.service.js: Model REST API client with events
- training.service.js: Training + recording API client with WebSocket progress
UI (enhancements):
- LiveDemoTab: Model selector, LoRA profile switcher, A/B split view toggle,
training quick-panel with 60s recording shortcut
- SettingsPanel: Full dark mode conversion (issue #92), model configuration
(device, threads, auto-load), training configuration (epochs, LR, patience)
- PoseDetectionCanvas: 10-frame pose trail with ghost keypoints and motion
trajectory lines, cyan trail toggle button
- pose.service.js: Model-inference confidence thresholds
UI (plumbing):
- index.html: Training tab (8th tab)
- app.js: Panel initialization and tab routing
- style.css: ~250 lines of training/model panel dark-mode styles
191 Rust tests pass, 0 failures. Closes#92.
Refs: ADR-036, #93
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix: real RuVector training pipeline + UI service fixes
Training pipeline (training_api.rs):
- Replace simulated training with real signal-based training loop
- Load actual CSI data from .csi.jsonl recordings or live frame history
- Extract 180 features per frame: subcarrier amplitudes, temporal variance,
Goertzel frequency analysis (9 bands), motion gradients, global stats
- Train calibrated linear CSI-to-pose mapping via mini-batch gradient descent
with L2 regularization (ridge regression), Xavier init, cosine LR decay
- Self-supervised: teacher targets from derive_pose_from_sensing() heuristics
- Real validation metrics: MSE and PCK@0.2 on 80/20 train/val split
- Export trained .rvf with real weights, feature normalization stats, witness
- Add infer_pose_from_model() for live inference from trained model
- 16 new tests covering features, training, inference, serialization
UI fixes:
- Fix double-URL bug in model.service.js and training.service.js
(buildApiUrl was called twice — once in service, once in apiService)
- Fix route paths to match Rust backend (/api/v1/train/*, /api/v1/recording/*)
- Fix request body formats (session_name, nested config object)
- Fix top-level await in LiveDemoTab.js blocking module graph
- Dynamic imports for ModelPanel/TrainingPanel in app.js
- Center nav tabs with flex-wrap for 8-tab layout
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix: WebSocket onOpen race condition, data source indicators, auto-start pose detection
- Fix WebSocket onOpen race condition in websocket.service.js where
setupEventHandlers replaced onopen after socket was already open,
preventing pose service from receiving connection signal
- Add 4-state data source indicator (LIVE/SIMULATED/RECONNECTING/OFFLINE)
across Dashboard, Sensing, and Live Demo tabs via sensing.service.js
- Add hot-plug ESP32 auto-detection in sensing server (auto mode runs
both UDP listener and simulation, switches on ESP32_TIMEOUT)
- Auto-start pose detection when backend is reachable
- Hide duplicate PoseDetectionCanvas controls when enableControls=false
- Add standalone Demo button in LiveDemoTab for offline animated demo
- Add data source banner and status styling
Co-Authored-By: claude-flow <ruv@ruv.net>
- Docker default changed from --source simulated to --source auto
(auto-detects ESP32 on UDP 5005, falls back to simulation)
- Pose derivation now driven by real sensing features: motion_band_power,
breathing_band_power, variance, dominant_freq_hz, change_points
- Temporal feature extraction: 100-frame circular buffer, Goertzel
breathing rate estimation (0.1-0.5 Hz), frame-to-frame L2 motion
detection, SNR-based signal quality metric
- Signal field driven by subcarrier variance spatial mapping instead
of fixed animation circle
- UI data source indicators: LIVE/RECONNECTING/SIMULATED banner on
sensing tab, estimation mode badge on live demo tab
- Setup guide panel explaining ESP32 count requirements for each
capability level (1x: presence, 3x: localization, 4x+: full pose)
- Tick rate improved from 500ms to 100ms (2fps to 10fps)
- Fixed Option<f64> division bug from PR #83
- ADR-035 documents all decisions
Closes#86
Co-Authored-By: claude-flow <ruv@ruv.net>
Fix 4 test failures in the ADR-030 exotic sensing tier modules:
- field_model::test_perturbation_extraction: Use 8 subcarriers with 2
modes and varied calibration data so perturbation on subcarrier 5
(not captured by any environmental mode) remains visible in residual.
- longitudinal::test_drift_detected_after_sustained_deviation: Use 30
baseline days with tiny noise to anchor Welford stats, then inject
deviation of 5.0 (vs 0.1 baseline) so z-score exceeds 2.0 even as
drifted values are accumulated into the running statistics.
- longitudinal::test_monitoring_level_escalation: Same strategy with 30
baseline days and deviation of 10.0 to sustain z > 2.0 for 7+ days,
reaching RiskCorrelation monitoring level.
- tomography::test_nonzero_attenuation_produces_density: Fix ISTA solver
oscillation by replacing max-column-norm Lipschitz estimate with
Frobenius norm squared upper bound, ensuring convergent step size.
Also use stronger attenuations (5.0-16.0) and lower lambda (0.001).
All 209 ruvsense tests now pass. Workspace compiles cleanly.
Co-Authored-By: claude-flow <ruv@ruv.net>
The make_noisy_kpts test helper used noise=0.1 with GT coordinates
spread across [0, 0.85], producing a large bbox diagonal that made
even noisy predictions fall within PCK@0.2 threshold. Reduce GT
coordinate range and increase noise to 0.5 so the test correctly
verifies that noisy predictions produce PCK < 1.0.
Co-Authored-By: claude-flow <ruv@ruv.net>
- tomography.rs: use checked_mul for nx*ny*nz to prevent integer overflow
on adversarial grid configurations
- phase_align.rs: add defensive bounds check in mean_phase_on_indices to
prevent panic on out-of-range subcarrier indices
- multistatic.rs: stabilize softmax in attention_weighted_fusion with
max-subtraction to prevent exp() overflow on extreme similarity values
Co-Authored-By: claude-flow <ruv@ruv.net>
- Add MacosCoreWlanScanner (macOS): CoreWLAN Swift helper adapter with
synthetic BSSID generation via FNV-1a hash for redacted MACs (ADR-025)
- Add LinuxIwScanner (Linux): parses `iw dev <iface> scan` output with
freq-to-channel conversion and BSS stanza parsing
- Both adapters produce Vec<BssidObservation> compatible with the
existing WindowsWifiPipeline 8-stage processing
- Platform-gate modules with #[cfg(target_os)] so each adapter only
compiles on its target OS
- Fix Python MacosWifiCollector: remove synthetic byte counters that
produced misleading tx_bytes/rx_bytes data (set to 0)
- Add compiled Swift binary (mac_wifi) to .gitignore
Co-Authored-By: claude-flow <ruv@ruv.net>