Commit graph

170 commits

Author SHA1 Message Date
jedarden
8a8b605a0b ota: implement auto-update with canary strategy and quiet window
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>
2026-05-05 12:41:39 -04:00
jedarden
16462e7671 test: improve HelpOverlay test mocking
- 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
2026-05-04 04:55:33 -04:00
jedarden
f2be2c1f6a fix(dashboard): fix Three.js OrbitControls touch event handling
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>
2026-05-04 02:27:00 -04:00
jedarden
68526e1871 feat(dashboard): add controls.js and expert.css for panel touch propagation 2026-05-04 01:53:32 -04:00
jedarden
d0326e6383 feat(help): improve search scoring to match command palette
- 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>
2026-05-04 01:28:19 -04:00
jedarden
c227dccdd3 fix(timeline): include category types in server query params 2026-05-04 01:21:06 -04:00
jedarden
670d7cef87 feat(timeline): add search and filter bar to sidebar timeline
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>
2026-05-04 01:17:07 -04:00
jedarden
a33ca37920 fix(proactive): use best-matching key for server-side repeated-edit hint
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.
2026-05-04 00:59:38 -04:00
jedarden
758bef0138 feat(timeline): add search and filter bar to sidebar timeline
- 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>
2026-05-04 00:54:21 -04:00
jedarden
4c221418c1 test(proactive): fix localStorage state isolation between tests
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>
2026-05-04 00:37:50 -04:00
jedarden
16a4c658d8 feat(feedback): enhance false positive explanations with diagnostic context
- 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>
2026-04-25 12:18:54 -04:00
jedarden
af5101e9e4 feat(feedback): enhance false positive explanations with diagnostic context
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>
2026-04-25 12:11:39 -04:00
jedarden
ef9cd3fe15 feat(expert-mode): improve mobile touch controls for 3D scene
- Simplified OrbitControls touch gesture configuration:
  - ONE: THREE.TOUCH.ROTATE (one-finger orbit)
  - TWO: THREE.TOUCH.DOLLY (pinch zoom only, no accidental pan)
  - THREE: THREE.TOUCH.PAN (three-finger pan)

- Removed complex dynamic enablePan toggling that didn't work reliably
  with OrbitControls' internal touch processing

- Added iOS Safari-specific touch improvements:
  - Double-tap zoom prevention
  - Touch action and user select CSS properties
  - -webkit-tap-highlight-color: transparent

- Enhanced CSS for better mobile touch handling:
  - touch-action: none on scene container and canvas
  - -webkit-touch-callout: none
  - -webkit-user-select: none
  - user-select: none

All mobile tests (29 tests) and quick-actions tests (22 tests) pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 11:01:34 -04:00
jedarden
c0416fee6c feat(dashboard): anomaly detection & security mode UI with WS consistency fix
- 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>
2026-04-25 09:03:51 -04:00
jedarden
fb5937af2f feat(dashboard): add Fresnel zone debug overlay with shared geometry module
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>
2026-04-25 01:43:51 -04:00
jedarden
2bd1f9096b fix(dashboard): resolve Fresnel tooltip MAC address lookup bug
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>
2026-04-25 00:57:57 -04:00
jedarden
3150dadf32 fix(dashboard): clean up Fresnel toggle sync and fix semi-minor axis formula
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>
2026-04-25 00:25:24 -04:00
jedarden
71377b9efc feat(dashboard): add layer management module for toggle controls
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>
2026-04-25 00:15:39 -04:00
jedarden
8708c02929 feat(dashboard): polish tap-to-jump UX and remove expert-mode gating
- 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>
2026-04-24 23:16:56 -04:00
jedarden
ccaaaded55 feat(onboarding): harden node onboarding UX with migration window and unpaired flow
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>
2026-04-24 23:16:56 -04:00
jedarden
cf934ba2a2 feat(dashboard): tap-to-jump time-travel removes expert mode gating
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>
2026-04-24 21:47:32 -04:00
jedarden
e6fa1a7bf6 chore(dashboard): remove last simple/expert mode remnants
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>
2026-04-24 21:30:28 -04:00
jedarden
959a0c5962 feat(timeline): tap-to-jump time-travel with correct mode handling
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>
2026-04-24 20:11:44 -04:00
jedarden
cf963ad905 feat(explainability): add explain.js entry point module for right-click/long-press explain mode
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 20:05:10 -04:00
jedarden
6bf1e0394a feat(explainability): detection explainability overlay with per-link contributions, Fresnel zones, and BLE identity
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>
2026-04-24 19:23:55 -04:00
jedarden
3038f632b7 fix(dashboard): CSS syntax fixes and timeline mode detection cleanup
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>
2026-04-24 19:20:58 -04:00
jedarden
03fd4e2752 feat(onboarding): migration window, Unpaired badge, re-provision flow
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>
2026-04-24 18:46:47 -04:00
jedarden
ce7108a277 fix(dashboard): register sidebar timeline for simple mode detection changes
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>
2026-04-24 18:31:17 -04:00
jedarden
4960fbfd51 feat(dashboard): implement tap-to-jump time-travel coordination
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>
2026-04-24 18:14:51 -04:00
jedarden
8db0795017 feat(dashboard): make mobile frame rate cap configurable via URL parameter
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>
2026-04-24 17:46:26 -04:00
jedarden
e3cd805629 fix(dashboard): remove ES module export from notifications.js
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>
2026-04-24 17:45:12 -04:00
jedarden
bc5ebc0028 feat(dashboard): implement command palette with fuzzy search and time navigation
Ctrl+K / Cmd+K universal search interface for expert mode with:
- Fuzzy matching (prefix, substring, word-level, subsequence)
- Time navigation via @ prefix (@3am, @yesterday 11pm, @this morning, @last night)
- 36 commands across navigation, view, system, security, appearance, actions, help
- Entity search across zones, people, nodes, events
- Recent history with localStorage persistence
- Expert-mode gating (disabled in simple/ambient modes)
- Keyboard navigation (arrow keys, Enter, Escape, Tab)
- Toolbar shortcut hint with platform-aware display (⌘K on Mac)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 17:36:14 -04:00
jedarden
5707a89ad5 fix(dashboard): repair CSS syntax errors and complete token migration
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>
2026-04-24 16:57:32 -04:00
jedarden
1e8876d6b4 style(dashboard): continue design token migration across remaining CSS
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>
2026-04-24 16:41:18 -04:00
jedarden
07abc03ef4 style(dashboard): complete design token migration and live view cleanup
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>
2026-04-24 16:39:53 -04:00
jedarden
c02a682361 feat(dashboard): add onJumpToTime callback for tap-to-jump time-travel
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>
2026-04-24 15:41:39 -04:00
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
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
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