Repaint sidebar after session archive or delete

This commit is contained in:
Frank Song
2026-05-25 16:31:15 +08:00
parent 4ea762ae0d
commit cfca26f2e8
3 changed files with 54 additions and 3 deletions
+4
View File
@@ -3,6 +3,10 @@
## [Unreleased]
### Fixed
- Session sidebar Archive/Delete menu actions now repaint from local sidebar state immediately after the server confirms the mutation, instead of waiting for the full `/api/sessions` refresh before the row disappears.
## [v0.51.134] — 2026-05-25 — Release DF (stage-batch16 — single-PR Windows path defaults)
### Fixed
+24 -3
View File
@@ -1544,6 +1544,24 @@ function _sessionArchiveToast(response, session){
function _sessionDeleteDescription(session){
return session&&session.worktree_path?t('session_delete_worktree_desc'):t('session_delete_desc');
}
function _optimisticallyArchiveSessionInList(sid, archived){
if(!sid||!Array.isArray(_allSessions)) return;
let changed=false;
_allSessions=_allSessions.map(s=>{
if(!s||s.session_id!==sid) return s;
changed=true;
return {...s,archived:!!archived};
});
if(changed) renderSessionListFromCache();
}
function _optimisticallyRemoveSessionFromList(sid){
if(!sid||!Array.isArray(_allSessions)) return;
const before=_allSessions.length;
_allSessions=_allSessions.filter(s=>!s||s.session_id!==sid);
if(_selectedSessions&&_selectedSessions.has(sid)) _selectedSessions.delete(sid);
if(typeof _dropStaleOptimisticSessionRow==='function') _dropStaleOptimisticSessionRow(sid);
if(_allSessions.length!==before) renderSessionListFromCache();
}
function _sessionIdFromLocation(){
if(typeof window==='undefined'||!window.location) return null;
@@ -1866,9 +1884,10 @@ function _openSessionActionMenu(session, anchorEl){
closeSessionActionMenu();
try{
const response=await api('/api/session/archive',{method:'POST',body:JSON.stringify({session_id:session.session_id,archived:!session.archived})});
_optimisticallyArchiveSessionInList(session.session_id,!session.archived);
session.archived=!session.archived;
if(S.session&&S.session.session_id===session.session_id) S.session.archived=session.archived;
await renderSessionList();
void renderSessionList();
showToast(session.archived?_sessionArchiveToast(response,session):t('session_restored'));
}catch(err){showToast(t('session_archive_failed')+err.message);}
}
@@ -1882,9 +1901,10 @@ function _openSessionActionMenu(session, anchorEl){
closeSessionActionMenu();
try{
await api('/api/session/archive',{method:'POST',body:JSON.stringify({session_id:session.session_id,archived:true})});
_optimisticallyArchiveSessionInList(session.session_id,true);
session.archived=true;
if(S.session&&S.session.session_id===session.session_id) S.session.archived=true;
await renderSessionList();
void renderSessionList();
showToast(t('session_hidden'));
}catch(err){showToast(t('session_archive_failed')+err.message);}
}
@@ -3874,6 +3894,7 @@ async function deleteSession(sid){
let response=null;
try{
response=await api('/api/session/delete',{method:'POST',body:JSON.stringify({session_id:sid})});
_optimisticallyRemoveSessionFromList(sid);
_clearHandoffStorageForSession(sid);
}catch(e){setStatus(`Delete failed: ${e.message}`);return;}
if(S.session&&S.session.session_id===sid){
@@ -3894,7 +3915,7 @@ async function deleteSession(sid){
}
}
showToast(_sessionResponseRetainsWorktree(response,session)?t('session_deleted_worktree'):t('session_deleted'));
await renderSessionList();
void renderSessionList();
}
// ── Project helpers ─────────────────────────────────────────────────────
@@ -29,3 +29,29 @@ def test_session_list_refresh_does_not_close_open_conversation_actions():
assert "if(_renamingSid) return;" in body
assert "if(_sessionActionMenu) return;" in body
assert body.index("if(_sessionActionMenu) return;") < body.index("closeSessionActionMenu();")
def test_archive_action_repaints_sidebar_before_full_refresh():
"""Archive should hide the row from cached sidebar state before /api/sessions returns."""
body = _function_block(SESSIONS_JS, "_openSessionActionMenu")
api_call = "const response=await api('/api/session/archive'"
optimistic = "_optimisticallyArchiveSessionInList(session.session_id,!session.archived);"
full_refresh = "void renderSessionList();"
assert optimistic in body
assert body.index(api_call) < body.index(optimistic) < body.index(full_refresh)
def test_delete_action_repaints_sidebar_before_loading_remaining_sessions():
"""Delete should remove the row locally before loading replacement session data."""
body = _function_block(SESSIONS_JS, "deleteSession")
api_call = "response=await api('/api/session/delete'"
optimistic = "_optimisticallyRemoveSessionFromList(sid);"
remaining_fetch = "const remaining=await api('/api/sessions');"
full_refresh = "void renderSessionList();"
assert optimistic in body
assert body.index(api_call) < body.index(optimistic) < body.index(full_refresh)
assert body.index(optimistic) < body.index(remaining_fetch)