From a9422518e59107436852998964cbaafd6aeb6c2e Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 31 May 2026 12:05:10 +0200 Subject: [PATCH 1/3] docs: scrub selection-bar #443/#596 cookies in tabs.js, add interaction-model preface (#712 batch 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit selection + selection-bar in tabs.js carried six #443 cookies + two #596 cookies — most marking the bulk-bar architectural decision ('actions live in bar, not on per-card buttons; manager not special-cased') or the SW4RM-tab gate. All scrubbed; the canonical docs/web-ui.md::Selection bar section already covered the bulk actions in detail but was missing a preface for the **interaction model** itself (how selection enters/exits, why per-card buttons moved). Added that as the opening paragraph of the section so the in-code refs have something to point at. Net in this batch: - tabs.js: -8 #NNN refs (#443 ×6, #596 ×2) - docs/web-ui.md: +9 lines (interaction-model preface) - functional code unchanged; build clean refs #712 --- docs/web-ui.md | 9 +++++ frontend/packages/dashboard/src/tabs.js | 52 ++++++++++++------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/docs/web-ui.md b/docs/web-ui.md index d2775493..a6d27464 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -504,6 +504,15 @@ stops at the row's icon midline). ### Selection bar +Per-card action buttons (`R3ST4RT` / `ST0P` / `ST4RT` / `R3BU1LD` / +`DESTR0Y` / `PURG3`) used to live on each container row; the +operator picked the bulk-bar model instead. Clicking an agent's +icon toggles its selection (an in-memory `Set`); `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 (`#selection-bar`, `position: fixed; bottom: 0`). It shows: diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index b0b3ef54..3a86af62 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -287,11 +287,11 @@ window.marked = marked; if (s) renderContainers(s); } - // ─── selection (#443) ─────────────────────────────────────────────── - // Set of selected agent logical names. Toggled by clicking the - // container-row icon. When non-empty, the sticky #selection-bar - // becomes visible with the bulk actions. Per-card action buttons - // are gone — actions live in the bar. + // ─── selection ────────────────────────────────────────────────────── + // In-memory set of selected agent logical names backing the sticky + // #selection-bar. See docs/web-ui.md::Selection bar for the + // interaction model (icon-click toggle, Esc/clear button drop, + // tab-gated visibility). const selectionState = new Set(); function toggleSelection(name) { if (selectionState.has(name)) selectionState.delete(name); @@ -534,7 +534,7 @@ window.marked = marked; // hyperhive mark (`/favicon.svg`, served by the dashboard // itself, always reachable). (issues #195, #202) const iconImg = el('img', { class: 'container-icon-img', alt: '' }); - // #443: icon is the selection toggle. Click → add/remove from + // Icon is the selection toggle. Click → add/remove from // `selectionState` → re-render. role=button + tabindex makes it // keyboard-accessible; aria-pressed reflects the toggle state. const icon = el('div', { @@ -698,10 +698,9 @@ window.marked = marked; )); } - // Per-card action buttons used to live here (R3ST4RT / ST0P / - // ST4RT / R3BU1LD / DESTR0Y / PURG3). Per mara on #443: "dont - // show all the restart buttons etc., just show state and links. - // instead, clicking an agent icon selects that agent." Actions + // Per-card action buttons (R3ST4RT / ST0P / ST4RT / R3BU1LD / + // DESTR0Y / PURG3) moved to the selection bar — see + // docs/web-ui.md::Selection bar. Actions // moved into the sticky #selection-bar (see renderSelectionBar) // which appears when the operator has at least one agent // selected via the icon click. The contextual `needs update ↻` @@ -730,12 +729,13 @@ window.marked = marked; renderSelectionBar(containers); } - // ─── selection bar (#443) ─────────────────────────────────────────── - // Sticky-bottom strip; visible when ≥1 agent selected. mara picked - // option B: show every action button, disable the ones that don't - // apply to the full selection, hover tooltip explains why. Actions - // POST per agent in a loop (no new backend wire — endpoints already - // exist and are individually idempotent / event-covered). + // ─── selection bar ────────────────────────────────────────────────── + // Sticky-bottom strip; visible when ≥1 agent selected on the SW4RM + // tab. See docs/web-ui.md::Selection bar for the interaction model + // and the per-action availability rules (disabled-with-tooltip for + // actions that don't apply to the full selection). Actions POST per + // agent in a loop (endpoints are individually idempotent / + // event-covered, so no new bulk backend wire is needed). function renderSelectionBar(containers) { const bar = $('selection-bar'); if (!bar) return; @@ -745,11 +745,9 @@ window.marked = marked; if (!countSpan || !namesSpan || !actions) return; const selected = containers.filter((c) => selectionState.has(c.name)); - // #596: the bar's actions only make sense on the SW4RM tab — that's - // where the agent cards are visible to cross-reference against the - // selection. On other tabs the operator just sees a floating bar - // with no context, so hide it. Selection state persists in-memory - // and the bar reappears on return to SW4RM if still non-empty. + // Tab-gate: bar only renders on SW4RM (the only tab with agent + // cards to cross-reference). Selection state lives in-memory and + // the bar reappears on return to SW4RM if still non-empty. const onSwarmTab = (document.body.dataset.activeTab || 'swarm') === 'swarm'; if (!selected.length || !onSwarmTab) { bar.hidden = true; @@ -785,8 +783,8 @@ window.marked = marked; confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)), }); - // #443 also lifts the manager-stop guard: when the whole selection - // is running, ST0P applies — manager included. host-side hive-c0re + // ST0P does not special-case the manager: when the whole selection + // is running it applies, manager included. host-side hive-c0re // keeps serving the dashboard either way + per-agent approvals + // meta-input updates still work without the manager up, so we // don't special-case the confirm prompt when the manager is in @@ -844,7 +842,7 @@ window.marked = marked; // No client-side manager special-case (mara on #695): backend // `topology::set_parent` refuses to move the manager and surfaces the // refusal as a per-agent failure in the bulk-action error roll-up. Same - // pattern as #443 ST0P (which also doesn't special-case manager). + // pattern as ST0P (which also doesn't special-case manager). function addMoveActions(parent, selected, containers) { // M0V3 → ROOT: parent=null for every selected agent. Only meaningful // when at least one selected agent currently has a non-null parent; @@ -3266,9 +3264,9 @@ window.marked = marked; if (tab) tab.classList.toggle('active', t === target); if (pane) pane.classList.toggle('tab-pane-active', t === target); } - // #596: track active tab on the body so renderSelectionBar can - // gate visibility (bar only belongs on SW4RM where agent cards - // live). Re-render the bar so the toggle takes effect immediately + // Track active tab on the body so renderSelectionBar can gate + // visibility (bar only belongs on SW4RM where agent cards live). + // Re-render the bar so the toggle takes effect immediately // on hashchange without waiting for the next SSE update. document.body.dataset.activeTab = target; renderSelectionBar(Array.from(containersState.values())); From 69debe6b7dab2010bec12c5c3fc56d1f3eef0e0b Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 31 May 2026 12:13:28 +0200 Subject: [PATCH 2/3] tabs.js + web-ui.md: drop 'no special-case manager' framing (mara on #728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mara's review nit: explaining the absence of a manager special-case implicitly endorses the idea that special cases would be normal. Default is no special case; the doc shouldn't dwell on it. - ST0P comment block removed entirely (the bulk-button line above is self-explanatory; the substantive 'c0re survives manager-down' rationale lives in the host docs, not here). - M0V3 helper comment reframed: trim the 'no special-case' framing but keep the substantive note that the backend refuses moves it can't satisfy and the failure surfaces in the bulk roll-up. - docs/web-ui.md::Selection bar bullet list: drop the 'manager included; no special-case' qualifiers; just describe the action. M0V3→ROOT keeps the rationale that backend refusals surface in the roll-up but stops calling out manager specifically. --- docs/web-ui.md | 8 ++++---- frontend/packages/dashboard/src/tabs.js | 20 ++++++-------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/docs/web-ui.md b/docs/web-ui.md index a6d27464..397122fc 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -522,15 +522,15 @@ frosted-mauve bar slides up from the bottom of the viewport support the action; disabled with a tooltip naming the blockers when the selection is mixed: - `↺ R3ST4RT` — running agents only - - `■ ST0P` — running agents only (manager included; no special-case) + - `■ ST0P` — running agents only - `▶ ST4RT` — stopped agents only - `↻ R3BU1LD` — always available - `DESTR0Y` / `PURG3` — sub-agents only (disabled if manager selected) - `⇡ M0V3 → ROOT` (#486) — promote selected agents to top-level (parent = null); disabled when all selected are already at root. - Manager included with no special-case (matches the `ST0P` policy); - the backend's `topology::set_parent` refuses to move the manager - and the refusal surfaces in the failure roll-up. + Backend `topology::set_parent` refuses moves it can't satisfy + (e.g. moving the manager) and the refusal surfaces in the + failure roll-up. - `⇢ M0V3 → [select]` (#486) — inline picker available for any selection size. The dropdown lists every container that isn't IN the selection itself nor a descendant of any selected agent diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index 3a86af62..618f67c1 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -783,12 +783,6 @@ window.marked = marked; confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)), }); - // ST0P does not special-case the manager: when the whole selection - // is running it applies, manager included. host-side hive-c0re - // keeps serving the dashboard either way + per-agent approvals + - // meta-input updates still work without the manager up, so we - // don't special-case the confirm prompt when the manager is in - // the selection (mara: "dont special case manager for stopping"). addBulkButton(actions, 'btn-stop', '■ ST0P', allRunning, selected, { action: '/kill/', confirm: (names) => `stop ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, @@ -835,14 +829,12 @@ window.marked = marked; addMoveActions(actions, selected, containers); } - // #486 — render the M0V3 affordances inside the selection bar. Split - // into its own helper because the picker variant needs a select + button - // pair, not the single-button shape addBulkButton ships. - // - // No client-side manager special-case (mara on #695): backend - // `topology::set_parent` refuses to move the manager and surfaces the - // refusal as a per-agent failure in the bulk-action error roll-up. Same - // pattern as ST0P (which also doesn't special-case manager). + // Render the M0V3 affordances inside the selection bar — separate + // helper because the picker variant needs a select + button pair, + // not the single-button shape addBulkButton ships. Backend + // `topology::set_parent` refuses moves it can't satisfy (manager + // included) and the refusal surfaces in the bulk-action error + // roll-up. function addMoveActions(parent, selected, containers) { // M0V3 → ROOT: parent=null for every selected agent. Only meaningful // when at least one selected agent currently has a non-null parent; From 10097bb9b6a67bb66c2f1baf102c39dec8375d8b Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 31 May 2026 12:15:37 +0200 Subject: [PATCH 3/3] tabs.js: collapse stale dangling sentence in per-card actions comment (argus #728) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When I rewrote the original 'Per-card action buttons … moved into the sticky #selection-bar (see renderSelectionBar) which appears when the operator has at least one agent selected via the icon click' comment to use the docs ref, I shrunk part of it but left the second sentence as a dangling fragment glued onto the new short ref. Collapse cleanly: docs ref + the still-useful `needs update ↻` chip note. --- frontend/packages/dashboard/src/tabs.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index 618f67c1..db8ef08e 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -700,12 +700,9 @@ window.marked = marked; // Per-card action buttons (R3ST4RT / ST0P / ST4RT / R3BU1LD / // DESTR0Y / PURG3) moved to the selection bar — see - // docs/web-ui.md::Selection bar. Actions - // moved into the sticky #selection-bar (see renderSelectionBar) - // which appears when the operator has at least one agent - // selected via the icon click. The contextual `needs update ↻` + // docs/web-ui.md::Selection bar. The contextual `needs update ↻` // chip in the head row stays — it's a state-hint, not an - // action button per se. + // action button. // ── drill-ins ──────────────────────────────────────────────── const drill = el('div', { class: 'drill-ins' });