mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 19:20:16 +00:00
397d851bdb
Closes the remaining gaps to first-party Hermes Agent dashboard parity: multi-board CRUD on /api/kanban/boards and a real-time event stream over Server-Sent Events. Builds on top of #1660 (review-feedback hardening). == Multi-board == Five new endpoints mirror the agent dashboard plugin contract verbatim (plugins/kanban/dashboard/plugin_api.py) so a single CLI / gateway slash command / dashboard / WebUI all share the same active-board pointer: GET /api/kanban/boards POST /api/kanban/boards PATCH /api/kanban/boards/<slug> DELETE /api/kanban/boards/<slug> POST /api/kanban/boards/<slug>/switch All existing endpoints accept ?board=<slug> (and writes also accept 'board' in the JSON body) — query takes precedence over body. The slug travels through the kanban_db library which already had multi-board support; the bridge is mostly thin wrappers around create_board / remove_board / list_boards / set_current_board / get_current_board. The default board is protected from deletion. Slugs are normalised through kb._normalize_board_slug() with path-traversal rejection. Archive is the default for DELETE; ?delete=1 hard-deletes. Frontend gets a 'Default ▾' switcher pill in the panel header. The menu lists every board (current first), per-status total badges, plus three actions (New / Rename / Archive). Create + rename use the same modal with a slug auto-derived from the name. Archive routes through the existing showConfirmDialog with a clear 'tasks remain on disk and the board can be restored from kanban/boards/_archived/' message. Active-board state is persisted to localStorage so a refresh stays put. The on-disk pointer in kanban/current is the cross-process source of truth, kept in sync via POST /boards/<slug>/switch. == SSE event stream == GET /api/kanban/events/stream is a long-lived Server-Sent Events feed that mirrors the agent dashboard's WebSocket /events contract. The WebUI uses SSE rather than WebSocket because (1) the existing transport is BaseHTTPServer, not async — WS would require a significant refactor or a hijack-the-socket hack; (2) SSE is the right tool for unidirectional server-pushed event streams; (3) browsers auto-reconnect on drop; (4) the existing /api/approval/stream and /api/clarify/stream patterns are proven and easy to copy. The handler polls task_events at 300ms (matching the agent dashboard's WebSocket poll cadence) so write-to-receive latency is identical. Heartbeats every 15s prevent proxy/CDN reaping. Hard cap of 200 events per batch. Frontend uses EventSource by default and falls back to 30s HTTP polling after 3 SSE failures. A 250ms debounce coalesces bursts of N events into a single board re-fetch. Stream is torn down when the user leaves the Kanban panel. == Bugs fixed during build == (1) read_only=True legacy lie. _board_payload, _events_payload, _task_log_payload, and the no-change short-circuit all hardcoded read_only=True from the read-only-bridge era of #1645. Bridge has been writable since #1649 — flag now matches reality. (2) Modal + dropdown menu transparent backgrounds. The PR stack used var(--panel) which is undefined in the WebUI design system (uses --surface, --bg, gradient panels). Replaced with the same gradient + accent border pattern used by the .app-dialog overlay. (3) Archive race. kb.connect(board=<slug>) auto-materialises the directory + sqlite on first call, so any in-flight SSE poll on a board mid-archive would silently un-archive it by re-creating the directory. Two-layer fix: (a) frontend stops the SSE stream BEFORE the DELETE call, restarts on failure; (b) bridge's _kanban_sse_fetch_new checks kb.board_exists() before connect(), returning empty results when the board is gone. (4) Save vs. Cancel button visual hierarchy. Both rendered as identical secondary buttons in the modal. Save now uses the .primary class with accent-tinted gold styling. (5) Mobile viewport gaps. Added 9 rules under @media (max-width: 640px) covering the switcher button (smaller padding/font), name truncation (max-width:140px), menu sizing (min(280px, 100vw - 24px)), modal padding, and inline-row stacking. == Tests == +45 new tests across two files. Bridge tests: 18 covering board CRUD endpoints, slug validation, default-board protection, dispatcher routing, board isolation (verified via connect() spy), and 3 SSE tests including a worker-thread integration test with threading.Event watchdog. UI static tests: 11 covering switcher markup, modal markup, JS handler presence, REST verb usage, board-param plumbing, localStorage persistence, showConfirmDialog usage, EventSource subscription, polling fallback, panel-switch teardown, and 250ms debouncing. Bridge tests: 18 → 36 (+18 multi-board, +3 SSE) UI static tests: 15 → 26 (+11) Total kanban: 33 → 63 Full repo test suite: 4351 passed, 0 regressions. == Live verification == End-to-end browser walkthrough on port 8789: - Create Sprint 12 + Backlog via modal: switcher updates ✓ - Switch between boards: count isolation correct ✓ - Add task on Sprint 12 via API: SSE delivers in 400ms ✓ - 5-task burst: 250ms debounce coalesces to single render ✓ - Rename board via modal: switcher label updates ✓ - Archive board: confirm dialog → board moved to _archived/, no zombie directory (race fix verified) ✓ - Zero JS errors throughout 11-step flow Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>