- 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>
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
- 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
- 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>
- 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>
- 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
- 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>
- 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>
- 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.
- 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>
- 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>
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>
- 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
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>
- 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>
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>
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.
- 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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
notifications.js was loaded as a classic <script> in simple.html but used
`export class`, causing "Unexpected token 'export'" and a blank page.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fix systemic missing-colon bugs in layout.css where property values
like top, left, right, bottom, gap, padding were directly followed by
var() without a colon separator. This broke all fixed-position panels
in the live view. Also add missing --space-half token to tokens.css
and complete design token migration across remaining CSS files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace additional hard-coded colors with design tokens in layout,
notifications, panels, timeline, and other CSS files.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace remaining hard-coded colors across all CSS files with design
tokens from tokens.css. Remove duplicate inline positioning from
live.html panels (now in layout.css). Add replay session blob fetch
for immediate 3D scene state on seek.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire the replay module's onJumpToTime callback so the full-page timeline
can coordinate with the replay system after a jump-to-time API call. The
callback syncs replay state, shows the control bar, and feeds replay
blobs to the 3D scene.
All CSS (.selected, .now-replaying-chip), DOM elements, sidebar timeline
click handling, Go backend jump-to-time endpoint, and tests were already
in place. This closes the cross-module coordination gap.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Continued CSS tokenization pass across ambient, fleet, live, simple,
integrations pages and their component stylesheets. Replaces hardcoded
`white`, `#1a1a2e`, and raw rgba values with semantic tokens from
tokens.css.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Home page (index.html) is now a clean status+cards layout (109 lines):
- Row 1: status headline with ok/warn/alert states
- Row 2: three cards linking to /live#zones, /fleet, /live#timeline
- Row 3: optional briefing, anomaly, security toggle
- Mobile bottom nav for Home/Live/Fleet/Setup
3D viewer lives at /live (live.html), fleet at /fleet, setup at /setup.
All routes served by Go chi router. home-cards.js connects to /ws/dashboard
for snapshot+incremental updates.
Remaining CSS tokenization: live.html buttons and layout.css status bar
now use design tokens instead of hardcoded colors. Added --orange token
for GDOP fair quality. timeline.js gains replay state fields.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cache the full WebSocket snapshot so incremental updates always have
complete state for banner and card rendering. Add fall/security alert
detection in the status banner with --alert level. Add armed security
state styling in home.css.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>