fix(sessions): give a background row one project chip on every path

`_interactive_row_project_id` resolved state.db assignments for anything that
was not cron or webhook, but the interactive projection loop is also what a
`source_filter='kanban'` scan walks. The same kanban row therefore reported two
different chips depending on the caller: None in the default sidebar (its own
bounded second pass, upstream behaviour) and a resolved project_id under
?source=kanban.

Route every source that owns a bounded project-chip pass back through
`_state_row_project_id`, and name that set once (BACKGROUND_CLI_SOURCES) so the
exclusion list and the chip decision cannot drift apart.
This commit is contained in:
Rodrigo Gomes da Silva committed 2026-09-24 11:03:46 -03:00
1 parent bc1ee9d400
commit 8211acdd84
2 files changed
+50 -4

No files matched your search

@@ -740,6 +740,33 @@ def test_kanban_second_pass_still_projects_rows(fake_hermes_home, tmp_path):
assert by_id["kanban-old"]["project_id"] is None
def test_background_row_chip_is_the_same_on_both_projection_paths(
fake_hermes_home, tmp_path
):
"""One kanban row must not report two different chips.
The interactive projection loop is also what a ``source_filter='kanban'``
scan walks, so resolving state.db assignments there without excusing
background sources gave the SAME row ``None`` in the default view (its own
bounded second pass) and a resolved id under ``?source=kanban``.
"""
_register_projects(tmp_path, "project-live")
rows = [_session("kan-1", BASE_TS, source="kanban", project_id="project-live")]
rows.extend(
_session(f"recent-{index:02d}", BASE_TS + 100 + index) for index in range(25)
)
_write_state_db(fake_hermes_home / "state.db", rows)
default_view = {s["session_id"]: s for s in models.get_cli_sessions()}
kanban_view = {s["session_id"]: s for s in models.get_cli_sessions("kanban")}
assert "kan-1" in default_view
assert "kan-1" in kanban_view
assert default_view["kan-1"]["project_id"] is None
assert kanban_view["kan-1"]["project_id"] == default_view["kan-1"]["project_id"]
# ── The all-profiles sidebar view runs the same recovery passes ───────────────