← back to Wallco Ai
subject-mismatch: add Sort (root-id/subject) + Density slider, both persist per standing rule; no color-sort/date-chip (API exposes no hex/created_at) [yolo tick4, local-only]
642ed28ec04ff35124a4635fbab051249f57c61a · 2026-06-02 12:30:30 -0700 · Steve Abrams
Files touched
M public/subject-mismatch.html
Diff
commit 642ed28ec04ff35124a4635fbab051249f57c61a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 2 12:30:30 2026 -0700
subject-mismatch: add Sort (root-id/subject) + Density slider, both persist per standing rule; no color-sort/date-chip (API exposes no hex/created_at) [yolo tick4, local-only]
---
public/subject-mismatch.html | 94 ++++++++++++++++++++++++++++++++------------
1 file changed, 68 insertions(+), 26 deletions(-)
diff --git a/public/subject-mismatch.html b/public/subject-mismatch.html
index 79e1b6d..641cbdd 100644
--- a/public/subject-mismatch.html
+++ b/public/subject-mismatch.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Subject-Mismatch Gallery — dropped-subject failures</title>
<style>
- :root { --bg:#fafaf8; --fg:#1a1a1a; --muted:#6a6a6a; --card:#fff; --border:#e8e6e1; --accent:#b3261e; --warn:#c98a00; }
+ :root { --bg:#fafaf8; --fg:#1a1a1a; --muted:#6a6a6a; --card:#fff; --border:#e8e6e1; --accent:#b3261e; --warn:#c98a00; --gold:#c9a14b; --card-min:360px; }
* { box-sizing: border-box; }
body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.5 -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; }
header { padding:22px 28px 16px; border-bottom:1px solid var(--border); background:var(--card); position:sticky; top:0; z-index:10; }
@@ -14,8 +14,12 @@
header .stats { color:var(--muted); font-size:13px; }
header .stats b { color:var(--fg); font-weight:600; }
header .meta { color:var(--muted); font-size:12px; margin-top:6px; }
+ .filters { margin-top:12px; display:flex; gap:8px; flex-wrap:wrap; align-items:center; }
+ .filters select, .filters input { padding:6px 10px; border:1px solid var(--border); border-radius:4px; background:var(--card); font:inherit; }
+ .filters label { font-size:12px; color:var(--muted); margin-right:4px; }
+ .filters input[type=range] { accent-color:var(--gold); vertical-align:middle; padding:0; }
main { padding:22px 28px 64px; }
- .grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(360px, 1fr)); gap:20px; }
+ .grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(var(--card-min), 1fr)); gap:20px; }
.card { background:var(--card); border:1px solid var(--border); border-radius:8px; overflow:hidden; position:relative; }
.card .img { aspect-ratio:1; background:#f0eee8; overflow:hidden; position:relative; }
.card .img img { width:100%; height:100%; object-fit:cover; display:block; }
@@ -38,40 +42,78 @@
<div class="stats">Roots flagged as <b>primary subject dropped</b> by the post-gen vision detector
(<code>lib/subject-detector</code>). Image shows only background/foliage — the animal/object the prompt requested never landed.</div>
<div class="meta">Scanned <b id="scanned">…</b> drunk-animals roots · use <code>node scripts/scan-subject-mismatch.js --limit=N --seed=S</code> to rescan.</div>
+ <div class="filters">
+ <label>Sort</label>
+ <select id="sortBy">
+ <option value="default">Default (scan order)</option>
+ <option value="id_desc">Root ID ↓</option>
+ <option value="id">Root ID ↑</option>
+ <option value="subject">Subject A→Z</option>
+ </select>
+ <label>Density</label>
+ <input type="range" id="density" min="280" max="600" step="20" title="card size">
+ </div>
</header>
<main>
<div class="grid" id="grid"><div class="empty">Loading…</div></div>
</main>
<script>
+const $ = (id) => document.getElementById(id);
+let ALL = [];
+
+// sort + density persist (Steve standing rule: every card grid; both persist).
+// Note: this QA API exposes no created_at / dominant_hex, so no date chip + no color sort.
+$('sortBy').value = localStorage.getItem('subjMismatch_sort') || 'default';
+$('density').value = localStorage.getItem('subjMismatch_density') || '360';
+document.documentElement.style.setProperty('--card-min', $('density').value + 'px');
+$('sortBy').addEventListener('change', () => { localStorage.setItem('subjMismatch_sort', $('sortBy').value); render(); });
+$('density').addEventListener('input', () => {
+ localStorage.setItem('subjMismatch_density', $('density').value);
+ document.documentElement.style.setProperty('--card-min', $('density').value + 'px');
+});
+
+function sorted(rows) {
+ const r = rows.slice(), m = $('sortBy').value;
+ if (m === 'id_desc') r.sort((a, b) => b.root_id - a.root_id);
+ else if (m === 'id') r.sort((a, b) => a.root_id - b.root_id);
+ else if (m === 'subject') r.sort((a, b) => String(a.primary_subject || '').localeCompare(String(b.primary_subject || '')) || (b.root_id - a.root_id));
+ return r; // 'default' keeps scan order
+}
+
+function render() {
+ const grid = $('grid');
+ if (!ALL.length) {
+ grid.innerHTML = '<div class="empty">No flagged designs yet. Run <code>node scripts/scan-subject-mismatch.js</code>.</div>';
+ return;
+ }
+ grid.innerHTML = sorted(ALL).map(p => `
+ <div class="card">
+ <div class="img">
+ <span class="label">${p.primary_subject || '?'} MISSING</span>
+ <img loading="lazy" src="/designs/img/by-id/${p.root_id}?asis=1" alt="root ${p.root_id}">
+ </div>
+ <div class="meta">
+ <div class="row1">
+ <div class="id"><a href="/design/${p.root_id}?asis=1">root #${p.root_id}</a></div>
+ <div class="subject">prompt asked: <span class="missing">${(p.subjects || []).join(', ')}</span></div>
+ </div>
+ <div class="prompt">"${p.prompt_head || ''}"</div>
+ <div class="saw"><b>Vision saw:</b> ${p.what_you_see || '—'}</div>
+ </div>
+ </div>
+ `).join('');
+}
+
async function load() {
try {
const r = await fetch('/api/subject-mismatch');
const d = await r.json();
- document.getElementById('count').textContent = d.count || 0;
- document.getElementById('scanned').textContent = d.scanned || 0;
- const grid = document.getElementById('grid');
- if (!d.items || !d.items.length) {
- grid.innerHTML = '<div class="empty">No flagged designs yet. Run <code>node scripts/scan-subject-mismatch.js</code>.</div>';
- return;
- }
- grid.innerHTML = d.items.map(p => `
- <div class="card">
- <div class="img">
- <span class="label">${p.primary_subject || '?'} MISSING</span>
- <img loading="lazy" src="/designs/img/by-id/${p.root_id}?asis=1" alt="root ${p.root_id}">
- </div>
- <div class="meta">
- <div class="row1">
- <div class="id"><a href="/design/${p.root_id}?asis=1">root #${p.root_id}</a></div>
- <div class="subject">prompt asked: <span class="missing">${(p.subjects || []).join(', ')}</span></div>
- </div>
- <div class="prompt">"${p.prompt_head || ''}"</div>
- <div class="saw"><b>Vision saw:</b> ${p.what_you_see || '—'}</div>
- </div>
- </div>
- `).join('');
+ $('count').textContent = d.count || 0;
+ $('scanned').textContent = d.scanned || 0;
+ ALL = d.items || [];
+ render();
} catch (e) {
- document.getElementById('grid').innerHTML = '<div class="empty">Failed: ' + e.message + '</div>';
+ $('grid').innerHTML = '<div class="empty">Failed: ' + e.message + '</div>';
}
}
load();
← 1256df9 yolo loop tick 4: smoke-test health check (focus B)
·
back to Wallco Ai
·
yolo-backlog: T4 done — N4 subject-mismatch sort+density [lo 55e320e →