[object Object]

← 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

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 →