← back to Fentucci Naturals
fentucci-naturals: href-drill — card data points are filter-links (multi-select, URL-addressable)
c32b78df39ba64c6e97343d6243c8260ce85b195 · 2026-08-01 02:06:16 -0700 · Steve Abrams
Card pattern / color / material / category / tag data points are now <a href="?dim=val">
that ADD that facet to the multi-select selection (href-drill HARD rule); the full facet
state (comma-encoded per dimension) + search are URL-addressable — shareable, deep-linkable,
back-button-safe (popstate/readURL). Click adds the facet in place; cmd/ctrl-click opens
the shareable href. Existing facet checkboxes / search / sort / memo-stock-price actions
untouched.
Verified LOCAL (Playwright + basic auth, :9981): click material 'Wood Veneer' -> ?materials
+ grid 504->27; deep-link restores; back-button->504; canary FLAT->DONE; 0 pageerrors.
CUSTOMER-FACING storefront — local commit only; DEPLOY is gated (pending-approval). grid 8/92. TK-10089.
Files touched
Diff
commit c32b78df39ba64c6e97343d6243c8260ce85b195
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Aug 1 02:06:16 2026 -0700
fentucci-naturals: href-drill — card data points are filter-links (multi-select, URL-addressable)
Card pattern / color / material / category / tag data points are now <a href="?dim=val">
that ADD that facet to the multi-select selection (href-drill HARD rule); the full facet
state (comma-encoded per dimension) + search are URL-addressable — shareable, deep-linkable,
back-button-safe (popstate/readURL). Click adds the facet in place; cmd/ctrl-click opens
the shareable href. Existing facet checkboxes / search / sort / memo-stock-price actions
untouched.
Verified LOCAL (Playwright + basic auth, :9981): click material 'Wood Veneer' -> ?materials
+ grid 504->27; deep-link restores; back-button->504; canary FLAT->DONE; 0 pageerrors.
CUSTOMER-FACING storefront — local commit only; DEPLOY is gated (pending-approval). grid 8/92. TK-10089.
---
public/index.html | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/public/index.html b/public/index.html
index f5a650a..53e554a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -147,6 +147,13 @@ const state = {
prices:loadSet(SETS.prices), images:loadSet(SETS.images),
};
function persistFacets(){ for(const [k,key] of Object.entries(SETS)) localStorage.setItem(key, JSON.stringify([...state[k]])); }
+// ── href-drill: multi-select facet state is URL-addressable (shareable + deep-link + back-button) ──
+const URLDIMS=['materials','patterns','series','categories','colorNames','colors','tags','settlements','statuses','prices','images'];
+const escU=s=>encodeURIComponent(String(s==null?'':s));
+function qstr(over){ const u=[]; for(const k of URLDIMS){ const set=(over&&over[k])?over[k]:state[k]; if(set&&set.size) u.push(k+'='+[...set].map(escU).join(',')); } if(state.q)u.push('q='+escU(state.q)); return u.length?('?'+u.join('&')):location.pathname; }
+function drill(dim,val,label){ if(val==null||val==='')return esc(label!=null?label:val); const s=new Set(state[dim]); s.add(String(val)); return `<a class="drill" data-filter="${dim}" data-v="${esc(val)}" href="${esc(qstr({[dim]:s}))}" title="Filter to ${esc(val)}">${esc(label!=null?label:val)}</a>`; }
+function readURL(){ const u=new URLSearchParams(location.search); if(![...u.keys()].length) return; for(const k of URLDIMS){ const v=u.get(k); state[k]=new Set(v?v.split(',').filter(Boolean):[]); } state.q=(u.get('q')||''); const qi=document.getElementById('q'); if(qi)qi.value=state.q; }
+function writeURL(push){ history[push?'pushState':'replaceState']({},'',qstr()); }
const rowUI = {
matRow:{expand:false,find:''}, patternRow:{expand:false,find:'',typeahead:true}, seriesRow:{expand:false,find:'',typeahead:true},
catRow:{expand:false,find:''}, colorRow:{expand:false,find:''}, colorNameRow:{expand:false,find:'',typeahead:true},
@@ -195,13 +202,14 @@ async function load(){
document.getElementById('meta').textContent = p.count+' colorways · '+new Set(ALL.map(x=>x.pattern_name)).size+' patterns';
LAST_FACETS = f;
renderFieldChips();
+ readURL(); // href-drill: land in the facet state carried by the URL (deep-link / shareable)
renderFacets(f);
render();
}
/* ── facet list rows: checkbox + count + type-ahead + cap/expand ── */
function toggleSet(set,val){ set.has(val)?set.delete(val):set.add(val); applyFilterChange(); }
-function applyFilterChange(){ persistFacets(); renderFacets(LAST_FACETS); render(); }
+function applyFilterChange(){ persistFacets(); writeURL(true); renderFacets(LAST_FACETS); render(); }
function facetList(rowId, items, set, opts={}){
const row=document.getElementById(rowId); row.innerHTML='';
const ui=rowUI[rowId]||{expand:false,find:''};
@@ -293,10 +301,10 @@ function render(){
const tags=(p.tags_clean||[]).slice(0,4);
const anyBody = fieldOn.nm||fieldOn.sub||fieldOn.mat||fieldOn.tags||fieldOn.when;
const body = anyBody ? `<div class="body">
- ${fieldOn.nm?`<div class="nm">${esc(p.pattern_name||p.mfr_sku)} ${esc(p.color_name?'· '+p.color_name:'')}</div>`:''}
+ ${fieldOn.nm?`<div class="nm">${p.pattern_name?drill('patterns',p.pattern_name):esc(p.mfr_sku||'')} ${p.color_name?'· '+drill('colorNames',p.color_name):''}</div>`:''}
${fieldOn.sub?`<div class="sub">${fieldOn.dot&&hex?`<span class="dot" style="background:${esc(hex)}"></span>`:''}${esc(p.dw_sku||'')} · ${esc(p.mfr_sku||'')}</div>`:''}
- ${fieldOn.mat?`<div class="sub">${esc(p.material||'')}${p.source_category?' · '+esc(p.source_category):''}</div>`:''}
- ${fieldOn.tags&&tags.length?`<div class="tags">${tags.map(t=>`<span class="tag">${esc(t)}</span>`).join('')}</div>`:''}
+ ${fieldOn.mat?`<div class="sub">${p.material?drill('materials',p.material):''}${p.source_category?' · '+drill('categories',p.source_category):''}</div>`:''}
+ ${fieldOn.tags&&tags.length?`<div class="tags">${tags.map(t=>`<span class="tag">${drill('tags',t)}</span>`).join('')}</div>`:''}
${fieldOn.when?`<div class="when" title="${esc(p.created_at||'')}">🕓 ${fmtDate(p.created_at)}</div>`:''}
</div>`:'';
return `<div class="card" data-sku="${esc(p.dw_sku||'')}" data-mfr="${esc(p.mfr_sku||'')}" data-title="${esc((p.pattern_name||'')+(p.color_name?' '+p.color_name:''))}">
@@ -332,7 +340,11 @@ function toast(msg, ok){
/* ── top-bar wiring ── */
const qEl=document.getElementById('q');
-qEl.oninput=()=>{ state.q=qEl.value.trim().toLowerCase(); render(); };
+qEl.oninput=()=>{ state.q=qEl.value.trim().toLowerCase(); writeURL(false); render(); };
+// a card data point (pattern / color / material / category / tag) is an href-drill atom → adds that facet + reflects into the URL
+document.addEventListener('click',e=>{ const d=e.target.closest('a.drill[data-filter]'); if(!d)return; if(e.metaKey||e.ctrlKey||e.shiftKey||e.button===1)return; e.preventDefault(); e.stopPropagation();
+ const dim=d.dataset.filter,v=d.dataset.v; if(state[dim]){ state[dim].add(v); applyFilterChange(); } });
+addEventListener('popstate',()=>{ const u=new URLSearchParams(location.search); for(const k of URLDIMS) state[k]=new Set((u.get(k)||'').split(',').filter(Boolean)); state.q=u.get('q')||''; qEl.value=state.q; persistFacets(); renderFacets(LAST_FACETS); render(); });
const sortEl=document.getElementById('sort'); sortEl.value=state.sort;
sortEl.onchange=e=>{ state.sort=e.target.value; localStorage.setItem('ftc_sort',state.sort); render(); };
const dirBtn=document.getElementById('dir');
← ed29985 auto-save: 2026-07-28T05:27:36 (2 files) — logs/golive-launc
·
back to Fentucci Naturals
·
add .deploy.conf to .gitignore (TK-10123) 6ef3ae9 →