← back to Apartmentwallpaper
fix(facets): drive /api/facets from live filter state so chip counts actually update
66f45ffff0570764e4e32f82e058ca9e5db4d7bd · 2026-06-01 12:40:13 -0700 · SteveStudio2
claude-codex debate finding #1/#1a: loadFacets() fetched /api/facets with no
params and ran once at init, so commit 7f2f2b6's drill-down counting always saw
an empty query — chip counts never moved as filters applied (feature dead on the
frontend). Now loadFacets() sends q + selected aesthetic, is re-called from
resetGrid() on every filter change, clears #facets/#footerFacets before
re-rendering (no double-stacked chips), wires the click listener once (container
survives innerHTML clears), preserves the active chip across refreshes, and uses
closest('button') so clicking the count span registers. Verified live: facets
honor aesthetic+q params; limit=-5 clamps to 1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 66f45ffff0570764e4e32f82e058ca9e5db4d7bd
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon Jun 1 12:40:13 2026 -0700
fix(facets): drive /api/facets from live filter state so chip counts actually update
claude-codex debate finding #1/#1a: loadFacets() fetched /api/facets with no
params and ran once at init, so commit 7f2f2b6's drill-down counting always saw
an empty query — chip counts never moved as filters applied (feature dead on the
frontend). Now loadFacets() sends q + selected aesthetic, is re-called from
resetGrid() on every filter change, clears #facets/#footerFacets before
re-rendering (no double-stacked chips), wires the click listener once (container
survives innerHTML clears), preserves the active chip across refreshes, and uses
closest('button') so clicking the count span registers. Verified live: facets
honor aesthetic+q params; limit=-5 clamps to 1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/index.html | 36 ++++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/public/index.html b/public/index.html
index b582137..7ece6f7 100644
--- a/public/index.html
+++ b/public/index.html
@@ -662,35 +662,50 @@ function openDetails(p) {
}
+// Facet chip counts are drill-down: each is recomputed against the live filter
+// state (q + selected aesthetic) so the numbers track the result set, not the
+// whole catalog. Called on init AND from resetGrid() on every filter change.
+let _facetsWired = false;
async function loadFacets() {
+ const fp = new URLSearchParams();
+ if (state.q) fp.set('q', state.q);
+ if (state.facet && state.facet !== 'all') fp.set('aesthetic', state.facet);
let f;
try {
- const r = await fetch('/api/facets');
+ const r = await fetch('/api/facets?' + fp);
if (!r.ok) throw new Error('HTTP ' + r.status);
f = await r.json();
} catch (e) { console.error('loadFacets failed:', e); return; }
const el = document.getElementById('facets');
+ el.innerHTML = ''; // clear before re-render so refreshed counts don't double-stack chips
for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
const b = document.createElement('button');
- b.className = 'chip'; b.dataset.facet = k;
+ b.className = 'chip' + (state.facet === k ? ' active' : ''); // keep the active chip lit across refreshes
+ b.dataset.facet = k;
b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
el.appendChild(b);
}
- el.addEventListener('click', e => {
- if (e.target.tagName !== 'BUTTON') return;
- document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
- e.target.classList.add('active');
- state.facet = e.target.dataset.facet;
- resetGrid();
- });
+ // Wire the delegated click handler ONCE — the #facets container survives the
+ // innerHTML clears above, so re-adding it every call would stack listeners.
+ // closest('button') so clicking the count <span> still registers the chip.
+ if (!_facetsWired) {
+ el.addEventListener('click', e => {
+ const btn = e.target.closest('button');
+ if (!btn || !el.contains(btn)) return;
+ state.facet = btn.dataset.facet;
+ resetGrid();
+ });
+ _facetsWired = true;
+ }
document.getElementById('totalCount').textContent = f.total;
document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
// Footer aesthetic links
const footerEl = document.getElementById('footerFacets');
+ footerEl.innerHTML = ''; // clear before re-render (same double-stack guard)
for (const [k] of Object.entries(f.aesthetics)) {
const b = document.createElement('button');
b.textContent = LABELS[k] || k;
- b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+ b.onclick = () => { state.facet = k; resetGrid(); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
footerEl.appendChild(b);
}
}
@@ -699,6 +714,7 @@ function resetGrid() {
state.page = 1; state.exhausted = false;
document.getElementById('grid').innerHTML = '';
document.getElementById('loading').textContent = 'Loading…';
+ loadFacets(); // refresh drill-down chip counts for the new filter state
loadGridPage();
}
← 0ac44de fix(theme2): sticky data-theme-preview flag so Theme 1 isn't
·
back to Apartmentwallpaper
·
refactor(theme): extract page-theme to shared css/js; replac 8f5a5fa →