Compare commits

...
Author SHA1 Message Date
iris
10097bb9b6 tabs.js: collapse stale dangling sentence in per-card actions comment (argus #728)
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.
2026-05-31 12:16:27 +02:00
iris
69debe6b7d tabs.js + web-ui.md: drop 'no special-case manager' framing (mara on #728)
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.
2026-05-31 12:16:27 +02:00
iris
a9422518e5 docs: scrub selection-bar #443/#596 cookies in tabs.js, add interaction-model preface (#712 batch 3)
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
2026-05-31 12:16:27 +02:00
2 changed files with 42 additions and 46 deletions

View file

@ -504,6 +504,15 @@ stops at the row's icon midline).
### Selection bar ### 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<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 When one or more agents are selected (via icon click), a sticky
frosted-mauve bar slides up from the bottom of the viewport frosted-mauve bar slides up from the bottom of the viewport
(`#selection-bar`, `position: fixed; bottom: 0`). It shows: (`#selection-bar`, `position: fixed; bottom: 0`). It shows:
@ -513,15 +522,15 @@ frosted-mauve bar slides up from the bottom of the viewport
support the action; disabled with a tooltip naming the blockers support the action; disabled with a tooltip naming the blockers
when the selection is mixed: when the selection is mixed:
- `↺ R3ST4RT` — running agents only - `↺ R3ST4RT` — running agents only
- `■ ST0P` — running agents only (manager included; no special-case) - `■ ST0P` — running agents only
- `▶ ST4RT` — stopped agents only - `▶ ST4RT` — stopped agents only
- `↻ R3BU1LD` — always available - `↻ R3BU1LD` — always available
- `DESTR0Y` / `PURG3` — sub-agents only (disabled if manager selected) - `DESTR0Y` / `PURG3` — sub-agents only (disabled if manager selected)
- `⇡ M0V3 → ROOT` (#486) — promote selected agents to top-level - `⇡ M0V3 → ROOT` (#486) — promote selected agents to top-level
(parent = null); disabled when all selected are already at root. (parent = null); disabled when all selected are already at root.
Manager included with no special-case (matches the `ST0P` policy); Backend `topology::set_parent` refuses moves it can't satisfy
the backend's `topology::set_parent` refuses to move the manager (e.g. moving the manager) and the refusal surfaces in the
and the refusal surfaces in the failure roll-up. failure roll-up.
- `⇢ M0V3 → [select]` (#486) — inline picker available for any - `⇢ M0V3 → [select]` (#486) — inline picker available for any
selection size. The dropdown lists every container that isn't IN selection size. The dropdown lists every container that isn't IN
the selection itself nor a descendant of any selected agent the selection itself nor a descendant of any selected agent

View file

@ -287,11 +287,11 @@ window.marked = marked;
if (s) renderContainers(s); if (s) renderContainers(s);
} }
// ─── selection (#443) ─────────────────────────────────────────────── // ─── selection ──────────────────────────────────────────────────────
// Set of selected agent logical names. Toggled by clicking the // In-memory set of selected agent logical names backing the sticky
// container-row icon. When non-empty, the sticky #selection-bar // #selection-bar. See docs/web-ui.md::Selection bar for the
// becomes visible with the bulk actions. Per-card action buttons // interaction model (icon-click toggle, Esc/clear button drop,
// are gone — actions live in the bar. // tab-gated visibility).
const selectionState = new Set(); const selectionState = new Set();
function toggleSelection(name) { function toggleSelection(name) {
if (selectionState.has(name)) selectionState.delete(name); if (selectionState.has(name)) selectionState.delete(name);
@ -534,7 +534,7 @@ window.marked = marked;
// hyperhive mark (`/favicon.svg`, served by the dashboard // hyperhive mark (`/favicon.svg`, served by the dashboard
// itself, always reachable). (issues #195, #202) // itself, always reachable). (issues #195, #202)
const iconImg = el('img', { class: 'container-icon-img', alt: '' }); 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 // `selectionState` → re-render. role=button + tabindex makes it
// keyboard-accessible; aria-pressed reflects the toggle state. // keyboard-accessible; aria-pressed reflects the toggle state.
const icon = el('div', { const icon = el('div', {
@ -698,15 +698,11 @@ window.marked = marked;
)); ));
} }
// Per-card action buttons used to live here (R3ST4RT / ST0P / // Per-card action buttons (R3ST4RT / ST0P / ST4RT / R3BU1LD /
// ST4RT / R3BU1LD / DESTR0Y / PURG3). Per mara on #443: "dont // DESTR0Y / PURG3) moved to the selection bar — see
// show all the restart buttons etc., just show state and links. // docs/web-ui.md::Selection bar. The contextual `needs update ↻`
// instead, clicking an agent icon selects that agent." 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 ↻`
// chip in the head row stays — it's a state-hint, not an // chip in the head row stays — it's a state-hint, not an
// action button per se. // action button.
// ── drill-ins ──────────────────────────────────────────────── // ── drill-ins ────────────────────────────────────────────────
const drill = el('div', { class: 'drill-ins' }); const drill = el('div', { class: 'drill-ins' });
@ -730,12 +726,13 @@ window.marked = marked;
renderSelectionBar(containers); renderSelectionBar(containers);
} }
// ─── selection bar (#443) ─────────────────────────────────────────── // ─── selection bar ──────────────────────────────────────────────────
// Sticky-bottom strip; visible when ≥1 agent selected. mara picked // Sticky-bottom strip; visible when ≥1 agent selected on the SW4RM
// option B: show every action button, disable the ones that don't // tab. See docs/web-ui.md::Selection bar for the interaction model
// apply to the full selection, hover tooltip explains why. Actions // and the per-action availability rules (disabled-with-tooltip for
// POST per agent in a loop (no new backend wire — endpoints already // actions that don't apply to the full selection). Actions POST per
// exist and are individually idempotent / event-covered). // agent in a loop (endpoints are individually idempotent /
// event-covered, so no new bulk backend wire is needed).
function renderSelectionBar(containers) { function renderSelectionBar(containers) {
const bar = $('selection-bar'); const bar = $('selection-bar');
if (!bar) return; if (!bar) return;
@ -745,11 +742,9 @@ window.marked = marked;
if (!countSpan || !namesSpan || !actions) return; if (!countSpan || !namesSpan || !actions) return;
const selected = containers.filter((c) => selectionState.has(c.name)); const selected = containers.filter((c) => selectionState.has(c.name));
// #596: the bar's actions only make sense on the SW4RM tab — that's // Tab-gate: bar only renders on SW4RM (the only tab with agent
// where the agent cards are visible to cross-reference against the // cards to cross-reference). Selection state lives in-memory and
// selection. On other tabs the operator just sees a floating bar // the bar reappears on return to SW4RM if still non-empty.
// with no context, so hide it. Selection state persists in-memory
// and the bar reappears on return to SW4RM if still non-empty.
const onSwarmTab = (document.body.dataset.activeTab || 'swarm') === 'swarm'; const onSwarmTab = (document.body.dataset.activeTab || 'swarm') === 'swarm';
if (!selected.length || !onSwarmTab) { if (!selected.length || !onSwarmTab) {
bar.hidden = true; bar.hidden = true;
@ -785,12 +780,6 @@ window.marked = marked;
confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)), 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
// 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, { addBulkButton(actions, 'btn-stop', '■ ST0P', allRunning, selected, {
action: '/kill/', action: '/kill/',
confirm: (names) => `stop ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`, confirm: (names) => `stop ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
@ -837,14 +826,12 @@ window.marked = marked;
addMoveActions(actions, selected, containers); addMoveActions(actions, selected, containers);
} }
// #486 — render the M0V3 affordances inside the selection bar. Split // Render the M0V3 affordances inside the selection bar — separate
// into its own helper because the picker variant needs a select + button // helper because the picker variant needs a select + button pair,
// pair, not the single-button shape addBulkButton ships. // not the single-button shape addBulkButton ships. Backend
// // `topology::set_parent` refuses moves it can't satisfy (manager
// No client-side manager special-case (mara on #695): backend // included) and the refusal surfaces in the bulk-action error
// `topology::set_parent` refuses to move the manager and surfaces the // roll-up.
// 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).
function addMoveActions(parent, selected, containers) { function addMoveActions(parent, selected, containers) {
// M0V3 → ROOT: parent=null for every selected agent. Only meaningful // M0V3 → ROOT: parent=null for every selected agent. Only meaningful
// when at least one selected agent currently has a non-null parent; // when at least one selected agent currently has a non-null parent;
@ -3266,9 +3253,9 @@ window.marked = marked;
if (tab) tab.classList.toggle('active', t === target); if (tab) tab.classList.toggle('active', t === target);
if (pane) pane.classList.toggle('tab-pane-active', t === target); if (pane) pane.classList.toggle('tab-pane-active', t === target);
} }
// #596: track active tab on the body so renderSelectionBar can // Track active tab on the body so renderSelectionBar can gate
// gate visibility (bar only belongs on SW4RM where agent cards // visibility (bar only belongs on SW4RM where agent cards live).
// live). Re-render the bar so the toggle takes effect immediately // Re-render the bar so the toggle takes effect immediately
// on hashchange without waiting for the next SSE update. // on hashchange without waiting for the next SSE update.
document.body.dataset.activeTab = target; document.body.dataset.activeTab = target;
renderSelectionBar(Array.from(containersState.values())); renderSelectionBar(Array.from(containersState.values()));