From 296d54b6144a28de256ebaad35837bf519be283c Mon Sep 17 00:00:00 2001 From: jedarden Date: Sat, 25 Apr 2026 11:09:48 -0400 Subject: [PATCH] close(spaxel-trsm): Make expert mode mobile-responsive Implemented mobile touch controls for expert mode 3D scene: - 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) - Mobile-specific touch handling improvements: - CSS touch-action: none on scene container and canvas - -webkit-touch-callout: none - -webkit-user-select: none - user-select: none - -webkit-tap-highlight-color: transparent - iOS Safari-specific touch improvements: - Double-tap zoom prevention - Visual viewport handling for orientation changes All 29 mobile tests pass. Implementation already committed in ef9cd3f. Co-Authored-By: Claude Opus 4.7 --- .beads/issues.jsonl | 2 +- .needle-predispatch-sha | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index b698606..7bfbdbf 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -182,7 +182,7 @@ {"id":"spaxel-tim","title":"Adaptive sensing rate","description":"Implement mothership-controlled sensing rate with on-device burst detection.\n\n## Deliverables\n- Mothership rate control: send rate change commands (2Hz idle ↔ 50Hz active) per link via WebSocket\n- Rate decision logic: drop to 2Hz when no motion for 30s, burst to 50Hz on motion\n- On-device amplitude variance check at low rate (2Hz) — if variance exceeds threshold, burst to full rate and notify mothership\n- Motion hints from ESP32 to preemptively ramp adjacent links\n- Firmware changes: firmware/main/csi.c to support dynamic rate changes\n\n## Acceptance Criteria\n- Idle links automatically drop to 2Hz packet rate\n- Motion triggers burst to configured active rate\n- ESP32 can locally detect motion onset and self-burst before mothership commands\n- Adjacent links ramp up on motion hints from nearby nodes\n- Tests for rate decision logic in mothership\n\n## References\n- Plan: docs/plan/plan.md item 12\n- CSI capture: firmware/main/csi.c\n- Signal pipeline: mothership/internal/signal/processor.go","status":"closed","priority":2,"issue_type":"task","assignee":"spaxel-alpha","created_at":"2026-03-27T01:56:21.876231481Z","created_by":"coding","updated_at":"2026-03-28T05:36:02.592406514Z","closed_at":"2026-03-28T05:36:02.592346365Z","close_reason":"Implemented: ratecontrol.go (bcfd1e3, fb69190) — mothership-controlled 2Hz↔50Hz rate changes, firmware csi.c/websocket.c dynamic rate config, on-device amplitude variance burst detection","source_repo":".","compaction_level":0,"original_size":0} {"id":"spaxel-tqj","title":"CSI recording buffer","description":"Implement disk-backed circular buffer for CSI frame recording.\n\n## Deliverables\n- New package: mothership/internal/recording/\n- Append incoming CSI frames to disk-backed circular buffer (48h default retention)\n- Binary format for efficient storage (same frame format as WebSocket)\n- Configurable retention period via environment variable\n- Foundation for time-travel replay feature (Phase 8)\n\n## Acceptance Criteria\n- CSI frames are persisted to disk as they arrive\n- Buffer auto-prunes frames older than retention period\n- Can read back frames for a given time range\n- Storage is bounded (auto-prune prevents disk exhaustion)\n- Tests cover write, read-back, and pruning\n\n## References\n- Frame format: mothership/internal/ingestion/frame.go (24-byte header + payload)\n- Ring buffer: mothership/internal/ingestion/ring.go (in-memory reference)","status":"closed","priority":2,"issue_type":"task","assignee":"spaxel-alpha","created_at":"2026-03-27T01:56:09.947974130Z","created_by":"coding","updated_at":"2026-03-28T01:34:05.658150061Z","closed_at":"2026-03-27T03:02:15.596740568Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"spaxel-tqj","depends_on_id":"spaxel-cxm","type":"blocks","created_at":"2026-03-28T01:34:05.658124716Z","created_by":"coding","metadata":"{}","thread_id":""}]} {"id":"spaxel-tr7","title":"3D spatial visualization","description":"Full Three.js 3D scene with humanoid figures, room bounds, and node meshes.\n\n## Deliverables\n- Room bounds visualization (walls, floor, ceiling from configured dimensions)\n- Floor plan texture support (uploaded image mapped to ground plane)\n- Humanoid figures using SkinnedMesh + AnimationMixer (standing/walking/seated/lying)\n- Vertical pillar anchors and footprint trails for tracked blobs\n- Node meshes at configured 3D positions with link lines between pairs\n- View presets (top-down, perspective, first-person follow)\n- WebSocket integration to receive blob positions from mothership\n\n## Acceptance Criteria\n- Humanoid figures animate smoothly between postures\n- User can orbit, pan, zoom with OrbitControls\n- Node positions and link lines update in real-time\n- Works with existing dashboard skeleton (dashboard/js/app.js)\n\n## References\n- Plan: docs/plan/plan.md item 17\n- Dashboard: dashboard/index.html, dashboard/js/app.js","status":"closed","priority":2,"issue_type":"task","assignee":"spaxel-alpha","created_at":"2026-03-27T01:57:04.504533558Z","created_by":"coding","updated_at":"2026-03-28T05:36:26.148595152Z","closed_at":"2026-03-28T05:36:26.148493523Z","close_reason":"Implemented: dashboard/js/viz3d.js 566 lines (bcd19ad) — room bounds, humanoid SkinnedMesh 13-bone skeleton 4 postures, footprint trails, node meshes, link lines, 3 view presets","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred"],"dependencies":[{"issue_id":"spaxel-tr7","depends_on_id":"spaxel-cxm","type":"blocks","created_at":"2026-03-28T03:29:13.740185994Z","created_by":"coding","metadata":"{}","thread_id":""}]} -{"id":"spaxel-trsm","title":"Make expert mode mobile-responsive","description":"Touch orbit/pan/zoom functionality for mobile devices.","status":"in_progress","priority":2,"issue_type":"task","assignee":"charlie","created_at":"2026-04-10T02:03:09.684731821Z","created_by":"coding","updated_at":"2026-04-25T14:45:08.582968286Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["mitosis-child","mitosis-depth:1","parent-spaxel-17u"]} +{"id":"spaxel-trsm","title":"Make expert mode mobile-responsive","description":"Touch orbit/pan/zoom functionality for mobile devices.","status":"closed","priority":2,"issue_type":"task","assignee":"charlie","created_at":"2026-04-10T02:03:09.684731821Z","created_by":"coding","updated_at":"2026-04-25T15:08:19.815764069Z","closed_at":"2026-04-25T15:08:19.815704417Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred","mitosis-child","mitosis-depth:1","parent-spaxel-17u"]} {"id":"spaxel-ts2","title":"Implement image upload endpoint","description":"## Task\nImplement POST /api/floorplan/image endpoint.\n\n## Specification\n- Multipart form handling\n- Accept PNG/JPG max 10 MB\n- Save to /data/floorplan/image.png\n- Reject > 10 MB upload with 413 error\n\n## Acceptance\n- Image upload saves file to /data/floorplan/image.png\n- > 10 MB upload rejected with 413 error","status":"closed","priority":2,"issue_type":"task","assignee":"charlie","created_at":"2026-04-07T17:55:50.419717078Z","created_by":"coding","updated_at":"2026-04-07T18:43:14.895237433Z","closed_at":"2026-04-07T18:43:14.895178829Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["failure-count:2","mitosis-child","mitosis-depth:1","parent-spaxel-klk"]} {"id":"spaxel-tvq","title":"Command palette","description":"## Background\n\nExpert users — system installers, power users, home automation enthusiasts — want to operate the dashboard entirely from the keyboard. Reaching for the mouse to navigate between nodes, zones, and features breaks focus. The command palette (Ctrl+K / Cmd+K) provides a keyboard-driven interface to every feature: navigate to any person, zone, or node by typing their name, execute commands like \"Start OTA\" or \"Toggle Fresnel overlay\", and even jump the 3D scene to specific timestamps using a time syntax.\n\n## Trigger and Appearance\n\nKeyboard shortcut: Ctrl+K (Windows/Linux) or Cmd+K (macOS). Also triggerable via a small search icon button in the dashboard header bar.\n\nAvailable in: expert mode only. Not available in simple mode or ambient mode (those are for non-technical users who don't benefit from a command palette).\n\nThe palette appears as a centred modal overlay:\n- Semi-transparent dark backdrop (opacity 0.5, backdrop-filter: blur(4px))\n- Centred container: max-width 600px, border-radius 12px, background #1e293b\n- Top: search input (auto-focused, placeholder \"Search people, zones, nodes, commands...\")\n- Below: results list (max 8 visible, scrollable)\n\nClose on: Escape key, click on backdrop, second Ctrl+K/Cmd+K.\n\n## Search Scope\n\nAll entities are cached client-side in the dashboard's state store (already maintained from WebSocket updates). No server round-trips for search — purely client-side matching.\n\nSearch categories (in priority order in results):\n1. People: names from the people registry. Icon: person silhouette. Action: jump 3D camera to person's current blob.\n2. Zones: zone names. Icon: location pin. Action: jump 3D camera to zone centroid, select zone.\n3. Nodes: node labels or MAC addresses. Icon: radio antenna. Action: jump 3D camera to node position, select node.\n4. Recent events: last 20 timeline events (titles). Icon: clock. Action: open event detail in timeline.\n5. Commands: static list of dashboard commands (see Commands below). Icon: lightning bolt. Action: execute command.\n6. Time navigation: if query starts with \"@\", attempt time parsing (see below). Icon: clock. Action: seek replay to that time.\n\n## Fuzzy Matching\n\nClient-side fuzzy matching for all categories. Use a simple Jaro-Winkler or Levenshtein-distance based scorer:\n- Exact prefix match: highest score. \"Kit\" -> \"Kitchen\" gets top score.\n- Subsequence match: \"kitch rm\" -> \"Kitchen\" matches (not adjacent characters but in order).\n- Typo tolerance: 1 character substitution tolerated for strings > 4 characters.\n\nImplementation: a custom 30-line fuzzy match function in JavaScript. No dependencies needed. The function returns a score in [0, 1]; results with score < 0.3 are excluded.\n\nSort: primary by category priority (commands highest if starting with \"/\"), secondary by match score.\n\n## Commands\n\nStatic list of commands accessible from the palette:\n\nNavigation commands:\n- \"Open settings\" -> navigate to /settings\n- \"Open fleet page\" -> navigate to /fleet\n- \"Open automations\" -> navigate to /automations\n- \"Open simulator\" -> navigate to /simulate\n\nView commands:\n- \"Toggle Fresnel overlay\" -> toggle the Fresnel zone debug layer\n- \"Toggle flow map\" -> toggle the flow map layer\n- \"Toggle dwell heatmap\" -> toggle the dwell heatmap layer\n- \"Toggle zone volumes\" -> toggle zone cuboid visibility\n- \"Reset camera\" -> fly camera back to default top-down position\n\nSystem commands:\n- \"Enter away mode\" -> POST /api/mode {\"mode\":\"away\"}\n- \"Enter home mode\" -> POST /api/mode {\"mode\":\"home\"}\n- \"Enter sleep mode\" -> POST /api/mode {\"mode\":\"sleep\"}\n- \"Trigger fleet OTA\" -> opens the fleet OTA dialog\n- \"Add a person\" -> opens the Add Person form in the People & Devices panel\n- \"Add a zone\" -> starts zone creation mode in the 3D view\n- \"Add a portal\" -> starts portal creation mode\n\nDebug commands (shown at bottom, lower priority):\n- \"Export all events CSV\" -> GET /api/events?format=csv and download\n- \"Show link health table\" -> opens the link health panel\n- \"Run diagnostics\" -> triggers a diagnostics pass and shows results\n- \"Check firmware updates\" -> fetches latest firmware version and compares to all nodes\n\n## Time Navigation\n\nIf the query starts with \"@\": attempt to parse as a time expression and offer a \"Jump to time\" result.\n\nSupported formats:\n- \"@3am\" -> today at 03:00\n- \"@3:15am\" -> today at 03:15\n- \"@yesterday 11pm\" -> yesterday at 23:00\n- \"@2026-03-27 14:23\" -> specific datetime\n- \"@-30min\" -> 30 minutes ago from now\n- \"@-2h\" -> 2 hours ago\n\nOn selection: triggers time-travel replay to the parsed timestamp (same as clicking a timeline event's tap-to-jump).\n\nShow parsing preview in the result item: \"Jump to 2026-03-27 03:00:00\" with a clock icon.\n\n## Result Item Rendering\n\nEach result item in the list:\n- Left: icon (category-appropriate SVG, 16px)\n- Centre-left: primary text (entity name or command label, 14px)\n- Centre-right: secondary text (grey, 12px): for zones: \"[N] people currently\", for nodes: \"[online/offline]\", for commands: keyboard shortcut hint if any\n- Right: arrow icon (shows the item is actionable)\n\nSelected item (keyboard navigation): blue #3b82f6 background highlight.\n\n## Keyboard Navigation\n\n- Arrow up/down: move selection through results\n- Enter: execute selected item\n- Tab: same as Enter (for keyboard-first users who use Tab to confirm)\n- Escape: close palette\n- Character keys: type to refine search (selection resets to first result on each keystroke)\n\n## Recent History\n\nWhen the palette opens with an empty query: show \"Recent\" header with the last 5 palette actions (stored in localStorage \"spaxel_palette_history\"). Format: same as search results but without scores. \"Recent\" category shown before search results.\n\nRecent history excludes time navigation entries (those are ephemeral).\n\n## Files to Create or Modify\n\n- dashboard/js/commandpalette.js: CommandPaletteManager, fuzzy match, time parsing, result rendering\n- dashboard/js/commandpalette.css: modal overlay, input, result list styles\n- dashboard/js/app.js: keyboard shortcut listener (Ctrl+K / Cmd+K), integrate CommandPaletteManager\n\n## Tests\n\n- Test fuzzy matching: \"kit\" -> \"Kitchen\" score > 0.7; \"livig rm\" -> \"Living Room\" score > 0.5; \"xyz\" -> \"Kitchen\" score < 0.3 (excluded)\n- Test time navigation parsing: \"@3am\" parses to today at 03:00; \"@-30min\" parses to 30 minutes ago; \"@2026-03-27 14:23\" parses correctly\n- Test that commands list is complete (all documented commands present in the registry)\n- Test keyboard navigation: arrow down moves selection, Enter executes, Escape closes\n- Test recent history: execute 5 actions, open palette with empty query -> 5 recent items shown\n- Test that palette does not activate in simple mode or ambient mode (keyboard listener absent)\n- Test that viewport reposition correctly positions the palette centred on the screen\n\n## Acceptance Criteria\n\n- Command palette opens with Ctrl+K (or Cmd+K on macOS) in < 50ms\n- Fuzzy search returns \"Kitchen\" for query \"kitch\", \"kit\", \"ktchn\"\n- Time navigation \"@3am\" correctly seeks replay to 03:00 today\n- All documented commands are accessible and execute correctly\n- Arrow key navigation works correctly through results\n- Recent history shows last 5 palette actions on empty query\n- Palette unavailable in simple and ambient modes\n- All features accessible in 3 keystrokes or fewer from palette open\n- Tests pass","status":"closed","priority":3,"issue_type":"task","assignee":"sp-needle-claude-anthropic-sonnet-sp-20260413234000-0","created_at":"2026-03-28T02:02:28.058307267Z","created_by":"coding","updated_at":"2026-04-13T23:51:33.726667682Z","closed_at":"2026-04-13T23:51:33.726603608Z","close_reason":"Implemented command palette (Component 34): commandpalette.js with CommandPaletteManager, fuzzy scorer, time expression parser, 20-command registry, recent history, entity search. commandpalette.css with modal overlay styles. 64 passing Jest tests covering all acceptance criteria. Integrated into app.js and index.html.","source_repo":".","compaction_level":0,"original_size":0,"labels":["failure-count:1"],"dependencies":[{"issue_id":"spaxel-tvq","depends_on_id":"spaxel-s70","type":"blocks","created_at":"2026-03-28T02:02:31.595593278Z","created_by":"coding","metadata":"{}","thread_id":""},{"issue_id":"spaxel-tvq","depends_on_id":"spaxel-sl2","type":"blocks","created_at":"2026-03-28T03:29:15.028093011Z","created_by":"coding","metadata":"{}","thread_id":""}]} {"id":"spaxel-u7y","title":"Firmware: NTP clock sync for TX stagger accuracy","description":"## Overview\nImplement NTP synchronization on ESP32-S3 so all nodes share a common clock, enabling accurate TX stagger scheduling to avoid CSI collisions.\n\n## Firmware (firmware/main/wifi.c or ntp.c)\n- Call esp_sntp_setservername(0, ntp_server) before esp_sntp_init() on boot\n- ntp_server read from NVS 'ntp_server' key (default: 'pool.ntp.org')\n- Attempt sync for up to 10 seconds after WiFi connect; log WARN to UART if sync fails\n- On sync failure: proceed without stagger (rely on CSMA/CA for collision avoidance)\n- Resync every 10 minutes via esp_timer periodic callback\n- Include NTP sync status in health JSON message: {type:'health', ..., ntp_synced: true/false}\n\n## Mothership (provisioning payload)\n- Read SPAXEL_NTP_SERVER env var (default: pool.ntp.org)\n- Embed ntp_server field in provisioning payload JSON\n- Support config downstream message field ntp_server to push updated server to nodes\n\n## Acceptance\n- Node health messages show ntp_synced: true when pool is reachable\n- ntp_synced: false when NTP blocked — node still operates normally\n- Resync occurs every ~600s (verified via UART logs)","status":"closed","priority":2,"issue_type":"task","assignee":"alpha","created_at":"2026-04-06T16:42:26.894640218Z","created_by":"coding","updated_at":"2026-04-07T17:46:19.521425117Z","closed_at":"2026-04-07T17:46:19.521245004Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["failure-count:1"],"dependencies":[{"issue_id":"spaxel-u7y","depends_on_id":"spaxel-288","type":"blocks","created_at":"2026-04-07T14:37:00.359263571Z","created_by":"coding","metadata":"{}","thread_id":""},{"issue_id":"spaxel-u7y","depends_on_id":"spaxel-qgj","type":"blocks","created_at":"2026-04-07T14:37:00.321904383Z","created_by":"coding","metadata":"{}","thread_id":""}]} diff --git a/.needle-predispatch-sha b/.needle-predispatch-sha index 8747467..caacdc6 100644 --- a/.needle-predispatch-sha +++ b/.needle-predispatch-sha @@ -1 +1 @@ -ab5e77352d2cf097851d9b0e8b9a8edf2d8e5605 +ef9cd3fe150ad1102da788c2b33ece882e68e68a