← back to Wallco Ai
drunk-curator: inch ruler + stretch tile to 24/36/52in print width (buttons+slider, persists); room wall repeat scales to match
66faa13a3c2401a3e4498fa10c8ff9eeb1f040c5 · 2026-05-27 16:08:02 -0700 · Steve Abrams
Files touched
M data/auto-decide-ledger.jsonlM lib/composition-detector.jsM public/admin/drunk-curator.htmlM scripts/auto-decide-drunk.js
Diff
commit 66faa13a3c2401a3e4498fa10c8ff9eeb1f040c5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 27 16:08:02 2026 -0700
drunk-curator: inch ruler + stretch tile to 24/36/52in print width (buttons+slider, persists); room wall repeat scales to match
---
data/auto-decide-ledger.jsonl | 75 +++++++++++++++++++++++++++++++++++++++++
lib/composition-detector.js | 23 +++++++++++--
public/admin/drunk-curator.html | 64 ++++++++++++++++++++++++++++++++++-
scripts/auto-decide-drunk.js | 7 ++--
4 files changed, 162 insertions(+), 7 deletions(-)
diff --git a/data/auto-decide-ledger.jsonl b/data/auto-decide-ledger.jsonl
index c50cd2f..c186d2c 100644
--- a/data/auto-decide-ledger.jsonl
+++ b/data/auto-decide-ledger.jsonl
@@ -2768,3 +2768,78 @@
{"t":"2026-05-27T23:04:09.876Z","id":42289,"verdict":"hot_candidate","n":9,"see":"A repeating pattern of bird and floral motifs on a brown background."}
{"t":"2026-05-27T23:04:12.789Z","id":42291,"verdict":"hot_candidate","n":9,"see":"A repeating floral motif on a blue background."}
{"t":"2026-05-27T23:04:15.824Z","id":42292,"verdict":"hot_candidate","n":12,"see":"A repeating pattern of birds and foliage."}
+{"t":"2026-05-27T23:04:18.911Z","id":42293,"verdict":"hot_candidate","n":6,"see":"A repeating pattern of bird and flower motifs."}
+{"t":"2026-05-27T23:04:21.914Z","id":42294,"verdict":"hot_candidate","n":9,"see":"A repeating pattern of birds and flowers."}
+{"t":"2026-05-27T23:04:24.964Z","id":42295,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif in a diamond grid."}
+{"t":"2026-05-27T23:04:27.988Z","id":42296,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif in vertical stripes."}
+{"t":"2026-05-27T23:04:31.341Z","id":42297,"verdict":"hot_candidate","n":7,"see":"A repeating floral motif in vertical rows."}
+{"t":"2026-05-27T23:04:34.317Z","id":42298,"verdict":"hot_candidate","n":4,"see":"A repeating pattern of birds on branches."}
+{"t":"2026-05-27T23:04:37.057Z","id":42299,"verdict":"hot_candidate","n":9,"see":"A repeating floral damask pattern."}
+{"t":"2026-05-27T23:04:40.041Z","id":42300,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a gray background."}
+{"t":"2026-05-27T23:04:43.329Z","id":42301,"verdict":"hot_candidate","n":9,"see":"A repeating floral pattern with a brown background."}
+{"t":"2026-05-27T23:04:46.278Z","id":42302,"verdict":"hot_candidate","n":12,"see":"A repeating wallpaper pattern of birds on branches."}
+{"t":"2026-05-27T23:04:49.262Z","id":42303,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a brown background."}
+{"t":"2026-05-27T23:04:52.775Z","id":42304,"verdict":"hot_candidate","n":4,"see":"A wallpaper tile with a repeating bird and floral motif."}
+{"t":"2026-05-27T23:04:55.768Z","id":42305,"verdict":"hot_candidate","n":7,"see":"A repeating floral motif on a brown background."}
+{"t":"2026-05-27T23:04:58.811Z","id":42306,"verdict":"hot_candidate","n":4,"see":"This is a wallpaper tile with a repeating bird and floral motif."}
+{"t":"2026-05-27T23:05:01.549Z","id":42307,"verdict":"hot_candidate","n":9,"see":"A repeating floral motif covers the tile."}
+{"t":"2026-05-27T23:05:05.351Z","id":42308,"verdict":"hot_candidate","n":7,"see":"A repeating floral damask pattern."}
+{"t":"2026-05-27T23:05:08.637Z","id":42309,"verdict":"hot_candidate","n":5,"see":"A repeating pattern of birds on branches with flowers."}
+{"t":"2026-05-27T23:05:11.513Z","id":42310,"verdict":"hot_candidate","n":6,"see":"A repeating floral pattern with a brown background."}
+{"t":"2026-05-27T23:05:14.473Z","id":42311,"verdict":"hot_candidate","n":6,"see":"A repeating pattern of birds and flowers."}
+{"t":"2026-05-27T23:05:17.374Z","id":42312,"verdict":"hot_candidate","n":4,"see":"A repeating floral motif within a diamond grid."}
+{"t":"2026-05-27T23:05:20.675Z","id":42313,"verdict":"hot_candidate","n":6,"see":"A repeating pattern of birds and floral motifs."}
+{"t":"2026-05-27T23:05:23.868Z","id":42314,"verdict":"hot_candidate","n":6,"see":"I see a repeating floral pattern."}
+{"t":"2026-05-27T23:05:26.929Z","id":42315,"verdict":"hot_candidate","n":6,"see":"I see a repeating floral pattern."}
+{"t":"2026-05-27T23:05:29.658Z","id":42319,"verdict":"hot_candidate","n":6,"see":"I see a repeating floral pattern."}
+{"t":"2026-05-27T23:05:32.443Z","id":42320,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a gray background."}
+{"t":"2026-05-27T23:05:35.319Z","id":42321,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a blue background."}
+{"t":"2026-05-27T23:05:38.181Z","id":42322,"verdict":"hot_candidate","n":5,"see":"I see a repeating pattern of birds on branches."}
+{"t":"2026-05-27T23:05:40.989Z","id":42323,"verdict":"hot_candidate","n":12,"see":"A repeating floral pattern with a diamond grid."}
+{"t":"2026-05-27T23:05:44.108Z","id":42324,"verdict":"hot_candidate","n":7,"see":"A repeating floral pattern with a gray background."}
+{"t":"2026-05-27T23:05:46.931Z","id":42325,"verdict":"hot_candidate","n":6,"see":"A repeating pattern of leafy branches."}
+{"t":"2026-05-27T23:05:49.947Z","id":42326,"verdict":"hot_candidate","n":25,"see":"A repeating floral pattern with a diamond grid."}
+{"t":"2026-05-27T23:05:52.997Z","id":42328,"verdict":"hot_candidate","n":4,"see":"A repeating floral pattern with blue and beige flowers."}
+{"t":"2026-05-27T23:05:56.140Z","id":42329,"verdict":"hot_candidate","n":6,"see":"A repeating floral damask pattern."}
+{"t":"2026-05-27T23:05:59.477Z","id":42330,"verdict":"hot_candidate","n":6,"see":"A repeating wallpaper pattern with birds and floral motifs."}
+{"t":"2026-05-27T23:06:02.331Z","id":42332,"verdict":"hot_candidate","n":4,"see":"A repeating wallpaper pattern with birds and flowers."}
+{"t":"2026-05-27T23:06:05.332Z","id":42333,"verdict":"hot_candidate","n":9,"see":"A repeating floral damask pattern."}
+{"t":"2026-05-27T23:06:08.458Z","id":42334,"verdict":"hot_candidate","n":4,"see":"A repeating pattern of birds on branches with flowers."}
+{"t":"2026-05-27T23:06:11.741Z","id":42335,"verdict":"hot_candidate","n":8,"see":"A repeating floral and bird motif wallpaper."}
+{"t":"2026-05-27T23:06:14.576Z","id":42337,"verdict":"hot_candidate","n":9,"see":"A repeating floral bouquet pattern."}
+{"t":"2026-05-27T23:06:17.724Z","id":42338,"verdict":"hot_candidate","n":9,"see":"A repeating floral damask pattern."}
+{"t":"2026-05-27T23:06:20.673Z","id":42339,"verdict":"hot_candidate","n":4,"see":"I see a repeating pattern of birds on branches."}
+{"t":"2026-05-27T23:06:23.490Z","id":42340,"verdict":"hot_candidate","n":7,"see":"A repeating floral bouquet pattern."}
+{"t":"2026-05-27T23:06:26.414Z","id":42341,"verdict":"hot_candidate","n":6,"see":"A repeating pattern of floral motifs."}
+{"t":"2026-05-27T23:06:29.337Z","id":42344,"verdict":"hot_candidate","n":6,"see":"A wallpaper tile with a repeating bird and floral motif."}
+{"t":"2026-05-27T23:06:32.384Z","id":42345,"verdict":"hot_candidate","n":6,"see":"I see a repeating floral and bird pattern."}
+{"t":"2026-05-27T23:06:35.371Z","id":42346,"verdict":"hot_candidate","n":6,"see":"A repeating pattern of birds and flowers."}
+{"t":"2026-05-27T23:06:38.555Z","id":42347,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a blue background."}
+{"t":"2026-05-27T23:06:41.523Z","id":42348,"verdict":"hot_candidate","n":4,"see":"A repeating pattern of birds and flowers."}
+{"t":"2026-05-27T23:06:44.376Z","id":42349,"verdict":"hot_candidate","n":5,"see":"A wallpaper tile with a repeating bird and floral motif."}
+{"t":"2026-05-27T23:06:47.366Z","id":42350,"verdict":"hot_candidate","n":9,"see":"A repeating floral motif on a beige background."}
+{"t":"2026-05-27T23:06:50.303Z","id":42351,"verdict":"hot_candidate","n":4,"see":"A repeating wallpaper tile with birds on branches."}
+{"t":"2026-05-27T23:06:53.278Z","id":42352,"verdict":"hot_candidate","n":7,"see":"A repeating floral motif in a wallpaper tile."}
+{"t":"2026-05-27T23:06:56.309Z","id":42354,"verdict":"hot_candidate","n":7,"see":"A repeating floral bouquet pattern."}
+{"t":"2026-05-27T23:06:59.270Z","id":42355,"verdict":"hot_candidate","n":4,"see":"A repeating wallpaper pattern with birds and floral motifs."}
+{"t":"2026-05-27T23:07:02.614Z","id":42356,"verdict":"hot_candidate","n":4,"see":"A repeating pattern of birds on branches."}
+{"t":"2026-05-27T23:07:05.596Z","id":42357,"verdict":"hot_candidate","n":8,"see":"A repeating pattern of leafy branches."}
+{"t":"2026-05-27T23:07:08.448Z","id":42358,"verdict":"hot_candidate","n":5,"see":"A repeating floral motif on a brown background."}
+{"t":"2026-05-27T23:07:11.317Z","id":42359,"verdict":"hot_candidate","n":4,"see":"I see a repeating pattern of birds and flowers."}
+{"t":"2026-05-27T23:07:14.163Z","id":42360,"verdict":"hot_candidate","n":7,"see":"A repeating floral bouquet pattern."}
+{"t":"2026-05-27T23:07:16.976Z","id":42361,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a brown background."}
+{"t":"2026-05-27T23:07:19.954Z","id":42363,"verdict":"hot_candidate","n":4,"see":"A repeating wallpaper pattern with birds and floral motifs."}
+{"t":"2026-05-27T23:07:22.938Z","id":42364,"verdict":"hot_candidate","n":12,"see":"A repeating pattern of flowers, birds, and butterflies within a diamond grid."}
+{"t":"2026-05-27T23:07:26.089Z","id":42366,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif on a brown background."}
+{"t":"2026-05-27T23:07:29.000Z","id":42367,"verdict":"hot_candidate","n":9,"see":"A repeating floral bouquet pattern."}
+{"t":"2026-05-27T23:07:32.010Z","id":42369,"verdict":"hot_candidate","n":9,"see":"A repeating floral motif on a green background."}
+{"t":"2026-05-27T23:07:34.932Z","id":42370,"verdict":"hot_candidate","n":6,"see":"A repeating floral damask pattern."}
+{"t":"2026-05-27T23:07:37.695Z","id":42371,"verdict":"hot_candidate","n":7,"see":"A repeating floral pattern with bouquets."}
+{"t":"2026-05-27T23:07:40.617Z","id":42372,"verdict":"hot_candidate","n":5,"see":"A repeating pattern of birds and flowers."}
+{"t":"2026-05-27T23:07:43.552Z","id":42373,"verdict":"hot_candidate","n":9,"see":"A repeating floral motif on a green background."}
+{"t":"2026-05-27T23:07:46.556Z","id":42374,"verdict":"hot_candidate","n":9,"see":"A repeating floral pattern with a diamond grid."}
+{"t":"2026-05-27T23:07:50.126Z","id":42375,"verdict":"hot_candidate","n":6,"see":"A repeating wallpaper pattern with birds and flowers."}
+{"t":"2026-05-27T23:07:53.130Z","id":42376,"verdict":"hot_candidate","n":12,"see":"A repeating floral motif within a diamond grid."}
+{"t":"2026-05-27T23:07:56.052Z","id":42377,"verdict":"hot_candidate","n":6,"see":"A repeating wallpaper pattern with birds and flowers."}
+{"t":"2026-05-27T23:07:58.999Z","id":42379,"verdict":"hot_candidate","n":6,"see":"A repeating floral motif in a diamond grid."}
+{"t":"2026-05-27T23:08:01.997Z","id":42380,"verdict":"hot_candidate","n":6,"see":"A repeating floral pattern with a brick background."}
diff --git a/lib/composition-detector.js b/lib/composition-detector.js
index e36d239..f91bb8a 100644
--- a/lib/composition-detector.js
+++ b/lib/composition-detector.js
@@ -61,8 +61,10 @@ CENTERED HERO (the defect):
Count the visible instances of the dominant motif. A partial/cropped motif near an edge counts as 1 if you can see it's the same motif. Don't count background florals or small filler — count the primary repeating motif only.
+Also judge LARGE EMPTY AREA: TRUE if a big contiguous region (roughly a third or more of the tile) is flat empty background — a blank band, a void, or a large gap with no motif in it — so the tile reads as half-empty rather than a full all-over field. A textured/tone-on-tone background densely populated with motifs is NOT a large empty area. A single broad blank diagonal/horizontal band IS.
+
Return JSON ONLY (no markdown):
-{"is_repeating_field": <bool>, "instance_count": <int>, "hero_centered": <bool>, "confidence": <0..1>, "what_you_see": "<one short sentence>"}
+{"is_repeating_field": <bool>, "instance_count": <int>, "hero_centered": <bool>, "large_empty_area": <bool>, "confidence": <0..1>, "what_you_see": "<one short sentence>"}
Decision rule for hero_centered: TRUE if there's ONE clearly-dominant motif at or near the centre that's larger than the others (or all alone), with corners showing wrap-fragments or empty ground.
@@ -140,12 +142,14 @@ async function analyzeComposition(input, opts = {}) {
// Coerce field types; tolerate missing fields with sane defaults
const instance_count = Number.isFinite(parsed.instance_count) ? Math.max(0, parseInt(parsed.instance_count, 10)) : 0;
const hero_centered = parsed.hero_centered === true;
+ const large_empty_area = parsed.large_empty_area === true;
// Recompute is_repeating_field locally — don't trust Gemini's boolean math
const is_repeating_field = instance_count >= 3 && !hero_centered;
return {
is_repeating_field,
instance_count,
hero_centered,
+ large_empty_area,
confidence: Number(parsed.confidence) || 0,
what_you_see: String(parsed.what_you_see || '').slice(0, 250),
cost_estimate: 0.0005,
@@ -191,6 +195,9 @@ async function analyzeCompositionStable(input, opts = {}) {
// Mode hero_centered
const heroTrue = votes.filter(v => v.hero_centered).length;
const hero_centered = heroTrue > votes.length / 2;
+ // Mode large_empty_area
+ const voidTrue = votes.filter(v => v.large_empty_area).length;
+ const large_empty_area = voidTrue > votes.length / 2;
const avgConf = votes.reduce((s, v) => s + v.confidence, 0) / votes.length;
// Reason = whichever winning-side vote has highest confidence
const winners = votes.filter(v => v.is_repeating_field === is_repeating_field);
@@ -199,6 +206,7 @@ async function analyzeCompositionStable(input, opts = {}) {
is_repeating_field,
instance_count: median,
hero_centered,
+ large_empty_area,
confidence: avgConf,
consensus: Math.max(trueVotes, falseVotes) / votes.length, // 1.0 unanimous, 0.67 split
unanimous,
@@ -218,8 +226,17 @@ async function gateComposition(input, opts = {}) {
const r = stable ? await analyzeCompositionStable(input, opts) : await analyzeComposition(input, opts);
// Scenic exemption always passes the gate
if (r.scenic_exempt) return { ok: true, result: r, reason: 'scenic-exempt' };
- const ok = !r.hero_centered && r.instance_count >= minInstances;
- return { ok, result: r, reason: ok ? 'pass' : (r.hero_centered ? `centered-hero (n=${r.instance_count})` : `too-few-instances (n=${r.instance_count}<${minInstances})`) };
+ // Open-space guard is OPT-IN (opts.rejectOpenSpace) so the generate/smart-fix
+ // retry loop keeps its original behavior; the triage decider passes it true.
+ const voidReject = opts.rejectOpenSpace === true && r.large_empty_area === true;
+ const ok = !r.hero_centered && r.instance_count >= minInstances && !voidReject;
+ let reason = 'pass';
+ if (!ok) {
+ if (r.hero_centered) reason = `centered-hero (n=${r.instance_count})`;
+ else if (r.instance_count < minInstances) reason = `too-few-instances (n=${r.instance_count}<${minInstances})`;
+ else if (voidReject) reason = `large-empty-void`;
+ }
+ return { ok, result: r, reason };
}
module.exports = { analyzeComposition, analyzeCompositionStable, gateComposition, isScenicCategory, SCENIC_CATEGORIES, GEMINI_MODEL };
diff --git a/public/admin/drunk-curator.html b/public/admin/drunk-curator.html
index a1d2b0e..ece0679 100644
--- a/public/admin/drunk-curator.html
+++ b/public/admin/drunk-curator.html
@@ -49,6 +49,18 @@
.done{text-align:center;color:var(--mut);margin-top:18vh;} .done h2{color:var(--ink);}
.flash{position:fixed;inset:0;pointer-events:none;opacity:0;transition:opacity .25s;font-size:140px;display:flex;align-items:center;justify-content:center;z-index:9;}
.flash.show{opacity:1;}
+ /* repeat-scale control: stretch the tile to a real print width + inch ruler */
+ .scalebar{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--mut);}
+ .scalebar .sb{background:#2a2a2a;color:var(--ink);border:1px solid #444;padding:6px 13px;font-size:12px;font-weight:700;border-radius:8px;}
+ .scalebar .sb.on{background:var(--ink);color:#111;border-color:var(--ink);}
+ .scalebar input[type=range]{width:150px;accent-color:var(--hot);}
+ .scalewrap{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;}
+ .scaleimg{position:relative;background:#000;flex:0 0 auto;box-shadow:0 0 0 1px #333;}
+ .scaleimg .tile{width:100%;height:100%;}
+ .ruler{position:relative;height:15px;flex:0 0 auto;}
+ .ruler .tick{position:absolute;top:0;width:1px;height:5px;background:#7a7a7a;}
+ .ruler .tick.maj{height:10px;background:#cfcfcf;}
+ .ruler .lab{position:absolute;top:8px;transform:translateX(-50%);font:700 8.5px sans-serif;color:#cfcfcf;white-space:nowrap;}
</style>
</head>
<body>
@@ -86,6 +98,43 @@ const ADMIN=new URLSearchParams(location.search).get('admin');
const CATEGORY=new URLSearchParams(location.search).get('category')||'';
const q=s=>ADMIN?(s+(s.includes('?')?'&':'?')+'admin='+encodeURIComponent(ADMIN)):s;
let QUEUE=[], i=0, hot=0, not=0, streak=0, decided={};
+// Repeat-scale preview: show the flat tile stretched to a real print width with an
+// inch ruler. 24/36/52″ are the standard wallpaper/panel widths. Persists.
+let scaleInches = Math.min(52, Math.max(24, parseInt(localStorage.getItem('drunk_scale_in'))||36));
+const WALL_INCHES = 96; // the room pane represents ~an 8ft wall, so the repeat shows at true relative size
+function setScale(n){
+ scaleInches = Math.min(52, Math.max(24, parseInt(n)||36));
+ localStorage.setItem('drunk_scale_in', scaleInches);
+ applyScale();
+}
+// Size the tile so its width = scaleInches at a constant px/inch (52″ fills the pane,
+// smaller widths render proportionally narrower — it literally "stretches"), then
+// lay an inch ruler beneath it and scale the room wall's repeat to match.
+function applyScale(){
+ const slider=document.getElementById('scaleslider'); if(slider) slider.value=scaleInches;
+ document.querySelectorAll('.scalebar .sb').forEach(b=>b.classList.toggle('on', +b.dataset.in===scaleInches));
+ const num=document.getElementById('scalenum'); if(num) num.textContent=scaleInches;
+ const sv=document.getElementById('scaleval'); if(sv) sv.textContent=scaleInches;
+ const wall=document.querySelector('.wall'); if(wall) wall.style.backgroundSize=(scaleInches/WALL_INCHES*100).toFixed(1)+'% auto';
+ const wrap=document.querySelector('.scalewrap'); if(!wrap) return;
+ const pane=wrap.closest('.pane'); if(!pane) return;
+ const avail=Math.max(120, Math.min(pane.clientWidth-12, pane.clientHeight-30)); // square fits both dims, leave room for ruler
+ const ppi=avail/52; // 52″ = the widest, fills the pane
+ const W=Math.round(scaleInches*ppi);
+ const img=document.getElementById('scaleimg'); const ruler=document.getElementById('ruler');
+ if(img){ img.style.width=W+'px'; img.style.height=W+'px'; }
+ if(ruler){
+ ruler.style.width=W+'px';
+ let h='';
+ for(let inch=0; inch<=scaleInches; inch++){
+ const x=(inch*ppi).toFixed(1), maj=(inch%12===0)||inch===scaleInches;
+ h+=`<div class="tick${maj?' maj':''}" style="left:${x}px"></div>`;
+ if(maj) h+=`<div class="lab" style="left:${x}px">${inch}″</div>`;
+ }
+ ruler.innerHTML=h;
+ }
+}
+addEventListener('resize',()=>{ if(document.querySelector('.scalewrap')) applyScale(); });
// Move through the queue WITHOUT deciding — lets Steve go back/forward to review
// or fix items. Clamps to [0, length] (length = the cleared screen).
function go(delta){ if(busy) return; const ni=i+delta; if(ni<0||ni>QUEUE.length) return; i=ni; render(); }
@@ -100,7 +149,12 @@ function render(){
const d=QUEUE[i], src=`/designs/img/by-id/${d.id}`;
$('#stage').innerHTML=`
<div class="panes">
- <div class="pane"><span class="tag">flat tile</span><img class="tile" src="${src}" onerror="this.style.opacity=.25"><div class="eboxes" id="eboxes"></div></div>
+ <div class="pane"><span class="tag">flat tile · <span id="scaleval">${scaleInches}</span>″ wide</span>
+ <div class="scalewrap">
+ <div class="scaleimg" id="scaleimg"><img class="tile" src="${src}" onerror="this.style.opacity=.25"><div class="eboxes" id="eboxes"></div></div>
+ <div class="ruler" id="ruler"></div>
+ </div>
+ </div>
<div class="pane"><span class="tag">room setting</span>
<div class="room">
<div class="wall" style="background-image:url('${src}')"></div>
@@ -109,11 +163,19 @@ function render(){
</div>
</div>
</div>
+ <div class="scalebar">📏 Width
+ <button class="sb" data-in="24" onclick="setScale(24)">24″</button>
+ <button class="sb" data-in="36" onclick="setScale(36)">36″</button>
+ <button class="sb" data-in="52" onclick="setScale(52)">52″</button>
+ <input type="range" id="scaleslider" min="24" max="52" step="1" value="${scaleInches}" oninput="setScale(this.value)">
+ <span><b id="scalenum">${scaleInches}</b>″ repeat · ruler in inches</span>
+ </div>
<div class="meta"><span>#${d.id} · ${d.category||''}</span><span>🕓 ${fmtDate(d.created_at)}</span></div>
<div class="prompt" onclick="this.classList.toggle('open')">${(d.prompt||'').replace(/</g,'<').slice(0,240)}</div>
<div class="nav"><button class="navb" onclick="go(-1)" ${i<=0?'disabled':''}>‹ Prev</button><span class="pos">${i+1} / ${QUEUE.length}${decided[d.id]?' · '+decided[d.id]:''}</span><button class="navb" onclick="go(1)" ${i>=QUEUE.length-1?'disabled':''}>Next ›</button></div>
<div class="btns"><button class="hot" onclick="decide('hot')">🔥 HOT</button><button class="fix" onclick="decide('fix')">🔧 Fix</button><button class="not" onclick="decide('not')">🗑 Trash</button><button class="extract" onclick="extract()">✂️ Extract</button></div>
<div class="hint">🔥/→ hot · 🔧/F fix · 🗑/← trash · ✂️/E extract · ‹ Prev , / [ · Next › . / ] (skip without deciding) · dotted = auto-detected errors</div>`;
+ applyScale(); // size tile to the chosen print width + draw the inch ruler
if(autoIdOn) autoIdCurrent(); // opt-in: only scan when the Auto-ID toggle is on
}
diff --git a/scripts/auto-decide-drunk.js b/scripts/auto-decide-drunk.js
index d2c46c7..5cd1bac 100644
--- a/scripts/auto-decide-drunk.js
+++ b/scripts/auto-decide-drunk.js
@@ -65,7 +65,7 @@ function log(o) { try { fs.appendFileSync(LEDGER, JSON.stringify({ t: new Date()
log({ id: d.id, verdict: 'not', reason: 'file_missing' }); not++; done++; continue;
}
let g;
- try { g = await gateComposition(d.lp, { minInstances: 3, category: d.cat }); }
+ try { g = await gateComposition(d.lp, { minInstances: 3, category: d.cat, rejectOpenSpace: true }); }
catch (e) {
const msg = (e.message || '').toLowerCase();
if (msg.includes('rate') || msg.includes('429') || msg.includes('quota') || msg.includes('overload')) {
@@ -84,11 +84,12 @@ function log(o) { try { fs.appendFileSync(LEDGER, JSON.stringify({ t: new Date()
// publish decision; the loop just surfaces the dense winners.
exec(`UPDATE all_designs SET notes = COALESCE(notes,'') || ' | AUTO_HOT_CANDIDATE' WHERE id=${d.id} AND (notes IS NULL OR notes NOT LIKE '%AUTO_HOT_CANDIDATE%');`);
log({ id: d.id, verdict: 'hot_candidate', n: r.instance_count, see: r.what_you_see }); hot++;
- } else { // single-hero / sparse → NOT → registry (kept)
+ } else { // single-hero / sparse / large-void → NOT → registry (kept)
+ const dtype = g.reason === 'large-empty-void' ? 'auto_decide_open_space' : 'auto_decide_single_hero';
exec(`UPDATE all_designs SET is_published=FALSE, user_removed=TRUE WHERE id=${d.id};`);
exec(
"INSERT INTO wallco_defect_registry (design_id, category, generator, local_path, defect_type, defect_detail, prompt, seed, source) " +
- `SELECT id, category, generator, local_path, 'auto_decide_single_hero', '${esc(g.reason + ' :: ' + (r.what_you_see || ''))}', prompt, seed, 'auto_decide_drunk' FROM all_designs WHERE id=${d.id} ` +
+ `SELECT id, category, generator, local_path, '${dtype}', '${esc(g.reason + ' :: ' + (r.what_you_see || ''))}', prompt, seed, 'auto_decide_drunk' FROM all_designs WHERE id=${d.id} ` +
"ON CONFLICT (design_id, defect_type) DO NOTHING;"
);
log({ id: d.id, verdict: 'not', reason: g.reason, see: r.what_you_see }); not++;
← 6c3d280 drunk-curator: held-HOT toast attaches to body so render() c
·
back to Wallco Ai
·
add one-off re-screen of already-flagged candidates for larg 3327937 →