From 7c479e7afe67488145ec09dd2f5c8738293c49c3 Mon Sep 17 00:00:00 2001 From: jedarden Date: Sat, 11 Apr 2026 04:43:06 -0400 Subject: [PATCH] test: quiet hours gate tests passing - LOW at 23:00 with 22:00-07:00 quiet hours -> queued (TestQuietHoursGate_LowAt23pmQueued) - URGENT at 23:00 -> delivered immediately (TestQuietHoursGate_UrgentAt23pmDelivered) - MEDIUM at 23:00 -> queued (TestQuietHoursGate_MediumAt23pmQueued) - HIGH at 23:00 -> queued during quiet hours, delivered outside (TestQuietHoursGate_HighAt23pmDelivered) Acceptance criteria met: Quiet hours tests pass (queueing, bypass). --- .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 edb57e2..6ec8930 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,5 +1,5 @@ {"id":"spaxel-05a","title":"Implement calibration GET endpoint","description":"## Task\nImplement GET /api/floorplan/calibrate endpoint.\n\n## Specification\n- Return current calibration from SQLite\n- Return 404 if no calibration exists\n\n## Acceptance\n- Returns calibration data when present\n- Returns 404 when no calibration exists","status":"closed","priority":2,"issue_type":"task","assignee":"charlie","created_at":"2026-04-07T17:55:53.178762085Z","created_by":"coding","updated_at":"2026-04-07T18:58:38.551564957Z","closed_at":"2026-04-07T18:58:38.551463596Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["mitosis-child","mitosis-depth:1","parent-spaxel-klk"]} -{"id":"spaxel-0fm8","title":"Add quiet hours gate tests","description":"Write tests for quiet hours gate: LOW at 23:00 with 22:00-07:00 quiet hours -> queued, URGENT at 23:00 -> delivered. Acceptance Criteria: Quiet hours tests pass (queueing, bypass).","status":"in_progress","priority":2,"issue_type":"task","assignee":"bravo","created_at":"2026-04-11T08:15:07.990827798Z","created_by":"coding","updated_at":"2026-04-11T08:30:28.547342816Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["mitosis-child","mitosis-depth:1","parent-spaxel-40tl"]} +{"id":"spaxel-0fm8","title":"Add quiet hours gate tests","description":"Write tests for quiet hours gate: LOW at 23:00 with 22:00-07:00 quiet hours -> queued, URGENT at 23:00 -> delivered. Acceptance Criteria: Quiet hours tests pass (queueing, bypass).","status":"in_progress","priority":2,"issue_type":"task","assignee":"bravo","created_at":"2026-04-11T08:15:07.990827798Z","created_by":"coding","updated_at":"2026-04-11T08:39:52.275270077Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["failure-count:1","mitosis-child","mitosis-depth:1","parent-spaxel-40tl"]} {"id":"spaxel-0ii","title":"Implement Zones CRUD REST endpoints","description":"Implement CRUD endpoints for zones: GET/POST /api/zones, PUT/DELETE /api/zones/{id}. Include OpenAPI-style godoc comments. Zone changes must reflect in live 3D view within one WebSocket cycle.","status":"closed","priority":2,"issue_type":"task","assignee":"echo","created_at":"2026-04-07T13:56:27.275139529Z","created_by":"coding","updated_at":"2026-04-07T19:01:48.974563569Z","closed_at":"2026-04-07T19:01:48.974408083Z","close_reason":"Zones CRUD REST endpoints already fully implemented: GET/POST /api/zones, PUT/DELETE /api/zones/{id}, GET /api/zones/{id}/history, plus portals CRUD. OpenAPI godoc comments, WebSocket broadcasting for live 3D view, 31 table-driven tests. go vet and go test pass.","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred","failure-count:1","mitosis-child","mitosis-depth:1","parent-spaxel-21n"],"dependencies":[{"issue_id":"spaxel-0ii","depends_on_id":"spaxel-3rd","type":"blocks","created_at":"2026-04-07T17:01:33.629176640Z","created_by":"coding","metadata":"{}","thread_id":""},{"issue_id":"spaxel-0ii","depends_on_id":"spaxel-5lo","type":"blocks","created_at":"2026-04-07T17:01:33.542274773Z","created_by":"coding","metadata":"{}","thread_id":""}]} {"id":"spaxel-0j0k","title":"Implement responsive canvas resize and orientation handling","description":"Handle canvas resize on window resize and orientation change events, including iOS Safari visual viewport quirks and bottom navigation bar spacing.\n\n**Files:** dashboard/js/app.js (resize/orientationchange listeners), dashboard/css/expert.css (canvas height calculation)\n\n**Acceptance Criteria:**\n- Canvas resizes correctly on window resize event\n- Canvas resizes correctly on orientationchange event\n- renderer.setSize() called with updated dimensions\n- camera.aspect updated and camera.updateProjectionMatrix() called\n- Uses visualViewport.width/height on iOS Safari (fallback to window.innerWidth/Height)\n- Canvas height uses calc(100vh - 56px) when simple mode nav is visible","status":"closed","priority":2,"issue_type":"task","assignee":"bravo","created_at":"2026-04-11T06:26:50.081049506Z","created_by":"coding","updated_at":"2026-04-11T06:51:20.327121642Z","closed_at":"2026-04-11T06:51:20.327061414Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0,"labels":["mitosis-child","mitosis-depth:1","parent-spaxel-kth"]} {"id":"spaxel-0w4","title":"Fleet status page","description":"## Background\n\nThe 3D scene is great for spatial context but poor for bulk fleet management tasks. With 6+ nodes, finding a specific node in the 3D view, checking its firmware version, and triggering an update involves hunting through the scene. The fleet status page provides a flat table view of all nodes with their key metrics and inline actions — the same information you would find in a server management panel, adapted for ESP32 nodes. It complements the 3D view rather than replacing it.\n\n## Fleet Status Table\n\nNew dashboard route: /fleet\n\nThe page layout:\n- Page header: \"Fleet Status\" title, total node count, online count, \"Update All\" button, \"Download report\" button\n- Filter and sort bar (below header)\n- Fleet table (main content)\n\nTable columns:\n1. Checkbox (for multi-select)\n2. Label (editable inline on double-click)\n3. MAC address (truncated, full on hover tooltip)\n4. Status: coloured dot + text. \"Online\" (green) / \"Offline\" (red) / \"Updating\" (yellow spinner)\n5. Firmware version: current version string. If a newer version is in the firmware manifest: version displayed in amber with an \"→ {new_version}\" indicator and an \"Update\" action badge.\n6. Uptime: formatted as \"3d 4h 12m\". Only valid when online.\n7. Current role: TX / RX / TX-RX / Passive. Small badge.\n8. Signal health: composite health score for this node's links as a small colour bar (green → red)\n9. Packet rate: \"{actual} / {configured} Hz\" as a fraction. Colour-coded: > 90% = green, 70-90% = amber, < 70% = red.\n10. Temperature: from health message. Shown as \"{N}°C\" or \"--\" if not reported. Alert colour if > 75°C.\n11. Actions column: [Locate] [OTA] [...more] buttons\n\nThe table rows are clickable (full row click = fly 3D camera to that node's position). Only clicking action buttons or the checkbox should not trigger the row fly-to.\n\n## Inline Label Edit\n\nDouble-clicking the label cell makes it inline-editable:\n- Input field replaces the text\n- Enter to confirm, Escape to cancel\n- Blur (click outside) to confirm\n- On confirm: PATCH /api/nodes/{mac}/label with the new label. Update the display.\n- Validation: max 32 characters, no control characters.\n\n## Action Buttons\n\n\"Locate\" (flash LED): sends a downstream command {\"type\":\"identify\"} to the node via WebSocket. The node flashes its onboard LED rapidly for 5 seconds. The button shows a spinner while the command is in-flight, then a brief green checkmark.\n\n\"OTA\" (firmware update): available only if the node's firmware version != latest in the manifest. Clicking shows a confirmation tooltip: \"Update Node [label] from v{current} to v{latest}? [Confirm] [Cancel]\". On confirm: POST /api/nodes/{mac}/ota. The node's row shows \"Updating\" status and a progress bar (populated from ota_status WebSocket messages for this node).\n\n\"More actions\" (... button): dropdown with: \"Re-assign role\", \"View health history\", \"View event history\", \"Remove from fleet\". Each with an appropriate icon.\n\n## Bulk Actions\n\nCheckbox column allows multi-selecting rows. When any row is selected, a bulk-actions bar slides in above the table:\n- \"Update {N} selected to latest firmware\" — confirms and triggers OTA for all selected nodes in sequence (with 30s stagger)\n- \"Re-assign roles\" — opens the role optimiser with the selected nodes included\n- \"Remove {N} from fleet\" — confirmation required: lists the nodes to be removed\n\nDeselect all: \"Clear selection\" button in the bulk actions bar, or uncheck all checkboxes.\n\n## Camera Fly-To\n\nClicking a table row (non-action click) triggers a smooth camera fly-to the node's position in the expert mode 3D view. The fleet page and expert mode are on different routes, so this requires:\n1. Store the target node MAC in localStorage or URL parameter (\"?highlight={mac}\")\n2. Redirect to the expert mode route / (or open expert mode in a second tab)\n3. On load, if ?highlight={mac} parameter is present, fly camera to that node's position\n\nAlternatively: if the fleet page is opened alongside the expert mode in a split-pane layout (future enhancement), coordinate via a shared state store. For Phase 9, the redirect approach is sufficient.\n\n## Sorting and Filtering\n\nColumn header click: sort by that column. First click = ascending, second = descending. Sort state shown with a small arrow indicator.\n\nFilter row (below column headers, toggle-able with a \"Filter\" button):\n- Label / MAC: text input, filters rows containing the substring\n- Status: dropdown \"All / Online / Offline\"\n- Firmware: dropdown \"All / Outdated only\"\n- Role: multi-select dropdown \"All / TX / RX / TX-RX / Passive\"\n\nActive filters: shown as chips above the table with individual dismiss buttons. \"Clear all filters\" link.\n\n## Download Report\n\n\"Download report\" button: exports the current fleet table (including all filters) as a CSV file. Columns: MAC, label, status, firmware_version, uptime_s, role, health_score, packet_rate_hz, temperature_c, last_seen.\n\nImplemented as a client-side CSV generation from the current table data (no API call needed if data is cached in the dashboard state). Use Blob + URL.createObjectURL for download.\n\n## REST API\n\nGET /api/fleet: returns all provisioned nodes with full details (same as GET /api/nodes but with more fields: uptime, firmware_version, temperature, health_score, packet_rate).\nPATCH /api/nodes/{mac}/label: update label\nPOST /api/nodes/{mac}/locate: send identify command\nPOST /api/nodes/{mac}/role: assign new role\nDELETE /api/nodes/{mac}: remove from fleet (disconnects and archives)\n\n## Files to Create or Modify\n\n- dashboard/fleet.html: fleet page HTML shell\n- dashboard/js/fleet.js: table rendering, sorting, filtering, bulk actions, inline edit\n- dashboard/css/fleet.css: fleet page styles\n- mothership/internal/dashboard/routes.go: fleet-specific API routes, /fleet HTML route\n\n## Tests\n\n- Test fleet table renders correctly with mock data: 4 nodes, verify all columns populated\n- Test inline label edit: double-click cell, type new label, Enter -> PATCH API called with correct body\n- Test bulk selection: check 3 nodes, verify bulk actions bar appears with correct count\n- Test bulk OTA triggers OTA for all 3 selected nodes\n- Test sorting: click \"Firmware version\" header -> rows sorted ascending by version string\n- Test filter: enter \"living\" in label filter -> only rows matching \"living\" visible\n- Test camera fly-to: clicking a row stores MAC in localStorage and redirects to expert mode with ?highlight parameter\n- Test CSV download: verify blob is created with correct headers and values\n\n## Acceptance Criteria\n\n- Fleet page loads with all nodes and their current metrics\n- Inline label edit saves correctly to the API and updates the display\n- Bulk OTA fires for all selected nodes with correct stagger\n- Sorting and filtering work correctly for all columns\n- Camera fly-to positions the 3D view correctly on the selected node after redirect\n- CSV download contains correct headers and all fleet data\n- Tests pass","status":"open","priority":3,"issue_type":"task","created_at":"2026-03-28T02:06:06.562532476Z","created_by":"coding","updated_at":"2026-04-11T08:20:42.548134891Z","source_repo":".","compaction_level":0,"original_size":0,"labels":["deferred"],"dependencies":[{"issue_id":"spaxel-0w4","depends_on_id":"spaxel-sl2","type":"blocks","created_at":"2026-03-28T03:29:14.955755499Z","created_by":"coding","metadata":"{}","thread_id":""}]} diff --git a/.needle-predispatch-sha b/.needle-predispatch-sha index e7f3249..bebfeb0 100644 --- a/.needle-predispatch-sha +++ b/.needle-predispatch-sha @@ -1 +1 @@ -1903085e12c3d3ab21ce46553aaa862773089f12 +576ec415093ffd592c81daf0dab3442671c1fdcd