← back to Reid Witlin Onboarding
Fix preview.py %-format crash (width:100% broke str%); token-replace; regen clean preview.html
9dd09faba03ea0eb3edfed33c29db807a11d5261 · 2026-07-30 16:42:17 -0700 · Steve
Files touched
M preview.htmlM preview.py
Diff
commit 9dd09faba03ea0eb3edfed33c29db807a11d5261
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 30 16:42:17 2026 -0700
Fix preview.py %-format crash (width:100% broke str%); token-replace; regen clean preview.html
---
preview.html | 16 ++++++++++------
preview.py | 14 +++++++++-----
2 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/preview.html b/preview.html
index 65fc38a..09abd4c 100644
--- a/preview.html
+++ b/preview.html
@@ -13,8 +13,10 @@ select,input[type=search]{font:13px inherit;padding:5px 8px;border:1px solid #d4
#grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px;padding:20px}
.card{background:#fff;border:1px solid #e4e4e7;border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
.card.held{border-color:#f59e0b;border-width:2px}
-.thumb{aspect-ratio:1;background:#fafafa center/cover no-repeat;border-bottom:1px solid #f1f1f4}
-.thumb.none{display:flex;align-items:center;justify-content:center;color:#a1a1aa;font-size:12px}
+.thumb{aspect-ratio:1;width:100%;background:#fafafa center/cover no-repeat;border-bottom:1px solid #f1f1f4}
+img.thumb{object-fit:cover;display:block}
+.thumb.none{display:flex;align-items:center;justify-content:center;color:#a1a1aa;font-size:12px;text-align:center}
+.card.img-failed{border-color:#ef4444;border-width:2px}
.body{padding:10px 12px;display:flex;flex-direction:column;gap:5px}
.title{font-weight:600;font-size:14px}
.sku{font:11px ui-monospace,monospace;color:#71717a}
@@ -26,7 +28,7 @@ select,input[type=search]{font:13px inherit;padding:5px 8px;border:1px solid #d4
</style></head><body>
<header>
<h1>Reid Witlin QA</h1>
- <span class="meta">859 ready · <b style="color:#b45309">22 held</b> · generated Jul 30, 2026, 4:38 PM</span>
+ <span class="meta">859 ready · <b style="color:#b45309">22 held</b> · generated Jul 30, 2026, 4:42 PM</span>
<label>Sort <select id="sort">
<option value="natural">Newest (natural)</option>
<option value="title">Title A→Z</option>
@@ -55,18 +57,20 @@ function specLine(r){
function render(){
const q=$('q').value.toLowerCase().trim(), heldonly=$('heldonly').checked;
let rows=DATA.filter(r=>!heldonly||r._pile==='held');
- if(q) rows=rows.filter(r=>(r.title+' '+r.sku+' '+r.tags).toLowerCase().includes(q));
+ if(q) rows=rows.filter(r=>(r.title+' '+r.sku+' '+r.tags+' '+r.town+' '+r.colorway).toLowerCase().includes(q));
const s=$('sort').value;
+ // least-trusted first, so "sort by confidence" bubbles the dodgy images up
+ const confRank={'collision-held':0,'unconfirmed-held':1,none:2,prefix:3,'vendor-js':4,exact:5};
const cmp={title:(a,b)=>a.title.localeCompare(b.title),sku:(a,b)=>a.sku.localeCompare(b.sku),
town:(a,b)=>(a.town+a.colorway).localeCompare(b.town+b.colorway),
- conf:(a,b)=>a.image_confidence.localeCompare(b.image_confidence),
+ conf:(a,b)=>(confRank[a.image_confidence]??9)-(confRank[b.image_confidence]??9),
pile:(a,b)=>(a._pile==='held'?0:1)-(b._pile==='held'?0:1)};
if(cmp[s]) rows=rows.slice().sort(cmp[s]);
document.documentElement.style.setProperty('--cols',$('dens').value);
grid.innerHTML=rows.map(r=>{
const img=r.image_url&&r.image_url.startsWith('http');
return `<div class="card ${r._pile==='held'?'held':''}">
- ${img?`<div class="thumb" style="background-image:url('${r.image_url}')"></div>`
+ ${img?`<img class="thumb" loading="lazy" src="${r.image_url}" onerror="this.closest('.card').classList.add('img-failed');this.replaceWith(Object.assign(document.createElement('div'),{className:'thumb none',textContent:'image failed'}))">`
:`<div class="thumb none">no image</div>`}
<div class="body">
<span class="title">${r.title||'(untitled)'}</span>
diff --git a/preview.py b/preview.py
index a9b831d..6b8cc95 100644
--- a/preview.py
+++ b/preview.py
@@ -30,7 +30,7 @@ def main():
gen = datetime.datetime.now().strftime("%b %d, %Y, %-I:%M %p")
html = """<!doctype html><html><head><meta charset="utf-8">
-<title>Reid Witlin QA — %d ready / %d held</title>
+<title>Reid Witlin QA — __READY__ ready / __HELD__ held</title>
<style>
:root{--cols:5}
*{box-sizing:border-box}
@@ -59,7 +59,7 @@ img.thumb{object-fit:cover;display:block}
</style></head><body>
<header>
<h1>Reid Witlin QA</h1>
- <span class="meta">%d ready · <b style="color:#b45309">%d held</b> · generated %s</span>
+ <span class="meta">__READY__ ready · <b style="color:#b45309">__HELD__ held</b> · generated __GEN__</span>
<label>Sort <select id="sort">
<option value="natural">Newest (natural)</option>
<option value="title">Title A→Z</option>
@@ -74,7 +74,7 @@ img.thumb{object-fit:cover;display:block}
</header>
<div id="grid"></div>
<script>
-const DATA = %s;
+const DATA = __DATA__;
const grid = document.getElementById('grid');
const $ = id => document.getElementById(id);
function save(){localStorage.setItem('rw_qa',JSON.stringify({s:$('sort').value,d:$('dens').value}))}
@@ -114,8 +114,12 @@ function render(){
}
['sort','dens','q','heldonly'].forEach(id=>$(id).addEventListener('input',render));
restore();render();
-</script></body></html>""" % (
- len(ready), len(held), len(ready), len(held), gen, json.dumps(data))
+</script></body></html>"""
+ # token replace (not % / .format — the CSS uses '%' and the JS uses '{}')
+ html = (html.replace("__READY__", str(len(ready)))
+ .replace("__HELD__", str(len(held)))
+ .replace("__GEN__", gen)
+ .replace("__DATA__", json.dumps(data)))
open(os.path.join(HERE, "preview.html"), "w").write(html)
print(json.dumps({"ready": len(ready), "held": len(held),
← 72892c7 Cody-gate cycle3: <img> lazy+onerror (404->red card, perf),
·
back to Reid Witlin Onboarding
·
Cycle 3 log + loop PAUSE idle-on-gated (safe backlog exhaust 7aca80f →