[object Object]

← back to Flock Fix Viewer

non-astek flock (b): verified specs to 29 real-brand flock (DG 9 + Osborne 20 Regency); per-key metafield types; reversible

93a9bfdb7e67a1d2529560ca9b1c49cd5d0b2d33 · 2026-09-02 14:50:50 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8mCr1GHE1PGSWJd1yydnh

Files touched

Diff

commit 93a9bfdb7e67a1d2529560ca9b1c49cd5d0b2d33
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 2 14:50:50 2026 -0700

    non-astek flock (b): verified specs to 29 real-brand flock (DG 9 + Osborne 20 Regency); per-key metafield types; reversible
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01A8mCr1GHE1PGSWJd1yydnh
---
 spec-osborne-dg-snapshot.json                  | 263 ++++++++++++++++++++++++
 spec-osborne-dg.mjs                            | 113 +++++++++++
 strip-mistag.mjs                               |  40 ++++
 theme-backup/spec-sheet-button.PRE-SQFT.liquid | 271 +++++++++++++++++++++++++
 4 files changed, 687 insertions(+)

diff --git a/spec-osborne-dg-snapshot.json b/spec-osborne-dg-snapshot.json
new file mode 100644
index 0000000..239916f
--- /dev/null
+++ b/spec-osborne-dg-snapshot.json
@@ -0,0 +1,263 @@
+[
+ {
+  "id": "gid://shopify/Product/6610816991283",
+  "handle": "chennai-cobalt-blue-wallpaper",
+  "vendor": "Designers Guild",
+  "pattern": "chennai",
+  "oldDesc": "<p>This is a luxurious flock velvet wallcovering in shades of blue. The deep colors and soft texture create a sophisticated and elegant mood.</p>",
+  "oldWidth": null,
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/6610836848691",
+  "handle": "dwdg-985631",
+  "vendor": "Designers Guild",
+  "pattern": "chennai",
+  "oldDesc": "<p>This is a luxurious velvet wallcovering with a plaid pattern. The fuschia pink color palette creates a bold and glamorous statement.</p>",
+  "oldWidth": "19.75 in",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/6610836881459",
+  "handle": "dwdg-985641",
+  "vendor": "Designers Guild",
+  "pattern": "chennai",
+  "oldDesc": "<p>This is a black and beige plaid velvet wallcovering. It has a luxurious and sophisticated feel, suitable for adding depth and texture to a room.</p>",
+  "oldWidth": "19.75 in",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/6610836914227",
+  "handle": "dwdg-985651",
+  "vendor": "Designers Guild",
+  "pattern": "chennai",
+  "oldDesc": "<p>This is a luxurious velvet plaid wallcovering in chocolate and beige tones. It evokes a warm, sophisticated, and classic mood.</p>",
+  "oldWidth": "19.75 in",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/6610836979763",
+  "handle": "dwdg-985661",
+  "vendor": "Designers Guild",
+  "pattern": "chennai",
+  "oldDesc": "<p>This is a chocolate brown plaid velvet wallcovering. It evokes a sense of luxury and warmth with a classic, sophisticated style.</p>",
+  "oldWidth": "19.75 in",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/6610837045299",
+  "handle": "dwdg-985761",
+  "vendor": "Designers Guild",
+  "pattern": "chennai",
+  "oldDesc": "<p>This is a luxurious velvet wallcovering with a classic plaid pattern. The deep cobalt blue and subtle sheen create a sophisticated and elegant mood.</p>",
+  "oldWidth": "19.75 in",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7513954156595",
+  "handle": "eur-90320-designer-wallcoverings-los-angeles",
+  "vendor": "Designers Guild",
+  "pattern": "ardassa",
+  "oldDesc": "Ardassa Flock Velvet Scroll 04 Cobalt is a traditional wallcovering rendered in cobalt blue, dark navy and light gray. Its scroll motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "19.75 in",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7514183467059",
+  "handle": "eur-70022-designer-wallcoverings-los-angeles",
+  "vendor": "Designers Guild",
+  "pattern": "mattiazzo",
+  "oldDesc": "Mattiazzo Flock Velvet Azure is a traditional wallcovering rendered in azure blue and dark navy. Its flocked motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": null,
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7514183499827",
+  "handle": "eur-70023-designer-wallcoverings-los-angeles",
+  "vendor": "Designers Guild",
+  "pattern": "mattiazzo",
+  "oldDesc": "Mattiazzo Flock Velvet Damson is a traditional wallcovering rendered in damson and dark purple. Its textured motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": null,
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515288830003",
+  "handle": "eur-71099-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe Green is a traditional wallcovering rendered in dark green, light green and beige. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515288862771",
+  "handle": "eur-71100-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features bold vertical stripes in forest green and off-white, accented by subtle pink undertones. The minimalist design offers a contemporary and clean aesthetic. Ideal for creating a striking visual impact in residential or commercial spaces.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515288895539",
+  "handle": "eur-71101-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe Aqua is a traditional wallcovering rendered in aqua, light gray and dark gray. Its striped motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515288928307",
+  "handle": "eur-71102-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features bold vertical stripes in light blue and navy, separated by thin off-white lines. The design evokes a sense of calm and order, making it suitable for coastal or Scandinavian-inspired interiors. Ideal for creating a serene atmosphere in bedrooms, bathrooms, or living areas.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515288961075",
+  "handle": "eur-71103-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe Navy is a traditional wallcovering rendered in navy blue, light blue and dark gray. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515288993843",
+  "handle": "eur-71104-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe Black is a traditional wallcovering rendered in black and dark gray. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289026611",
+  "handle": "eur-71105-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features a classic stripe pattern in muted shades of blue and peach. The design evokes a sense of calm and understated elegance, suitable for a variety of spaces. Recommended for bedrooms, living rooms, or any area where a touch of sophistication is desired.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289059379",
+  "handle": "eur-71106-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe is a traditional wallcovering rendered in beige and brown. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289092147",
+  "handle": "eur-71107-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe is a traditional wallcovering rendered in beige, brown and cream. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289124915",
+  "handle": "eur-71108-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features a classic stripe pattern in a palette of soft, neutral tones. The design evokes a sense of calm and understated elegance, perfect for creating a serene atmosphere. Ideal for bedrooms, living rooms, or any space where a touch of refined simplicity is desired.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289157683",
+  "handle": "eur-71109-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe Blush Pink is a traditional wallcovering rendered in blush pink and dark pink. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289190451",
+  "handle": "eur-71110-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features bold vertical stripes in pale pink and burgundy, separated by thin gold lines. The design offers a modern and clean aesthetic. Recommended for adding a touch of sophistication to any interior space.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289223219",
+  "handle": "eur-71111-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe Red is a traditional wallcovering rendered in red and dark red. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289255987",
+  "handle": "eur-71112-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features bold vertical stripes in dark brown and beige, separated by thin pink lines. The design evokes a sense of classic elegance and understated sophistication. Ideal for creating a refined atmosphere in living rooms, bedrooms, or studies.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289288755",
+  "handle": "eur-71113-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe is a traditional wallcovering rendered in beige, dark brown and light brown. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289321523",
+  "handle": "eur-71114-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe is a traditional wallcovering rendered in beige and brown. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289354291",
+  "handle": "eur-71115-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features bold vertical stripes in golden brown and off-white, creating a clean and modern aesthetic. The minimalist design and neutral color palette evoke a sense of calm and sophistication. Ideal for adding visual interest to bedrooms, living rooms, or hallways.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289387059",
+  "handle": "eur-71116-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe is a traditional wallcovering rendered in beige and brown. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289419827",
+  "handle": "eur-71117-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "Regency Flock Velvet Stripe is a traditional wallcovering rendered in beige, brown and gold. Its stripe motif brings a timeless, refined feel to the wall. Well suited to refined residential and commercial interiors.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ },
+ {
+  "id": "gid://shopify/Product/7515289452595",
+  "handle": "eur-71118-designer-wallcoverings-los-angeles",
+  "vendor": "Osborne & Little",
+  "pattern": "regency stripe",
+  "oldDesc": "This wallcovering features bold vertical stripes in black and gray, separated by thin beige lines. The minimalist design creates a modern and sophisticated look. Ideal for adding a touch of contemporary elegance to any interior space.",
+  "oldWidth": "10m x 52cm (11yds x 20 ins)",
+  "oldLength": null
+ }
+]
\ No newline at end of file
diff --git a/spec-osborne-dg.mjs b/spec-osborne-dg.mjs
new file mode 100644
index 0000000..d12f107
--- /dev/null
+++ b/spec-osborne-dg.mjs
@@ -0,0 +1,113 @@
+// (b) Apply VERIFIED manufacturer specs to Osborne & Designers Guild active flock products.
+// DG specs from designers_guild_catalog + designersguild.com; Osborne from osborne_catalog + osborneandlittle.com.
+// Pattern-level EXACT match (colorway-agnostic; all colorways of a pattern share these). Reversible+ledgered+snapshot.
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
+const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
+const SNAP=new URL('./spec-osborne-dg-snapshot.json',import.meta.url);
+const DRY=!process.argv.includes('--apply');
+const API=`https://${SHOP}/admin/api/2024-10`;
+const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const gql=async(q,v={})=>(await (await fetch(`${API}/graphql.json`,{method:'POST',headers:H,body:JSON.stringify({query:q,variables:v})})).json());
+
+// verified per-pattern facts
+const DG={
+ chennai:{w:'20.5 in',rep:'20.75 Inches',len:'32.8 ft',wi:20.5,lenft:32.8},
+ ardassa:{w:'20.5 in',rep:'20.5 Inches',len:'32.8 ft',wi:20.5,lenft:32.8},
+ mattiazzo:{w:'27 in',rep:'25.25 Inches',len:'32.8 ft',wi:27,lenft:32.8},
+ fioravanti:{w:'20.5 in',rep:'28.25 Inches',len:'32.8 ft',wi:20.5,lenft:32.8},
+};
+const DG_UNIFORM={match:'Straight',application:'Paste the Wall',backing:'Non-Woven',removal:'Strippable',print:'Flock',material:'Flocked Velvet',fire:'ASTM E84 Class A'};
+const OSB={w:'20.5 in',len:'10m / 11 yd (single roll)',wi:20.5,lenft:32.8};
+const OSB_UNIFORM={application:'Paste the Wall',backing:'Non-Woven',removal:'Strippable',print:'Flock',material:'Flocked Velvet'};
+const r2=n=>Math.round(n*100)/100;
+
+function trim1(html){ if(!html) return null;
+  const parts=html.split(/<\/p>/i).map(s=>s.replace(/<p[^>]*>/i,'').trim()).filter(s=>s.replace(/<[^>]+>/g,'').trim().length>3);
+  if(parts.length<=1) return null;
+  return '<p>'+parts[0]+'</p>';
+}
+function dgKey(t){t=(t||'').toLowerCase(); for(const k of Object.keys(DG)) if(t.includes(k)) return k; return null;}
+
+async function fetchVendor(v){
+  let cur=null,all=[];
+  const Q=`query($c:String){ products(first:100, query:${JSON.stringify(`tag:'Flock Velvet' vendor:'${v}' status:active`)}, after:$c){ pageInfo{hasNextPage endCursor} edges{node{ id handle title descriptionHtml ow:metafield(namespace:"global",key:"width"){value} ol:metafield(namespace:"global",key:"length"){value} }}}}`;
+  do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));break;}all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
+  return all.map(e=>e.node).filter(n=>/flock/i.test(n.title));
+}
+// keys with a store definition requiring multi_line
+const MULTILINE=new Set(['custom.material','custom.google_product_category']);
+const typeFor=(ns,k)=>MULTILINE.has(ns+'.'+k)?'multi_line_text_field':'single_line_text_field';
+function mfSet(id,pairs){
+  const mf=[];
+  for(const [gk,ck,val] of pairs){ if(val==null) continue;
+    mf.push({ownerId:id,namespace:'global',key:gk,type:typeFor('global',gk),value:val});
+    if(ck) mf.push({ownerId:id,namespace:'custom',key:ck,type:typeFor('custom',ck),value:val});
+  }
+  return mf;
+}
+(async()=>{
+  const snap=[]; let applied=0, skipped=[];
+  const dg=await fetchVendor('Designers Guild');
+  console.log('DG active flock:',dg.length);
+  for(const n of dg){
+    const k=dgKey(n.title);
+    if(!k){ skipped.push('DG no-pattern: '+n.title.slice(0,40)); continue; }
+    const s=DG[k];
+    const sr=r2((s.wi/12)*s.lenft), dr=r2(sr*2);
+    const pairs=[
+      ['width',null,s.w],['length',null,s.len],
+      ['repeat','pattern_repeat',s.rep],
+      ['Match','match_type',DG_UNIFORM.match],
+      ['application','application',DG_UNIFORM.application],
+      ['Substrate','backing',DG_UNIFORM.backing],
+      ['removability','removal',DG_UNIFORM.removal],
+      ['print_type','print_type',DG_UNIFORM.print],
+      ['material','material',DG_UNIFORM.material],
+      ['fire_rating','fire_rating',DG_UNIFORM.fire],
+      ['sqft_single_roll',null,sr+' Sq Ft'],['sqft_double_roll',null,dr+' Sq Ft'],
+    ];
+    const newDesc=trim1(n.descriptionHtml);
+    if(DRY){ console.log(`  DG[${k}] ${n.title.slice(0,38)} rep=${s.rep} SR=${sr} descTrim=${newDesc?'Y':'-'}`); continue; }
+    let mf=mfSet(n.id,pairs);
+    mf.push({ownerId:n.id,namespace:'custom',key:'sqft_single_roll',type:'single_line_text_field',value:sr+' Sq Ft'});
+    mf.push({ownerId:n.id,namespace:'custom',key:'sqft_double_roll',type:'single_line_text_field',value:dr+' Sq Ft'});
+    const rr=await gql('mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){userErrors{field message}}}',{mf});
+    const errs=rr?.data?.metafieldsSet?.userErrors; if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
+    if(newDesc){ await gql('mutation($id:ID!,$b:String!){productUpdate(input:{id:$id,descriptionHtml:$b}){userErrors{message}}}',{id:n.id,b:newDesc}); }
+    snap.push({id:n.id,handle:n.handle,vendor:'Designers Guild',pattern:k,oldDesc:n.descriptionHtml||null,oldWidth:n.ow?.value||null,oldLength:n.ol?.value||null});
+    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'vp-dw-commerce:spec-dg',ticket:'TK-11128',action:`DG verified specs (${k}: ${s.w},${s.rep},straight,non-woven,paste-the-wall,Class A,sqft) + 1-para desc on ${n.handle}`,blast_radius:1,undo_cmd:`delete added spec metafields on ${n.id}; restore desc from snapshot`,verify:'PDP spec table complete'})+'\n');
+    applied++; await sleep(220);
+  }
+  const osb=await fetchVendor('Osborne & Little');
+  console.log('Osborne active flock:',osb.length);
+  for(const n of osb){
+    if(!/regency/i.test(n.title)){ skipped.push('OSB non-regency: '+n.title.slice(0,40)); continue; }
+    const sr=r2((OSB.wi/12)*OSB.lenft), dr=r2(sr*2);
+    const pairs=[
+      ['width',null,OSB.w],['length',null,OSB.len],
+      ['application','application',OSB_UNIFORM.application],
+      ['Substrate','backing',OSB_UNIFORM.backing],
+      ['removability','removal',OSB_UNIFORM.removal],
+      ['print_type','print_type',OSB_UNIFORM.print],
+      ['material','material',OSB_UNIFORM.material],
+      ['sqft_single_roll',null,sr+' Sq Ft'],['sqft_double_roll',null,dr+' Sq Ft'],
+    ];
+    const newDesc=trim1(n.descriptionHtml);
+    if(DRY){ console.log(`  OSB ${n.title.slice(0,38)} SR=${sr} descTrim=${newDesc?'Y':'-'}`); continue; }
+    let mf=mfSet(n.id,pairs);
+    mf.push({ownerId:n.id,namespace:'custom',key:'sqft_single_roll',type:'single_line_text_field',value:sr+' Sq Ft'});
+    mf.push({ownerId:n.id,namespace:'custom',key:'sqft_double_roll',type:'single_line_text_field',value:dr+' Sq Ft'});
+    const rr=await gql('mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){userErrors{field message}}}',{mf});
+    const errs=rr?.data?.metafieldsSet?.userErrors; if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
+    if(newDesc){ await gql('mutation($id:ID!,$b:String!){productUpdate(input:{id:$id,descriptionHtml:$b}){userErrors{message}}}',{id:n.id,b:newDesc}); }
+    snap.push({id:n.id,handle:n.handle,vendor:'Osborne & Little',pattern:'regency stripe',oldDesc:n.descriptionHtml||null,oldWidth:n.ow?.value||null,oldLength:n.ol?.value||null});
+    fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'vp-dw-commerce:spec-osborne',ticket:'TK-11128',action:`Osborne Regency verified specs (20.5in,10m single,non-woven flock,paste-the-wall,sqft; repeat/match omitted-unverified) + 1-para desc on ${n.handle}`,blast_radius:1,undo_cmd:`delete added spec metafields on ${n.id}; restore desc`,verify:'PDP spec table (no repeat/match)'})+'\n');
+    applied++; await sleep(220);
+  }
+  if(!DRY) fs.writeFileSync(SNAP,JSON.stringify(snap,null,1));
+  console.log(`${DRY?'DRY':'DONE'} applied=${applied} skipped=${skipped.length}`);
+  if(skipped.length) console.log('  SKIP:',skipped.join(' | '));
+})();
diff --git a/strip-mistag.mjs b/strip-mistag.mjs
new file mode 100644
index 0000000..a4142d2
--- /dev/null
+++ b/strip-mistag.mjs
@@ -0,0 +1,40 @@
+// (a) Reversibly strip 'Flock Velvet' tag from mis-tagged NON-flock products.
+// Non-flock = title/handle/productType does NOT contain flock/flocked/velvet. Preserve all other tags.
+// Snapshot tags before, ledger each with re-add undo. --apply to write.
+import fs from 'fs'; import os from 'os'; import path from 'path';
+const SHOP='designer-laboratory-sandbox.myshopify.com';
+const TOKEN=fs.readFileSync(new URL('./.token',import.meta.url),'utf8').trim();
+const LEDGER=path.join(os.homedir(),'.claude/yolo-queue/executed-reversible/ledger.jsonl');
+const SNAP=new URL('./strip-mistag-snapshot.json',import.meta.url);
+const DRY=!process.argv.includes('--apply');
+const API=`https://${SHOP}/admin/api/2024-10`;
+const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const gql=async(q,v={})=>(await (await fetch(`${API}/graphql.json`,{method:'POST',headers:H,body:JSON.stringify({query:q,variables:v})})).json());
+const Q=`query($c:String){ products(first:100, query:"tag:'Flock Velvet' -vendor:'Phillipe Romano'", after:$c){
+  pageInfo{hasNextPage endCursor}
+  edges{node{ id handle title vendor productType status tags }}}}`;
+let cur=null,all=[];
+do{const r=await gql(Q,{c:cur});const p=r?.data?.products;if(!p){console.error(JSON.stringify(r));break;}all=all.concat(p.edges);cur=p.pageInfo.hasNextPage?p.pageInfo.endCursor:null;}while(cur);
+// genuine flock signal: title/handle/type mentions flock OR velvet. Only strip when NEITHER present.
+const flockish=n=>/flock|velvet/i.test(n.title||'')||/flock|velvet/i.test(n.handle||'')||/flock|velvet/i.test(n.productType||'');
+const norm=t=>String(t).replace(/["{}]/g,'').trim();
+const FLOCKTAG=t=>{const s=norm(t).toLowerCase();return s==='flock velvet'||s==='material-group-flock-velvet';};
+const targets=all.map(e=>e.node).filter(n=>!flockish(n) && (n.tags||[]).some(FLOCKTAG));
+console.log(`mis-tagged non-flock (no flock/velvet in title/handle/type): ${targets.length}`);
+const snap=[];
+let done=0;
+for(const n of targets){
+  const tags=(n.tags||[]).map(norm);
+  const kept=tags.filter(t=>!FLOCKTAG(t));
+  if(DRY){ console.log(`  ${n.vendor.padEnd(24)} ${n.status.padEnd(8)} ${n.title.slice(0,44)}`); done++; continue; }
+  const r=await gql(`mutation($id:ID!,$tags:[String!]!){productUpdate(input:{id:$id,tags:$tags}){userErrors{message}}}`,{id:n.id,tags:kept});
+  const errs=r?.data?.productUpdate?.userErrors;
+  if(errs&&errs.length){console.log('  FAIL',n.handle,JSON.stringify(errs));continue;}
+  snap.push({id:n.id,handle:n.handle,vendor:n.vendor,oldTags:tags});
+  fs.appendFileSync(LEDGER,JSON.stringify({ts:new Date().toISOString(),agent:'vp-dw-commerce:strip-mistag-flock',ticket:'TK-11128',action:`strip 'Flock Velvet' tag from ${n.handle} [${n.vendor}] (non-flock)`,blast_radius:1,undo_cmd:`productUpdate ${n.id} re-add 'Flock Velvet' tag`,verify:'product no longer tagged Flock Velvet'})+'\n');
+  done++; if(done%15===0)console.log(`  …${done}/${targets.length}`);
+  await sleep(200);
+}
+if(!DRY) fs.writeFileSync(SNAP,JSON.stringify(snap,null,1));
+console.log(`${DRY?'DRY':'DONE'} · ${done}/${targets.length}`);
diff --git a/theme-backup/spec-sheet-button.PRE-SQFT.liquid b/theme-backup/spec-sheet-button.PRE-SQFT.liquid
new file mode 100644
index 0000000..f2f2a98
--- /dev/null
+++ b/theme-backup/spec-sheet-button.PRE-SQFT.liquid
@@ -0,0 +1,271 @@
+{% comment %}
+  Spec Sheet Button — INLINE PDF download (no page navigation, no iframe).
+
+  FIX HISTORY
+  -----------
+  v1 (broken): fetch('/pages/spec-sheet') + DOMParser + html2canvas. DOMParser
+    never runs scripts, so #pdf-content was always the "Loading Product..."
+    placeholder -> 0-dim canvas -> jsPDF threw -> "Error".
+
+  v2 (broken on LIVE): hidden same-origin iframe -> /pages/spec-sheet -> call its
+    downloadPDF(). DEAD ON ARRIVAL: the storefront sends `X-Frame-Options: DENY`
+    + CSP `frame-ancestors 'none'` STORE-WIDE (verified 2026-06-23 on /, /pages/*,
+    /collections/*, /products/*). DENY blocks ALL framing — including same-origin —
+    so the iframe never renders and contentDocument is inaccessible.
+
+  v3 (this version — works against live CSP):
+    Build the spec-sheet DOM RIGHT HERE on the product page from Liquid-injected
+    product data + metafields (no fetch, no iframe, no /pages/spec-sheet
+    dependency). On click we lazy-load jsPDF + html2canvas from cdnjs (the live
+    CSP has NO script-src/default-src directive, so cdnjs is allowed — verified),
+    render the offscreen node with html2canvas (useCORS:true; the product image
+    is on cdn.shopify.com which returns `access-control-allow-origin: *`, so the
+    canvas does NOT taint), and jsPDF.save() it. One click, fully self-contained.
+
+  The standalone /pages/spec-sheet page still works for a direct visit (it has
+  {% layout none %} so it's a clean print page), but the BUTTON no longer needs it.
+{% endcomment %}
+
+{%- liquid
+  assign p = product
+  assign mf = product.metafields
+  # ---- Color: real color name only (spreadsheet/metafield/variant option), never AI ----
+  assign spec_color = ''
+  if mf.custom.color != blank
+    assign spec_color = mf.custom.color
+  elsif mf.global.Color != blank
+    assign spec_color = mf.global.Color
+  elsif mf.dwc.color != blank
+    assign spec_color = mf.dwc.color
+  endif
+  # ---- Mfr SKU ----
+  assign mfr_sku = ''
+  if mf.custom.manufacturer_sku != blank
+    assign mfr_sku = mf.custom.manufacturer_sku
+  elsif mf.dwc.manufacturer_sku != blank
+    assign mfr_sku = mf.dwc.manufacturer_sku
+  endif
+  # ---- DW SKU (strip -Sample) ----
+  assign dw_sku = p.selected_or_first_available_variant.sku | default: p.variants.first.sku
+  assign dw_sku = dw_sku | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+-%}
+
+<div class="spec-sheet-controls">
+  <button type="button"
+          class="spec-sheet-btn pdf-download-btn"
+          data-dw-spec-trigger
+          aria-label="Download spec sheet PDF for {{ p.title | escape }}">
+    🖨️ Download PDF
+  </button>
+</div>
+
+{%- comment %} Offscreen, fully-rendered spec sheet built from Liquid data {% endcomment -%}
+<div id="dw-spec-sheet" aria-hidden="true"
+     style="position:fixed;left:-10000px;top:0;width:816px;background:#fff;z-index:-1;">
+  <div class="dw-spec-page">
+    <div class="dw-spec-header">
+      <img class="dw-spec-logo"
+           crossorigin="anonymous"
+           src="https://www.designerwallcoverings.com/cdn/shop/files/Screen_Shot_2019-02-06_at_12.08.24_PM_300x172.png?v=1613539653"
+           alt="Designer Wallcoverings">
+    </div>
+    <div class="dw-spec-body">
+      <h1 class="dw-spec-title">{{ p.title | escape }}</h1>
+      {%- if p.featured_image -%}
+        <img class="dw-spec-img" crossorigin="anonymous"
+             src="{{ p.featured_image | image_url: width: 600 }}"
+             alt="{{ p.title | escape }}">
+      {%- endif -%}
+      <div class="dw-spec-grid">
+        <div class="dw-spec-item"><strong>DW SKU:</strong><span>{{ dw_sku | escape }}</span></div>
+        {%- if spec_color != blank -%}<div class="dw-spec-item"><strong>Color:</strong><span>{{ spec_color | escape }}</span></div>{%- endif -%}
+        {%- assign pname = mf.custom.pattern_name | default: mf.dwc.pattern_name -%}
+        {%- if pname != blank -%}<div class="dw-spec-item"><strong>Pattern Name:</strong><span>{{ pname | escape }}</span></div>{%- endif -%}
+        {%- if p.vendor != blank -%}<div class="dw-spec-item"><strong>Brand:</strong><span>{{ p.vendor | escape }}</span></div>{%- endif -%}
+        {%- assign coll = mf.custom.collection_name | default: mf.global.Collection -%}
+        {%- if coll != blank -%}<div class="dw-spec-item"><strong>Collection:</strong><span>{{ coll | escape }}</span></div>{%- endif -%}
+        {%- assign spec_style = mf.specs.style | default: mf.global.Style -%}
+        {%- if spec_style != blank -%}<div class="dw-spec-item"><strong>Style:</strong><span>{{ spec_style | escape }}</span></div>{%- endif -%}
+        {%- assign comp = mf.custom.material | default: mf.specs.material | default: mf.global.Content | default: mf.global.Contents -%}
+        {%- if comp != blank -%}<div class="dw-spec-item"><strong>Composition:</strong><span>{{ comp | escape }}</span></div>{%- endif -%}
+        {%- assign w = mf.custom.width | default: mf.global.width | default: mf.global.Width -%}
+        {%- if w != blank -%}<div class="dw-spec-item"><strong>Width:</strong><span>{{ w | escape }}</span></div>{%- endif -%}
+        {%- assign len = mf.custom.length | default: mf.global.length | default: mf.global.Length -%}
+        {%- if len != blank -%}<div class="dw-spec-item"><strong>{% if product.vendor == 'Designtex' %}Bolt size:{% else %}Length:{% endif %}</strong><span>{{ len | escape }}</span></div>{%- endif -%}
+        {%- assign rep = mf.custom.pattern_repeat | default: mf.custom.repeat | default: mf.specs.pattern_repeat | default: mf.specs.repeat_v | default: mf.dwc.repeat | default: mf.global.repeat -%}
+        {%- if rep != blank -%}<div class="dw-spec-item"><strong>Repeat:</strong><span>{{ rep | escape }}</span></div>{%- endif -%}
+        {%- assign mt = mf.custom.match_type | default: mf.global.Match | default: mf.global.MATCH -%}
+        {%- if mt != blank -%}<div class="dw-spec-item"><strong>Match:</strong><span>{{ mt | escape }}</span></div>{%- endif -%}
+        {%- assign wt = mf.custom.product_weight | default: mf.global.Weight -%}
+        {%- if wt != blank -%}<div class="dw-spec-item"><strong>Weight:</strong><span>{{ wt | escape }}</span></div>{%- endif -%}
+        {%- assign fin = mf.custom.finish | default: mf.global.Finish | default: mf.global.FINISH -%}
+        {%- if fin != blank -%}<div class="dw-spec-item"><strong>Finish:</strong><span>{{ fin | escape }}</span></div>{%- endif -%}
+        {%- assign care = mf.custom.care | default: mf.global.Cleaning -%}
+        {%- if care != blank -%}<div class="dw-spec-item"><strong>Care:</strong><span>{{ care | escape }}</span></div>{%- endif -%}
+        {%- assign fire = mf.custom.fire_rating | default: mf.global.fire_rating | default: mf.global.FLAMMABILITY -%}
+        {%- if fire != blank -%}<div class="dw-spec-item"><strong>Fire Rating:</strong><span>{{ fire | escape }}</span></div>{%- endif -%}
+        {%- assign origin = mf.custom.origin | default: mf.global.Country -%}
+        {%- if origin != blank -%}<div class="dw-spec-item"><strong>Country of Origin:</strong><span>{{ origin | escape }}</span></div>{%- endif -%}
+        {%- assign uom = mf.custom.unit_of_measure | default: mf.global.unit_of_measure -%}
+        {%- if uom != blank -%}<div class="dw-spec-item"><strong>Sold:</strong><span>{{ uom | escape }}</span></div>{%- endif -%}
+        {%- assign minord = mf.custom.minimum.value | default: mf.dwc.minimum_order_quantity.value | default: mf.global.v_prods_quantity_order_min.value -%}
+        {%- assign ulabel = '' -%}
+        {%- if uom contains 'Double Roll' -%}{%- assign ulabel = 'double rolls' -%}{%- elsif uom contains 'Roll' -%}{%- assign ulabel = 'rolls' -%}{%- elsif uom contains 'Yard' -%}{%- assign ulabel = 'yards' -%}{%- elsif uom contains 'Meter' -%}{%- assign ulabel = 'meters' -%}{%- endif -%}
+        {%- if minord != blank and minord != '1' and minord != 1 -%}<div class="dw-spec-item"><strong>Minimum Order:</strong><span>{{ minord | escape }}{%- if ulabel != blank %} {{ ulabel }}{% endif -%}</span></div>{%- endif -%}
+        {%- comment -%} mfr SKU "Extra Info" row removed 2026-07-16 (DTD verdict A, unanimous): private-label reverse-lookup key — backend metafield only, never on the downloadable spec sheet {%- endcomment -%}
+      </div>
+    </div>
+    <div class="dw-spec-footer">
+      <div class="dw-spec-footer-line company">Designer Wallcoverings Purchasing Agency</div>
+      <div class="dw-spec-footer-line">15442 Ventura Bl #102, Sherman Oaks, CA 91403</div>
+      <div class="dw-spec-footer-line">Phone: (888) 373-4564 | info@designerwallcoverings.com | www.designerwallcoverings.com</div>
+      <div class="dw-spec-footer-line italic">Purchasing Agency for the Wallcovering and Surface Material Industry</div>
+      <div class="dw-spec-footer-line tiny">Spec sheet information valid only when purchased through Designer Wallcoverings Purchasing</div>
+    </div>
+  </div>
+</div>
+
+<script>
+(function () {
+  if (window.__dwSpecPdfInit) return;
+  window.__dwSpecPdfInit = true;
+
+  var JSPDF_SRC = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
+  var H2C_SRC   = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
+
+  function loadScript(src) {
+    return new Promise(function (resolve, reject) {
+      var existing = document.querySelector('script[src="' + src + '"]');
+      if (existing) {
+        if (existing.dataset.loaded === '1') return resolve();
+        existing.addEventListener('load', function () { resolve(); });
+        existing.addEventListener('error', function () { reject(new Error('load fail ' + src)); });
+        return;
+      }
+      var s = document.createElement('script');
+      s.src = src;
+      s.async = true;
+      s.addEventListener('load', function () { s.dataset.loaded = '1'; resolve(); });
+      s.addEventListener('error', function () { reject(new Error('load fail ' + src)); });
+      document.head.appendChild(s);
+    });
+  }
+
+  function ensureLibs() {
+    var need = [];
+    if (typeof window.html2canvas !== 'function') need.push(loadScript(H2C_SRC));
+    if (!(window.jspdf && window.jspdf.jsPDF)) need.push(loadScript(JSPDF_SRC));
+    return Promise.all(need);
+  }
+
+  // Decode the protocol-relative shopify logo to avoid a tainting surprise; the
+  // product image + logo are both cdn.shopify.com (ACAO:*), so useCORS handles them.
+  async function generate(btn) {
+    var node = document.getElementById('dw-spec-sheet');
+    if (!node) throw new Error('spec node missing');
+    // Make it renderable (html2canvas needs layout; keep it offscreen, not display:none).
+    node.style.zIndex = '-1';
+    node.style.visibility = 'visible';
+
+    await ensureLibs();
+
+    var canvas = await window.html2canvas(node.querySelector('.dw-spec-page'), {
+      scale: 2,
+      useCORS: true,
+      allowTaint: false,
+      backgroundColor: '#ffffff',
+      logging: false,
+      windowWidth: 816
+    });
+
+    var jsPDF = window.jspdf.jsPDF;
+    var imgData = canvas.toDataURL('image/png');
+    var pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'letter' });
+    var imgWidth = 216;
+    var imgHeight = (canvas.height * imgWidth) / canvas.width;
+    pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
+
+    var sku = (node.querySelector('.dw-spec-item span') || {}).textContent || 'spec';
+    pdf.save('Designer-Wallcoverings-Los-Angeles-' + sku.trim().replace(/[^A-Za-z0-9\-]+/g, '-') + '.pdf');
+  }
+
+  function onClick(e) {
+    var btn = e.currentTarget;
+    var orig = btn.textContent;
+    btn.textContent = '⏳ Generating...';
+    btn.disabled = true;
+    generate(btn).then(function () {
+      btn.textContent = '✓ Downloaded!';
+      setTimeout(function () { btn.textContent = orig; btn.disabled = false; }, 2000);
+    }).catch(function (err) {
+      console.error('Spec PDF generation failed:', err);
+      btn.textContent = '✗ Error - Try Again';
+      setTimeout(function () { btn.textContent = orig; btn.disabled = false; }, 2500);
+    });
+  }
+
+  function wire() {
+    var btns = document.querySelectorAll('[data-dw-spec-trigger]');
+    for (var i = 0; i < btns.length; i++) {
+      if (btns[i].dataset.dwWired === '1') continue;
+      btns[i].dataset.dwWired = '1';
+      btns[i].addEventListener('click', onClick);
+    }
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', wire);
+  } else {
+    wire();
+  }
+})();
+</script>
+
+<style>
+.spec-sheet-controls {
+  display: inline-block;
+  margin: 0;
+  /* nudge the Download-PDF button up to align with the REQUEST SAMPLE row (Steve 2026-06-24) */
+  transform: translateY(-6px);
+}
+.spec-sheet-btn {
+  display: inline-block;
+  padding: 18px 24px;
+  background: #2c5f7f;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 13px;
+  font-weight: 600;
+  transition: all 0.3s ease;
+  white-space: nowrap;
+}
+.spec-sheet-btn:hover {
+  background: #1e4557;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+.spec-sheet-btn:active { transform: translateY(0); }
+.spec-sheet-btn:disabled { opacity: 0.6; cursor: not-allowed; }
+.pdf-download-btn { vertical-align: top; }
+
+/* Offscreen spec-sheet print styles (rendered by html2canvas, never shown to the user) */
+#dw-spec-sheet * { margin: 0; padding: 0; box-sizing: border-box; }
+#dw-spec-sheet .dw-spec-page { width: 816px; background: #fff; font-family: Arial, Helvetica, sans-serif; }
+#dw-spec-sheet .dw-spec-header { padding: 30px 40px; text-align: center; border-bottom: 2px solid #2c5f7f; }
+#dw-spec-sheet .dw-spec-logo { max-width: 200px; display: block; margin: 0 auto; }
+#dw-spec-sheet .dw-spec-body { padding: 30px 40px; }
+#dw-spec-sheet .dw-spec-title { font-size: 18px; color: #333; margin-bottom: 15px; font-weight: normal; text-align: center; text-transform: capitalize; }
+#dw-spec-sheet .dw-spec-img { max-width: 500px; max-height: 500px; object-fit: contain; display: block; margin: 0 auto 20px; width: 100%; }
+#dw-spec-sheet .dw-spec-grid { max-width: 560px; margin: 0 auto; display: grid; grid-template-columns: 1fr; gap: 10px; }
+#dw-spec-sheet .dw-spec-item { font-size: 14px; line-height: 1.6; display: flex; align-items: baseline; }
+#dw-spec-sheet .dw-spec-item strong { color: #333; font-weight: bold; margin-right: 6px; white-space: nowrap; }
+#dw-spec-sheet .dw-spec-item span { color: #555; flex: 1; }
+#dw-spec-sheet .dw-spec-footer { padding: 20px 40px; text-align: center; border-top: 2px solid #2c5f7f; background: #f9f9f9; }
+#dw-spec-sheet .dw-spec-footer-line { font-size: 10px; color: #666; margin: 3px 0; }
+#dw-spec-sheet .dw-spec-footer-line.company { font-size: 12px; font-weight: bold; color: #333; margin-bottom: 6px; }
+#dw-spec-sheet .dw-spec-footer-line.italic { font-style: italic; margin-top: 10px; }
+#dw-spec-sheet .dw-spec-footer-line.tiny { font-size: 9px; margin-top: 8px; }
+</style>
+

← 21f8dea auto-data-snapshot: 2026-09-02T14:42:45 (1 data files) — str  ·  back to Flock Fix Viewer  ·  non-astek flock (c): house private-label flock specs for 8 R 258c4cf →