When timeline event is clicked in expert mode, emit jump_to_time command
with event timestamp. The time-travel player pauses live playback, seeks
CSI recording buffer to timestamp, and begins replay. Selected event
highlights in timeline and "Now replaying" chip appears in header.
Backend: POST /api/replay/jump-to-time creates replay session centered
on timestamp, replaces previous active session. Frontend: handleSeek()
in sidebar-timeline delegates to SpaxelReplay.jumpToTime() which calls
the API, shows replay control bar, and notifies Viz3D.
Tests: 7 Go test cases for jump-to-time endpoint, 8 JS test cases for
tap-to-jump interaction, event highlighting, and now-replaying chip.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add server-side types filter (comma-separated) for category-based filtering,
fuzzy text search with FTS5 fallback on Enter, and improved client-side
filtering with character-sequence matching. Category checkboxes now send
types to server for efficient loading. Includes table-driven tests for types
filter, pagination, and combined filter scenarios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add optional mothership IP override to the provisioning flow so nodes
on networks where mDNS is blocked (enterprise WiFi, mesh, VLANs) can
connect on first boot without manual intervention.
- Add ms_ip field to provisioning Payload and request structs
- Firmware writes ms_ip to both NVS_KEY_MS_IP and NVS_KEY_MS_IP_PROV
- Discovery prefers provisioned IP on first attempt, falls back to
mDNS, then cached IP
- Web Serial wizard adds Mothership IP field in Network Troubleshooting
- Auto-populates IP when browser accesses dashboard by IP address
- Document when/how to use the override in docs/notes/mdns-override.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The tokenValidator field and hello-handler validation logic existed but were
never wired from main.go. Now ingestSrv.SetTokenValidator(provSrv.ValidateToken)
connects the provisioning server's HMAC-SHA256(installSecret, mac) derivation
to the ingestion server, so nodes with missing or mismatched tokens are
rejected via sendReject and disconnected. Includes unit tests covering valid
token, missing token, wrong token, no-validator backward compat, and an e2e
test verifying unprovisioned nodes cannot post CSI frames.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix portal crossing direction bug in collectCrossings: direction was
determined by currSide (where blob is now), but should be determined
by prevSide (where blob came from). A→B means blob was on A side before.
- Add Enabled: true to portal test fixtures so they participate in
crossing detection (portals default to disabled).
- Refactor health.Checker: add injectable checkDB field defaulting to
defaultCheckDB() for cleaner unit test overriding.
- Fix TestHealthCheckSheddingLevelJSON: use getShedLevel override instead
of trying to poke shedder internals; fixes type mismatch in closure.
- Fix TestHealthCheckUptimeIncrement: backdate startTime to guarantee
the second check crosses a 1-second boundary.
Collect zone crossing/transition events under lock, then fire callbacks
after releasing the lock to prevent re-entrant deadlock when callbacks
themselves call zone manager methods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add espressif/idf:v5.2 as a multi-stage build step so the firmware
binary is baked into the image at /firmware/spaxel-firmware.bin.
On startup the mothership copies it into /data/firmware/ (PVC) if not
already present, making it immediately available for the onboarding
wizard without a manual upload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StorageSubscriber: refactor to two-phase shutdown (forwarders drain
EventBus channels into queue, then worker drains queue to SQLite)
ensuring no in-flight events are lost on Stop()
- Fix bus_test: increase channel capacity to avoid non-blocking drops
in TestEventBusConcurrentPublish
- Fix events_test: set MaxOpenConns(1) for in-memory SQLite to prevent
concurrent-connection data visibility issues
- Fix storage_test: remove manual ctx/cancel initialization after
StorageSubscriber struct was refactored to separate workerCtx/forwarderCtx
- Fix api/events.go: zone_id and person_id always take precedence;
until timestamp uses < (cutoffMs+1000) to include full RFC3339 second
- Fix api/events_test: default mode is expert, simple mode requires
explicit ?mode=simple parameter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zero connected nodes is valid for headless/standby deployments. The
5-minute grace period was causing an infinite crash loop: healthz
returned 503 after 5min uptime → liveness probe failed → pod restarted.
The "no nodes connected" reason is still reported but no longer sets
status to degraded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of serving the full dashboard HTML (which exposes the UI shell
when the auth overlay is deleted), serve a minimal page that only loads
the auth JS and CSS. Deleting the overlay now reveals a blank page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The PIN overlay was client-side only — deleting the DOM element bypassed
auth entirely. Add global chi middleware that returns 401 on protected
endpoints when no valid session cookie is present. Static files pass
through so the login page renders. During onboarding (no PIN set), all
routes remain open.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SQLite doesn't support IF NOT EXISTS on ALTER TABLE ADD COLUMN.
The error_message and error_count columns already exist in the
triggers table from v0.1.33, causing migration 7 to fail on upgrade.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fleet/handler.go: add ota. package prefix to NodeOTAProgress
- notification_settings.go: remove duplicate declarations of
testNotificationRequest, validateChannelConfig, writeJSON, and
writeJSONError that conflict with notifications.go and utils.go;
fix missing closing brace in validateTimeFormat
- cmd/mothership/main.go: use sigproc.HealthLogEntry (the actual
return type of GetHealthHistory) instead of diagnostics.LinkHealthSnapshot
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add real-time OTA update progress broadcasting to dashboard clients:
- Hub.BroadcastOTAProgress() sends progress updates over WebSocket
- OTA Manager now broadcasts state transitions: pending → downloading → rebooting → verified/failed/rollback
- Dashboard can show live OTA status per node during fleet updates
- Includes test suite for fleet page functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Show "updating" status for nodes undergoing OTA updates
- Add OTAInProgress field to FleetNode response
- Improve updateAllNodes to trigger rolling OTA updates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Show non-blocking prompt card when ambient confidence drops below 0.6 for >5 minutes
- Add 'Diagnose' button that fetches and displays root cause analysis
- Add 'Dismiss for today' option with localStorage persistence
- Implement pulsing amber highlight on 3D link lines for degraded links
- Display diagnostic results in plain English with possible causes and actions
- Do NOT show prompts for transient drops (< 5 minutes)
- Add GetDiagnosticFor method to diagnostics package for timestamp-based queries
- Wire up /api/links/{linkID}/diagnostics endpoint with health snapshots
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add mode switching for timeline panel. Expert mode shows all event
types with system events as secondary (smaller, greyed). Simple mode
shows only person-relevant events: ZoneTransition, FallDetected,
AnomalyDetected, SleepSessionEnd. Mode is set by dashboard mode and
passed as ?mode=expert or ?mode=simple to API.
Changes:
- dashboard/index.html: Add sidebar timeline panel HTML and script include
- dashboard/js/sidebar-timeline.js: Add sidebar timeline with mode switching
- dashboard/js/sidebar-timeline.test.js: Add tests for mode switching behavior
- mothership/internal/ingestion/server.go: Add health score to link info
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add mode switching for timeline panel with ?mode=expert or ?mode=simple
- Expert mode displays all event types with system events as secondary (smaller, greyed)
- Simple mode shows only person-relevant events: ZoneTransition, FallDetected, AnomalyDetected, SleepSessionEnd, zone_entry/exit, portal_crossing, fall_alert, anomaly, security_alert
- Backend defaults to expert mode when mode parameter is empty or invalid
- Frontend syncs dashboard mode with SpaxelSimpleModeDetection for mode changes
- Add CSS styling for new event types (ZoneTransition, FallDetected, AnomalyDetected, sleep_session_end)
- Update isValidEventType to include new event types
The writeJSON function uses json.NewEncoder which adds a newline
character. Changed raw string literals to interpreted strings
so \n becomes an actual newline character.
- Added dropdown menu for More actions button with options:
- Re-assign Role
- View Health History
- View Event History
- Remove from Fleet
- Added CSS styles for dropdown menus with proper positioning
and hover states
- Extended FleetHandler with additional API endpoints:
- PATCH /api/nodes/{mac}/label - update node label
- POST /api/nodes/{mac}/locate - send identify command
- POST /api/nodes/{mac}/role - assign new role
- DELETE /api/nodes/{mac} - remove from fleet
- Added label validation (max 32 characters)
- Improved test code quality with helper functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix TestAnalyticsHandler_ErrorHandling to use proper in-memory database
instead of nil database which caused nil pointer dereference
- Update handleGetCorridors to return corridors wrapped in {corridors: [...]}
for consistency with frontend expectations from crowdflow.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- cmd/mothership/main.go: fix GetAllZones (single return), LastSeenAt vs LastSeenMs,
remove undefined fusionEngine block, fix weights.GetLinkWeight usage, hoist
learningHandler scope, remove unused recordingBuf/lastDetectionEvent vars, remove
sync import, fix computeZoneQuality pointer dereference, fix pred field names
(PredictedNextZoneID/PredictionConfidence), fix AccuracyStats.TotalPredictions,
add GetNodeOfflineDuration to healthProviderAdapter, fix GetAccuracyDelta stub
- internal/api/guided.go: refactor GuidedManager interface to use time.Duration for
TriggerNodeOffline, use any for zonesHandler/nodesHandler, remove diagnostics.Tooltip
dependency, add GetTooltipAny type-assertion approach for cross-package tooltip access
- internal/api/tracks.go: unify TracksProvider to use signal.TrackedBlob directly via
type alias to resolve interface mismatch
- internal/api/diurnal.go: add signalProcessorManagerAdapter and
NewDiurnalHandlerFromSignal to bridge signal.ProcessorManager to DiurnalProcessorManager
- internal/guidedtroubleshoot/quality.go: add RecordEdit, MarkHintShown, GetTooltipAny
methods to Manager to satisfy api interfaces
- internal/fusion/fusion.go: remove unused log import, fix oy declared-and-not-used
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The crowdflow.js module expected a person filter dropdown in the patterns
section of the dashboard UI. This dropdown allows filtering flow and dwell
data by specific people or viewing all people together.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Viz3D exports to include flow visualization functions
- Export setFlowLayerVisible, setDwellLayerVisible, setCorridorLayerVisible
- Export setFlowTimeFilter, setFlowData, setDwellData, setCorridorData
- Remove duplicate setDwellLayerVisible function definition
This completes the crowd flow visualization feature that was
already implemented in the backend (flow.go) and frontend
(crowdflow.js, viz3d.js) but had missing exports in the Viz3D module.
Add comprehensive MQTT and webhook integration for Home Assistant and external services:
MQTT Client (internal/mqtt/client.go):
- Optional MQTT client with exponential backoff reconnect (5s-120s)
- TLS support for mqtts:// connections
- Home Assistant auto-discovery for persons, zones, fall detection, system health, system mode
- Topic structure: spaxel/{mothership_id}/person/{id}/presence, zone/{id}/occupancy, etc.
- LWT (Last Will and Testament) for availability
- Dynamic configuration updates via API
- Retained messages for presence and occupancy states
MQTT Publisher (internal/mqtt/publisher.go):
- Event bus subscriber publishing zone entry/exit, fall alerts, anomalies
- Person presence tracking across zones with home/not_home states
- Zone occupancy counting with occupants list
- Periodic system health publishing (60s interval)
- HA discovery methods for all entity types
- Person and zone discovery removal on delete
System Webhook (internal/webhook/publisher.go):
- Single webhook URL receiving all events with X-Spaxel-Event header
- JSON payload with event_type, timestamp, zone, person, blob_id, severity, detail
- Retry policy: one retry after 30s on 5xx errors
- Test webhook endpoint for configuration verification
API Integration Handler (internal/api/integrations.go):
- GET/POST /api/settings/integration for MQTT and webhook configuration
- POST /api/settings/integration/test for testing connections
- Settings persisted in database settings table
- Integration with existing MQTTClient and WebhookPublisher interfaces
Dashboard Integration UI (dashboard/integrations.html, js/integrations.js):
- Settings panel with MQTT broker URL, username, password (masked), TLS toggle
- Discovery prefix configuration
- Test Connection and Publish Discovery buttons
- System webhook URL configuration with enable toggle
- Connection status indicator with error messages
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix summary title format: only add "+" when events > maxBatchSize
- Fix TestQuietHoursHighPriority: HIGH priority respects quiet hours (queued)
- Fix TestGetHistory: flush batched events before checking history
All morning digest tests now pass, validating that queued events are
bundled and delivered at quiet_hours_end.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix TestNtfyClientSend body verification using bytes.Buffer
- Fix TestAttachPNGImage slice bounds error with flexible assertions
- Add new tests: TestNtfyClientValidPriorities, TestNtfyClientMessageFieldPriority,
TestNtfyClientEmptyMessage, TestNtfyClientCustomURL, TestNtfyClientClickHeader,
TestNtfyClientEmailHeader
- All 18 ntfy delivery client tests now pass with mock HTTP server
- Add TestZoneBoundariesAtCorrectCoordinates to verify zone boundaries appear at correct pixel coordinates
- Add TestZoneBoundaryEdges to verify zone edge detection
- Enhance TestPixelColors with accurate background color verification (#1a1a2e)
- Fix person color detection by sampling multiple pixels to find red fill
- Tests verify 300x300 PNG dimensions, correct zone boundary coordinates, and accurate colors
- All renderer tests pass
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix compilation errors from unused variables in manager_test.go and
pushover_test.go. The batching logic tests were already in place and
passing:
- TestBatchingThreeLowEvents: 3 LOW events in 10s -> 1 notification
- TestBatchingUrgentBypassesBatch: 1 URGENT -> immediate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The TestBriefing_GenerateWithAlerts test was inserting events without
the detail_json field, causing a NULL scan error. Fixed by including
detail_json='{}' in the INSERT statement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement single non-blocking notifications when features become available.
Events:
- DiurnalBaselineActivated (7 days)
- FirstSleepSessionComplete
- WeightUpdateApproved
- AutomationFirstFired
- PredictionModelReady (7 days per person)
Each notification is keyed by unique event ID in SQLite (feature_notifications table).
Never fires twice. Dismissed by tapping. Respects quiet hours.
Files:
- mothership/internal/help/notifier.go: Notifier manages one-time feature notifications
- mothership/internal/help/notifier_test.go: Tests for notifier
- mothership/internal/help/monitor.go: FeatureMonitor checks for feature availability
- mothership/internal/help/monitor_test.go: Tests for monitor
- mothership/cmd/mothership/main.go: Integration with mothership
- mothership/internal/db/migrations.go: Add migration_015 for feature_notifications table
Acceptance:
- Each notification fires exactly once per feature
- Plain language messages
- Respects quiet hours
- SQLite persistence prevents duplicates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add '?' button in expert mode status bar that opens help overlay
- Search input with fuzzy search (same algorithm as command palette)
- 43 help articles covering major features (sensing links, Fresnel zones,
detection quality, presence prediction, fall detection, etc.)
- Articles stored as static JSON (dashboard/help_articles.json)
- No server round-trip - loads articles client-side
- Category filter buttons for easy navigation
- Keyboard shortcut: Ctrl+? to open help overlay
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detects when user changes same config setting 3+ times within 24 hours.
Shows non-intrusive prompt offering help with guided calibration flow.
Guided calibration features:
- Test for false positives (walk around room)
- Test for missed motion (sit still)
- Suggest optimal value based on diurnal baseline SNR and link health
- Apply suggested value button
Files:
- dashboard/js/proactive.js: Complete implementation with localStorage tracking
Acceptance:
- Help prompt fires after 3+ changes in 24h
- Calibration flow tests both directions
- Suggests value based on system data
- Apply button works
- Added /ambient route serving ambient.html for wall-mounted tablet display
- Canvas 2D renderer at 2Hz with lerp interpolation for smooth person movement
- Time-of-day palette with 30-minute transitions (morning/day/evening/night)
- Auto-dim: reduces brightness to 40% after 60s of no presence
- Alert mode: pulsing red background for fall/security alerts
- Morning briefing overlay: 15-second overlay on first detection after 6am
- Unified alerts API for fall, anomaly, and node_offline events
- Jest test setup mocking Canvas 2D context for jsdom
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The import statement had incorrect syntax with the alias before
the package path. Fixed to standard Go import format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix SimulateCSIData to accept []*Walker instead of []*SimWalker
- Remove unused imports (path/filepath, time) from virtual_state_test.go
- Fix assignment mismatch for CreateVirtualNode error handling
- Fix deadlock in VirtualNodeStore by using saveLocked() when mutex is held
- Refactor CreateAPNode to avoid race condition with state modification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>