Bag: hint and Clear selection stay visible under the list (t20)

The hint and Clear button were inside the scrolling bag list, so a long
Ctrl list scrolled them away. They now sit in a footer under the list, and
Clear is a full-width, highlighted "Clear selection" button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 14:29:02 -04:00
co-authored by Claude Opus 5.5
parent 248aba439e
commit 318de3ed76
7 changed files with 33 additions and 14 deletions
+1 -1
View File
@@ -404,7 +404,7 @@ The build menu along the bottom: categories, piece tiles and their icons.
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
### src/ui/selection.js (242 lines)
### src/ui/selection.js (243 lines)
The selected tool and piece: R / Q / E, drill width, filter picker, the selection card, the bag panel.
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleBlow, pickFilterUnderCursor, toggleFilterMat, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
- variables: lastBagKey
+8
View File
@@ -4,6 +4,14 @@ 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: t20 follow-up: bag hint and Clear stay visible (Claude Code)
- The hint and Clear were inside the scrolling `#baglist`, so a long Ctrl list scrolled them away.
They now render into a new `#bagfoot` under the list (`src/shell.html`, `renderBag`); Clear is a
full-width, highlighted "Clear selection" button (`style.css`).
- `test/play.py`: a long Ctrl list scrolls while the hint and a full-width Clear stay visible.
Screenshots `shots/t20c-ctrl{,-900}.png`. Tests: `run_all.py --browser` all passed (play.py 232).
## 2026-10-24: t20 follow-ups: deselect when blown out, Ctrl + wheel, Ctrl+click (Claude Code)
- Blowing the last of a selected material removes it from the selection (`toolTick`); materials
+1
View File
@@ -25,6 +25,7 @@
</div>
<div class="bar" id="bagbar"><i></i></div>
<div id="baglist"></div>
<div id="bagfoot"></div>
</div>
</div>
<div class="corner bl">
+5 -2
View File
@@ -180,11 +180,14 @@ table.mt td.dip { color: var(--melt); }
#baglist .tag { flex: none; padding: 0 3px; color: var(--patina); font-size: 11px; }
#selcard .vacrow { flex-wrap: wrap; gap: 4px; }
#selcard .vacrow .sw { width: 9px; height: 9px; }
#baglist .baghint { color: var(--ash-dim); font-size: 0.82em; padding: 4px 4px 0; }
#bagfoot:empty { display: none; }
#bagfoot { border-top: 1px solid var(--soot-3); margin-top: 4px; }
#bagfoot .baghint { color: var(--ash-dim); font-size: 0.82em; padding: 4px 4px 0; }
.bagsep { margin: 6px 4px 2px; padding-top: 5px; border-top: 1px solid var(--ash-dim); color: var(--ash-dim); font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.08em; }
#baglist .bagrow.none { opacity: 0.55; }
#baglist .bagrow.none:hover { opacity: 1; }
#baglist .bagclear { display: inline-flex; margin-left: 5px; padding: 1px 5px; background: transparent; color: var(--ash-dim); border: 1px solid var(--soot-3); font-size: inherit; }
#bagfoot .bagclear { display: block; width: 100%; margin-top: 6px; padding: 4px 8px; background: rgba(92, 186, 166, 0.12); color: var(--bone); border: 1px solid var(--patina-dim); border-radius: 3px; font-weight: 600; cursor: pointer; }
#bagfoot .bagclear:hover { background: rgba(92, 186, 166, 0.24); border-color: var(--patina); }
#bagtoggle { background: none; border: none; color: var(--ash); cursor: pointer; padding: 0 2px; }
#info { padding: 7px 10px; min-width: calc(220px * var(--ui)); max-width: min(360px, 60vw); display: flex; flex-direction: column; gap: 2px; font-size: 0.92em; }
+10 -9
View File
@@ -210,6 +210,16 @@ function renderBag() {
if (key === lastBagKey) return;
lastBagKey = key;
const box = $('baglist'); box.innerHTML = '';
// the hint and Clear sit under the list, outside its scroll, so they never scroll away
const foot = $('bagfoot'); foot.innerHTML = '';
if (t === 'vacuum') {
foot.appendChild(el('div', { class: 'baghint' }, "Click: select only this · Ctrl+click: add to the selection (hold Ctrl to see everything you've found) · Q/E: next"));
if (GAME.blow.size) {
const clear = el('button', { class: 'bagclear', 'data-bag-clear': '' }, 'Clear selection');
clear.onclick = () => { GAME.blow.clear(); lastBagKey = ''; hudDirty = true; playSfx('ui'); };
foot.appendChild(clear);
}
}
if (!l.length) { box.appendChild(el('div', { class: 'empty' }, 'Empty. Break rock with the Drill, then use the Vacuum.')); return; }
for (const m of l) {
if (m === missing[0]) box.appendChild(el('div', { class: 'bagsep' }, 'Not in bag'));
@@ -230,13 +240,4 @@ function renderBag() {
row.appendChild(b);
box.appendChild(row);
}
if (t === 'vacuum') {
const hint = el('div', { class: 'baghint' }, "Click: select only this · Ctrl+click: add to the selection (hold Ctrl to see everything you've found) · Q/E: next");
if (GAME.blow.size) {
const clear = el('button', { class: 'bagclear', 'data-bag-clear': '' }, 'Clear');
clear.onclick = () => { GAME.blow.clear(); lastBagKey = ''; hudDirty = true; playSfx('ui'); };
hint.appendChild(clear);
}
box.appendChild(hint);
}
}
+2 -1
View File
@@ -14,7 +14,7 @@ report: |
Hold Ctrl and the bag also lists everything you've found, under "Not in bag", so you can select
materials you don't have yet. Blowing out the last of a material deselects it (one picked
before you had any stays selected). Ctrl + wheel scrolls the bag list. Clear the
selection with the Clear button in the bag, by clicking the only selected row, or gamepad B.
selection with the Clear selection button under the bag list, by clicking the only selected row, or gamepad B.
Shift takes or blows everything.
To check: hold Ctrl, Ctrl+click two materials you don't have, then sweep a mixed pile (only
those come up); blow a coal + ore mix until the coal runs out (ore keeps going, coal is
@@ -39,3 +39,4 @@ I dont like the current tool. I want to completely rethink how to pick up and dr
- [x] An easy way to clear the selection (a Clear button, clicking the only selected row, gamepad B)
- [x] Ctrl + wheel over the bag scrolls the list instead of zooming the page
- [x] Ctrl+click adds to the selection on setups where the click arrives without the Ctrl flag
- [x] The bag hint and a wider, more visible Clear selection button stay visible below the list instead of scrolling away
+6 -1
View File
@@ -368,7 +368,7 @@ with sync_playwright() as p:
pg.locator('#baglist .pick').filter(has_text='Copper ore').click(button='right'); settle(80)
check('right click on a bag row leaves the selection alone', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
check('bag rows have no pickup tag', '⬇' not in pg.inner_text('#baglist'))
check('bag hint describes the selection', "Click: select only this · Ctrl+click: add to the selection (hold Ctrl to see everything you've found) · Q/E: next" in pg.locator('#baglist .baghint').inner_text())
check('bag hint describes the selection', "Click: select only this · Ctrl+click: add to the selection (hold Ctrl to see everything you've found) · Q/E: next" in pg.locator('#bagfoot .baghint').inner_text())
ore_row.click(modifiers=['Control']); settle(80)
check('vacuum bag has no pick or blow buttons', pg.locator('#baglist [data-pick], #baglist [data-blow]').count() == 0)
pg.mouse.move(mx_, my_); pg.mouse.down(button='right'); settle(500); pg.mouse.up(button='right'); settle(600)
@@ -401,6 +401,11 @@ with sync_playwright() as p:
check('Meta release hides unselected missing materials', ev('!GAME.ctrl'))
ev("window.dispatchEvent(new KeyboardEvent('keydown', {key:'Control', ctrlKey:true})); window.dispatchEvent(new Event('blur')); renderBag()")
check('blur clears the modifier', ev('!GAME.ctrl'))
r = ev("""(() => { const old = seen.slice(), sel = GAME.blow; for (const m of BAG_ORDER) seen[m] = 1; GAME.blow = new Set([COAL]); GAME.ctrl = true; lastBagKey = ''; renderBag();
const b = $('baglist'), f = $('bagfoot'), h = f.querySelector('.baghint'), c = f.querySelector('.bagclear'), fr = f.getBoundingClientRect(), br = b.getBoundingClientRect();
const out = [b.scrollHeight > b.clientHeight, !b.contains(h) && !b.contains(c), fr.top >= br.bottom - 1 && fr.bottom <= innerHeight, c.getBoundingClientRect().width >= f.clientWidth - 2, c.textContent];
seen.set(old); GAME.blow = sel; GAME.ctrl = false; lastBagKey = ''; renderBag(); return out; })()""")
check('a long Ctrl list scrolls while the hint and a full-width Clear stay visible below it', r[0] and r[1] and r[2] and r[3] and r[4] == 'Clear selection', r)
# Ctrl + wheel over the bag scrolls the list instead of zooming the page
r = ev("""(() => { const b = $('baglist'), mh = b.style.maxHeight; b.style.maxHeight = '40px'; b.scrollTop = 0;
const w = new WheelEvent('wheel', { deltaY: 30, ctrlKey: true, bubbles: true, cancelable: true }); b.querySelector('.pick').dispatchEvent(w);