mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-30 08:32:07 -04:00
fix(ui): route transient dropdown menus through escMenuStack to stop listener leaks (#4684)
The app's ad-hoc dropdown/context menus each wire their own document-level outside-click listener, but that listener only removes itself on an *outside* click. Every other dismissal path -- clicking a menu item (which calls el.remove() directly), a Cancel button, Escape, or the "close the previously-open menu" reopen sweep -- tears the node down without unregistering the listener, orphaning it on `document`. The stranded listener then lingers and can break the next menu interaction: the recurring "the button stops working until I refresh the page" class of bug (e.g. delete an email, then the kebab/more button is dead on the other rows). Route all 16 of these menus through the existing escMenuStack helper (bindMenuDismiss / dismissOrRemove), exactly as documentLibrary.js _showLibDropdown, cookbookRunning.js, and research/panel.js already do: a single idempotent close() owns the teardown and is released on every dismissal path, reopen sweeps use dismissOrRemove() instead of a bare .remove(), and Escape flows through the central LIFO esc-stack arbiter. Net -49 lines. Menus migrated: cookbook _showDepMenu; document export menu and _openDocAiReplyChoice; emailInbox _showEmailMenu; emailLibrary _showReaderMoreMenu / _showCardMenu / _showBulkActionsMenu; gallery _showGalleryBulkMenu; notes _pickCustomDate / _openNoteCornerMenu; settings (3 unified-integrations dropdowns); skills _openSkillMenu; tasks _showTaskDropdown; compare _toggleExportMenu. Per-menu semantics preserved (anchor-as-inside tests, the tasks 250ms ghost-click guard, emailLibrary's reader-more-active anchor class and the bulk-Cancel select-mode reset, settings' reused-vs-recreated lifecycles). Six menus with custom lifecycles (notes _openReminderMenu, sessions long-press, document markdown-toolbar, emojiPicker, compare model selector) are intentionally left for a follow-up -- each needs individual review. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+7
-10
@@ -33,6 +33,8 @@ import {
|
||||
_fetchCachedModels, _cachedAllModels, _filterCachedList, _rerenderCachedModels, _deleteCachedModel,
|
||||
} from './cookbookServe.js';
|
||||
|
||||
import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||
|
||||
const STORAGE_KEY = 'cookbook-presets';
|
||||
const LAST_STATE_KEY = 'cookbook-last-state';
|
||||
const SERVE_STATE_KEY = 'cookbook-serve-state';
|
||||
@@ -1514,7 +1516,7 @@ async function _fetchDependencies() {
|
||||
|
||||
// Wire the installed-package menu.
|
||||
function _showDepMenu(anchor) {
|
||||
document.querySelectorAll('.cookbook-dep-menu').forEach(d => d.remove());
|
||||
document.querySelectorAll('.cookbook-dep-menu').forEach(dismissOrRemove);
|
||||
const row = anchor.closest('.cookbook-dep-row');
|
||||
if (!row) return;
|
||||
const pipName = row.dataset.depPip;
|
||||
@@ -1535,7 +1537,7 @@ async function _fetchDependencies() {
|
||||
it.title = `Update ${pkgName} to the latest version (pip install -U)`;
|
||||
it.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
dropdown.remove();
|
||||
close();
|
||||
await _installDep(pipName, pkgName, isLocalOnly, true, null);
|
||||
});
|
||||
dropdown.appendChild(it);
|
||||
@@ -1563,19 +1565,14 @@ async function _fetchDependencies() {
|
||||
dropdown.appendChild(source);
|
||||
}
|
||||
document.body.appendChild(dropdown);
|
||||
const close = (ev) => {
|
||||
if (!dropdown.contains(ev.target) && ev.target !== anchor && !anchor.contains(ev.target)) {
|
||||
dropdown.remove();
|
||||
document.removeEventListener('click', close, true);
|
||||
}
|
||||
};
|
||||
setTimeout(() => document.addEventListener('click', close, true), 10);
|
||||
const close = bindMenuDismiss(dropdown, () => { dropdown.remove(); }, (ev) =>
|
||||
!dropdown.contains(ev.target) && ev.target !== anchor && !anchor.contains(ev.target));
|
||||
}
|
||||
list.querySelectorAll('.cookbook-dep-installed-btn').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (document.querySelector('.cookbook-dep-menu')) {
|
||||
document.querySelectorAll('.cookbook-dep-menu').forEach(d => d.remove());
|
||||
document.querySelectorAll('.cookbook-dep-menu').forEach(dismissOrRemove);
|
||||
return;
|
||||
}
|
||||
_showDepMenu(btn);
|
||||
|
||||
Reference in New Issue
Block a user