{"repo":"NousResearch/hermes-agent","public_id":"20879","display_label":"Kanban dashboard board selection and card rendering bugs","summary":"These issues and fixes concern the Kanban dashboard rendering the wrong board's cards or no cards at all when the selected board diverges from the server's current file. The changes attempt to make the pinned or default board authoritative for what the dashboard displays.","subsystem":null,"counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":2,"merged_prs":2,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":21230},"resolution":{"state":"resolved","by":"pr-21230,pr-22940"},"freshness":{"state":"verified","last_verified_at":"2026-05-10T02:31:33+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.33599999999999997},"fix_edges":[{"issue_number":20879,"pr_number":21230,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":21819,"pr_number":22940,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":20879,"title":"`[Bug]: Kanban dashboard shows correct task count but renders no cards when localStorage board and CLI current-file board diverge`","url":null,"state":"closed"},{"kind":"issue","number":21819,"title":"Kanban dashboard Default board can render current non-default board cards","url":null,"state":"closed"},{"kind":"pr","number":21230,"title":"fix(kanban): make dashboard board pin authoritative over server current file (#20879)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/kanban/dashboard/dist/index.js"],"fix_hunks":[{"file":"plugins/kanban/dashboard/dist/index.js","hunks":["@@ -112,17 +112,30 @@\n \n   function writeSelectedBoard(slug) {\n     try {\n-      if (slug && slug !== \"default\") window.localStorage.setItem(LS_BOARD_KEY, slug);\n+      // Persist the user's dashboard-side board pin even for \"default\".\n+      // Previously this stripped \"default\" to keep localStorage empty,\n+      // but the fetch layer read that absence as \"no opinion\" and fell\n+      // through to the server-side ``current`` file — which the board\n+      // switcher also writes. Result: selecting the default tab after\n+      // creating a new board with \"switch\" checked showed the new\n+      // board's (wrong) data because the URL omitted ``?board=`` and\n+      // the backend happily returned whichever board was \"current\".\n+      // Persisting every selection keeps the dashboard's board opinion\n+      // independent of the CLI's active board, which was the original\n+      // design intent. Regression: #20879.\n+      if (slug) window.localStorage.setItem(LS_BOARD_KEY, slug);\n       else window.localStorage.removeItem(LS_BOARD_KEY);\n     } catch (_e) { /* ignore quota / private mode */ }\n   }\n \n   function withBoard(url, board) {\n-    // Append ?board=<slug> when a non-default board is active. Omitted\n-    // for default so the URL stays clean and the backend falls through\n-    // to its own resolution chain (env var → ``current`` file →\n-    // default) which is already correct.\n-    if (!board || board === \"default\") return url;\n+    // Always append ?board=<slug> when we have one picked — including\n+    // \"default\". Omitting the param would fall through to the backend's\n+    // resolution chain (env var → ``current`` file → default), which\n+    // means the dashboard's tab selection gets silently overridden by\n+    // whatever board the CLI or \"switch\" checkbox last activated.\n+    // Regression: #20879.\n+    if (!board) return url;\n     const sep = url.indexOf(\"?\") >= 0 ? \"&\" : \"?\";\n     return `${url}${sep}board=${encodeURIComponent(board)}`;\n   }","@@ -447,9 +460,11 @@\n           token: [redacted:secret-assignment]\n         };\n         // Pin the WS stream to the currently-selected board so events\n-        // from other boards don't bleed in. Only set for non-default so\n-        // single-board installs keep the cleaner URL.\n-        if (board && board !== \"default\") qsParams.board = board;\n+        // from other boards don't bleed in. Includes \"default\" so the\n+        // dashboard's own board pin always wins over the server-side\n+        // ``current`` file — same rationale as ``withBoard()`` above.\n+        // Regression: #20879.\n+        if (board) qsParams.board = board;\n         const qs = new URLSearchParams(qsParams);\n         const url = `${proto}//${window.location.host}${API}/events?${qs}`;\n         let ws;\n"]}]},{"kind":"pr","number":22106,"title":"fix(kanban): request default board explicitly (#21819)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22940","number":22940}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":22940,"title":"fix(kanban): request default board explicitly (salvage #22106)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/kanban/dashboard/dist/index.js","tests/plugins/test_kanban_dashboard_plugin.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}