Commit graph

147 commits

Author SHA1 Message Date
jedarden
dd2fdd789c style(dashboard): replace remaining hardcoded colors with design tokens
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>
2026-04-24 15:14:40 -04:00
jedarden
55675943ce refactor(dashboard): complete home page restructure with tokenized live view
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>
2026-04-24 15:14:40 -04:00
jedarden
8f878c6cea style(dashboard): complete CSS tokenization and add live-view grid layout classes
Replace remaining hardcoded border-radius and color values across 22 CSS
files with design system tokens. Add .live-status-bar, .live-scene, and
.live-panel-* classes to layout.css for the grid-based live view shell.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 15:14:40 -04:00
jedarden
8269ce3fd6 style(dashboard): replace all hardcoded colors with design tokens
panels.css: replaced 17 hardcoded rgba values with semantic token
references (--alert, --warn-bg, --ok-bg, --border-strong, etc).
ambient.css: replaced one hardcoded blue rgba with --blue-muted.

Zero hardcoded hex/rgba color values remain outside tokens.css.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:16:42 -04:00
jedarden
1ade7732e4 fix(dashboard): wrap setup.html in app-shell grid container
setup.html was the only page not using the .app-shell CSS grid — its
elements were directly on the body with inline position styles. Wraps
content in .app-shell--live (viewport-filling variant), moves header
into .app-header grid area, and nests #scene-container inside .app-main.

Verified: grep for position:absolute across dashboard/css/ returns only
intentional decorative/component uses (pseudo-elements, toggles, tooltips,
dropdowns, canvas overlays) — zero on layout containers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:14:02 -04:00
jedarden
beb6bd2af3 fix(dashboard): improve home-cards.js snapshot caching and alert handling
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>
2026-04-24 13:07:18 -04:00
jedarden
bd3e89f2e5 feat(dashboard): tokenize CSS to design system tokens and fix home page styling
Convert hardcoded rgba colors across all dashboard CSS files to use
--ok-bg, --warn-bg, --alert-bg tokens from tokens.css per §8e design
system. Home page status banner and card tags now use proper semantic
tokens. Add layout.css import to live.html for shared nav structure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 12:46:02 -04:00
jedarden
21020e9fc9 feat(timeline): add tap-to-jump time-travel coordination
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>
2026-04-24 12:08:39 -04:00
jedarden
12c344ea61 feat(dashboard): add /live and /setup routes for home page restructuring
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>
2026-04-24 10:11:37 -04:00
jedarden
7162d4523e feat(dashboard): restructure home page as status headline + 3 cards
Per plan.md §8e information architecture:
- index.html (109 lines) is now a home page with status headline,
  3 cards (People & Zones, Devices & Fleet Health, Recent Events),
  optional extras row, and mobile bottom nav
- live.html serves the full 3D viewer at /live route
- home-cards.js connects to /ws/dashboard for snapshot + incremental updates
- tokens.css provides the Radix dark design system
- layout.css provides the CSS Grid app shell with responsive breakpoints
- home.css provides card grid, status banner, responsive mobile layout

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 09:07:43 -04:00
jedarden
3a85eaacc3 refactor(dashboard): remove duplicate component files
Delete non-canonical commandpalette.* and blepanel.js in favor of the
hyphenated command-palette.* and ble-panel.* which match the fleet-page.*
naming convention. Rename test file accordingly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 07:43:13 -04:00
jedarden
c552ccd5a9 fix(viz3d): remove duplicate _renderer declaration
The IIFE had `let _renderer` on line 13 and `let _renderer = null` on
line 30, which throws SyntaxError and kills the entire Viz3D module,
cascading into panel layout failures on the home page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 07:20:48 -04:00
jedarden
efea321f19 feat(timeline): add search and filter to event timeline
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>
2026-04-24 07:07:08 -04:00
jedarden
e676694fdc feat(provisioning): carry ms_ip in payload for mDNS-less networks
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>
2026-04-24 01:06:05 -04:00
jedarden
33c2628e7e fix(onboard): lower flash baud rate to 115200 to prevent hang on embedded-flash boards
460800 causes the stub to hang mid-write on ESP32-S3 QFN56 boards with XMC
embedded flash. 115200 skips the post-stub baud rate negotiation entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 21:37:44 -04:00
jedarden
d83759899f fix(firmware): redesign partition table for 4MB flash; fix flash size in esptool.js
The XMC embedded flash on ESP32-S3 (QFN56) has JEDEC ID 164020 which esptool.js
does not recognise — it returns flash size -1 causing 'File doesn't fit in
available flash' before any bytes are written.

firmware/sdkconfig.defaults:
  CONFIG_ESPTOOLPY_FLASHSIZE 16MB → 4MB
  4MB is the minimum supported flash. The spi_flash check only panics when
  physical < header, so 4MB header is safe on 16MB boards as well.

firmware/partitions.csv:
  Redesigned to fit within 4MB flash:
    factory  0x010000–0x200000  (~1.9MB, fits current 1.7MB binary + 330KB headroom)
    ota_0    0x200000–0x3F0000  (~1.9MB, A/B OTA + rollback preserved)
    otadata  0x3F0000–0x3F2000  (8KB)
  Total flash used: 0x3F2000 (98.6% of 4MB). Drops ota_1 (was at 8MB, unusable
  on 4MB devices anyway); rollback still works factory↔ota_0.

dashboard/js/onboard.js:
  flashSize: 'detect' → '4MB'  (detect returned -1 for this chip's JEDEC ID;
  hardcoding '4MB' correctly sets the binary header for all supported boards)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:40:58 -04:00
jedarden
d347bdb09a fix(onboard): detect flash size, validate chip family, auto-derive mothership host
- flashSize: 'detect' — esptool.js now detects the physical flash size and
  updates the binary header before writing. Fixes boot panic on devices with
  4MB embedded flash (e.g. ESP32-S3 WROOM-1-N4R2) when firmware was compiled
  for 16MB: spi_flash_init() no longer sees a header/physical mismatch.

- Chip family validation — after loader.main() returns the detected chip,
  check it against build.chipFamily from the manifest. Throw a UserError if
  a non-ESP32-S3 (or unsupported chip family) is connected, instead of
  silently flashing incompatible firmware.

- Better post-flash error messaging — track flashSucceeded flag; if flashing
  succeeded but provisioning failed, show "Provisioning failed / unplug and
  replug" instead of the misleading "Device not in download mode" help.

- Mothership auto-detection — offline fallback and ms-host placeholder now
  use window.location.hostname instead of the hardcoded spaxel-mothership.local.
  The browser is already talking to the mothership via this hostname, so it is
  the correct default for the device to reach it as well.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:48:37 -04:00
jedarden
deaf2b5653 fix(provisioning): wait for SPAXEL READY before sending payload over serial
The firmware prints 'SPAXEL READY <MAC>' a few seconds after the flash reset,
once the UART driver has initialised. Previously the browser sent the JSON
payload immediately when the port opened — the bytes arrived while the device
was still booting and were lost, causing a 15 s timeout.

New flow:
1. Open port (with retries for USB re-enumeration)
2. Read serial stream waiting for 'SPAXEL READY' line (up to 30 s)
3. Only then send the JSON provisioning payload
4. Wait for firmware's {"ok":true} acknowledgment (up to 10 s)

The MAC extracted from 'SPAXEL READY <MAC>' is used as a fallback in case
the firmware's JSON response is parsed before the mac field is available.
2026-04-17 09:20:27 -04:00
jedarden
a3dbbf1170 fix: support special characters in WiFi credentials
- SSID input: add autocorrect=off, autocapitalize=none, spellcheck=false to
  prevent mobile browsers from silently altering SSIDs with special chars
- Password input: same attrs for consistency
- Firmware: accept WPA/WPA2 mixed mode (WIFI_AUTH_WPA_WPA2_PSK) so networks
  with special characters in the password connect regardless of WPA version
- Firmware: detect open networks (empty password) and use WIFI_AUTH_OPEN so
  passwordless networks are not rejected by the auth threshold

JSON encoding path (JSON.stringify → TextEncoderStream → cJSON) already
handles all characters correctly; these changes prevent browser-side mangling
and firmware-side connection rejection.
2026-04-17 08:42:20 -04:00
jedarden
48cdcca8ea Collect WiFi credentials before flashing, provision immediately after
Previously: flash firmware → wait for 120s boot window → fill form → send over serial
Now: fill WiFi form → flash firmware → device reboots → send over serial immediately

The provisioning window opens at boot. With credentials already collected, the
browser sends the payload the moment the flash+reboot cycle completes — no human
action required and no race condition. Flash step now handles both flashing and
provisioning in one automated sequence (progress 0→80% flash, 80→100% provision).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 16:18:18 -04:00
jedarden
cc84a6eea8 Add WiFi provisioning observability and extend firmware window
- Show real-time status messages and a collapsible log panel during WiFi provisioning
- Thread addProvLog/setProvStatus callbacks through provisionAndSend and sendPayloadOverSerial
- Log every stage: mothership fetch, payload assembly, port open retries, serial send/response
- All log lines also go to browser console.log/warn/error
- Firmware: extend provisioning window from 10s to 120s for fresh boards (15s for re-provisioning)
- Firmware: include MAC address in SPAXEL READY message for display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:35:39 -04:00
jedarden
e63d245716 feat: add Start Over button to wizard header
Clears session state and returns to step 1. Useful when onboarding
multiple devices back-to-back without a page reload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:00:04 -04:00
jedarden
bebc22c4e3 fix: use state.port directly for provisioning, retry port open after flash
getAuthorizedPort() is unreliable after esptool reboots the device.
Use state.port (the port the user explicitly selected) and retry port.open
up to 5 times with 1s gaps to handle the brief window while the device
re-enumerates. Show specific UserError messages instead of the generic one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 12:24:56 -04:00
jedarden
808472819b fix: reset to connect step on reload; add back link to flash step
On page reload the serial port reference is lost. If saved state points
to flash_firmware or later and port is null, drop back to connect_device
so the user can re-select their device.

Also add a small '← Back to Connect' link at the bottom of the flash
step so there is always a visible escape route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:27:01 -04:00
jedarden
179b7f5e99 refactor: replace esp-web-tools with vendored esptool-js, auto-start flash
Remove <esp-web-install-button> and all shadow DOM polling hacks.
Use esptool-js (vendored as dashboard/js/esptool-bundle.js) via dynamic
import to flash directly. Flash starts automatically when step 3 loads —
no button click required. Progress shown inline. On failure, shows
BOOT+RST instructions and a retry button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 09:54:35 -04:00
jedarden
ab93db0489 fix: broaden shadow root dialog detection to any non-style element
The ewt-install-dialog tag name check was too strict — drop it and take
the first non-style element child that appears in the shadow root, then
poll _installState on it. This matches how the previous version found
the dialog before we narrowed the check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 09:41:01 -04:00
jedarden
0e70b777b8 fix: poll _installState instead of listening for state-changed event
esp-web-tools v10 does not dispatch a state-changed DOM event for
firmware flashing — that event only exists for Improv WiFi provisioning.
Flash state lives on ewt-install-dialog._installState (a LitElement
@state property). Replace the broken event listener with a 100ms poll
on _installState once the dialog appears in the shadow root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 23:10:54 -04:00
jedarden
89c242ed3c Fix flash step: use state-changed on shadow DOM dialog, hide overlay on flash start
esp-web-tools v10 removed flash-start/flash-progress/flash-success/flash-error
events. Only state-changed fires — but from inside the shadow root (not composed),
so host-element listeners never receive it.

Fix: MutationObserver watches the shadow root for the dialog element, then
attaches state-changed directly to it. States: erasing/writing drive inline
progress; finished auto-advances; error shows retry.

Also suppress the dialog overlay (CSS injection into shadow root) once erasing
starts, so the flash progress shows inline in the wizard with no separate modal.
The dialog remains visible briefly for the 'preparing' confirm step only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 20:45:34 -04:00
jedarden
fd441058f4 Add manual Continue button if flash-success doesn't fire after 100%
If esp-web-tools throws a JS error during progress display (e.g. the
manifest offset bug), flash-success never fires and the wizard gets stuck
with no way to advance. After reaching 100% progress, wait 4s for the
event; if it doesn't come, show a 'Continue →' button instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:11:11 -04:00
jedarden
c3034ae9a2 Fix firmware flashing: correct merge binary, seed overwrite, inline flash UI
- 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>
2026-04-15 15:41:51 -04:00
jedarden
d29bd1d76b Add live install log panel to flash step
Captures console.log/warn/error and esp-web-tools state-changed events
into a scrollable monospace log panel (collapsed by default, auto-opens
on flash-start and flash-error). Includes timestamps and error detail
from the flash-error event for diagnosing failures like the toString crash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:32:19 -04:00
jedarden
7d431f4a8b Make flash step resilient: bootloader-mode detection and guided retry
On flash-error, show BOOT+RST instructions with board diagram and a
"Try Again" button instead of a dead-end error message. Escalates to
USB cable/hub advice after 2 failures. Adds a proactive collapsible
tips section before flashing starts for users who read ahead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:35:53 -04:00
jedarden
6c3bb713d3 Fix esp-web-tools URL to use bundled web build
/dist/install-button.js has bare 'tslib' imports that browsers reject.
/dist/web/install-button.js is the fully bundled build with only
relative imports — works without an import map.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 11:21:03 -04:00
jedarden
f6cbfe2aaa Fix esp-web-tools CDN URL (espressif.github.io returned 404)
Switch to unpkg.com/esp-web-tools@10.2.1 which resolves correctly.
The old espressif.github.io/esp-web-tools path no longer exists,
causing the firmware flashing component to silently fail to load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 10:13:39 -04:00
jedarden
299ae24916 fix(simple): load esp-web-tools for firmware flashing
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>
2026-04-13 21:58:53 -04:00
jedarden
ac1812aadf feat(simple): add node onboarding wizard to simple view
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>
2026-04-13 21:38:41 -04:00
jedarden
33c751b7b2 fix(dashboard): use window.BLEPanel in onclick to avoid ReferenceError
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>
2026-04-13 21:09:33 -04:00
jedarden
cf7d0c790a remove(auth): drop PIN-based auth — Google OAuth handles access
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>
2026-04-13 20:44:12 -04:00
jedarden
aaa622d410 feat(ui): implement command palette (Component 34) with tests
- commandpalette.js: CommandPaletteManager with fuzzy scorer, time parsing,
  command registry (20 commands), recent history, entity search, mode gating
- commandpalette.css: modal overlay, search input, result list styles
- commandpalette.test.js: 64 tests covering fuzzy match, time parsing, commands
  completeness, keyboard nav, recent history, expert-mode gating, positioning
- app.js: spaxelGetState() exposure and Ctrl+K fallback listener
- index.html: includes commandpalette.css and commandpalette.js
- explainability.js + explain.go: detection explainability backend/frontend
- hub.go + server.go: dashboard WebSocket and API updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:51:16 -04:00
jedarden
136b2ecd97 feat: add OTA progress tracking to fleet status page
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>
2026-04-11 22:41:51 -04:00
jedarden
d500900e0b feat: add feature discovery notification UI to proactive dashboard
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>
2026-04-11 21:02:43 -04:00
jedarden
70745bb577 feat: implement proactive quality prompts with link diagnostics
- 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>
2026-04-11 19:51:57 -04:00
jedarden
4734e62aa1 feat: implement notification configuration UI for dashboard
Add comprehensive notification settings with delivery channel selector,
channel-specific credential fields, test notification button, event
type toggles, quiet hours picker, smart batching, and morning digest.

Backend:
- Add NotificationSettingsHandler with GET/PUT /api/settings/notifications
- Add POST /api/notifications/test endpoint
- Support ntfy, pushover, webhook channels with validation
- Store settings in settings table with proper JSON encoding

Frontend:
- Integrate notification settings into settings panel
- Channel selector with dynamic credential fields
- Event type toggles for filtering notifications
- Quiet hours time picker with day-of-week bitmask
- Smart batching toggle (default on)
- Morning digest toggle (default on)
- Test notification button with immediate feedback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 17:55:31 -04:00
jedarden
2d34f77d87 feat: implement expert vs simple mode for timeline panel
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>
2026-04-11 13:37:17 -04:00
jedarden
6bfe4aad01 feat: implement expert vs simple mode for timeline panel
- 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
2026-04-11 13:22:34 -04:00
jedarden
a984576be9 feat: complete fleet status page implementation
- 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>
2026-04-11 10:34:31 -04:00
jedarden
a97960bf67 fix: resolve analytics API test failures and improve corridor response format
- 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>
2026-04-11 08:34:24 -04:00
jedarden
33e96d82d0 feat: add person filter dropdown to crowd flow visualization
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>
2026-04-11 07:37:05 -04:00
jedarden
f99dc15a2d feat: complete crowd flow visualization implementation
- 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.
2026-04-11 07:27:21 -04:00
jedarden
af64a30af6 feat: home automation integration (MQTT and webhooks)
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>
2026-04-11 06:29:51 -04:00