fix(sidebar): refresh project-chip payload after filter switch (#4775)

This commit is contained in:
Rod Boev 2026-06-23 10:40:40 -04:00
parent e27b7cf042
commit df6dba7a94
2 changed files with 51 additions and 5 deletions

View file

@ -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);};

View file

@ -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",