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>
mdns was moved out of the built-in ESP-IDF tree in v5.x and must be
declared via idf_component.yml for the component manager to resolve it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
led.c was missing from the build, causing undefined reference errors
for led_init, led_stop_blink, and led_blink_identify at link time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ESP-IDF 5.x requires explicit set-target even when CONFIG_IDF_TARGET
is present in sdkconfig.defaults.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
espressif/idf entrypoint is not invoked in multi-stage builds, so
idf.py is not in PATH. Sourcing export.sh activates the toolchain.
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>
The onboard wizard requires the esp-web-install-button custom element
from esp-web-tools. It was loaded in index.html but missing from
simple.html, causing "Firmware flashing component failed to load."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add "+ Add Node" button to bottom nav and load onboard.js with wizard
CSS. Also fix broken init that still referenced removed SpaxelAuth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bare `BLEPanel` in an inline onclick throws ReferenceError if the script
hasn't loaded yet. Using `window.BLEPanel` returns undefined instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <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>
The fleet status page is fully implemented with all required features:
- HTML structure with navigation, filters, bulk actions, modals
- JavaScript with state management, filtering, sorting, inline editing, camera fly-to, CSV export
- CSS with responsive design and comprehensive styling
- REST API endpoints for all fleet operations
- Comprehensive test coverage
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>
Adds frontend implementation for feature discovery notifications:
- Polls /api/help/notifications every 30 seconds
- Displays notification cards with slide-in animation
- Handles action button clicks for navigation
- Persists dismissed notifications to prevent re-display
- Auto-dismisses after 30 seconds
Completes the feature discovery notifications feature for Phase 9,
complementing the backend notifier and monitor implementations.
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>