Commit graph

213 commits

Author SHA1 Message Date
jedarden
7a06c8e1dd chore: remove committed code artifacts (binaries + node_modules)
Delete compiled Go binaries (sim, spaxel-sim, cmd/sim/spaxel-sim, mothership/{sim,spaxel-sim},
*.test, acceptance.test) and the tracked dashboard/node_modules/ (6689 files) that were
polluting the repo. Add .gitignore rules so they stay out. Dashboard deps regenerate via npm ci.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:54:12 -04:00
jedarden
55a09ec0f0 Implement TX slot collision detection and adaptive re-stagger
- Add CollisionDetector in fleet/collision.go that tracks frame arrivals from TX nodes
- Detect collisions when CSI frames from different TX nodes arrive within 3ms
- Calculate collision rate over 60-second sliding window
- Trigger adaptive re-stagger when collision rate exceeds 5% threshold
- Re-stagger generates random slot offsets to break up persistent collisions
- Rate limit re-stagger to minimum 30-second intervals
- Integrate with ingestion server to record frame arrivals
- Integrate with fleet manager to push updated config messages on re-stagger
- Add comprehensive unit tests for collision detection logic

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 08:28:24 -04:00
jedarden
b7bb1e00b0 Implement fleet status page with full table view and bulk actions
- Added dashboard/static/js/fleet.js: core fleet management module with API functions for node operations (identify, reboot, update, remove), bulk operations (update all, re-baseline all), role assignment, config export/import, and camera fly-to integration
- Added dashboard/static/css/fleet-page.css: complete styling for fleet table with sortable columns, status indicators, action buttons, bulk actions bar, filters, modals, and responsive layout
- Fleet table columns: Name, MAC, Status, Firmware, Uptime, Position, Role, Health, Packet Rate, Temperature, Actions
- Bulk actions: Update All (rolling OTA with 30s stagger), Re-baseline All, Export Config, Import Config
- Camera fly-to: clicking position coordinates stores MAC in localStorage and navigates to live view with highlight
- All node actions execute correctly: identify (blink LED), update firmware, remove from fleet, re-assign role
- CSV export generates downloadable report with all node metrics

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 07:47:34 -04:00
jedarden
b55147b8d9 Implement fleet status page with full table view, bulk actions, and camera fly-to
Features implemented:
- Full table with sortable columns: Name, MAC, Role, Position, Firmware, RSSI, Status, Uptime, Actions
- Role dropdown for changing node roles
- Position column with camera fly-to on click
- Firmware version with update badge
- Bulk actions: Update All, Re-baseline All, Export Config, Import Config, CSV Report
- Node actions: Restart, Update, Remove, Identify (blink LED)
- Responsive design for desktop and mobile
- Toast notifications and modal dialogs
2026-05-06 07:34:01 -04:00
jedarden
c7d37b834d Implement mobile-responsive expert mode for Spaxel dashboard
- Add hamburger menu for mobile panel navigation
- Create bottom sheet panels that slide from bottom on mobile
- Implement touch-optimized UI with 44x44px minimum tap targets
- Add mobile-specific panel content for Fleet Status, Zones, Triggers, Settings
- Support drag-to-close gesture on bottom sheets
- Maintain existing desktop panel behavior
- Integrate with existing systems (Viz3D, SpaxelPanels, SpatialQuickActions)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 07:04:24 -04:00
jedarden
7ed7806828 Implement Component 35: Morning Briefing
- Go function GenerateBriefing(date, person) assembles briefing in priority order
- Sections: critical alerts → sleep → who's home → anomalies → system health → predictions → learning
- Stored as daily record in briefings table with sections_json
- Expert mode: card overlay on first open, dismissible, slides away after 10s
- Simple mode: morning card as first card in layout
- Ambient mode: text fades in on first person detection, stays for 30s
- Delivery via dashboard, push notification, or webhook
- All acceptance criteria met
2026-05-06 05:01:05 -04:00
jedarden
0141820981 Implement Component 31: Ambient Dashboard Mode
- Fix auto-dim timeout from 60 seconds to 30 minutes (30 * 60 * 1000ms)
- Add auth.js dependency to ambient.html for proper authentication
- Rewrite ambient.test.js to remove problematic require() statements
- Update ambient.js with proper time-of-day handling
- Enhance ambient.css with time-of-day palette themes

Ambient mode provides a simplified, always-on display for wall-mounted
tablets with Canvas 2D rendering, time-of-day awareness, auto-dim after
30min of no presence, alert mode with pulsing red border, and morning
briefing integration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 03:59:12 -04:00
jedarden
1092642893 Implement simple mode (progressive disclosure) for household members
- Card-based mobile-first UI for non-technical users
- Room cards showing occupancy count, person names, and status color
- Activity feed with chronological event list from timeline
- Alert banner for fall detection, anomaly alerts, and system warnings
- Quick actions: arm/disarm security, re-baseline, silence alerts
- Sleep summary card showing last night's sleep data
- Toggle between simple/expert mode with localStorage preference
- Added /simple route in mothership for serving simple mode page
- Added purple color scale to tokens.css for sleep features

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 03:01:04 -04:00
jedarden
c0057b5e82 Implement simple mode (progressive disclosure) for household members
- Add simple.html: mobile-first card-based UI without 3D scene
- Room cards show occupancy count, person names, and status color
- Activity feed displays chronological events from timeline
- Alert banner for fall detection, anomaly alerts, system warnings
- Quick actions: arm/disarm security, re-baseline, silence alerts
- Sleep summary card showing last night's sleep data
- Simple/expert mode toggle button in all views
- Mobile-responsive layout with touch-friendly interface
- Per-user default mode stored in localStorage

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 01:57:28 -04:00
jedarden
654f86bdfc Fix Fresnel zone debug overlay mouse interaction bugs
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
- Raycast against both wireframe and fill meshes for reliable detection
- Wireframe is always present even when fill has 0 opacity (zones 2+)
- Sort intersections by distance to get the closest intersection
- Fixes hover detection not working properly for multi-zone Fresnel ellipsoids
2026-05-06 01:04:46 -04:00
jedarden
9739d87ac8 Fix Fresnel zone debug overlay mouse interaction bugs
- Fixed onFresnelMouseMove to properly iterate through arrays of ellipsoids
- Fixed showFresnelTooltip to access ellipsoid data from array correctly
- Enhanced tooltip to show zone count and improved units display

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 00:56:42 -04:00
jedarden
a2b0a73dd7 simulator: fix GDOP overlay and shopping list JSON output
- Fix GetShoppingList to use proper GDOP-based accuracy estimation
  instead of simplified implementation
- Fix simulation flow to run synchronously and display results
  immediately instead of polling
- Update GDOP legend HTML structure with proper styling hooks
- Add shopping list container with "add node at worst spot" button

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 23:34:43 -04:00
jedarden
80d070d0d3 feat(placement): add space key to place virtual nodes at camera target
- Press space bar to place a new virtual node at camera target position
- Node is placed at intersection of camera ray with ground plane
- Position is clamped to room bounds
- Height defaults to 80% of room height or 1.5m minimum
- Virtual nodes are rendered as ghost wireframe nodes (via viz3d.js)
- Links to/from virtual nodes are dashed (via viz3d.js)

This completes the 'space + virtual node placement' feature for the
simulator, reusing the 3D editor with ghost wireframe nodes and dashed links.
2026-05-05 21:40:48 -04:00
jedarden
c46dfa21fc feat(simulator): implement 3D editor reuse with ghost wireframe nodes and dashed links
- Refactored simulate.js to properly use existing HTML structure from simulator.html
- Added ghost wireframe node visualization using THREE.OctahedronGeometry with wireframe material
- Implemented dashed links between virtual nodes using THREE.LineDashedMaterial
- Added CSS for selected state highlighting and proper panel positioning
- Reused TransformControls from 3D editor for node placement
- Virtual nodes appear as teal-colored wireframe octahedrons
- Links between virtual nodes render as teal dashed lines
- GDOP overlay updates in real-time as nodes are dragged

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 21:23:33 -04:00
jedarden
d185817a0b feat(simulator): add viz3d.js dependency to simulator.html
The simulator page now includes the viz3d.js 3D visualization layer
which provides the scene management API needed by the simulator.
This ensures the simulator 3D scene is properly initialized.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 20:27:22 -04:00
jedarden
f18916d3a9 feat(replay): implement time-travel debugging with parameter tuning
- Add 'Pause Live' button that freezes 3D view and reveals timeline scrubber
- Implement scrubbing backward/forward through recorded history (1×, 2×, 5×)
- 3D scene renders blobs exactly as detected at scrubbed time, including trails
- Add parameter tuning overlay with sliders for detection parameters:
  - Detection threshold (deltaRMS)
  - Baseline time constant (tau)
  - Fresnel weight decay rate
  - Subcarrier selection count (NBVI)
  - Breathing sensitivity
- Adjusting slider re-runs pipeline on recorded CSI with new parameters
- Add 'Apply to Live' button that writes tuned parameters to running pipeline

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 18:33:37 -04:00
jedarden
8a8b605a0b ota: implement auto-update with canary strategy and quiet window
Implements Component 6 from the plan - automatic OTA updates with canary
deployment strategy and configurable quiet window scheduling.

Features:
- Canary strategy: updates one node first, monitors detection quality
  for 10 minutes (configurable), then rolls out fleet-wide if quality
  degradation is below threshold (default 5%)
- Quiet window: configurable time range (default 02:00-05:00) when
  auto-updates are allowed. Supports overnight windows (e.g., 22:00-06:00)
- Zone vacancy check: only updates when all zones have been vacant
  for >10 minutes
- Auto-update mode toggle: enable/disable via settings
- REST API endpoints for status, config, trigger, cancel, and history
- Dashboard integration for real-time progress updates

Settings keys:
- auto_update_enabled: bool (default false)
- quiet_window_start: HH:MM format (default "02:00")
- quiet_window_end: HH:MM format (default "05:00")
- canary_duration_min: 5-60 minutes (default 10)
- auto_update_quality_threshold: 0.01-0.5 (default 0.05)

Implementation:
- internal/ota/autoupdate.go: AutoUpdateManager with canary selection,
  monitoring, and fleet rollout
- internal/ota/autoapi.go: REST API handlers
- internal/autoupdate/adapters.go: integration with quality provider,
  node provider, event notifier, zone vacancy checker
- internal/api/settings.go: auto-update settings with validation
- dashboard/js/ota.js: auto-update state tracking

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 12:41:39 -04:00
jedarden
16462e7671 test: improve HelpOverlay test mocking
- Add dispatchEvent and classList mocks for more complete DOM element simulation
- Fix help_articles.json path expectation (relative, not absolute)
- Ensure window.HelpOverlay is properly loaded from module
2026-05-04 04:55:33 -04:00
jedarden
f2be2c1f6a fix(dashboard): fix Three.js OrbitControls touch event handling
Fix touch event propagation from panels to canvas, resolve iOS Safari
passive event listener warnings, prevent double-tap zoom conflicts,
improve pinch gesture accuracy, and enable three-finger pan.

Changes:
- Add maximum-scale=1.0, user-scalable=no to viewport meta tag (live.html)
- Add touch-action: none to canvas elements (expert.css)
- Change panel touch listeners from passive:false to passive:true with
  stopPropagation() to prevent iOS warnings (panels.js)
- Enhance controls.js module with comprehensive panel class coverage
  and auto-apply functionality

Acceptance Criteria Met:
✓ Touch events on sidebar panels do not propagate to the canvas
✓ No iOS Safari passive event listener warnings
✓ Double-tap to zoom is disabled (user-scalable=no in meta viewport)
✓ Pinch gesture is accurate on actual devices (zoomSpeed=1.0)
✓ Three-finger pan is enabled in OrbitControls

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 02:27:00 -04:00
jedarden
68526e1871 feat(dashboard): add controls.js and expert.css for panel touch propagation 2026-05-04 01:53:32 -04:00
jedarden
d0326e6383 feat(help): improve search scoring to match command palette
- Use relevance-based scoring (exact > prefix > substring > subsequence)
- Add title weight boost (1.5x) for better title matching
- Add category filter (0.5x) for category matching
- Minimum 0.6 score threshold for better result quality
- Sort results by relevance score

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 01:28:19 -04:00
jedarden
c227dccdd3 fix(timeline): include category types in server query params 2026-05-04 01:21:06 -04:00
jedarden
670d7cef87 feat(timeline): add search and filter bar to sidebar timeline
Implement comprehensive filter bar with checkboxes for event categories
(Presence, Zones, Alerts, System, Learning), person and zone dropdowns,
date range selector, and text search with fuzzy matching.

- Client-side filtering on loaded events for instant response
- Server-side date range queries with since/until parameters
- FTS5 full-text search for fuzzy matching on descriptions
- Cursor-based pagination supporting 500+ results
- Virtualized rendering with IntersectionObserver for performance
- Active filters display with removable tags

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 01:17:07 -04:00
jedarden
a33ca37920 fix(proactive): use best-matching key for server-side repeated-edit hint
When the server sets repeated_edit_hint:true, pick the most-changed
qualifying setting from localStorage history instead of passing the
literal string 'detected_by_server' into formatSettingName, which
rendered as broken text in the hint banner.
2026-05-04 00:59:38 -04:00
jedarden
758bef0138 feat(timeline): add search and filter bar to sidebar timeline
- Add collapsible filter panel with category checkboxes (Presence, Zones,
  Alerts, System, Learning) for client-side event type filtering
- Add person and zone dropdowns populated from /api/people and /api/zones
- Add date range selector (All Time / Today / Last 7 Days / Last 30 Days /
  Custom range) with server-side re-fetch on date changes
- Add text search input with fuzzy client-side matching and FTS5 server-side
  prefix matching for descriptions
- Add active filter tags with individual remove buttons and Clear All
- Add load-more cursor pagination for 500+ results
- Add virtualized rendering with IntersectionObserver for 1000+ events
- Render event feedback buttons (thumbs up/down) inline on each event
- Add now-replaying chip showing current replay timestamp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 00:54:21 -04:00
jedarden
4c221418c1 test(proactive): fix localStorage state isolation between tests
Move localStorage.clear() to parent beforeEach to ensure module
initialization always starts with clean state. This fixes test
isolation where localStorage data from previous tests was being
loaded by the module before the nested beforeEach could clear it.

The repeated-setting change detection feature is already fully
implemented in proactive.js with:
- Setting change tracking in localStorage (24h window)
- Help prompt after 3+ changes for qualifying settings
- Guided calibration flow with false positive and missed motion tests
- Value suggestions based on diurnal baseline SNR and link health
- Apply suggested value button

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 00:37:50 -04:00
jedarden
28e0f6239e feat(dashboard): add iOS Safari safe area CSS support
Add CSS environment variables for safe-area-inset to prevent content
overlap with notch/home indicator on iOS devices.

- Add padding-top and padding-bottom to body using env(safe-area-inset-*)
- Mobile bottom navigation already respects safe-area-inset-bottom
- viewport-fit=cover meta tag already present in all HTML pages
2026-04-25 12:40:52 -04:00
jedarden
03f765639b feat(feedback): enhance false positive explanations with diagnostic context
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 12:35:04 -04:00
jedarden
16a4c658d8 feat(feedback): enhance false positive explanations with diagnostic context
- Update renderFeedbackExplanation to properly display diagnosis info
- Fix showInlineResponse to access explainability from inline_response
- Show contributing link name with deltaRMS and threshold ratio
- Display diagnostic result or default ambient RF interference message
- Add correction note for all feedback types

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 12:18:54 -04:00
jedarden
af5101e9e4 feat(feedback): enhance false positive explanations with diagnostic context
When users mark detections as incorrect, the system now provides:
- Contributing link name (MAC prefix)
- DeltaRMS value and threshold ratio
- Root cause from diagnostic checks
- Note about applying corrections

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 12:11:39 -04:00
jedarden
ef9cd3fe15 feat(expert-mode): improve mobile touch controls for 3D scene
- Simplified OrbitControls touch gesture configuration:
  - ONE: THREE.TOUCH.ROTATE (one-finger orbit)
  - TWO: THREE.TOUCH.DOLLY (pinch zoom only, no accidental pan)
  - THREE: THREE.TOUCH.PAN (three-finger pan)

- Removed complex dynamic enablePan toggling that didn't work reliably
  with OrbitControls' internal touch processing

- Added iOS Safari-specific touch improvements:
  - Double-tap zoom prevention
  - Touch action and user select CSS properties
  - -webkit-tap-highlight-color: transparent

- Enhanced CSS for better mobile touch handling:
  - touch-action: none on scene container and canvas
  - -webkit-touch-callout: none
  - -webkit-user-select: none
  - user-select: none

All mobile tests (29 tests) and quick-actions tests (22 tests) pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 11:01:34 -04:00
jedarden
c0416fee6c feat(dashboard): anomaly detection & security mode UI with WS consistency fix
- Add security status card with arm/disarm dialog, DISARMED/LEARNING/ARMED/ALERT
  badge, learning progress bar (N of 7 days), and last-anomaly summary line
- Add full-width alert banner with acknowledge button for armed-mode anomalies;
  acknowledged alerts disappear from banner but remain in history
- Add anomaly timeline panel (24h) with severity scores and timeline navigation
- Fix WS broadcast field names to match AnomalyEvent JSON/REST API:
  anomaly_type→type, timestamp_ms→RFC3339 timestamp so JS handles both
  WS pushes and polled history uniformly
- Fix formatTimeAgo() to parse RFC3339 string timestamps in addition to Unix-ms
- Fix fetchAnomalyCount() to use /api/anomalies?since=24h (structured response)
  instead of /api/anomalies/history (returns plain array)
- Add security-card detail area styling to anomaly.css
- Add BlobIdentityProvider wiring in zones API for people resolution in zone responses
- Add linkweather diagnostic engine tests (Rules 1-5 + helpers)

All go test ./... pass; go vet ./... clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 09:03:51 -04:00
jedarden
fb5937af2f feat(dashboard): add Fresnel zone debug overlay with shared geometry module
Fresnel zone ellipsoids render for all active links when the debug layer
is toggled on. Uses shared fresnel.js helper for geometry computation,
with hover tooltips showing link details and click-to-select. viz3d.js
refactored to use the shared module instead of duplicating calculations.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 01:43:51 -04:00
jedarden
2bd1f9096b fix(dashboard): resolve Fresnel tooltip MAC address lookup bug
The showFresnelTooltip function referenced data.txMAC/data.rxMAC which
don't exist on the ellipsoid geometry data object. Use link.nodeMAC and
link.peerMAC from the link state instead, which are always available.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:57:57 -04:00
jedarden
3150dadf32 fix(dashboard): clean up Fresnel toggle sync and fix semi-minor axis formula
Simplify Fresnel debug overlay toggle to use single toggleFresnelDebugOverlay
call instead of duplicating Viz3D sync logic. Fix semi-minor axis calculation
in viz3d.js (was missing factor of 2 in distance term). Add Math.max(0,...)
guard against negative sqrt. Add missing dispose mocks in fresnel tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:25:24 -04:00
jedarden
71377b9efc feat(dashboard): add layer management module for toggle controls
Wire Fresnel zone toggle through Layers module for consistent
state management across toolbar, debug panel, and layer controls.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 00:15:39 -04:00
jedarden
8708c02929 feat(dashboard): polish tap-to-jump UX and remove expert-mode gating
- Improve link highlighting with Fresnel health color mapping
- Route re-provision through live page via query param for reliability
- Remove simple-mode gating from command palette (now available everywhere except ambient)
- Update Fresnel tests, fleet page unpaired node UX, and help articles

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 23:16:56 -04:00
jedarden
ccaaaded55 feat(onboarding): harden node onboarding UX with migration window and unpaired flow
Add 24h migration window for legacy/unprovisioned nodes to connect while
flagged as Unpaired rather than silently rejected. Surface unpaired nodes
in the fleet UI with amber badge, pulsing status indicator, and migration
window countdown banner. Add re-provision wizard entry point from fleet
panel — clicking "Pair" on an unpaired node opens the onboarding wizard
in re-provisioning mode, skipping firmware flash and going straight to
serial credential provisioning. After migration window closes, nodes
without valid tokens are rejected outright.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 23:16:56 -04:00
jedarden
cf934ba2a2 feat(dashboard): tap-to-jump time-travel removes expert mode gating
Timeline event clicks now always trigger jump-to-time replay regardless
of dashboard mode, not just in expert mode. Simplified handleSeek to
remove the dashboardMode state variable and expert mode check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 21:47:32 -04:00
jedarden
e6fa1a7bf6 chore(dashboard): remove last simple/expert mode remnants
Rename expert.css → scene.css (file contained WCAG touch targets and
3D scene layout, nothing mode-specific). Remove dead dashboardMode !==
'expert' guard in timeline seek handler. Simple/expert toggle and
localStorage key were already removed in prior commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 21:30:28 -04:00
jedarden
85c763253b chore(dashboard): remove stale simple/expert mode references from CSS comments
The Simple/Expert toggle was removed in an earlier commit but left behind
stale comments referencing "mode toggle bar", "expert mode", and
"simple.html" in CSS files. Clean these up to avoid confusion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 20:37:18 -04:00
jedarden
959a0c5962 feat(timeline): tap-to-jump time-travel with correct mode handling
Wire sidebar timeline handleSeek to navigate to timeline view in simple
mode instead of attempting replay. Fix onRouterModeChange to properly
detect expert vs simple modes based on route name. All 41 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 20:11:44 -04:00
jedarden
cf963ad905 feat(explainability): add explain.js entry point module for right-click/long-press explain mode
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 20:05:10 -04:00
jedarden
6bf1e0394a feat(explainability): detection explainability overlay with per-link contributions, Fresnel zones, and BLE identity
Implements the full explainability overlay for understanding why a blob was detected:
- ExplainabilitySnapshot generation with per-link contribution tracking and zone decay
- Fresnel zone ellipsoid geometry computation and 3D wireframe rendering
- WebSocket request_explain / blob_explain flow for on-demand snapshots
- Right-click, long-press, click, and hover tooltip activation paths
- X-ray overlay dims non-contributing elements, highlights contributing links
- Sidebar panel with confidence gauge, links table, sparklines, BLE match card
- Escape key and backdrop click to exit, restoring scene state

Also includes: simple mode removal, CSS cleanup, fleet page enhancements, sidebar timeline fixes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 19:23:55 -04:00
jedarden
3038f632b7 fix(dashboard): CSS syntax fixes and timeline mode detection cleanup
Fix missing colons in CSS declarations across timeline.css and replay.css
that broke gap/padding/margin/bottom properties. Simplify timeline mode
detection to use router callbacks instead of SpaxelSimpleModeDetection.
Add aria-labels to select elements for accessibility.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 19:20:58 -04:00
jedarden
03fd4e2752 feat(onboarding): migration window, Unpaired badge, re-provision flow
Nodes that connect without a valid token during the migration window are
now accepted and flagged as Unpaired rather than rejected.  Fleet health
surfaces the flag so the dashboard can show an amber Unpaired badge in
the MAC column, an "Unpaired" status badge, and a ↺ re-provision action
button that re-opens the onboarding wizard in reprove mode (skips the
firmware-flash step, targets the specific node's MAC during detect).

- ingestion/server: migrationDeadline + Unpaired flag on NodeConnection
- fleet/fleethandler: UnpairedProvider interface, merges unpaired MACs
  into fleet health response
- config: SPAXEL_MIGRATION_WINDOW_HOURS (default 24 h, range 0-168)
- main: wires migration deadline and unpaired provider at startup
- onboard.js: reprove(mac) public API, skip-flash + targeted-detect mode
- fleet.js: Unpaired badge, Re-provision button, unpaired banner, ⚠ in
  role list, reproveNode public API
- wizard.css: .wizard-reprove-banner amber styling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 18:46:47 -04:00
jedarden
ce7108a277 fix(dashboard): register sidebar timeline for simple mode detection changes
The sidebar timeline module was not listening for SpaxelSimpleModeDetection
mode changes, causing the tap-to-jump simple mode test to fail. Added
onSimpleModeChange handler and registration so the sidebar correctly
switches to simple mode and navigates to timeline view instead of replay.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 18:31:17 -04:00
jedarden
6951ff1d00 feat(dashboard): add axe-core accessibility CI gate for all dashboard pages
Add @axe-core/playwright with Playwright test runner that asserts zero
WCAG 2A/2AA violations across index, live, fleet, setup, and
integrations pages. Fix contrast violations on integrations page: use
darker blue for primary buttons, lighter text for descriptions/hints,
and add body-level dark background in layout.css.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 18:31:17 -04:00
jedarden
4960fbfd51 feat(dashboard): implement tap-to-jump time-travel coordination
Timeline events now delegate to SpaxelReplay.jumpToTime() for coordinated
replay session creation instead of making direct fetch calls. Selected
events highlight with timeline-event-selected class, and cross-module
selection is cleared between sidebar and full-page timeline views.

- timeline.js: use SpaxelReplay.jumpToTime() in handleSeek, export
  clearSelection and hideNowReplayingChip, clear sidebar on jump
- sidebar-timeline.js: clear full-page timeline selection on sidebar jump
- replay.js: return promise from exitReplayMode for proper chaining
- timeline.test.js: 14 tests covering timestamp emission, highlighting,
  now-replaying chip, cross-module coordination, simple mode gating,
  and error handling

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 18:14:51 -04:00
jedarden
8db0795017 feat(dashboard): make mobile frame rate cap configurable via URL parameter
Add ?maxFps=N URL parameter to configure the frame rate cap at renderer
initialization time. ?maxFps=30 caps at 30fps (default for struggling
mobile devices), ?maxFps=60 disables the initial cap, and ?maxFps=0
disables both the cap and auto-detection. Also respects the
autoDetectStrugglingDevice config flag in the detection function.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 17:46:26 -04:00