fix(988): hide inapplicable actions in agent context menu

Per mara feedback: don't show disabled actions at all.
- restart/stop only rendered when agent is running
- start only rendered when agent is stopped
- removed .agent-menu-item:disabled CSS rule (no longer needed)

Also switch Escape handler to stopImmediatePropagation so the
selection-clear keydown listener doesn't co-fire when closing a menu.
This commit is contained in:
iris 2026-06-01 19:01:56 +02:00 committed by mara
commit ba43b5f869
2 changed files with 23 additions and 42 deletions

View file

@ -384,13 +384,9 @@ a:hover {
padding: 0.4em 0.9em; padding: 0.4em 0.9em;
cursor: pointer; cursor: pointer;
} }
.agent-menu-item:not(:disabled):hover { .agent-menu-item:hover {
background: var(--surface1); background: var(--surface1);
} }
.agent-menu-item:disabled {
opacity: 0.35;
cursor: default;
}
.agent-menu-sep { .agent-menu-sep {
height: 1px; height: 1px;
background: var(--surface2); background: var(--surface2);

View file

@ -328,8 +328,8 @@ window.marked = marked;
// ─── per-agent context menu ────────────────────────────────────────── // ─── per-agent context menu ──────────────────────────────────────────
// Three-dot (⋮) button on each agent card for quick single-agent // Three-dot (⋮) button on each agent card for quick single-agent
// lifecycle actions without needing to select first. State-aware: // lifecycle actions without needing to select first. State-aware:
// restart/stop disabled when agent is stopped, start disabled when // restart/stop only shown when running, start only shown when stopped,
// running, destroy/purge hidden for the manager. // destroy/purge hidden for the manager.
// The button is CSS-invisible until the row is hovered (or menu is // The button is CSS-invisible until the row is hovered (or menu is
// open) so it doesn't clutter quiet rows. // open) so it doesn't clutter quiet rows.
@ -351,12 +351,12 @@ window.marked = marked;
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (!e.target.closest('.agent-menu')) closeAllAgentMenus(); if (!e.target.closest('.agent-menu')) closeAllAgentMenus();
}, true); }, true);
// Close on Escape (shares the keydown listener below with selection-clear; // Close on Escape. stopImmediatePropagation so the selection-clear
// close happens first since no selection change is needed here). // handler on the same element doesn't also fire when a menu is open.
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && openAgentMenu) { if (e.key === 'Escape' && openAgentMenu) {
closeAllAgentMenus(); closeAllAgentMenus();
e.stopPropagation(); e.stopImmediatePropagation();
} }
}, true); }, true);
@ -401,16 +401,11 @@ window.marked = marked;
class: 'agent-menu-item', class: 'agent-menu-item',
role: 'menuitem', role: 'menuitem',
}, label); }, label);
if (opts.disabled) { item.addEventListener('click', async () => {
item.disabled = true; closeAllAgentMenus();
if (opts.title) item.title = opts.title; if (opts.confirm && !confirm(opts.confirm)) return;
} else { await agentMenuPost(opts.action, c.name, opts.body || null);
item.addEventListener('click', async () => { });
closeAllAgentMenus();
if (opts.confirm && !confirm(opts.confirm)) return;
await agentMenuPost(opts.action, c.name, opts.body || null);
});
}
li.append(item); li.append(item);
return li; return li;
} }
@ -419,29 +414,19 @@ window.marked = marked;
return el('li', { class: 'agent-menu-sep', role: 'separator' }); return el('li', { class: 'agent-menu-sep', role: 'separator' });
} }
// Show only actions that are applicable in the current state.
if (c.running) {
dropdown.append(
menuItem('↺ R3ST4RT', { action: '/restart/', confirm: `restart ${c.name}?` }),
menuItem('■ ST0P', { action: '/kill/', confirm: `stop ${c.name}?` }),
);
} else {
dropdown.append(
menuItem('▶ ST4RT', { action: '/start/', confirm: `start ${c.name}?` }),
);
}
dropdown.append( dropdown.append(
menuItem('↺ R3ST4RT', { menuItem('↻ R3BU1LD', { action: '/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }),
action: '/restart/',
disabled: !c.running,
title: 'agent is stopped',
confirm: `restart ${c.name}?`,
}),
menuItem('■ ST0P', {
action: '/kill/',
disabled: !c.running,
title: 'agent is already stopped',
confirm: `stop ${c.name}?`,
}),
menuItem('▶ ST4RT', {
action: '/start/',
disabled: c.running,
title: 'agent is already running',
confirm: `start ${c.name}?`,
}),
menuItem('↻ R3BU1LD', {
action: '/rebuild/',
confirm: `rebuild ${c.name}? hot-reloads the container.`,
}),
); );
if (!c.is_manager) { if (!c.is_manager) {