diff --git a/.beads/.br_recovery/beads.db-shm.20260409_193343_182842531.bak b/.beads/.br_recovery/beads.db-shm.20260409_193343_182842531.bak new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/.beads/.br_recovery/beads.db-shm.20260409_193343_182842531.bak differ diff --git a/.beads/.br_recovery/beads.db-wal.20260409_193343_182842531.bak b/.beads/.br_recovery/beads.db-wal.20260409_193343_182842531.bak new file mode 100644 index 0000000..e69de29 diff --git a/.beads/.br_recovery/beads.db.20260409_193343_182842531.bak b/.beads/.br_recovery/beads.db.20260409_193343_182842531.bak new file mode 100644 index 0000000..1ae3e2a Binary files /dev/null and b/.beads/.br_recovery/beads.db.20260409_193343_182842531.bak differ diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 48dff43..b0f8ea3 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -54,7 +54,7 @@ {"id":"spaxel-axa.1","title":"Interactive onboarding wizard","description":"## Background\n\nPhase 4's central goal is that a non-technical user can go from an unboxed ESP32-S3 to streaming CSI in under 5 minutes. The onboarding wizard is the centrepiece of this experience. It uses the Web Serial API (available in Chrome/Edge) to communicate with the ESP32 over USB — no driver installation needed, no CLI, no app download. The wizard is embedded in the existing mothership dashboard, accessible at /onboard.\n\n## Why Web Serial?\n\nThe alternative approaches — a dedicated mobile app, a WiFi provisioning AP, or a CLI tool — all have significant UX friction. Web Serial lets us flash firmware, provision WiFi credentials, and guide the user through calibration all in one browser session. The dashboard already knows the mothership IP/port. Chrome and Edge (95%+ of desktop browser market) support Web Serial natively since 2021. The only caveat is that Web Serial is not available in Firefox or Safari — this must be documented prominently at the start of the wizard.\n\n## Wizard Steps\n\n1. **Browser check**: Detect navigator.serial availability. If missing, show: 'Please use Google Chrome or Microsoft Edge to use the setup wizard. Firefox and Safari do not support USB device access.'\n\n2. **Connect device**: Call navigator.serial.requestPort(). Guide the user to hold BOOT button while plugging in if the device does not appear. Show a GIF or SVG illustration of the ESP32-S3 board with the BOOT button highlighted.\n\n3. **Flash firmware** (if not already spaxel firmware): Use esp-web-tools (espressif/esp-web-tools, CDN: https://unpkg.com/esp-web-tools@10/dist/web/install-button.js). This open-source library handles the full ESP32 flashing pipeline via Web Serial, including ROM bootloader protocol, chip detection, and progress reporting. It needs a firmware manifest.json at GET /api/firmware/manifest describing binary addresses and offsets. Show a progress bar during flashing. Estimated time: 45-90 seconds.\n\n4. **Provision WiFi**: Show a form for SSID and password. Optional: mothership host/port override (for non-mDNS setups). Assemble the provisioning payload and send to the ESP32 over serial as JSON (see Provisioning Payload bead for format). If using esp-web-tools, the provisioning can be injected via custom serial commands after flashing.\n\n5. **Detect mothership**: Once provisioned and rebooted, the ESP32 boots and discovers the mothership via mDNS (spaxel-mothership.local) or the configured host. Poll GET /api/nodes every 3s for up to 120s waiting for the new node to appear. Show animated 'Connecting...' indicator. On timeout: show WiFi troubleshooting guidance (5GHz check, SSID typo check, distance check).\n\n6. **Guided calibration**: Show the CSI waveform for the new node's links as they come online. Steps:\n a. 'Walk around your space for 30 seconds' — CSI amplitude should show activity. If flat: check node orientation.\n b. 'Stand still at the far end of the room' — capture baseline. Show countdown. Green check when baseline is captured.\n c. 'Walk through the centre of the room' — Fresnel zone lights up in 3D view, blob appears. 'The sensor can see you!'\n d. 'Sit down and stay still for 30 seconds' — test stationary detection (if Phase 5 available). Otherwise skip.\n\n7. **Node placement guidance**: Transition to the coverage painting UI (spaxel-qq6) for optimal node positioning. Show GDOP overlay for the current node placement. Suggest additional node positions if coverage is poor.\n\n## Files to Create/Modify\n\n- dashboard/js/onboard.js: wizard state machine, Web Serial API calls, step rendering\n- dashboard/index.html: add /onboard route and wizard container div, import esp-web-tools\n- mothership/internal/dashboard/routes.go (or similar): add GET /api/firmware/manifest route\n- mothership/internal/dashboard/hub.go: no changes needed (wizard uses REST polling, not WebSocket for this flow)\n\n## esp-web-tools Integration\n\n\n\nManifest served at GET /api/firmware/manifest:\n\n\n## Wizard State Machine\n\nStates: BROWSER_CHECK → CONNECT_DEVICE → FLASH_FIRMWARE → PROVISION_WIFI → DETECT_NODE → CALIBRATE → PLACEMENT → COMPLETE\n\nEach state has: render() function, onEnter() side effects, onNext() transition, onBack() for revert, onError() for failure handling.\n\nPersisted in sessionStorage so a page refresh during onboarding resumes from the last step (critical for the reboot-then-detect step).\n\n## Error Handling\n\nMap every known failure to a human-friendly message:\n- 'NotFoundError: No port selected' → 'No device detected. Make sure the USB cable is connected and hold the BOOT button while plugging in.'\n- 'NetworkError' during flash → 'The connection was interrupted. Check the USB cable is not loose and try again.'\n- Node not appearing after 120s → 'Your node connected to WiFi but cannot reach the mothership. Check: 1) Your router blocks device-to-device communication (AP isolation). 2) The mothership address is correct. 3) Your network uses a VLAN that separates devices.'\n- Wrong SSID/password → Node will fall into captive portal mode after 10 failures, triggering a 'Captive portal detected' guidance flow.\n\nNever show stack traces, WebSocket error codes, or Go error strings to the user.\n\n## Tests\n\n- Mock navigator.serial API in Jest (using jest-serial-port or a hand-written mock) to test wizard state transitions without real hardware\n- Test that provisioning payload is correctly assembled and sent over the mocked serial port\n- Test that polling GET /api/nodes correctly detects node appearance and transitions to DETECT_NODE → CALIBRATE\n- Test that BROWSER_CHECK step correctly detects missing serial API and shows the correct error\n- Test that sessionStorage correctly restores wizard state on page refresh at each step\n\n## Acceptance Criteria\n\n- Wizard completes in under 5 minutes on a fresh ESP32-S3 with a working WiFi network\n- User sees live CSI waveform during calibration step\n- Node appears in dashboard after wizard completion, with correct label\n- All known error conditions show human-friendly guidance, not technical errors\n- All existing dashboard tests pass\n- Wizard state is resumable after page refresh","status":"tombstone","priority":2,"issue_type":"task","created_at":"2026-03-28T01:34:58.168170967Z","created_by":"coding","updated_at":"2026-03-28T01:35:25.110026775Z","closed_at":"2026-03-28T01:35:25.110026775Z","source_repo":".","deleted_at":"2026-03-28T01:35:25.110008642Z","deleted_by":"coding","delete_reason":"delete","original_type":"task","compaction_level":0,"original_size":0} {"id":"spaxel-b6a","title":"Implement calibration POST endpoint","description":"## Task\nImplement POST /api/floorplan/calibrate endpoint.\n\n## Specification\n- Accept {ax,ay,bx,by,distance_m,rotation_deg}: two pixel coordinates and their real-world distance\n- Compute and persist pixel-to-meter transform to SQLite floorplan table\n\n## Acceptance\n- Calibration data persists to SQLite floorplan table","status":"closed","priority":2,"issue_type":"task","assignee":"charlie","created_at":"2026-04-07T17:55:52.516620827Z","created_by":"coding","updated_at":"2026-04-07T18:53:06.721066Z","closed_at":"2026-04-07T18:53:06.720893263Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["mitosis-child","mitosis-depth:1","parent-spaxel-klk"]} {"id":"spaxel-bf5","title":"Build crowd flow visualization","description":"Create visualization tools for occupancy patterns and movement.\n\nDeliverables:\n- Trajectory accumulation over time\n- Directional flow map rendering\n- Dwell time hotspot visualization\n\nAcceptance: Dashboard shows accumulated movement patterns and hotspots.","status":"closed","priority":2,"issue_type":"task","assignee":"sp3","created_at":"2026-03-29T19:25:04.155117811Z","created_by":"coding","updated_at":"2026-03-29T19:39:12.378329742Z","closed_at":"2026-03-29T19:39:12.378228906Z","close_reason":"Implemented crowd flow visualization with three components:\n\nBackend (Go):\n- FlowAccumulator records trajectory segments and dwell time in SQLite\n- REST endpoints for flow map, dwell heatmap, and detected corridors\n- Bresenham rasterization, angular variance analysis, connected component labeling\n\nFrontend (JavaScript):\n- Pattern controls in dashboard sidebar (flows, dwell, corridors toggles)\n- Time filter dropdown (7d, 30d, all time)\n- 3D visualization with ArrowHelper, PlaneGeometry, pulsating animations\n\nFiles: dashboard/index.html, dashboard/js/app.js, mothership/internal/analytics/","source_repo":".","compaction_level":0,"original_size":0,"labels":["mitosis-child","mitosis-depth:1","parent-spaxel-i28"]} -{"id":"spaxel-bsek","title":"Add spatial quick actions","description":"Right-click context menus on 3D elements with follow camera functionality.","status":"in_progress","priority":2,"issue_type":"task","assignee":"golf","created_at":"2026-04-10T02:03:09.410752186Z","created_by":"coding","updated_at":"2026-04-10T02:44:30.791579191Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["failure-count:2","mitosis-child","mitosis-depth:1","parent-spaxel-17u"]} +{"id":"spaxel-bsek","title":"Add spatial quick actions","description":"Right-click context menus on 3D elements with follow camera functionality.","status":"in_progress","priority":2,"issue_type":"task","assignee":"golf","created_at":"2026-04-10T02:03:09.410752186Z","created_by":"coding","updated_at":"2026-04-10T02:54:38.754127232Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred","failure-count:2","mitosis-child","mitosis-depth:1","parent-spaxel-17u"]} {"id":"spaxel-btj","title":"Pre-deployment simulator","description":"## Background\n\nA person considering buying 4+ ESP32-S3 nodes wants to know: will this work in my house? Where should I put the nodes? How accurate will it be with 4 nodes vs 6? The pre-deployment simulator lets users model their space, place virtual nodes, and run synthetic walkers to get an expected accuracy estimate — all before spending any money or touching hardware. It is also a powerful teaching tool for understanding how WiFi CSI sensing works.\n\n## Simulator Mode\n\nNew dashboard route /simulate. A separate UI state that reuses the 3D scene infrastructure but replaces all live data with simulated data.\n\nThe simulator does NOT connect to any hardware or the mothership's live pipeline. It runs entirely in the browser with synthetic CSI generation via a WebAssembly module or JavaScript implementation of the physics model.\n\n(Alternative: run the simulation on the mothership server side, with a separate \"simulation session\" API endpoint. This is more complex but allows reusing the Go signal processing code directly. Recommend the server-side approach for accuracy.)\n\n## Space Editor\n\nReuses the 3D editor from the node placement UI (spaxel-qq6). Additional tools for the simulator:\n- Wall tool: draw lines on the floor plane that represent walls. Walls are stored as line segments and affect the path loss model (each wall crossing reduces RSSI by ~3-6 dB, configurable).\n- Furniture tool: place box-shaped obstacle volumes. Obstacles block direct path and affect Fresnel zone intersection.\n- Room dimensions tool: define the room boundaries (already in node placement UI).\n\n## Virtual Node Placement\n\nSame node placement UI as the real dashboard. Virtual nodes have the same placement interface but no hardware connection. Node icons are greyed out to indicate virtual.\n\nThe placement UI renders the GDOP overlay (from Phase 3, spaxel-qq6) using the virtual node positions. This is the same GDOP computation as the real system.\n\n## Synthetic Walkers\n\n1-4 animated figures that move through the virtual space. Walker types:\n- Random walk: starts at a random position, takes random steps of 0.3-0.8m every 500ms, bounces off walls.\n- Path walk: user draws a polyline path in the 3D view. Walker follows the path and loops.\n- Zone walk: user selects a set of zones. Walker randomly transitions between zones using the zone transition portal geometry.\n\nWalker animation: rendered as the same humanoid mesh used for real blobs in the 3D view but with a distinctive \"ghost\" colour (semi-transparent white) to distinguish from real detections.\n\n## Simulated CSI Generation\n\nFor each virtual walker position at each 100ms timestep, compute the expected CSI frame for each virtual node pair:\n\n1. Path loss: RSSI = RSSI_at_1m - 20*log10(d) - n_walls * wall_attenuation_db\n where d = direct path distance, n_walls = number of walls crossed, wall_attenuation_db = 4 dB default.\n\n2. Fresnel zone contribution: for the walker at position P, compute the Fresnel zone overlap fraction for each link (TX, RX). This uses the same geometry as the FusionEngine (spaxel-m9a).\n\n3. deltaRMS simulation: expected_delta_rms = fresnel_overlap * signal_amplitude + gaussian_noise(sigma)\n signal_amplitude = 0.05 * exp(-distance_from_fresnel_centre / sigma_fresnel) where sigma_fresnel = 0.3m.\n gaussian_noise sigma is calibrated from real-world measurements (see docs/research/ for empirical noise floor).\n\n4. Generate binary CSI frame in the same format as real hardware (24-byte header + I/Q payload). Feed through the actual mothership signal processing pipeline via a simulation API endpoint.\n\n## Pipeline Integration\n\nThe mothership exposes a simulation API: POST /api/simulate/session creates a simulation session. Within a session:\n- Virtual nodes are registered as if they had connected via WebSocket\n- Synthetic frames are injected via POST /api/simulate/session/{id}/frames\n- The standard processing pipeline runs on the injected frames\n- Blob positions are returned in the response\n\nThe dashboard simulator mode polls this API to get blob positions for each simulation timestep.\n\n## Accuracy Estimation\n\nAfter running the simulation with N walkers for 30 seconds:\n- Collect all blob positions from the mothership pipeline\n- Compare to walker ground truth positions (known from the simulation)\n- Compute median position error: median(|blob_position - walker_position|) for matched pairs\n- Compute false positive rate: blob detections when no walker is in that area\n- Compute recall: fraction of walker positions that had a matched blob within 1m\n\nReport: \"With this layout, expected accuracy is ±{N}m median error, {M}% detection rate.\"\n\n## Recommendations Engine\n\nBased on the simulation results, generate actionable layout recommendations:\n- \"Adding a node near the hallway would reduce the east-side dead zone by ~30% GDOP improvement.\"\n- \"Node A and Node B are nearly collinear. Moving Node B 1.5m to the left would improve coverage.\"\n- \"With 4 nodes, you can achieve ±0.8m accuracy. Adding a 5th node would improve to ±0.5m.\"\n\nThese recommendations are generated by:\n1. Running the GDOP computation for the current layout\n2. Identifying zones with GDOP > 2.5 (poor coverage) — dead zone detection\n3. Trying a set of candidate additional-node positions and computing GDOP improvement\n\n## Shopping List\n\nBased on the virtual node count in the simulation:\n\"For this layout you need: {N} × ESP32-S3 Development Board, {N} × USB-C Power Supply (5V 1A), {N} × Adhesive Cable Clips for routing.\"\nInclude a pre-filled Amazon search URL template (not an affiliate link, just a query).\n\n## Files to Create or Modify\n\n- mothership/internal/simulator/session.go: SimulationSession, synthetic frame injection API\n- mothership/internal/simulator/physics.go: path loss model, Fresnel zone CSI generation\n- mothership/internal/simulator/accuracy.go: accuracy estimation, recommendation engine\n- dashboard/js/simulate.js: simulator UI, walker rendering, recommendations display\n- mothership/internal/dashboard/routes.go: POST/GET /api/simulate/ endpoints\n\n## Tests\n\n- Test Fresnel zone CSI simulation: walker at the midpoint of a TX-RX link should produce delta_rms > 0.03; walker at 2m off-axis should produce delta_rms < 0.01\n- Test path loss model: d=1m, n_walls=0 -> RSSI = RSSI_at_1m; d=2m, n_walls=1 -> RSSI = RSSI_at_1m - 6 - 4 = -10 dB relative\n- Test accuracy estimation: 1 walker at known position, simulation produces 1 blob within 0.5m -> accuracy report shows ≤ 0.5m error\n- Test recommendations engine: GDOP > 2.5 in east corner -> recommendation to add node near east corner\n\n## Acceptance Criteria\n\n- Simulator runs without any hardware (all computation in mothership API + browser)\n- GDOP overlay renders correctly for virtual node placements\n- Synthetic walkers produce blob detections via the real mothership pipeline\n- Accuracy estimate is produced after 30-second simulation run\n- Recommendation engine suggests at least one improvement for any layout with a dead zone\n- Shopping list rendered with correct node count\n- Tests pass","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-28T01:56:54.736166126Z","created_by":"coding","updated_at":"2026-04-09T23:28:13.602020044Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred"],"dependencies":[{"issue_id":"spaxel-btj","depends_on_id":"spaxel-i28","type":"blocks","created_at":"2026-03-28T03:29:14.783856424Z","created_by":"coding","metadata":"{}","thread_id":""},{"issue_id":"spaxel-btj","depends_on_id":"spaxel-o0e","type":"blocks","created_at":"2026-03-28T01:58:40.668968235Z","created_by":"coding","metadata":"{}","thread_id":""}]} {"id":"spaxel-c02","title":"Mothership: comprehensive /healthz endpoint","description":"## Overview\nExpand the /healthz endpoint from the current minimal response to the full spec required for Docker HEALTHCHECK and Traefik health routing.\n\n## Current state\nGET /healthz returns: {status:'ok', version:'...'} — missing required fields\n\n## Required response (plan lines 3507-3510):\nHealthy: HTTP 200 — {status:'ok', uptime_s:N, version:'...', nodes_online:N, db:'ok', load_level:0-3}\nDegraded: HTTP 503 — {status:'degraded', reason:'...', uptime_s:N, nodes_online:N, db:'ok'|'failing', load_level:0-3}\n\n## Implementation\n- Track start_time at mothership boot; compute uptime_s = int(time.Since(start).Seconds())\n- nodes_online: query ingestion server's connected node count (atomic counter)\n- db health: run 'SELECT 1' against SQLite with 100ms timeout; 'ok' or 'failing'\n- load_level: read from load shedding state (spaxel-zvb)\n- Degraded conditions: db='failing', or load_level=3 for >60s, or nodes_online=0 after 5min uptime\n- reason field: human-readable explanation of degradation\n\n## Docker integration\nUpdate Dockerfile HEALTHCHECK:\nHEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 CMD wget -qO- http://localhost:8080/healthz | grep -q '\"status\":\"ok\"' || exit 1\n\n## Acceptance\n- GET /healthz returns all required fields with correct types\n- HTTP 503 returned when SQLite unreachable (test by renaming DB file)\n- uptime_s increments correctly across multiple calls\n- Docker HEALTHCHECK transitions to 'healthy' within start-period","status":"closed","priority":2,"issue_type":"task","assignee":"alpha","created_at":"2026-04-06T16:43:31.728532118Z","created_by":"coding","updated_at":"2026-04-07T15:14:36.243697798Z","closed_at":"2026-04-07T15:14:36.243595147Z","close_reason":"Comprehensive /healthz endpoint already implemented in commit e44dd34. All required fields present: status, uptime_s, version, nodes_online, db, load_level, reason. Degraded conditions: DB failing, load_level=3 >60s, no nodes after 5min. HTTP 200/503 codes. Docker HEALTHCHECK configured.","source_repo":".","compaction_level":0,"original_size":0,"labels":["failure-count:3"]} {"id":"spaxel-c0q","title":"Phase 5: Reliability & Intelligence","description":"Goal: Production-quality detection for daily home use.\n\nDeliverables:\n- Diurnal adaptive baseline (24-slot hourly vectors, 7-day learning, crossfade, confidence indicator)\n- Stationary person detection (breathing band 0.1-0.5 Hz, long-dwell logic)\n- Ambient confidence score (per-link health: SNR, phase stability, packet rate, drift; composite gauge)\n- Self-healing fleet (auto role re-optimization on node loss/recovery, coverage comparison)\n- Link weather diagnostics (root-cause suggestions, weekly trends, repositioning advice)\n\nExit criteria: System runs unattended 7+ days with <5% false positive rate.","status":"closed","priority":3,"issue_type":"phase","assignee":"delta","created_at":"2026-03-27T01:55:24.131799292Z","created_by":"coding","updated_at":"2026-03-29T16:17:57.940335180Z","closed_at":"2026-03-29T16:17:57.940275703Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred"],"dependencies":[{"issue_id":"spaxel-c0q","depends_on_id":"spaxel-axa","type":"blocks","created_at":"2026-03-28T01:33:43.508871095Z","created_by":"coding","metadata":"{}","thread_id":""}]} diff --git a/.needle-predispatch-sha b/.needle-predispatch-sha index 8631f61..23abf24 100644 --- a/.needle-predispatch-sha +++ b/.needle-predispatch-sha @@ -1 +1 @@ -6d30c6341441df56c3d7d2cea37f4d31144eda47 +6b22ba65acb3e583cdd7cb786d7104402f85a495 diff --git a/dashboard/js/quick-actions.js b/dashboard/js/quick-actions.js index 76fe6b0..cc3193a 100644 --- a/dashboard/js/quick-actions.js +++ b/dashboard/js/quick-actions.js @@ -513,7 +513,7 @@ function markIncorrect(blob) { if (window.FeedbackPanel) { - window.FeeddownPanel.markIncorrect(blob.id); + window.FeedbackPanel.markIncorrect(blob.id); } else { // Send feedback directly fetch('/api/feedback', { diff --git a/dashboard/js/viz3d.js b/dashboard/js/viz3d.js index 8567be2..5aeeae7 100644 --- a/dashboard/js/viz3d.js +++ b/dashboard/js/viz3d.js @@ -272,6 +272,9 @@ const Viz3D = (function () { new THREE.MeshPhongMaterial({ color: col, emissive: col, emissiveIntensity: 0.35, shininess: 60 }) ); } + // Store MAC in userData for spatial quick actions raycasting + m.userData = m.userData || {}; + m.userData.mac = n.mac; _scene.add(m); _nodeMeshes.set(n.mac, m); } @@ -3200,7 +3203,6 @@ const Viz3D = (function () { // Blobs applyLocUpdate: applyLocUpdate, getBlobs3D: function() { return _blobs3D; }, - blobMeshes: function() { return _blobs3D; }, // alias for quick-actions // View presets setViewPreset: setViewPreset,