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>
The home page (index.html) was restructured into status headline + 3 cards.
The 3D viewer lives at /live (live.html), setup/calibration at /setup (setup.html).
Fleet page remains at /fleet. home-cards.js pulls snapshot from /ws/dashboard.
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>
esp-web-tools reads the flash offset from the 'offset' field in the
manifest parts array. The field was named 'address', causing the offset
to be undefined, which produced 'Writing at 0xNaN' and the
'Cannot read properties of undefined (reading toString)' error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: use --flash_size 4MB and drop OTA data from merge_bin (OTA
data at 0xc10000 inflated binary to 12.6MB, exceeding 4MB chip flash)
- main.go: seedFirmwareDir now overwrites when source size differs, fixing
PVC staleness where old 1.6MB app-only binary was never replaced
- onboard.js: renderFlashFirmware() rewritten so all elements (button,
progress bar, status text, retry help, log panel) are inline in one
container — no separate floating modal
Co-Authored-By: Claude Sonnet 4.6 <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>
Traefik forward-auth with Google OAuth already gates all non-device
routes. The in-app PIN system was redundant. Removes auth middleware,
/api/auth/* endpoints, auth.js from all HTML pages, and SpaxelAuth
references from JS. The auth package remains for install_secret/node
token derivation used by provisioning.
Co-Authored-By: Claude Opus 4.6 (1M context) <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>
Chi panics if r.Use() is called after any route registration.
Move the auth middleware registration before RegisterRoutes.
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>