From df6dba7a94e8145674dafbb9f52b19aeafaa2d42 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Tue, 23 Jun 2026 10:40:40 -0400 Subject: [PATCH] fix(sidebar): refresh project-chip payload after filter switch (#4775) --- static/sessions.js | 14 +++++-- ...75_sidebar_hidden_zero_message_pushdown.py | 42 ++++++++++++++++++- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/static/sessions.js b/static/sessions.js index 969271fc..fe0fa44d 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -1652,6 +1652,14 @@ function _sessionSourceTabCount(filter, renderedWebuiSessionCount, renderedCliSe return filter === 'cli' ? renderedCliSessionCount : renderedWebuiSessionCount; } +function _setActiveProjectFilter(projectId) { + const next = projectId === NO_PROJECT_FILTER ? NO_PROJECT_FILTER : (projectId || null); + if (_activeProject === next) return; + _activeProject = next; + renderSessionListFromCache(); + void renderSessionList({deferWhileInteracting:false}); +} + function _setSessionSourceFilter(filter) { const next = filter === 'cli' ? 'cli' : 'webui'; if (_sessionSourceFilter === next) return; @@ -5740,7 +5748,7 @@ function renderSessionListFromCache(){ const allChip=document.createElement('span'); allChip.className='project-chip'+(!_activeProject?' active':''); allChip.textContent='All'; - allChip.onclick=()=>{_activeProject=null;renderSessionListFromCache();}; + allChip.onclick=()=>{_setActiveProjectFilter(null);}; bar.appendChild(allChip); // "Unassigned" chip — only when there are sessions with no project to // filter to. Hidden in the common case where every session is already @@ -5750,7 +5758,7 @@ function renderSessionListFromCache(){ noneChip.className='project-chip no-project'+(_activeProject===NO_PROJECT_FILTER?' active':''); noneChip.textContent='Unassigned'; noneChip.title='Show conversations not yet assigned to a project'; - noneChip.onclick=()=>{_activeProject=NO_PROJECT_FILTER;renderSessionListFromCache();}; + noneChip.onclick=()=>{_setActiveProjectFilter(NO_PROJECT_FILTER);}; bar.appendChild(noneChip); } // Project chips @@ -5769,7 +5777,7 @@ function renderSessionListFromCache(){ let _pClickTimer=null; chip.onclick=(e)=>{ clearTimeout(_pClickTimer); - _pClickTimer=setTimeout(()=>{_pClickTimer=null;_activeProject=p.project_id;renderSessionListFromCache();},220); + _pClickTimer=setTimeout(()=>{_pClickTimer=null;_setActiveProjectFilter(p.project_id);},220); }; chip.ondblclick=(e)=>{e.stopPropagation();clearTimeout(_pClickTimer);_pClickTimer=null;_startProjectRename(p,chip);}; chip.oncontextmenu=(e)=>{e.preventDefault();_showProjectContextMenu(e,p,chip);}; diff --git a/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py b/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py index 4ab97577..d78d27fb 100644 --- a/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py +++ b/tests/test_issue4775_sidebar_hidden_zero_message_pushdown.py @@ -238,6 +238,7 @@ def test_omit_exclude_hidden_still_returns_default_hidden_rows(monkeypatch): 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") + project_filter_fn = _extract_function(src, "_setActiveProjectFilter") query_fn = _extract_function(src, "_sessionListQueryString") script = f""" global.window = {{ _showCliSessions: false }}; @@ -246,12 +247,15 @@ global._showAllProfiles = false; global._showArchived = false; global._activeProject = null; global.NO_PROJECT_FILTER = '__none__'; +global.renderSessionListFromCache = () => {{}}; +global.renderSessionList = () => Promise.resolve(); {requested_source_fn} +{project_filter_fn} {query_fn} const default_query = _sessionListQueryString(); -global._activeProject = '__none__'; +_setActiveProjectFilter('__none__'); const unassigned_query = _sessionListQueryString(); -global._activeProject = 'demo-project'; +_setActiveProjectFilter('demo-project'); const named_project_query = _sessionListQueryString(); console.log(JSON.stringify({{ default_query, unassigned_query, named_project_query }})); """ @@ -262,6 +266,40 @@ console.log(JSON.stringify({{ default_query, unassigned_query, named_project_que assert body["named_project_query"] == "?sidebar_source=webui" +@pytest.mark.skipif(NODE is None, reason="node not on PATH") +def test_project_filter_click_path_triggers_fresh_session_load(): + src = SESSIONS_JS.read_text(encoding="utf-8") + project_filter_fn = _extract_function(src, "_setActiveProjectFilter") + script = f""" +const calls = []; +global.NO_PROJECT_FILTER = '__none__'; +global._activeProject = null; +global.renderSessionListFromCache = () => {{ + calls.push('cache'); +}}; +global.renderSessionList = (opts) => {{ + calls.push(opts); + return Promise.resolve(); +}}; +{project_filter_fn} +_setActiveProjectFilter('demo-project'); +_setActiveProjectFilter('__none__'); +console.log(JSON.stringify({{ + activeProject: global._activeProject, + calls, +}})); +""" + body = _run_node(script) + + assert body["activeProject"] == "__none__" + assert body["calls"] == [ + "cache", + {"deferWhileInteracting": False}, + "cache", + {"deferWhileInteracting": False}, + ] + + def test_cache_key_varies_for_exclude_hidden_and_visible_only(): key_without_filters = routes._session_list_cache_key( active_profile="default",