← back to Dw Measure To Cart
Measure-to-Cart Phase-0 read-only spec: strip-method roll math verified on 5 pilot SKUs vs NPH sq-ft shortcut (TK-10233, Idea Council #1, Steve 'go dust2026')
81234b3dc0da36548f12ebf87d3821c96bc8a919 · 2026-08-04 22:27:11 -0700 · Steve Abrams
Files touched
Diff
commit 81234b3dc0da36548f12ebf87d3821c96bc8a919
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 4 22:27:11 2026 -0700
Measure-to-Cart Phase-0 read-only spec: strip-method roll math verified on 5 pilot SKUs vs NPH sq-ft shortcut (TK-10233, Idea Council #1, Steve 'go dust2026')
---
.gitignore | 8 +++++++
SPEC.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/SPEC.md b/SPEC.md
new file mode 100644
index 0000000..74ae21c
--- /dev/null
+++ b/SPEC.md
@@ -0,0 +1,77 @@
+# Measure-to-Cart — Read-Only Spec (Phase 0)
+
+**Ticket:** TK-10233 · **Origin:** Officer Idea Council 2026-08-04 #1 (34/40, vp-dw-commerce), Steve "#1 BUILD dust2026" + "go dust2026" (2026-08-04 22:14 PT email)
+**Status:** SPEC ONLY — no theme work, no PDP changes, no /measure code changes yet. Everything below is verified against live code + the local dw_unified mirror (read-only).
+
+## 1. What exists today (verified 2026-08-04)
+
+### NPH /measure (NationalPaperHangers)
+- Route `routes/public.js:1073`, view `views/public/measure.ejs`, math `public/js/measure-kit.js`, AR capture `public/js/room-measure.js` (WebXR 3-tap on Android, gyro clinometer on iPhone).
+- **Already has a product-context deep-link (P3):**
+ `/measure?sku=&name=&from=&repeat=&panelw=&format=&shop=` — `shop` return-URL allowlisted to designerwallcoverings.com, wallco.ai, philipperomano.com, novasuede.com (`routes/public.js:1047-1065`).
+- Entirely client-side, no server writes, no order handoff — the "Shop" CTA just links back to the PDP with **no quantity**.
+- **Current math is a flat sq-ft shortcut** (`measure-kit.js:66-85`): net area × (1.15 or 1.20 waste) ÷ 28 ft² per US single roll (57 ft² Euro), then ceil to doubles. It knows nothing about the product's actual width, repeat, or roll length.
+
+### dw_unified dimension data (local mirror, read-only)
+All dimensions live in `shopify_products.metafields` (jsonb) — no dedicated columns. ACTIVE-product population:
+
+| Field | Best keys (fallback order) | ACTIVE coverage |
+|---|---|---|
+| Width | `custom.width` → `dwc.width` → `global.width` | 76k / 73k / 57k of 86.5k |
+| Vert. repeat | `custom.pattern_repeat` → `dwc.repeat` → `global.repeat` / `specs.repeat_v` | 45k / 40k / 21k |
+| Roll length | `global.single_roll_length` + `global.double_roll_length` (D/R lines, ~5.7k) → `global.length` → `custom.roll_length` | 16k / 12k / 3.6k |
+| Coverage sq-ft | `specs.coverage` etc. | only ~3-6% — **not** a reliable path |
+| 2-roll minimum | `global.v_prods_quantity_order_min=2` + `v_prods_quantity_order_units=2` | 13.6k |
+
+Values are messy mixed-unit strings (`"36 Inches"`, `"27\""`, `"0.52"` m, `"4 yd (3.66 m)"`, Kravet fuses width×length as `"20.5\" x 11 yards"`). **A unit-normalizing parser is a hard prerequisite** (in/cm/m/ft/yd; split Kravet fused strings; sanity-check Anna French lengths — some "11 Feet" are likely 11 yd).
+
+Vendor staging tables (`*_catalog`, Mac2-canonical) carry clean columns (`width_inches`, `repeat_v`, `roll_length`…) and can seed/backfill the parser.
+
+## 2. Roll math — the strip method (source of truth)
+
+```
+strip_h = wall_h + 4in trim; if repeat > 0: strip_h = ceil(strip_h / repeat) × repeat
+strips = ceil(total_wall_width / material_width)
+per_unit = floor(unit_length / strip_h) # unit = S/R, D/R, or bolt
+units = ceil(strips / per_unit)
+# Double-roll lines (Thibaut, Malibu Wallpaper, Malibu Walls, York):
+# sell in D/R units; enforce 2-roll min; single-roll count always even
+```
+Openings are NOT deducted (standard conservative practice); the sq-ft shortcut may be shown as a sanity display only.
+
+## 3. Pilot verification (5 SKUs, computed + cross-checked)
+
+Wall A = 12ft × 9ft flat wall. Wall B = 14×16ft room, 8ft ceilings, 1 door + 1 window.
+
+| SKU | Vendor / packaging | Width | Repeat | Unit length | Wall A | Wall B |
+|---|---|---|---|---|---|---|
+| DWTT-73129 | Thibaut, D/R | 36" | 25.25" | D/R 8 yd | 2 D/R (4 S/R) | 10 D/R |
+| DWQW-60067 | Malibu Wallpaper (D/R) | 27" | 12.6" | D/R 9 yd | 3 D/R | 9 D/R |
+| DWKK-133346 | Cole & Son, Euro bolt | 20.5" | 24" | 33 ft | 3 bolts | 12 bolts |
+| DWKK-115581 | Kravet (2-roll order min) | 20.5" | 7" | 11 yd | 3 rolls | 12 rolls |
+| DWPT-200024-S | 1838, Euro roll | 20.47" | 20.87" | 32.97 ft | 3 rolls | 12 rolls |
+
+**Cross-check vs the vendor-calculator sq-ft shortcut (÷0.73 waste):** matches on the flat wall for 4/5, but on the room it **under-buys 1 unit on Cole & Son, Kravet, and 1838** (big repeats / narrow widths force real waste >27%), under-buys 1 D/R on Thibaut, and over-buys 2 D/R on Malibu. This is exactly the "computes rolls correctly, competitors can't" edge — the strip method over the product's real metafields is the differentiator.
+
+## 4. Handoff design (target, not yet built)
+
+1. **PDP → /measure:** DW PDP button builds the existing P3 deep-link, adding the missing physical params: `width` (in), `unitlen` (in), `unit` (`sr|dr|bolt`), `ordermin`. NPH `/measure` clamps + prefills as it already does for `repeat`/`panelw`.
+2. **/measure upgrade:** add strip-method engine alongside `rollsForArea()`; when width+unitlen are present use strip method as source of truth (fall back to sq-ft shortcut when absent).
+3. **/measure → cart:** "Add N rolls + a $4.25 sample" CTA using a Shopify **cart permalink**: `https://designerwallcoverings.com/cart/{roll_variant_id}:{qty},{sample_variant_id}:1` — no theme quantity-param support needed, works today. Variant ids ride the deep-link (`vid`, `svid` params) so /measure stays stateless. D/R lines pass qty in the store's selling unit (even S/R count per `order_units=2`).
+4. **Sample upsell** is always the second permalink line (every pilot SKU has the sample variant).
+
+## 5. Gaps / risks before build
+
+- **Unit parser** (§1) — build + test against the ~10 observed formats before anything user-facing.
+- Mirror `status`/price columns are stale (standing memory) — variant ids + metafields for the deep-link generator must be read **live** from Admin API at build time, not from the mirror.
+- ~10k ACTIVE products lack any roll length → strip method unavailable; fall back to shortcut + "estimate" label.
+- /measure is on nationalpaperhangers.com — cross-domain cart handoff is a plain permalink (no cookies needed), but analytics attribution should carry `utm_source=measure-kit`.
+- Theme work on the DW PDP (the button) is **customer-facing → Steve-gated** at build time.
+
+## 6. Proposed build order (next phases, each gated as appropriate)
+
+1. Unit parser + metafield extractor (local lib + tests) — reversible, ungated.
+2. Strip-method engine in `measure-kit.js` + unit tests mirroring §3 — NPH deploy gated.
+3. Deep-link generator (live Admin API reads) for the 5 pilots — ungated dry-run.
+4. PDP button on the 5 pilot SKUs only (theme change) — **Steve gate**.
+5. Measure → cart permalink CTA — **Steve gate** (customer-facing).
(oldest)
·
back to Dw Measure To Cart
·
Appendix A: TK-10232 independent verification — live-theme m c1e6f64 →