← back to Wallco Ai
defect-registry viewer: accurate filtered_total + Load-more paging (cap 8000) + root-cause summary banner (heal 78% / Replicate 17% / ComfyUI 1% clean)
ef42bbe9329237d859b5dd279d110d7588e211f5 · 2026-05-27 09:34:43 -0700 · Steve Abrams
Files touched
M public/admin/defect-registry.htmlM server.js
Diff
commit ef42bbe9329237d859b5dd279d110d7588e211f5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 27 09:34:43 2026 -0700
defect-registry viewer: accurate filtered_total + Load-more paging (cap 8000) + root-cause summary banner (heal 78% / Replicate 17% / ComfyUI 1% clean)
---
public/admin/defect-registry.html | 56 ++++++++++++++++++++++++++++++++-------
server.js | 9 ++++---
2 files changed, 52 insertions(+), 13 deletions(-)
diff --git a/public/admin/defect-registry.html b/public/admin/defect-registry.html
index 752dac3..df900c4 100644
--- a/public/admin/defect-registry.html
+++ b/public/admin/defect-registry.html
@@ -28,6 +28,14 @@
.when { margin-top:5px; color:var(--mut); font-size:10.5px; font-variant-numeric:tabular-nums; }
.id { color:#6b747c; font-size:10px; }
.empty { padding:60px; text-align:center; color:var(--mut); }
+ .summary { display:flex; flex-wrap:wrap; gap:10px; margin-top:9px; }
+ .scard { background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--accent); border-radius:8px; padding:7px 11px; font-size:11.5px; }
+ .scard b { font-size:14px; color:var(--ink); font-variant-numeric:tabular-nums; }
+ .scard .lbl { color:var(--mut); }
+ .scard.good { border-left-color:#7bc96f; }
+ .loadmore { grid-column:1/-1; display:flex; justify-content:center; padding:8px 0 40px; }
+ .loadmore button { background:var(--panel); color:var(--ink); border:1px solid var(--line); border-radius:8px; padding:9px 22px; cursor:pointer; font-size:13px; }
+ .loadmore button:hover { border-color:var(--accent); }
</style>
</head>
<body>
@@ -45,12 +53,32 @@
<label>Density <input type="range" id="density" min="2" max="9" step="1" value="5"></label>
<span class="count" id="count">loading…</span>
</div>
+ <div class="summary" id="summary"></div>
</header>
<div class="grid" id="grid"></div>
<script>
const $ = s => document.querySelector(s);
-let ALL = [], FACETS = {};
+let ALL = [], FACETS = {}, FILTERED_TOTAL = 0;
+const PAGE = 600;
+
+function renderSummary(){
+ const g = FACETS.by_gen || {}, total = FACETS.total || 0;
+ const sum = (...keys) => keys.reduce((n,k)=> n + (g[k]||0), 0);
+ // bucket generators by root cause
+ let heal=0, repl=0, comfy=0;
+ for (const [k,v] of Object.entries(g)){
+ if (/heal|smart-fix/.test(k)) heal+=v;
+ else if (/replicate/.test(k)) repl+=v;
+ else if (k==='comfy') comfy+=v;
+ }
+ const pct = n => total? Math.round(100*n/total) : 0;
+ $('#summary').innerHTML = `
+ <div class="scard"><b>${total.toLocaleString()}</b> <span class="lbl">total defects kept</span></div>
+ <div class="scard"><b>${heal.toLocaleString()}</b> <span class="lbl">heal-generator blur (${pct(heal)}%) → never publish heal output</span></div>
+ <div class="scard"><b>${repl.toLocaleString()}</b> <span class="lbl">raw Replicate SDXL seams (${pct(repl)}%) → use ComfyUI workflow</span></div>
+ <div class="scard good"><b>${comfy.toLocaleString()}</b> <span class="lbl">ComfyUI (${pct(comfy)}%) → the clean engine ✓</span></div>`;
+}
function fmtDate(iso){
if(!iso) return '—';
@@ -68,9 +96,10 @@ function sorter(mode){
}
function render(){
const items = [...ALL].sort(sorter($('#sort').value));
- $('#count').textContent = `${items.length} shown · ${FACETS.total||0} total in registry`;
+ const filterOn = $('#ftype').value || $('#fgen').value;
+ $('#count').textContent = `${items.length.toLocaleString()} of ${FILTERED_TOTAL.toLocaleString()}${filterOn?' (filtered)':''} · ${(FACETS.total||0).toLocaleString()} total in registry`;
if(!items.length){ $('#grid').innerHTML = '<div class="empty">No defect records match.</div>'; return; }
- $('#grid').innerHTML = items.map(d=>`
+ let html = items.map(d=>`
<div class="card">
<img loading="lazy" src="/designs/img/by-id/${d.design_id}" alt="defect ${d.design_id}" onerror="this.style.opacity=.25">
<div class="meta">
@@ -81,6 +110,11 @@ function render(){
<div class="when" title="recorded ${d.recorded_at||''} · created ${d.created_at||''}">🕓 rec ${fmtDate(d.recorded_at)} · made ${fmtDate(d.created_at)}</div>
</div>
</div>`).join('');
+ if(ALL.length < FILTERED_TOTAL){
+ html += `<div class="loadmore"><button id="more">Load more — ${(FILTERED_TOTAL-ALL.length).toLocaleString()} remaining</button></div>`;
+ }
+ $('#grid').innerHTML = html;
+ const mb = document.getElementById('more'); if(mb) mb.onclick = ()=> load(true);
}
function fillFacets(){
const t=$('#ftype'), g=$('#fgen');
@@ -91,21 +125,23 @@ function fillFacets(){
const o=document.createElement('option'); o.value=k; o.textContent=`${k} (${c})`; g.appendChild(o);
});
}
-async function load(){
+async function load(append=false){
const t=$('#ftype').value, g=$('#fgen').value;
- const qs = new URLSearchParams({ limit:'1200' });
+ const offset = append ? ALL.length : 0;
+ const qs = new URLSearchParams({ limit:String(PAGE), offset:String(offset) });
if(t) qs.set('defect_type', t);
if(g) qs.set('generator', g);
- $('#count').textContent='loading…';
+ if(!append) $('#count').textContent='loading…';
const r = await fetch('/api/admin/defect-registry/list?'+qs.toString());
const j = await r.json();
- ALL = j.items||[]; FACETS = j.facets||{};
- if(!$('#ftype').dataset.filled){ fillFacets(); $('#ftype').dataset.filled='1'; }
+ FACETS = j.facets||{}; FILTERED_TOTAL = j.filtered_total||0;
+ ALL = append ? ALL.concat(j.items||[]) : (j.items||[]);
+ if(!$('#ftype').dataset.filled){ fillFacets(); renderSummary(); $('#ftype').dataset.filled='1'; }
render();
}
$('#sort').onchange = render;
-$('#ftype').onchange = load;
-$('#fgen').onchange = load;
+$('#ftype').onchange = ()=>load(false);
+$('#fgen').onchange = ()=>load(false);
$('#density').oninput = e => applyDensity(e.target.value);
(function init(){
const c = localStorage.getItem('dr_cols'); if(c){ $('#density').value=c; }
diff --git a/server.js b/server.js
index 31b4298..8ad162c 100644
--- a/server.js
+++ b/server.js
@@ -1135,7 +1135,7 @@ app.get('/admin/defect-registry', (req, res) => {
app.get('/api/admin/defect-registry/list', (req, res) => {
if (!isAdmin(req)) return res.status(404).json({ error: 'not found' });
- const limit = Math.min(Math.max(parseInt(req.query.limit || '600', 10), 1), 3000);
+ const limit = Math.min(Math.max(parseInt(req.query.limit || '600', 10), 1), 8000);
const offset = Math.max(parseInt(req.query.offset || '0', 10), 0);
const dtype = (req.query.defect_type || '').replace(/[^a-z_]/g, '');
const gen = (req.query.generator || '').replace(/[^a-z0-9.\- ]/gi, '');
@@ -1153,7 +1153,9 @@ app.get('/api/admin/defect-registry/list', (req, res) => {
`LIMIT ${limit} OFFSET ${offset}` +
") t;"
);
- // facet counts for the filter dropdowns
+ // true total for the CURRENT filter (so paging / count label is accurate)
+ const filteredTotal = parseInt(psqlQuery(`SELECT count(*) FROM wallco_defect_registry r ${whereSql};`) || '0', 10);
+ // facet counts for the filter dropdowns + root-cause summary
const facetsJson = psqlQuery(
"SELECT json_build_object(" +
"'by_type', (SELECT COALESCE(json_object_agg(defect_type, c),'{}'::json) FROM (SELECT defect_type, count(*) c FROM wallco_defect_registry GROUP BY 1) a)," +
@@ -1161,7 +1163,8 @@ app.get('/api/admin/defect-registry/list', (req, res) => {
"'total', (SELECT count(*) FROM wallco_defect_registry)" +
");"
);
- res.json({ ok: true, items: JSON.parse(rawJson || '[]'), facets: JSON.parse(facetsJson || '{}') });
+ res.json({ ok: true, items: JSON.parse(rawJson || '[]'), facets: JSON.parse(facetsJson || '{}'),
+ filtered_total: filteredTotal, limit, offset });
} catch (e) {
res.status(500).json({ error: e.message });
}
← a1cbb55 add /admin/defect-registry web viewer over wallco_defect_reg
·
back to Wallco Ai
·
Quality benchmark = top-20 unified-db lines + DW Bespoke; ca 05fa1bc →