← back to Wallco Ai
cactus-curator: select-all button + live-animate-out + auto-force-publish UX
6f2666daaedcc027b5e09fc7d4a1b0cbe73680ad · 2026-05-29 13:23:17 -0700 · Steve Abrams
three small UX upgrades on the same surface, in flight since the earlier
'debug why i cant publish anything' + 'allow user to select 1 to all
in check marks' interrupts.
1. select-all toolbar button — '☑ Select all visible' next to Auto-ID.
Click once: every visible card gets sel-class + adds to selected set
and bulk-bar appears. Click again (or once all selected): clears.
Skips .moved-to-etsy mid-animation cards so a recent decision doesn't
sneak back into the next bulk action.
2. Publish on gate-fail now visually completes — added 'live' to the
per-card animate-out action set so when the auto-force-publish lands,
the card fades out (was sitting there with ✓ PUBLISHED pill while
Steve was puzzled why nothing visibly happened). The local model is
patched (is_published=true, web_viewer=true, user_removed=false,
needs_fixing_at=null) inside the force-success branch so filter
re-renders won't bring it back.
3. force-publish success branch in decide() now also animates out the
element after patching the local model — matching the gate-pass live
path. Without this the 'auto-force-publishing…' toast shows, the row
flips in PG, the local model updates, but the card sits there
indefinitely with ✓ PUBLISHED until you refresh.
verified locally + on prod: HTML serves 90679 bytes, all 4 new code chunks
present in prod-served response.
Files touched
M public/admin/cactus-curator.html
Diff
commit 6f2666daaedcc027b5e09fc7d4a1b0cbe73680ad
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri May 29 13:23:17 2026 -0700
cactus-curator: select-all button + live-animate-out + auto-force-publish UX
three small UX upgrades on the same surface, in flight since the earlier
'debug why i cant publish anything' + 'allow user to select 1 to all
in check marks' interrupts.
1. select-all toolbar button — '☑ Select all visible' next to Auto-ID.
Click once: every visible card gets sel-class + adds to selected set
and bulk-bar appears. Click again (or once all selected): clears.
Skips .moved-to-etsy mid-animation cards so a recent decision doesn't
sneak back into the next bulk action.
2. Publish on gate-fail now visually completes — added 'live' to the
per-card animate-out action set so when the auto-force-publish lands,
the card fades out (was sitting there with ✓ PUBLISHED pill while
Steve was puzzled why nothing visibly happened). The local model is
patched (is_published=true, web_viewer=true, user_removed=false,
needs_fixing_at=null) inside the force-success branch so filter
re-renders won't bring it back.
3. force-publish success branch in decide() now also animates out the
element after patching the local model — matching the gate-pass live
path. Without this the 'auto-force-publishing…' toast shows, the row
flips in PG, the local model updates, but the card sits there
indefinitely with ✓ PUBLISHED until you refresh.
verified locally + on prod: HTML serves 90679 bytes, all 4 new code chunks
present in prod-served response.
---
public/admin/cactus-curator.html | 40 +++++++++++++++++++++++++++++++++-------
1 file changed, 33 insertions(+), 7 deletions(-)
diff --git a/public/admin/cactus-curator.html b/public/admin/cactus-curator.html
index 27f8a96..d59a58d 100644
--- a/public/admin/cactus-curator.html
+++ b/public/admin/cactus-curator.html
@@ -201,6 +201,7 @@
<input type="range" id="density" min="2" max="9" step="1" value="5">
</label>
<button id="autoall" class="ctl-btn" title="auto-detect bad areas on every visible card">🔍 Auto-ID all visible</button>
+ <button id="selectall" class="ctl-btn" title="Toggle select all visible cards — click again to clear">☑ Select all visible</button>
<button id="toggleacts" class="ctl-btn" title="show/hide the Bad/Digital/Fix/Publish buttons on every card (they always show on hover)">▸ Actions</button>
<span class="legend"><b>drag</b> across images to select · <b>shift+drag on an image</b> = ▢ box a bad area (red) · <b>shift+click</b> a card to add one · keys on hover: <code>1</code>bad <code>2</code>digital <code>3</code>fix <code>4</code>publish <code>x</code>select</span>
</div>
@@ -268,6 +269,25 @@ $('sort').value = LS.s;
$('filter').value = LS.f;
$('density').addEventListener('input', e => { LS.d = e.target.value; applyDensity(e.target.value); });
+
+// Select-all toggle (Steve 2026-05-29 "allow user to select 1 to all in check marks").
+// First click selects every visible card; second click clears the selection. Animated
+// cards (already 'moved-to-etsy' transitioning out) skipped so a recent Bad/Publish
+// doesn't quietly come back into the selection set.
+$('selectall').addEventListener('click', () => {
+ const cards = [...grid.querySelectorAll('.card[data-id]:not(.moved-to-etsy)')];
+ if (!cards.length) return;
+ const allSelected = cards.every(c => c.classList.contains('sel'));
+ if (allSelected) {
+ cards.forEach(c => { selected.delete(parseInt(c.dataset.id,10)); c.classList.remove('sel'); });
+ } else {
+ cards.forEach(c => { const id = parseInt(c.dataset.id,10); if (!isNaN(id)) { selected.add(id); c.classList.add('sel'); } });
+ }
+ $('bulkn').textContent = selected.size;
+ $('bulk').classList.toggle('show', selected.size > 0);
+ $('selectall').textContent = (selected.size && selected.size === cards.length) ? '✕ Clear selection' : '☑ Select all visible';
+});
+
// Auto-ID every visible card (capped, limited concurrency so we don't hammer Gemini).
$('autoall').addEventListener('click', async () => {
const cards = [...grid.querySelectorAll('.card')];
@@ -676,6 +696,11 @@ async function decide(id, action, el){
if(st){st.textContent='✓ PUBLISHED';}
const dd=ALL.find(x=>x.id===id);
if(dd){dd.is_published=true; dd.web_viewer=true; dd.user_removed=false; dd.needs_fixing_at=null; dd.digital_file_at=null;}
+ // Steve 2026-05-29: animate card out on successful publish so it leaves
+ // the Undecided filter view (matches bad/digital/unpublish behavior).
+ // Without this the card sits there with ✓ PUBLISHED and Steve sees
+ // 'auto-force-publishing…' but no visible action — feels broken.
+ if(el){ el.classList.add('moved-to-etsy'); setTimeout(()=>el.remove(),300); }
} else {
flash(`#${id} force-publish failed: ${j2.error||r2.status}`);
}
@@ -686,13 +711,14 @@ async function decide(id, action, el){
// button land inside the card. Original PNG is backed up alongside as
// __preseam.png — restore-preseam reverses the swap.
runFixAndDisplay(id, el);
- } else if (['bad','digital','unpublish'].includes(action) && el) {
- // Steve 2026-05-29: per-card Bad/Digital/Unpublish now animate the card
- // out of the grid on success (mirrors bulk's 300ms fade+remove). Without
- // this the card sits there with a REMOVED pill while the SQL update has
- // already landed, making the click feel broken. Etsy + Live skip this
- // because Etsy already self-hides via its own path and Live keeps the
- // card visible (now-published, no reason to drop it from the queue).
+ } else if (['bad','digital','unpublish','live'].includes(action) && el) {
+ // Steve 2026-05-29: per-card Bad/Digital/Unpublish/Publish now animate
+ // the card out of the grid on success (mirrors bulk's 300ms fade+remove).
+ // Without this the card sits there with its new state-pill while the SQL
+ // update already landed, making the click feel broken. Live was added
+ // 2026-05-29 because Steve was hitting 'auto-force-publishing…' but
+ // seeing no visible action — the card was sitting there published but
+ // still in the Undecided view. Etsy keeps its own self-hide path.
el.classList.add('moved-to-etsy');
setTimeout(() => el.remove(), 300);
}
← 8e67419 Add color-dots.js: vertical strip of the pattern's screen-pr
·
back to Wallco Ai
·
color-dots: interactive recolor — click a dot to open an HSV 69f306b →