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;
cursor: pointer;
}
.agent-menu-item:not(:disabled):hover {
.agent-menu-item:hover {
background: var(--surface1);
}
.agent-menu-item:disabled {
opacity: 0.35;
cursor: default;
}
.agent-menu-sep {
height: 1px;
background: var(--surface2);

View file

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