Fix stale deleted sessions in sidebar (#1203)
Co-authored-by: ghreprimand <203024559+ghreprimand@users.noreply.github.com>
This commit is contained in:
@@ -3130,10 +3130,7 @@ function initializeEventListeners() {
|
|||||||
const idx = sessions.findIndex(s => s.id === currentId);
|
const idx = sessions.findIndex(s => s.id === currentId);
|
||||||
const nextSession = sessions.filter(s => !s.archived && s.id !== currentId)[Math.max(0, idx)] ||
|
const nextSession = sessions.filter(s => !s.archived && s.id !== currentId)[Math.max(0, idx)] ||
|
||||||
sessions.find(s => !s.archived && s.id !== currentId);
|
sessions.find(s => !s.archived && s.id !== currentId);
|
||||||
const res = await fetch(`${API_BASE}/api/session/${currentId}/archive`, {
|
const res = await fetch(`${API_BASE}/api/session/${currentId}`, { method: 'DELETE' });
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
});
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
await sessionModule.loadSessions();
|
await sessionModule.loadSessions();
|
||||||
if (nextSession) {
|
if (nextSession) {
|
||||||
|
|||||||
@@ -78,6 +78,42 @@ function _deselectCurrentSession(sid) {
|
|||||||
if (window._updateSendBtnIcon) window._updateSendBtnIcon();
|
if (window._updateSendBtnIcon) window._updateSendBtnIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _removeSessionFromLocalState(sid) {
|
||||||
|
if (!sid) return;
|
||||||
|
const id = String(sid);
|
||||||
|
sessions = sessions.filter(s => String(s.id) !== id);
|
||||||
|
_selectedIds.delete(id);
|
||||||
|
try {
|
||||||
|
const savedOrder = Storage.get('session-order');
|
||||||
|
if (savedOrder) {
|
||||||
|
const orderIds = JSON.parse(savedOrder);
|
||||||
|
if (Array.isArray(orderIds) && orderIds.some(x => String(x) === id)) {
|
||||||
|
Storage.set('session-order', JSON.stringify(orderIds.filter(x => String(x) !== id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to prune deleted session order:', e);
|
||||||
|
}
|
||||||
|
document.querySelectorAll('.list-item[data-session-id]').forEach(el => {
|
||||||
|
if (String(el.dataset.sessionId) === id) el.remove();
|
||||||
|
});
|
||||||
|
_deselectCurrentSession(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _normalizeSessionsList(fetched) {
|
||||||
|
if (!Array.isArray(fetched)) return [];
|
||||||
|
const seen = new Set();
|
||||||
|
const unique = [];
|
||||||
|
for (const session of fetched) {
|
||||||
|
if (!session || session.id == null) continue;
|
||||||
|
const id = String(session.id);
|
||||||
|
if (seen.has(id)) continue;
|
||||||
|
seen.add(id);
|
||||||
|
unique.push(session);
|
||||||
|
}
|
||||||
|
return unique;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize dependencies from app.js (no-op: dependencies now imported directly)
|
// Initialize dependencies from app.js (no-op: dependencies now imported directly)
|
||||||
export function initDependencies() {}
|
export function initDependencies() {}
|
||||||
|
|
||||||
@@ -620,15 +656,13 @@ function createSessionItem(s) {
|
|||||||
_forceSidebarOpen();
|
_forceSidebarOpen();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Optimistic: remove from UI immediately
|
|
||||||
const sessionEl = document.querySelector(`.list-item[data-session-id="${s.id}"]`);
|
|
||||||
if (sessionEl) sessionEl.remove();
|
|
||||||
const wasCurrentSession = currentSessionId === s.id;
|
const wasCurrentSession = currentSessionId === s.id;
|
||||||
// If streaming, abort it before deleting
|
// If streaming, abort it before deleting
|
||||||
if (wasCurrentSession && window.chatModule && window.chatModule.abortCurrentRequest) {
|
if (wasCurrentSession && window.chatModule && window.chatModule.abortCurrentRequest) {
|
||||||
window.chatModule.abortCurrentRequest();
|
window.chatModule.abortCurrentRequest();
|
||||||
}
|
}
|
||||||
_deselectCurrentSession(s.id);
|
_deselectCurrentSession(s.id);
|
||||||
|
_removeSessionFromLocalState(s.id);
|
||||||
_skipAutoSelect = true;
|
_skipAutoSelect = true;
|
||||||
// Clean up persistent chat mapping
|
// Clean up persistent chat mapping
|
||||||
try {
|
try {
|
||||||
@@ -1321,7 +1355,7 @@ export async function loadSessions() {
|
|||||||
const res = await fetch(`${API_BASE}/api/sessions`);
|
const res = await fetch(`${API_BASE}/api/sessions`);
|
||||||
fetched = await res.json();
|
fetched = await res.json();
|
||||||
}
|
}
|
||||||
sessions = fetched;
|
sessions = _normalizeSessionsList(fetched);
|
||||||
renderSessionList();
|
renderSessionList();
|
||||||
|
|
||||||
const sessionsSection = uiModule.el('sessions-section');
|
const sessionsSection = uiModule.el('sessions-section');
|
||||||
|
|||||||
31
tests/test_deleted_session_sidebar_regression.py
Normal file
31
tests/test_deleted_session_sidebar_regression.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
APP_JS = Path("static/app.js")
|
||||||
|
SESSIONS_JS = Path("static/js/sessions.js")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rail_delete_uses_hard_delete_endpoint():
|
||||||
|
source = APP_JS.read_text()
|
||||||
|
rail_block = source[source.index("const railDelete = el('rail-delete-session');"):]
|
||||||
|
rail_block = rail_block[:rail_block.index("// Textarea auto-resize")]
|
||||||
|
|
||||||
|
assert "fetch(`${API_BASE}/api/session/${currentId}`, { method: 'DELETE' })" in rail_block
|
||||||
|
assert "api/session/${currentId}/archive" not in rail_block
|
||||||
|
|
||||||
|
|
||||||
|
def test_deleted_sessions_are_pruned_from_local_sidebar_state():
|
||||||
|
source = SESSIONS_JS.read_text()
|
||||||
|
|
||||||
|
assert "function _removeSessionFromLocalState(sid)" in source
|
||||||
|
assert "sessions = sessions.filter(s => String(s.id) !== id);" in source
|
||||||
|
assert "Storage.set('session-order', JSON.stringify(orderIds.filter(x => String(x) !== id)))" in source
|
||||||
|
assert "_removeSessionFromLocalState(s.id);" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_fetch_normalizes_duplicate_ids_before_render():
|
||||||
|
source = SESSIONS_JS.read_text()
|
||||||
|
|
||||||
|
assert "function _normalizeSessionsList(fetched)" in source
|
||||||
|
assert "if (seen.has(id)) continue;" in source
|
||||||
|
assert "sessions = _normalizeSessionsList(fetched);" in source
|
||||||
Reference in New Issue
Block a user