← back to Wallco Ai
/generator: fix broken 'Run tick NOW' + DW palette slider grid
10447d13e6c80b9f19944ecad713e54b73ea0db7 · 2026-05-13 11:04:13 -0700 · SteveStudio2
Run tick NOW (was broken):
/api/generator/run-now used detached:true with stdio pipes, listening
for 'exit' to respond. Detached + parent owning pipes hangs forever:
exit never bubbled, request hung. Steve clicked, nothing came back.
Now: spawn detached with file-stdio, unref, return immediately with
{ok, started_at, log path}. Logs land in data/logs/run-now-<ts>.log
for later inspection.
DW Orders palette (last 40) slider grid (per Steve's standing 'every
grid gets sort + density slider' rule):
- 'Tile size' range slider [80..240px step 20], default 160, localStorage
persistence ('wallco-palette-density') — same pattern as the catalog
density slider
- Live updates the palette's grid-template-columns minmax
- Swatch markup upgraded: button (clickable) instead of div, shows
product_title + vendor + hex, clicking the swatch appends its hex
into the batch-colors input so palette → generator is one click
- Renamed the redundant palette-status span in the section header
to palette-status-2 (avoid duplicate IDs)
Smoke-tested:
/generator renders 8 markers (palette-density slider + readout + grid)
POST /api/generator/run-now returns immediately with log handle
(started_at + relative log path).
Files touched
Diff
commit 10447d13e6c80b9f19944ecad713e54b73ea0db7
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 13 11:04:13 2026 -0700
/generator: fix broken 'Run tick NOW' + DW palette slider grid
Run tick NOW (was broken):
/api/generator/run-now used detached:true with stdio pipes, listening
for 'exit' to respond. Detached + parent owning pipes hangs forever:
exit never bubbled, request hung. Steve clicked, nothing came back.
Now: spawn detached with file-stdio, unref, return immediately with
{ok, started_at, log path}. Logs land in data/logs/run-now-<ts>.log
for later inspection.
DW Orders palette (last 40) slider grid (per Steve's standing 'every
grid gets sort + density slider' rule):
- 'Tile size' range slider [80..240px step 20], default 160, localStorage
persistence ('wallco-palette-density') — same pattern as the catalog
density slider
- Live updates the palette's grid-template-columns minmax
- Swatch markup upgraded: button (clickable) instead of div, shows
product_title + vendor + hex, clicking the swatch appends its hex
into the batch-colors input so palette → generator is one click
- Renamed the redundant palette-status span in the section header
to palette-status-2 (avoid duplicate IDs)
Smoke-tested:
/generator renders 8 markers (palette-density slider + readout + grid)
POST /api/generator/run-now returns immediately with log handle
(started_at + relative log path).
---
server.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 50 insertions(+), 13 deletions(-)
diff --git a/server.js b/server.js
index 2214d5a..9bd14a2 100644
--- a/server.js
+++ b/server.js
@@ -9031,13 +9031,24 @@ app.post('/api/generator/batch', (req, res) => {
});
app.post('/api/generator/run-now', (req, res) => {
+ // Fire-and-return — generator_tick.js runs ~10-30s and the UI doesn't
+ // need to block the click. Buffer the tail of stdout so the response
+ // can include a snippet; finalize via the runs list later.
+ const logFile = path.join(__dirname, 'data', 'logs', `run-now-${Date.now()}.log`);
+ fs.mkdirSync(path.dirname(logFile), { recursive: true });
+ const out = fs.openSync(logFile, 'a');
const proc = spawn('node', [path.join(__dirname, 'scripts', 'generator_tick.js')], {
- cwd: __dirname, detached: true, stdio: ['ignore', 'pipe', 'pipe'],
+ cwd: __dirname,
+ detached: true,
+ stdio: ['ignore', out, out],
+ });
+ proc.unref();
+ res.json({
+ ok: true,
+ started_at: new Date().toISOString(),
+ log: path.relative(__dirname, logFile),
+ note: 'tick kicked — check the Runs list in ~30-60s, or tail data/logs/' + path.basename(logFile),
});
- let out = '';
- proc.stdout.on('data', d => out += d);
- proc.stderr.on('data', d => out += d);
- proc.on('exit', code => res.json({ ok: code === 0, exit: code, output: out.slice(-2000) }));
});
app.post('/api/generator/refresh-orders', (req, res) => {
@@ -9180,8 +9191,27 @@ ${htmlHeader('')}
<h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:24px 0 10px">Recent runs</h2>
<div id="runs"></div>
- <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:32px 0 10px">DW Orders palette (last 40)</h2>
- <div id="palette" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px"></div>
+ <h2 style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:22px;margin:32px 0 6px">DW Orders palette (last 40)</h2>
+ <div style="display:flex;gap:14px;align-items:center;margin:6px 0 12px;font:11px var(--sans,system-ui)">
+ <span id="palette-status-2" style="color:#555">Loading…</span>
+ <label style="display:inline-flex;align-items:center;gap:8px;letter-spacing:.08em;text-transform:uppercase;color:#888;margin-left:auto">Tile size
+ <input type="range" id="palette-density" min="80" max="240" step="20" value="160" style="flex:0 0 120px">
+ </label>
+ <span id="palette-density-readout" style="font-variant-numeric:tabular-nums;color:#555">160px</span>
+ </div>
+ <div id="palette" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px"></div>
+ <script>
+ (function(){
+ var sl = document.getElementById('palette-density');
+ var grid = document.getElementById('palette');
+ var ro = document.getElementById('palette-density-readout');
+ var saved = parseInt(localStorage.getItem('wallco-palette-density')||'',10);
+ if (saved >= 80 && saved <= 240) sl.value = saved;
+ function paint(v){ grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax('+v+'px, 1fr))'; ro.textContent = v+'px'; }
+ paint(sl.value);
+ sl.addEventListener('input', function(){ paint(this.value); localStorage.setItem('wallco-palette-density', this.value); });
+ })();
+ </script>
</main>
${FOOTER}
@@ -9199,13 +9229,20 @@ async function loadCron() {
}
async function loadPaletteStatus() {
var p = await get('/api/generator/palette');
- document.getElementById('palette-status').textContent = p.length + ' palette swatches indexed from recent DW orders';
+ var msg = p.length + ' palette swatches from recent DW orders';
+ var s1 = document.getElementById('palette-status'); if (s1) s1.textContent = msg;
+ var s2 = document.getElementById('palette-status-2'); if (s2) s2.textContent = msg;
document.getElementById('palette').innerHTML = p.map(function(x){
- return '<div style="text-align:center" title="'+(x.sku||'')+'">' +
- '<div style="aspect-ratio:1;background:'+x.dominant_hex+';border:1px solid rgba(0,0,0,.08);border-radius:6px"></div>' +
- '<div style="font-size:10px;color:#666;margin-top:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+x.dominant_hex+'</div>' +
- '<div style="font-size:9px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+(x.sku||'')+'</div>' +
- '</div>';
+ var title = (x.product_title||x.sku||'').replace(/"/g,'"');
+ var tagSummary = ((x.style_tags||[]).slice(0,3).join(' · '));
+ return '<button type="button" data-hex="'+x.dominant_hex+'" title="'+title+(tagSummary?' — '+tagSummary:'')+'"' +
+ ' onclick="(function(h){var el=document.getElementById(\\'batch-colors\\');if(!el)return;var cur=el.value.trim();el.value=cur?(cur+\\', \\'+h):h;el.focus();})(this.dataset.hex)"' +
+ ' style="text-align:left;background:transparent;border:0;padding:0;cursor:pointer">' +
+ '<div style="aspect-ratio:1;background:'+x.dominant_hex+';border:1px solid rgba(0,0,0,.08);border-radius:6px;transition:transform .15s"></div>' +
+ '<div style="font-size:10px;color:#666;margin-top:4px;font-family:ui-monospace,monospace">'+x.dominant_hex+'</div>' +
+ '<div style="font-size:10px;color:#333;margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+title+'</div>' +
+ (x.vendor ? '<div style="font-size:9px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+x.vendor+'</div>' : '') +
+ '</button>';
}).join('') || '<p style="color:#999;grid-column:1/-1">No palette data yet. Click "Refresh from Shopify" above.</p>';
}
async function loadRecipes() {
← d7d74bc designs: sort buttons (was dropdown) + /merge upload-and-fus
·
back to Wallco Ai
·
/api/generator/refresh-orders: stop hanging on the broken-pi 5bb011a →