← back to Wild Wallcoverings
GO-bar flow: local pending selections, batch save on GO, nothing persists until pressed
5cf4e01aa9a5c689566d1c1b98b7dcb9c14d8933 · 2026-07-22 20:13:48 -0700 · Steve Abrams
Files touched
M data/choices.jsonM data/designs.jsonM output/designs/lagoon-legends-v2-sdxl.pngM output/designs/lagoon-legends-v3-sdxl.pngM output/designs/plumbline-v4-nano.pngM public/index.htmlM server.js
Diff
commit 5cf4e01aa9a5c689566d1c1b98b7dcb9c14d8933
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 22 20:13:48 2026 -0700
GO-bar flow: local pending selections, batch save on GO, nothing persists until pressed
---
data/choices.json | 43 +--------------------------
data/designs.json | 9 +++---
output/designs/lagoon-legends-v2-sdxl.png | Bin 73494 -> 88546 bytes
output/designs/lagoon-legends-v3-sdxl.png | Bin 83727 -> 88724 bytes
output/designs/plumbline-v4-nano.png | Bin 38600 -> 47873 bytes
public/index.html | 47 ++++++++++++++++++++++++++++--
server.js | 21 +++++++++++++
7 files changed, 71 insertions(+), 49 deletions(-)
diff --git a/data/choices.json b/data/choices.json
index 5cad0fc..0967ef4 100644
--- a/data/choices.json
+++ b/data/choices.json
@@ -1,42 +1 @@
-{
- "mod-orbit": {
- "variant": "mod-orbit-v1-nano.png",
- "chosen_at": "2026-07-23T00:24:38.492Z"
- },
- "dream-clouds": {
- "variant": "dream-clouds-v5-nano.png",
- "chosen_at": "2026-07-23T01:31:00.321Z"
- },
- "campfire-toile": {
- "variant": "campfire-toile-v5-nano.png",
- "chosen_at": "2026-07-23T01:31:26.692Z"
- },
- "circle-fields": {
- "variant": "circle-fields-v6-nano.png",
- "chosen_at": "2026-07-23T01:31:38.459Z"
- },
- "daily-dose": {
- "variant": "daily-dose-v3-sdxl.png",
- "chosen_at": "2026-07-23T01:31:46.356Z"
- },
- "arcade-memory": {
- "variant": "none",
- "chosen_at": "2026-07-23T01:31:57.661Z"
- },
- "regatta-day": {
- "variant": "regatta-day-v1-nano.png",
- "chosen_at": "2026-07-23T01:32:08.410Z"
- },
- "fungi-skyline": {
- "variant": "fungi-skyline-v1-nano.png",
- "chosen_at": "2026-07-23T01:32:13.914Z"
- },
- "lagoon-legends": {
- "variant": "lagoon-legends-v2-sdxl.png",
- "chosen_at": "2026-07-23T01:32:24.771Z"
- },
- "plumbline": {
- "variant": "plumbline-v6-nano.png",
- "chosen_at": "2026-07-23T01:32:33.486Z"
- }
-}
\ No newline at end of file
+{}
diff --git a/data/designs.json b/data/designs.json
index 0526be5..f02c619 100644
--- a/data/designs.json
+++ b/data/designs.json
@@ -2065,7 +2065,8 @@
"acceptable": false
},
"tile": {
- "verdict": "FAIL"
+ "verdict": "EDGES-FAIL",
+ "healed": true
}
},
{
@@ -2133,8 +2134,8 @@
"acceptable": false
},
"tile": {
- "verdict": "FAIL",
- "max": null
+ "verdict": "EDGES-FAIL",
+ "healed": true
},
"copycheck": "CLEAN"
},
@@ -2881,7 +2882,7 @@
},
{
"id": "campfire-toile",
- "title": "Campfire Waltz",
+ "title": "Wilderness Waltz",
"theme": "modern camping toile de jouy",
"description": "Camping (theme only)",
"size": "24\" x 25\"",
diff --git a/output/designs/lagoon-legends-v2-sdxl.png b/output/designs/lagoon-legends-v2-sdxl.png
index 948290b..a7f71cf 100644
Binary files a/output/designs/lagoon-legends-v2-sdxl.png and b/output/designs/lagoon-legends-v2-sdxl.png differ
diff --git a/output/designs/lagoon-legends-v3-sdxl.png b/output/designs/lagoon-legends-v3-sdxl.png
index e1f3af6..8f6e510 100644
Binary files a/output/designs/lagoon-legends-v3-sdxl.png and b/output/designs/lagoon-legends-v3-sdxl.png differ
diff --git a/output/designs/plumbline-v4-nano.png b/output/designs/plumbline-v4-nano.png
index 0af7466..72e7a23 100644
Binary files a/output/designs/plumbline-v4-nano.png and b/output/designs/plumbline-v4-nano.png differ
diff --git a/public/index.html b/public/index.html
index e5d9729..d18ddb2 100644
--- a/public/index.html
+++ b/public/index.html
@@ -52,6 +52,16 @@
#lightbox { position: fixed; inset: 0; background: #000d; display: none;
align-items: center; justify-content: center; z-index: 9; cursor: zoom-out; }
#lightbox img { max-width: 92vw; max-height: 92vh; }
+ #gobar { position: fixed; bottom: 0; left: 0; right: 0; background: #161f1a;
+ border-top: 1px solid #2e4638; padding: 12px 24px; display: flex;
+ align-items: center; gap: 18px; z-index: 8; }
+ #gobar .info { color: #9dc7b0; font-size: 14px; }
+ #gobtn { background: #4caf7d; color: #08130d; border: none; border-radius: 10px;
+ padding: 12px 40px; font-size: 16px; font-weight: 700; letter-spacing: .08em;
+ cursor: pointer; }
+ #gobtn:disabled { background: #2a3a30; color: #567; cursor: not-allowed; }
+ #gomsg { color: #f0c060; font-size: 13px; }
+ body { padding-bottom: 70px; }
</style>
</head>
<body>
@@ -71,6 +81,11 @@
<label>Density <input type="range" id="density" min="1" max="4" value="3"></label>
</div>
<div id="board"></div>
+<div id="gobar">
+ <button id="gobtn" onclick="go()" disabled>GO</button>
+ <span class="info" id="goinfo">Select a version on each design, then hit GO — nothing is saved until you do.</span>
+ <span id="gomsg"></span>
+</div>
<div id="lightbox" onclick="this.style.display='none'"><img></div>
<script>
const fmtDate = iso => new Date(iso).toLocaleString(undefined,
@@ -152,15 +167,41 @@ function zoom(src) {
lb.style.display = 'flex';
}
-async function choose(id, variant) {
- await fetch('/api/choose', { method: 'POST', body: JSON.stringify({ id, variant }) });
+// selections stay LOCAL (pending) until GO is pressed
+let pending = JSON.parse(localStorage.getItem('whimsical.pending') || '{}');
+
+function choose(id, variant) {
+ if (pending[id] === variant) delete pending[id]; // click again to unselect
+ else pending[id] = variant;
+ localStorage.setItem('whimsical.pending', JSON.stringify(pending));
const d = designs.find(x => x.id === id);
- d.choice = { variant };
+ d.choice = pending[id] ? { variant: pending[id] } : null;
+ updateGo();
render();
}
+function updateGo() {
+ const n = Object.keys(pending).length;
+ document.getElementById('gobtn').disabled = n === 0;
+ document.getElementById('gobtn').textContent = n ? `GO — save ${n} pick${n>1?'s':''}` : 'GO';
+}
+
+async function go() {
+ const btn = document.getElementById('gobtn');
+ btn.disabled = true;
+ const r = await fetch('/api/choose-batch', { method: 'POST',
+ body: JSON.stringify({ choices: pending }) });
+ const res = await r.json();
+ document.getElementById('gomsg').textContent =
+ res.ok ? `✓ ${res.saved} picks saved ${new Date().toLocaleTimeString()} — Claude takes it from here.` : 'save failed — try again';
+ updateGo();
+ btn.disabled = false;
+}
+
async function load() {
designs = await (await fetch('/api/designs')).json();
+ designs.forEach(d => { if (pending[d.id]) d.choice = { variant: pending[d.id] }; });
+ updateGo();
render();
}
sortSel.onchange = render;
diff --git a/server.js b/server.js
index 29e4612..eff506a 100644
--- a/server.js
+++ b/server.js
@@ -33,6 +33,27 @@ const server = http.createServer((req, res) => {
return res.end(JSON.stringify(designs));
}
+ if (url.pathname === '/api/choose-batch' && req.method === 'POST') {
+ let body = '';
+ req.on('data', c => body += c);
+ req.on('end', () => {
+ try {
+ const { choices } = JSON.parse(body); // { id: variantFileOr'none', ... }
+ const out = {};
+ const now = new Date().toISOString();
+ for (const [id, variant] of Object.entries(choices)) {
+ out[id] = { variant, chosen_at: now };
+ }
+ fs.writeFileSync(CHOICES, JSON.stringify(out, null, 2));
+ res.writeHead(200, { 'Content-Type': 'application/json' });
+ res.end(JSON.stringify({ ok: true, saved: Object.keys(out).length }));
+ } catch (e) {
+ res.writeHead(400); res.end(String(e));
+ }
+ });
+ return;
+ }
+
if (url.pathname === '/api/choose' && req.method === 'POST') {
let body = '';
req.on('data', c => body += c);
← 3dd966b Wilderness Waltz -> Campfire Waltz (no wild-root words)
·
back to Wild Wallcoverings
·
auto-save: 2026-07-22T20:16:30 (4 files) — data/designs.json a572e80 →