feat: add inline kill confirmation in flyout, remove old confirm() dialog

This commit is contained in:
Brian Krabach
2026-04-15 21:07:08 -07:00
parent 51770b961e
commit e18f08a320
2 changed files with 105 additions and 1 deletions
+79 -1
View File
@@ -1890,6 +1890,85 @@ function _doRemoveFromView() {
});
}
/**
* Show inline kill confirmation inside the flyout menu.
* Replaces the "Kill Session" item with "Kill? [Yes] [No]".
* No timeout — stays until click-outside closes the menu.
* On error: "Failed" for 2 seconds then reverts.
* @param {HTMLElement} killItem - The "Kill Session" menu item element
*/
function _doKillSessionInline(killItem) {
var sessionName = _flyoutSessionName;
var remoteId = _flyoutRemoteId;
// Replace the kill item with confirmation UI
var confirmHtml =
'<div class="flyout-menu__confirm">' +
'<span>Kill?</span>' +
'<button class="flyout-menu__confirm-btn flyout-menu__confirm-btn--yes" data-action="confirm-kill">Yes</button>' +
'<button class="flyout-menu__confirm-btn" data-action="cancel-kill">No</button>' +
'</div>';
killItem.outerHTML = confirmHtml;
// Re-attach handlers on the confirm/cancel buttons
if (!_flyoutMenuEl) return;
var confirmBtn = _flyoutMenuEl.querySelector('[data-action="confirm-kill"]');
var cancelBtn = _flyoutMenuEl.querySelector('[data-action="cancel-kill"]');
if (confirmBtn) {
confirmBtn.addEventListener('click', function(e) {
e.stopPropagation();
_executeKill(sessionName, remoteId);
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeFlyoutMenu();
});
}
}
/**
* Execute the kill session API call from the flyout inline confirmation.
* On success: closes flyout, shows toast, refreshes sessions.
* On error: shows "Failed" for 2s in the confirm area, then reverts.
* @param {string} name
* @param {string} remoteId
*/
function _executeKill(name, remoteId) {
var endpoint = remoteId
? '/api/federation/' + encodeURIComponent(remoteId) + '/sessions/' + encodeURIComponent(name)
: '/api/sessions/' + encodeURIComponent(name);
api('DELETE', endpoint)
.then(function() {
closeFlyoutMenu();
showToast('Session \'' + name + '\' killed');
if (_viewingSession === name) {
closeSession();
}
pollSessions();
})
.catch(function(err) {
// Show "Failed" for 2 seconds
var confirmDiv = _flyoutMenuEl && _flyoutMenuEl.querySelector('.flyout-menu__confirm');
if (confirmDiv) {
confirmDiv.innerHTML = '<span style="color:var(--err)">Failed</span>';
setTimeout(function() {
// Revert to original kill button if menu is still open
if (_flyoutMenuEl && confirmDiv.parentNode) {
confirmDiv.outerHTML =
'<button class="flyout-menu__item flyout-menu__item--danger" role="menuitem" data-action="kill">Kill Session</button>';
}
}, 2000);
}
});
}
// ─── Notification permission ────────────────────────────────────────────────
/**
@@ -3190,7 +3269,6 @@ async function createNewSession(name, remoteId) {
* @param {string} [remoteId] - Remote instance index (empty or absent for local).
*/
function killSession(name, remoteId) {
if (!confirm('Kill session "' + name + '"?')) return;
var endpoint = remoteId
? '/api/federation/' + encodeURIComponent(remoteId) + '/sessions/' + encodeURIComponent(name)
: '/api/sessions/' + encodeURIComponent(name);
+26
View File
@@ -3856,3 +3856,29 @@ def test_hide_session_removes_from_all_views() -> None:
assert "views" in fn_body, (
"_doHideSession must remove session from all views (mutual exclusion)"
)
# ─── Task 7: Kill session inline confirmation ─────────────────────────────────
def test_do_kill_session_inline_function_exists() -> None:
"""app.js must define a _doKillSessionInline function."""
assert "function _doKillSessionInline" in _JS, (
"app.js must contain a _doKillSessionInline function"
)
def test_kill_session_no_confirm_dialog() -> None:
"""killSession must NOT use window.confirm() (replaced by inline confirmation)."""
fn_body = _JS.split("function killSession")[1].split("\nfunction ")[0]
assert "confirm(" not in fn_body, (
"killSession must not use confirm() — replaced by inline flyout confirmation"
)
def test_do_kill_inline_shows_confirmation_buttons() -> None:
"""_doKillSessionInline must render Yes/No confirmation buttons."""
fn_body = _JS.split("function _doKillSessionInline")[1].split("\nfunction ")[0]
assert "Yes" in fn_body and "No" in fn_body, (
"_doKillSessionInline must show 'Kill? [Yes] [No]' inline"
)