Skip to content

[Bug]: Kanban dashboard shows correct task count but renders no cards when localStorage board and CLI current-file board diverge #20879

@alycda

Description

@alycda

Bug Description

I added a task and created a new board and saw the original task disappear in the WebUI

Steps to Reproduce

  • I created a kanban board from cli: hermes kanban init
  • I viewed the WebUI: hermes dashboard
  • I created a basic: hermes kanban create "THING"
  • I confirmed it in the WebUI
  • I used the WebUI to create a new board (checkbox to switch was checked)
  • The new board rendered in the UI, the tab bar for the original board showed (1) item, but the UI displayed no tasks at all.
  • I re-created the task: hermes kanban create "THING" and it now showed up on the second board in the UI, the dashboard was stopped/restarted and the original/default board shoed 0 tasks
  • I then ran hermes kanban list and saw both tasks on both boards and realized this was a WebUI issue

Expected Behavior

UI should display tasks for selected board, not just the active board

Actual Behavior

switching boards prevents all tasks from rendering in the WebUI

Affected Component

Tools (terminal, file ops, web, code execution, etc.), Other

Messaging Platform (if gateway-related)

No response

Debug Report

Report     https://paste.rs/ahTfg
  agent.log  https://paste.rs/MJPsV

Operating System

MacOS 26.3.1

Python Version

3.11.15

Hermes Version

v0.12.0 (2026.4.30)

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

The docs expose the smoking gun in the WebSocket endpoint signature. From the REST surface table:

  • REST endpoints: GET /board, GET /tasks/:id, etc. — all accept ?board=<slug>
  • WebSocket: WS /events?since=<event_id>no board= parameter shown

The dashboard card render isn't driven purely by the initial REST response. Per the docs, "the board reflects changes the instant any profile acts" and "reloads are debounced so a burst of events triggers a single refetch." Cards are rendered/refreshed via WebSocket-triggered refetches, not purely from the initial load.

So after your CLI boards switch:

Surface Board resolution Result
REST /board?board=default localStorage → default Returns 1 task → count badge shows 1
WebSocket /events?since=0 No board= param → falls back to server current file → new board Connects to wrong board, no events fire for default's task → card refetch never triggers

The count and the cards are reading from different board scopes within the same page load. The initial REST response populates the count, but card column rendering depends on the WebSocket event stream to hydrate or confirm state — and that stream is silently connected to the wrong board.

The fix is that the WebSocket endpoint needs to accept and respect ?board=<slug>, sourced from the same localStorage value the REST calls use. As the docs note, "switching in the UI opens a fresh WS against the new board" — that mechanism exists for UI-initiated switches, but it's never triggered when the board changes externally via CLI.

Proposed Fix (optional)

Two parts: the WebSocket endpoint needs a board parameter, and the dashboard needs to detect external board changes.

Part 1 — Thread board through the WebSocket connection

The WS endpoint should accept ?board=<slug> and use it for scoping, the same way the REST endpoints do:

# plugins/kanban/dashboard/plugin_api.py

@router.websocket("/events")
async def events_ws(
    websocket: WebSocket,
    since: int = 0,
    board: str | None = None,          # add this
    token: str | None = None,
):
    await websocket.accept()
    resolved_board = board or _resolve_current_board()   # same helper REST calls use
    db = kanban_db.connect(board=resolved_board)
    ...

And on the frontend, pass the board slug from localStorage when opening the connection:

// plugins/kanban/dashboard/src/kanban.tsx (or wherever the WS is opened)

const board = localStorage.getItem('hermes-kanban-board') ?? 'default';
const ws = new WebSocket(
  `/api/plugins/kanban/events?since=${lastEventId}&token=${token}&board=${board}`
);

This makes the WS and REST calls consistent: both read board from localStorage, both scope to the same DB.

Part 2 — Detect external board changes and reconnect

Even with Part 1, if the CLI switches the board while the dashboard is open, the WS stays connected to the old board. The dashboard needs to poll GET /api/plugins/kanban/config (or a new GET /boards/current endpoint) on a slow interval and reconnect the WS if the server-side current board diverges from localStorage:

useEffect(() => {
  const interval = setInterval(async () => {
    const { current_board } = await fetchJSON('/api/plugins/kanban/config');
    const localBoard = localStorage.getItem('hermes-kanban-board') ?? 'default';
    if (current_board !== localBoard) {
      // Surface a non-destructive banner: "Board switched to X in CLI — switch here?"
      // Don't auto-switch; respect the intentional localStorage/current-file split
    }
  }, 30_000);
  return () => clearInterval(interval);
}, []);

The auto-switch is intentionally avoided — the docs call the localStorage/current decoupling a feature ("so it persists across reloads without shifting the CLI's current pointer out from under a terminal you left open"). The right UX is a dismissible banner that lets the user decide, not a silent takeover.

What this doesn't need: changes to kanban_db, the REST handlers, or the board resolution order. The bug is entirely in the WS connection setup; the underlying data is correct throughout.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/pluginsPlugin system and bundled pluginstype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions