diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md
index 61a9d17..ac70253 100644
--- a/docs/PROJECT_MAP.md
+++ b/docs/PROJECT_MAP.md
@@ -395,9 +395,9 @@ The notebook (N): milestones, market, wallet, blueprints, controls.
- functions: openNotebook, renderNotebook, fmtClock, orderName, orderLeft, renderMarketTab, fmtPrice, itemsText, renderWallet, renderBlueprintTab, copyCode, controlsHtml
- variables: nbTab, MARKET_GROUPS
-### src/ui/hud.js (39 lines)
+### src/ui/hud.js (40 lines)
The tool hotbar and what is selected.
-- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, ensureBlow, piece
+- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, ensureBlow, piece
### src/ui/build-menu.js (102 lines)
The build menu along the bottom: categories, piece tiles and their icons.
diff --git a/docs/agent-log.md b/docs/agent-log.md
index 1a11730..1bdecc3 100644
--- a/docs/agent-log.md
+++ b/docs/agent-log.md
@@ -4,6 +4,18 @@ What agents have done, newest first. Add an entry for every piece of work: the d
note or request it was for, what changed, the files touched, the tests run, and anything left
open or not verified. Keep each entry short; the git history has the details.
+## 2026-10-24: t19 Vacuum blow all visual (Claude Code, Codex)
+
+- The owner's description was lost in kandown; chosen in chat: bag rows light up and the card
+ shows it while Shift is held. t18 was deleted at the owner's request (to decide later: what
+ blow does when the chosen material runs out).
+- `hud.js` `blowingAll()` (Shift held and not flying, or blowing everything); `selection.js` card
+ ("Everything · mix of N", no swatches so it stays one line) and bag `.on` rows; `previews.js`
+ cursor swatches.
+- `test/play.py`: Shift preview on and off, flying doesn't preview (the bot is put back after,
+ or a later gamepad dig check fails). Tests: `run_all.py --browser` all pass (220 `play.py`
+ checks).
+
## 2026-10-24: t16 follow-up: Shift blows all, Ctrl+click in the bag, compact menu (Claude Code)
- The owner's subtask "shift to blow all" and two chat requests. Codex was out of credits, so
diff --git a/src/ui/hud.js b/src/ui/hud.js
index 39d70d1..7e6e9e6 100644
--- a/src/ui/hud.js
+++ b/src/ui/hud.js
@@ -31,6 +31,7 @@ function bagMaterials() { return BAG_ORDER.filter(m => bag[m] > 0); }
function blowList() { return [...GAME.blow]; }
// Shift + right click: an even mix of everything loose in the bag (bars never mix)
function blowAllList() { return bagMaterials().filter(m => PH[m] !== PH_ITEM && (PH[m] !== PH_LIQUID || player.flask)); }
+function blowingAll() { return toolId() === 'vacuum' && (GAME.rmb ? !GAME.rmbConsumed && GAME.blowAll : modShift()); }
// Empty materials stay chosen until the player changes the marks.
function ensureBlow() {
if (GAME.blow.size) return;
diff --git a/src/ui/previews.js b/src/ui/previews.js
index 67d3b67..e5a2793 100644
--- a/src/ui/previews.js
+++ b/src/ui/previews.js
@@ -153,7 +153,7 @@ function drawPreviews() {
if (t === 'vacuum') {
// what the right button would blow, next to the cursor; what the left is sucking, while it sucks
const sz = Math.max(6, zp * 1.6), qx = p.x + (r + 1.2) * zp, qy = p.y - (r + 1.2) * zp;
- const swatches = GAME.lmb ? GAME.vacFilter >= 0 && GAME.collect.size ? [...GAME.collect] : [] : GAME.rmb && GAME.blowAll ? blowAllList() : blowList();
+ const swatches = GAME.lmb ? GAME.vacFilter >= 0 && GAME.collect.size ? [...GAME.collect] : [] : blowingAll() ? blowAllList() : blowList();
swatches.slice(0, 4).forEach((m, k) => { ctx.fillStyle = rgbOf(m); ctx.fillRect(qx + k * (sz + 2), qy, sz, sz); });
if (blowing || GAME.lmb) {
// faint line from the nozzle: the path the grains take
diff --git a/src/ui/selection.js b/src/ui/selection.js
index f3c5f8b..89074e4 100644
--- a/src/ui/selection.js
+++ b/src/ui/selection.js
@@ -229,11 +229,11 @@ function renderSelCard() {
+ '
Q/E piece · Shift+Q/E tab · R turn or material · F material menu · hold G replace · right click take back · hold C/X copy/cut · V paste · Ctrl+Z undo
';
} else if (t === 'vacuum') {
ensureBlow();
- const list = blowList(), marks = [...GAME.collect];
+ const all = blowingAll(), list = all ? blowAllList() : blowList(), marks = [...GAME.collect];
const sws = ms => ms.slice(0, 4).map(m => ``).join('') + (ms.length > 4 ? `+${ms.length - 4}` : '');
const count = list.reduce((n, m) => n + bag[m], 0);
h = `