fix: delete active session returns to dashboard, sidebar/tile delete doesn't navigate

Bug 1: killSession() now checks if _viewingSession === name and calls
closeSession() before pollSessions(). Previously the user was stuck in
the expanded terminal view for a dead session after deleting it.

Bug 2: Sidebar item click handler now ignores clicks on .sidebar-delete
buttons. Tile click handler now ignores clicks on .tile-delete buttons.
Previously stopPropagation() in the document-level delete handler fired
too late (after the sidebar-item/tile handlers had already called
openSession), causing the session to be navigated to before being killed.

Tests added:
- killSession closes active session and returns to dashboard
- sidebar click handler ignores clicks on delete button
- tile click handler ignores clicks on tile-delete button

321 JS tests pass, 760 Python tests pass.
This commit is contained in:
Brian Krabach
2026-04-01 12:48:38 -07:00
parent 98d552557a
commit bdb3f069e5
2 changed files with 39 additions and 2 deletions
+12 -2
View File
@@ -733,7 +733,9 @@ function renderSidebar(sessions, currentSession) {
list.querySelectorAll('.sidebar-item').forEach((item) => {
const name = item.dataset.session;
const sourceUrl = item.dataset.sourceUrl || '';
on(item, 'click', () => {
on(item, 'click', (e) => {
// Don't navigate when clicking the delete button inside the item
if (e.target.closest && e.target.closest('.sidebar-delete')) return;
if (name !== currentSession) openSession(name, { sourceUrl });
});
});
@@ -992,7 +994,11 @@ function renderGrid(sessions) {
// Bind interaction handlers on each tile
document.querySelectorAll('.session-tile').forEach(function(tile) {
on(tile, 'click', () => openSession(tile.dataset.session, { sourceUrl: tile.dataset.sourceUrl || '' }));
on(tile, 'click', (e) => {
// Don't navigate when clicking the delete button inside the tile
if (e.target.closest && e.target.closest('.tile-delete')) return;
openSession(tile.dataset.session, { sourceUrl: tile.dataset.sourceUrl || '' });
});
on(tile, 'keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
openSession(tile.dataset.session, { sourceUrl: tile.dataset.sourceUrl || '' });
@@ -2237,6 +2243,10 @@ function killSession(name) {
api('DELETE', '/api/sessions/' + name)
.then(function() {
showToast('Session \'' + name + '\' killed');
// If we deleted the session we're currently viewing, return to dashboard
if (_viewingSession === name) {
closeSession();
}
pollSessions();
})
.catch(function(err) {
+27
View File
@@ -4406,3 +4406,30 @@ test('buildSidebarHTML trim happens BEFORE slice in source (structural order che
'trailing blank trim (.pop()) must appear BEFORE .slice() in buildSidebarHTML — trim the full snapshot first, then slice the last 20 lines',
);
});
// --- Bug fixes: delete UX ---
test('killSession closes active session and returns to dashboard', () => {
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
// Find killSession function body
const fnStart = source.indexOf('function killSession');
const fnBody = source.substring(fnStart, fnStart + 500);
assert.ok(fnBody.includes('_viewingSession'), 'killSession must check if deleted session is the active one');
assert.ok(fnBody.includes('closeSession'), 'killSession must call closeSession when deleting the active session');
});
test('sidebar click handler ignores clicks on delete button', () => {
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
assert.ok(
source.includes("closest('.sidebar-delete')"),
"sidebar click handler must guard against clicks on .sidebar-delete button"
);
});
test('tile click handler ignores clicks on tile-delete button', () => {
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
assert.ok(
source.includes("closest('.tile-delete')"),
"tile click handler must guard against clicks on .tile-delete button"
);
});