diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index 28ea93aa4e8..da5703240d9 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -91,7 +91,7 @@ from toolsets import get_toolset_names # Constants # --------------------------------------------------------------------------- -VALID_STATUSES = {"triage", "todo", "ready", "running", "blocked", "done", "archived"} +VALID_STATUSES = {"triage", "todo", "scheduled", "ready", "running", "blocked", "done", "archived"} VALID_WORKSPACE_KINDS = {"scratch", "worktree", "dir"} KNOWN_TOOLSET_NAMES = frozenset(name.casefold() for name in get_toolset_names()) _IS_WINDOWS = sys.platform == "win32" diff --git a/plugins/kanban/dashboard/plugin_api.py b/plugins/kanban/dashboard/plugin_api.py index d8ad71e9e24..5ed0e6144d2 100644 --- a/plugins/kanban/dashboard/plugin_api.py +++ b/plugins/kanban/dashboard/plugin_api.py @@ -129,8 +129,14 @@ def _conn(board: Optional[str] = None): # Columns shown by the dashboard, in left-to-right order. "archived" is # available via a filter toggle rather than a visible column. +# +# Keep this in sync with kanban_db.VALID_STATUSES. In particular, +# ``scheduled`` is a first-class waiting column used for time-based follow-ups; +# if it is omitted here, the board-level fallback below mis-buckets scheduled +# tasks into ``todo`` and makes the dashboard look like the Scheduled column +# disappeared. BOARD_COLUMNS: list[str] = [ - "triage", "todo", "ready", "running", "blocked", "done", + "triage", "todo", "scheduled", "ready", "running", "blocked", "done", ] diff --git a/tests/plugins/test_kanban_dashboard_plugin.py b/tests/plugins/test_kanban_dashboard_plugin.py index 6b9acac3a87..50da5071e3c 100644 --- a/tests/plugins/test_kanban_dashboard_plugin.py +++ b/tests/plugins/test_kanban_dashboard_plugin.py @@ -70,7 +70,8 @@ def test_board_empty(client): data = r.json() # All canonical columns present (triage + the rest), each empty. names = [c["name"] for c in data["columns"]] - for expected in ("triage", "todo", "ready", "running", "blocked", "done"): + assert set(names) == kb.VALID_STATUSES - {"archived"} + for expected in ("triage", "todo", "scheduled", "ready", "running", "blocked", "done"): assert expected in names, f"missing column {expected}: {names}" assert all(len(c["tasks"]) == 0 for c in data["columns"]) assert data["tenants"] == [] @@ -113,6 +114,31 @@ def test_create_task_appears_on_board(client): assert "researcher" in data["assignees"] +def test_scheduled_tasks_have_their_own_column_not_todo(client): + """Scheduled/time-delay tasks must not be silently bucketed into todo.""" + + task = client.post( + "/api/plugins/kanban/tasks", + json={"title": "wait for indexed data", "assignee": "ops"}, + ).json()["task"] + + conn = kb.connect() + try: + with kb.write_txn(conn): + conn.execute( + "UPDATE tasks SET status = 'scheduled' WHERE id = ?", + (task["id"],), + ) + finally: + conn.close() + + r = client.get("/api/plugins/kanban/board") + assert r.status_code == 200 + columns = {c["name"]: c["tasks"] for c in r.json()["columns"]} + assert any(t["id"] == task["id"] for t in columns["scheduled"]) + assert not any(t["id"] == task["id"] for t in columns["todo"]) + + def test_tenant_filter(client): client.post("/api/plugins/kanban/tasks", json={"title": "A", "tenant": "t1"}) client.post("/api/plugins/kanban/tasks", json={"title": "B", "tenant": "t2"})