hive dashboard: bulk pause + resume in the selection bar

Mechanical addition to the existing bulk-action framework
(renderSelectionBar/addBulkButton, swarm.js) -- two per-agent actions
already exist (POST /api/pause/, /api/resume/, see hive-agent-menu.js),
this just gives the selection bar the same all-or-nothing enablement
rule the other six bulk buttons already use (pause enabled only when
none of the selection is already paused, and vice versa for resume).

.btn-pause reuses .badge-paused's yellow so the trigger and the
resulting state pill read as one colour; .btn-resume is green like
.btn-start (both are "go" actions). Docs updated to list both in the
Selection bar reference.
This commit is contained in:
iris 2026-08-24 11:37:04 +02:00
commit 4af890f01d
3 changed files with 36 additions and 7 deletions

View file

@ -1070,13 +1070,13 @@ all no-ops and the layout reads like a plain flat list.
## Selection bar
Bulk actions (`R3ST4RT` / `ST0P` / `ST4RT` / `R3BU1LD` / `DESTR0Y` /
`PURG3`) live here rather than as per-card buttons — see **Container
row** above. Clicking an agent's icon toggles its selection (an
in-memory `Set<name>`); `Esc` or the bar's `✕ clear` button drops
everything. The selection persists across tab switches in-memory —
the bar just hides on non-SW4RM tabs since other tabs don't show the
agent cards needed to cross-reference.
Bulk actions (`R3ST4RT` / `ST0P` / `ST4RT` / `P4US3` / `R3SUM3` /
`R3BU1LD` / `DESTR0Y` / `PURG3`) live here rather than as per-card
buttons — see **Container row** above. Clicking an agent's icon
toggles its selection (an in-memory `Set<name>`); `Esc` or the bar's
`✕ clear` button drops everything. The selection persists across tab
switches in-memory — the bar just hides on non-SW4RM tabs since other
tabs don't show the agent cards needed to cross-reference.
When one or more agents are selected (via icon click), a sticky
frosted-mauve bar slides up from the bottom of the viewport
@ -1089,6 +1089,9 @@ frosted-mauve bar slides up from the bottom of the viewport
- `↺ R3ST4RT` — running agents only
- `■ ST0P` — running agents only
- `▶ ST4RT` — stopped agents only
- `⏸ P4US3` — unpaused agents only (orthogonal to running/stopped —
see **Container row**'s own pause/resume note)
- `▶ R3SUM3` — paused agents only
- `↻ R3BU1LD` — always available
- `DESTR0Y` / `PURG3` — always available
- `⇡ M0V3 → ROOT` — promote selected agents to top-level

View file

@ -152,6 +152,12 @@ code {
.btn-restart { color: var(--cyan); border-color: var(--cyan); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-stop { color: var(--pink); border-color: var(--pink); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-start { color: var(--green); border-color: var(--green); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
/* Same yellow as .badge-paused (the paused-state pill) pause enters
that state, so the trigger and the resulting badge read as one
colour, not two unrelated ones. Resume reads as green like
.btn-start: both are "go" actions, back to the turn loop running. */
.btn-pause { color: var(--yellow); border-color: var(--yellow); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
.btn-resume { color: var(--green); border-color: var(--green); font-size: 0.75em; padding: 0.15em 0.5em; margin-left: 0.6em; }
/* M0V3 affordance (selection bar) mauve reads as "structural
change" rather than the destructive red / amber chrome of
destroy / rebuild. See docs/web-ui.md::Selection bar. */

View file

@ -783,9 +783,19 @@ export function renderSelectionBar(containers) {
actions.replaceChildren();
const allRunning = selected.every((c) => c.running);
const allStopped = selected.every((c) => !c.running);
// Pause/resume is orthogonal to running (see hive-agent-menu.js's own
// comment on the per-agent version of these two actions) — a paused
// stopped agent boots paused, a paused running agent keeps its
// container but drives no turns. Same all-or-nothing enablement rule
// as every other bulk action here: mixed pause state disables the
// button rather than silently no-op'ing on part of the selection.
const allUnpaused = selected.every((c) => !c.paused);
const allPaused = selected.every((c) => c.paused);
const stoppedNames = selected.filter((c) => !c.running).map((c) => c.name);
const runningNames = selected.filter((c) => c.running).map((c) => c.name);
const pausedNames = selected.filter((c) => c.paused).map((c) => c.name);
const unpausedNames = selected.filter((c) => !c.paused).map((c) => c.name);
function why(label, blockers) {
if (!blockers.length) return null;
@ -811,6 +821,16 @@ export function renderSelectionBar(containers) {
confirm: (names) => `start ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
disabledTitle: why('▶ ST4RT', runningNames.map((n) => `\`${n}\` is already running`)),
});
addBulkButton(actions, 'btn-pause', '⏸ P4US3', allUnpaused, selected, {
action: '/api/pause/',
confirm: (names) => `pause ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? parks the turn loop — inbox messages queue unacked.`,
disabledTitle: why('⏸ P4US3', pausedNames.map((n) => `\`${n}\` is already paused`)),
});
addBulkButton(actions, 'btn-resume', '▶ R3SUM3', allPaused, selected, {
action: '/api/resume/',
confirm: (names) => `resume ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? the turn loop restarts and drains queued messages.`,
disabledTitle: why('▶ R3SUM3', unpausedNames.map((n) => `\`${n}\` is not paused`)),
});
addBulkButton(actions, 'btn-rebuild', '↻ R3BU1LD', true, selected, {
action: '/api/rebuild/',
confirm: (names) => `rebuild ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})? hot-reloads each container.`,