diff --git a/docs/web-ui.md b/docs/web-ui.md
index a54d2de9..094777b9 100644
--- a/docs/web-ui.md
+++ b/docs/web-ui.md
@@ -183,7 +183,8 @@ One operation runs at a time; the worker drains FIFO. Each row
shows a state glyph (`⏸` queued / `▶` running / `✔` done /
`✖` failed / `⊘` cancelled), kind glyph + verb (`↻ rebuild`,
`◆ meta_update`, `✨ spawn`, `🗑 destroy`), agent name, source
-chip (`manual | meta_update | auto_update | crash_recover`),
+chip (`manual | meta_update | auto_update | crash_recover | approval`
+— green for operator-approved config changes; #436),
timing, and an optional reason / error. Meta-update cascade
rebuilds nest under their parent entry (`parent_id` grouping;
`rqe-child` CSS class). Dedup: re-enqueueing a still-queued op
@@ -233,16 +234,14 @@ not `@hm1nd` (the container name).
### Container row
-A full-height **square agent icon** on the left (the agent's
-`/icon`, an `
` absolutely positioned inside a wrapper div so
-its load state can never reflow the row), and the card body on
-the right with three stacked lines
-(`assets/app.js::renderContainers`). The `
` points straight
-at `/icon`; if it actually fails to load (container stopped
-or mid-transient, web server not answering) the `error` handler
-falls it back to the dimmed hyperhive mark (`/favicon.svg`)
-instead of an empty box — a real load-failure fallback, not a
-guess from container state.
+A full-height **square agent icon** (5em, capped) on the left. The
+icon is the **selection toggle**: click (or Enter/Space) adds/removes
+the agent from the selection set; `aria-pressed` reflects the state;
+the tooltip says "select … for bulk actions" or "deselect … (or press
+Esc to clear all)". The `
` points at `/icon`; load failure
+falls back to the dimmed hyperhive mark (`/favicon.svg`). The card
+body sits to the right with three stacked lines
+(`assets/app.js::renderContainers`).
- Line 1: agent name (link → new tab), m1nd/ag3nt chip, an
**icon-only nav strip** populated async from the agent backend
@@ -285,10 +284,8 @@ guess from container state.
≥ 50% and red ≥ 75% of that window (the harness compaction
watermarks). When the window can't be resolved the badge falls
back to fixed 100k / 150k thresholds. (issue #66)
-- Line 2: action buttons — `↻ R3BU1LD` always, `DESTR0Y` + `PURG3`
- on sub-agents, `↺ R3ST4RT` + (sub-agents) `■ ST0P` when running,
- `▶ ST4RT` when stopped. Buttons dim + disable while a transient
- lifecycle action is in flight.
+- Line 2: status badges only (no per-card action buttons — actions
+ moved to the **selection bar**, see below).
- Line 3: drill-in triggers —
- `↳ logs · ` — opens the side panel and lazy-
fetches journald via `GET /api/journal/{name}?unit=&lines=`
@@ -306,6 +303,26 @@ guess from container state.
agent is stale. Banner pulses on each broker SSE event
(`pulseBanner` with a 4s grace timer).
+### Selection bar
+
+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:
+
+- **Count + names** — "N agents selected · name1, name2, …"
+- **Bulk action buttons** — only enabled when ALL selected agents
+ 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)
+ - `▶ ST4RT` — stopped agents only
+ - `↻ R3BU1LD` — always available
+ - `DESTR0Y` / `PURG3` — sub-agents only (disabled if manager selected)
+- **`✕ clear`** button + `Esc` key clear the entire selection.
+
+Stale selections (agents destroyed while selected) are pruned on
+every render before the bar appears.
+
### Approval card
Each pending approval renders as a card (`assets/app.js::