From e27b7cf04221b58d929995ca0a25b45506a0d3b0 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Tue, 23 Jun 2026 10:29:06 -0400 Subject: [PATCH] perf(sidebar): trim hidden and empty sidebar payload rows (#4775) --- api/routes.py | 44 +++ static/sessions.js | 1 + .../test_issue4766_sidebar_source_pushdown.py | 13 +- ...75_sidebar_hidden_zero_message_pushdown.py | 298 ++++++++++++++++++ 4 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py diff --git a/api/routes.py b/api/routes.py index 31c6ac34..bb63f4aa 100644 --- a/api/routes.py +++ b/api/routes.py @@ -1597,6 +1597,8 @@ def _session_list_cache_key( show_previous_messaging_sessions: bool, show_cron_sessions: bool, include_archived: bool = False, + exclude_hidden: bool = False, + visible_only: bool = False, source_filter: str | None = None, sidebar_source: str | None = None, ) -> tuple: @@ -1607,6 +1609,8 @@ def _session_list_cache_key( bool(show_previous_messaging_sessions), bool(show_cron_sessions), bool(include_archived), + bool(exclude_hidden), + bool(visible_only), source_filter, sidebar_source, ) @@ -1926,12 +1930,37 @@ def _build_session_list_cache_payload( show_previous_messaging_sessions: bool, show_cron_sessions: bool, include_archived: bool = False, + exclude_hidden: bool = False, + visible_only: bool = False, source_filter: str | None = None, sidebar_source: str | None = None, diag=None, ) -> dict: diag_stage = diag.stage if diag is not None else lambda *_a, **_k: None + def _session_has_server_visible_messages(session: dict) -> bool: + """Return True when a non-active sidebar row has a visibility signal. + + Keep this mirror of the non-active server filter narrow and local to + route behavior so model-layer behavior remains unchanged. + """ + if not isinstance(session, dict): + return False + if _numeric_count(session.get("message_count")) > 0: + return True + + attention = session.get("attention") + if isinstance(attention, dict) and attention.get("kind"): + if _numeric_count(attention.get("count")) > 0: + return True + + return bool( + session.get("is_streaming") + or session.get("active_stream_id") + or session.get("pending_user_message") + or session.get("has_pending_user_message") + ) + def _all_sessions_for_sidebar(): try: return all_sessions(diag=diag, include_lineage_metadata=False) @@ -2090,6 +2119,16 @@ def _build_session_list_cache_payload( diag_stage("cli_cap") archived_scoped = _cap_recent_cli_sessions(archived_scoped, cli_cap=CLI_VISIBLE_SESSION_CAP) visible_scoped = _cap_recent_cli_sessions(visible_scoped, cli_cap=CLI_VISIBLE_SESSION_CAP) + if visible_only: + archived_scoped = [ + s for s in archived_scoped if _session_has_server_visible_messages(s) + ] + visible_scoped = [ + s for s in visible_scoped if _session_has_server_visible_messages(s) + ] + if exclude_hidden: + archived_scoped = [s for s in archived_scoped if not s.get("default_hidden")] + visible_scoped = [s for s in visible_scoped if not s.get("default_hidden")] archived_webui_count = sum( 1 for s in archived_scoped if s.get("archived") and not _is_cli_session_for_settings(s) @@ -9794,6 +9833,7 @@ def handle_get(handler, parsed) -> bool: active_profile = get_active_profile_name() all_profiles = _all_profiles_enabled(parsed) include_archived = _query_flag(parsed, "include_archived") + exclude_hidden = _query_flag(parsed, "exclude_hidden") sidebar_source = parse_qs(parsed.query).get("sidebar_source", [""])[0].strip().lower() or None if sidebar_source not in ("webui", "cli"): sidebar_source = None @@ -9804,6 +9844,8 @@ def handle_get(handler, parsed) -> bool: show_previous_messaging_sessions=show_previous_messaging_sessions, show_cron_sessions=show_cron_sessions, include_archived=include_archived, + exclude_hidden=exclude_hidden, + visible_only=True, source_filter=agent_session_source_filter, sidebar_source=sidebar_source, ) @@ -9820,6 +9862,8 @@ def handle_get(handler, parsed) -> bool: show_previous_messaging_sessions=show_previous_messaging_sessions, show_cron_sessions=show_cron_sessions, include_archived=include_archived, + exclude_hidden=exclude_hidden, + visible_only=True, source_filter=agent_session_source_filter, sidebar_source=sidebar_source, diag=diag, diff --git a/static/sessions.js b/static/sessions.js index f01741bc..969271fc 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -1640,6 +1640,7 @@ function _requestedSessionSidebarSource() { function _sessionListQueryString() { const qs = new URLSearchParams(); qs.set('sidebar_source', _requestedSessionSidebarSource()); + if(_activeProject===null || _activeProject===NO_PROJECT_FILTER) qs.set('exclude_hidden','1'); if(_showAllProfiles) qs.set('all_profiles','1'); if(_showArchived) qs.set('include_archived','1'); return `?${qs.toString()}`; diff --git a/tests/test_issue4766_sidebar_source_pushdown.py b/tests/test_issue4766_sidebar_source_pushdown.py index 46b36c20..e1df9628 100644 --- a/tests/test_issue4766_sidebar_source_pushdown.py +++ b/tests/test_issue4766_sidebar_source_pushdown.py @@ -253,6 +253,7 @@ def test_session_list_query_string_respects_sidebar_source_and_flags(): query_fn = _extract_function(src, "_sessionListQueryString") script = f""" global.window = {{ _showCliSessions: true }}; +global._activeProject = null; global._sessionSourceFilter = 'cli'; global._showAllProfiles = true; global._showArchived = false; @@ -262,12 +263,18 @@ const first = _sessionListQueryString(); window._showCliSessions = false; global._showArchived = true; const second = _sessionListQueryString(); -console.log(JSON.stringify({{ first, second }})); +global._activeProject = '__none__'; +global.NO_PROJECT_FILTER = '__none__'; +global._showAllProfiles = false; +global._showArchived = false; +const third = _sessionListQueryString(); +console.log(JSON.stringify({{ first, second, third }})); """ body = _run_node(script) - assert body["first"] == "?sidebar_source=cli&all_profiles=1" - assert body["second"] == "?sidebar_source=webui&all_profiles=1&include_archived=1" + assert body["first"] == "?sidebar_source=cli&exclude_hidden=1&all_profiles=1" + assert body["second"] == "?sidebar_source=webui&exclude_hidden=1&all_profiles=1&include_archived=1" + assert body["third"] == "?sidebar_source=webui&exclude_hidden=1" @pytest.mark.skipif(NODE is None, reason="node not on PATH") diff --git a/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py b/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py new file mode 100644 index 00000000..4ab97577 --- /dev/null +++ b/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py @@ -0,0 +1,298 @@ +"""Regression coverage for issue #4775: hide zero-message and default_hidden rows for default sidebar loads.""" + +import io +import json +from pathlib import Path +import shutil +import subprocess +from urllib.parse import urlparse + +import api.profiles as profiles +import api.routes as routes +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +SESSIONS_JS = ROOT / "static" / "sessions.js" +NODE = shutil.which("node") + + +class _FakeHandler: + def __init__(self): + self.status = None + self.headers = {} + self.wfile = io.BytesIO() + + def send_response(self, status): + self.status = status + + def send_header(self, key, value): + self.headers[key] = value + + def end_headers(self): + pass + + def json_body(self): + return json.loads(self.wfile.getvalue().decode("utf-8")) + + +def _session_rows(): + return [ + { + "session_id": "visible-with-message", + "title": "Visible message", + "profile": "default", + "archived": False, + "message_count": 3, + "updated_at": 1000, + "last_message_at": 1000, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": False, + }, + { + "session_id": "hidden-by-default", + "title": "Default hidden", + "profile": "default", + "archived": False, + "message_count": 1, + "updated_at": 900, + "last_message_at": 900, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": True, + }, + { + "session_id": "zero-message", + "title": "Plain zero message", + "profile": "default", + "archived": False, + "message_count": 0, + "updated_at": 800, + "last_message_at": 800, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": False, + }, + { + "session_id": "active-stream-row", + "title": "Streaming row", + "profile": "default", + "archived": False, + "message_count": 0, + "active_stream_id": "stream-active", + "updated_at": 700, + "last_message_at": 700, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": False, + }, + { + "session_id": "attention-row", + "title": "Attention row", + "profile": "default", + "archived": False, + "message_count": 0, + "attention": {"kind": "clarify", "count": 1}, + "updated_at": 600, + "last_message_at": 600, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": False, + }, + { + "session_id": "pending-user-row", + "title": "Pending user row", + "profile": "default", + "archived": False, + "message_count": 0, + "pending_user_message": "user typing", + "updated_at": 500, + "last_message_at": 500, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": False, + }, + { + "session_id": "pending-flag-row", + "title": "Pending flag row", + "profile": "default", + "archived": False, + "message_count": 0, + "has_pending_user_message": True, + "updated_at": 550, + "last_message_at": 550, + "source": "webui", + "raw_source": "webui", + "session_source": "webui", + "source_tag": "webui", + "default_hidden": False, + }, + ] + + +def _handle_sessions(url): + handler = _FakeHandler() + routes.handle_get(handler, urlparse(url)) + return handler + + +def _extract_function(source_text, function_name): + marker = f"function {function_name}(" + start = source_text.index(marker) + brace_start = source_text.index("{", start) + depth = 0 + for index in range(brace_start, len(source_text)): + char = source_text[index] + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return source_text[start : index + 1] + raise AssertionError(f"Could not extract {function_name}") + + +def _run_node(script): + proc = subprocess.run([NODE, "-e", script], capture_output=True, text=True, check=True) + return json.loads(proc.stdout) + + +@pytest.fixture(autouse=True) +def _clear_cache(): + routes._session_list_cache_clear() + yield + routes._session_list_cache_clear() + + +def _install_common_monkeypatches(monkeypatch, rows): + session_ids = {str(row["session_id"]) for row in rows if row.get("session_id")} + monkeypatch.setattr(routes, "all_sessions", lambda diag=None: list(rows)) + monkeypatch.setattr(routes, "_reconcile_stale_stream_state_for_session_rows", lambda _rows: False) + monkeypatch.setattr(routes, "_enrich_sidebar_lineage_metadata", lambda _rows: None) + monkeypatch.setattr(routes, "get_cli_sessions", lambda source_filter=None, all_profiles=False: []) + monkeypatch.setattr(routes, "agent_session_rows_existing", lambda ids, profile=None: set(ids) & session_ids) + monkeypatch.setattr(routes, "load_settings", lambda: {"show_cli_sessions": True}) + monkeypatch.setattr(profiles, "get_active_profile_name", lambda: "default") + return rows + + +def test_default_sidebar_excludes_hidden_and_plain_zero_message_rows(monkeypatch): + rows = _session_rows() + _install_common_monkeypatches(monkeypatch, rows) + + handler = _handle_sessions("http://example.com/api/sessions?sidebar_source=webui&exclude_hidden=1") + body = handler.json_body() + + assert handler.status == 200 + assert [r["session_id"] for r in body["sessions"]] == [ + "visible-with-message", + "active-stream-row", + "attention-row", + "pending-flag-row", + "pending-user-row", + ] + + +def test_zero_message_rows_with_visibility_signals_survive(monkeypatch): + rows = _session_rows() + _install_common_monkeypatches(monkeypatch, rows) + + handler = _handle_sessions("http://example.com/api/sessions?sidebar_source=webui&exclude_hidden=1") + body = handler.json_body() + + assert handler.status == 200 + assert [r["session_id"] for r in body["sessions"]] == [ + "visible-with-message", + "active-stream-row", + "attention-row", + "pending-flag-row", + "pending-user-row", + ] + + +def test_omit_exclude_hidden_still_returns_default_hidden_rows(monkeypatch): + rows = _session_rows() + _install_common_monkeypatches(monkeypatch, rows) + + handler = _handle_sessions("http://example.com/api/sessions?sidebar_source=webui") + body = handler.json_body() + + assert handler.status == 200 + session_ids = {row["session_id"] for row in body["sessions"]} + assert "hidden-by-default" in session_ids + + +def test_default_and_unassigned_queries_send_exclude_hidden(monkeypatch): + src = SESSIONS_JS.read_text(encoding="utf-8") + requested_source_fn = _extract_function(src, "_requestedSessionSidebarSource") + query_fn = _extract_function(src, "_sessionListQueryString") + script = f""" +global.window = {{ _showCliSessions: false }}; +global._showCliSessions = false; +global._showAllProfiles = false; +global._showArchived = false; +global._activeProject = null; +global.NO_PROJECT_FILTER = '__none__'; +{requested_source_fn} +{query_fn} +const default_query = _sessionListQueryString(); +global._activeProject = '__none__'; +const unassigned_query = _sessionListQueryString(); +global._activeProject = 'demo-project'; +const named_project_query = _sessionListQueryString(); +console.log(JSON.stringify({{ default_query, unassigned_query, named_project_query }})); +""" + body = _run_node(script) + + assert body["default_query"] == "?sidebar_source=webui&exclude_hidden=1" + assert body["unassigned_query"] == "?sidebar_source=webui&exclude_hidden=1" + assert body["named_project_query"] == "?sidebar_source=webui" + + +def test_cache_key_varies_for_exclude_hidden_and_visible_only(): + key_without_filters = routes._session_list_cache_key( + active_profile="default", + all_profiles=False, + show_cli_sessions=True, + show_previous_messaging_sessions=False, + show_cron_sessions=False, + include_archived=False, + sidebar_source="webui", + ) + key_exclude_hidden = routes._session_list_cache_key( + active_profile="default", + all_profiles=False, + show_cli_sessions=True, + show_previous_messaging_sessions=False, + show_cron_sessions=False, + include_archived=False, + exclude_hidden=True, + sidebar_source="webui", + ) + key_visible_only = routes._session_list_cache_key( + active_profile="default", + all_profiles=False, + show_cli_sessions=True, + show_previous_messaging_sessions=False, + show_cron_sessions=False, + include_archived=False, + visible_only=True, + sidebar_source="webui", + ) + + assert key_without_filters != key_exclude_hidden + assert key_without_filters != key_visible_only + assert key_exclude_hidden != key_visible_only