[object Object]

← back to Designer Wallcoverings

DWTT qty stepper: add combined diffs, clean harness proof + screenshot, verification scripts

08928754421170ad145688b5cc70b01b7c124f2a · 2026-06-30 20:27:13 -0700 · Steve

Files touched

Diff

commit 08928754421170ad145688b5cc70b01b7c124f2a
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jun 30 20:27:13 2026 -0700

    DWTT qty stepper: add combined diffs, clean harness proof + screenshot, verification scripts
---
 .../custom-js-qty-2026-06-30/COMBINED.diff         | 169 +++++++++++++++++++++
 .../custom-js-qty-2026-06-30/custom.js.diff        | 149 ++++++++++++++++++
 .../product-form-content.liquid.diff               |  20 +++
 .../custom-js-qty-2026-06-30/proof-harness.png     | Bin 0 -> 10340 bytes
 .../theme-fixes/custom-js-qty-2026-06-30/shot.cjs  |  14 ++
 5 files changed, 352 insertions(+)

diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/COMBINED.diff b/shopify/theme-fixes/custom-js-qty-2026-06-30/COMBINED.diff
new file mode 100644
index 00000000..2cbe058d
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/COMBINED.diff
@@ -0,0 +1,169 @@
+--- custom.ORIGINAL.js	2026-06-30 20:14:43
++++ custom.js	2026-06-30 20:15:30
+@@ -38,27 +38,93 @@
+     mm_refresh_quantity_increments()
+ }), jQuery(document).on("click", ".plus, .minus", async function(event) {
+     event.preventDefault();
+-    
+-    var a = jQuery(this).closest(".mm_quantity").find(".qty"),
+-        b = parseFloat(a.val()),
+-        c = parseFloat(a.attr("max")),
+-        d = parseFloat(a.attr("min")),
+-        e = a.attr("step");
+-    //b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals())), a.trigger("change")
+-     b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals()))
+-     
+-     //alert();
+-     const quantity=jQuery(this).closest(".mm_quantity").find(".qty").val();
+-     jQuery(this).closest(".mm_quantity").find(".hquantity").attr('value',quantity);
+ 
+-     if($(this).hasClass('cart-page-quantity-value')){
+-      const variantId=jQuery(this).closest(".mm_quantity").find(".hquantity").attr('variantid');
+-      console.log('-----', variantId)
+-      await updateItem(variantId, quantity)
+-      window.location.href="/cart"
+-     }
+-    
+-     
++    // ===== 2026-06-30 qty-stepper repair (vp-dw-commerce) =====
++    // Prior code read step/min from the .qty element's live `step`/`min` attributes,
++    // but the variant-change handler (below) poisons those to 1/1 when the default-
++    // selected variant is the Sample — so on MOQ products (DWTT/Anna French, step=2,
++    // min=2) the + button could only go 1->2 and the floor was 1 instead of 2, and
++    // the original math line was left half-disabled (no a.trigger('change')), so the
++    // visible input often failed to reflect the change. This rewrite reads the ORDER
++    // step/min from the pristine data-order-step / data-order-min attributes the liquid
++    // renders straight from v_prods_quantity_order_units / v_prods_quantity_order_min
++    // (never mutated by JS), clamps correctly, and mirrors the hidden hquantity.
++    var $btn = jQuery(this);
++    try {
++      var $wrap = $btn.closest(".mm_quantity");
++      var a = $wrap.find(".qty");
++      if (!a.length) return;
++
++      // STEP = pristine order-units (fallback to step attr, then 1). Must be > 0.
++      var step = parseFloat(a.attr("data-order-step"));
++      if (!isFinite(step) || step <= 0) step = parseFloat(a.attr("step"));
++      if (!isFinite(step) || step <= 0) step = 1;
++
++      // MIN = pristine order-min (fallback to min attr, then step). Must be >= step floor.
++      var min = parseFloat(a.attr("data-order-min"));
++      if (!isFinite(min) || min <= 0) min = parseFloat(a.attr("min"));
++      if (!isFinite(min) || min <= 0) min = step;
++
++      // MAX only if a real numeric cap is present.
++      var maxRaw = a.attr("max");
++      var max = parseFloat(maxRaw);
++      var hasMax = (maxRaw != null && maxRaw !== "" && isFinite(max));
++
++      var decimals = String(step).getDecimals();
++      var current = parseFloat(a.val());
++      if (!isFinite(current)) current = min;
++
++      var next;
++      if ($btn.is(".plus")) {
++        next = current + step;
++        if (hasMax && next > max) next = max;
++      } else {
++        next = current - step;
++        if (next < min) next = min;   // never below the minimum order quantity
++      }
++      // Snap to the on-step grid anchored at min (typing 3 on a step-2 min-2 product -> 2, then +2 -> 4).
++      var gridSteps = Math.round((next - min) / step);
++      if (gridSteps < 0) gridSteps = 0;
++      next = min + gridSteps * step;
++      if (hasMax && next > max) next = max;
++
++      var nextStr = next.toFixed(decimals);
++      a.val(nextStr);
++
++      // Mirror the visible qty into the hidden hquantity that is actually submitted to cart.
++      $wrap.find(".hquantity").attr('value', nextStr).val(nextStr);
++
++      if ($btn.hasClass('cart-page-quantity-value')) {
++        var variantId = $wrap.find(".hquantity").attr('variantid');
++        await updateItem(variantId, nextStr);
++        window.location.href = "/cart";
++      }
++    } catch (err) {
++      if (window.console && console.warn) console.warn('qty stepper error:', err);
++    }
++});
++// Snap a manually-typed qty to the nearest valid on-step value (>= min) on blur/change.
++jQuery(document).on('change blur', '.mm_quantity .qty', function () {
++  try {
++    var $wrap = jQuery(this).closest('.mm_quantity');
++    var a = jQuery(this);
++    var step = parseFloat(a.attr('data-order-step'));
++    if (!isFinite(step) || step <= 0) step = parseFloat(a.attr('step'));
++    if (!isFinite(step) || step <= 0) step = 1;
++    var min = parseFloat(a.attr('data-order-min'));
++    if (!isFinite(min) || min <= 0) min = parseFloat(a.attr('min'));
++    if (!isFinite(min) || min <= 0) min = step;
++    var v = parseFloat(a.val());
++    if (!isFinite(v) || v < min) v = min;
++    var gridSteps = Math.round((v - min) / step);
++    if (gridSteps < 0) gridSteps = 0;
++    var snapped = min + gridSteps * step;
++    var snappedStr = snapped.toFixed(String(step).getDecimals());
++    if (a.val() !== snappedStr) a.val(snappedStr);
++    $wrap.find('.hquantity').attr('value', snappedStr).val(snappedStr);
++  } catch (err) {
++    if (window.console && console.warn) console.warn('qty snap error:', err);
++  }
+ });
+ 
+ async function updateItem(variantId, quantity,  properties={}){
+@@ -132,17 +198,22 @@
+ 
+     var $qty = $form.find('.mm_quantity .qty');
+     var $qtyWrapper = $form.find('.mm_quantity');
+-    var originalMin = $qty.data('min');
+-    var originalStep = $qty.data('step');
+ 
+-    if (!originalMin) {
+-      originalMin = parseFloat($qty.attr('min')) || 1;
+-      $qty.data('min', originalMin);
+-    }
+-    if (!originalStep) {
+-      originalStep = parseFloat($qty.attr('step')) || 1;
+-      $qty.data('step', originalStep);
+-    }
++    // 2026-06-30: read the TRUE minimum-order-quantity + step from the pristine
++    // data-order-min / data-order-step attributes the liquid renders straight from
++    // the v_prods_quantity_order_min / v_prods_quantity_order_units metafields.
++    // The old code cached these from the live step/min attrs, which get poisoned to
++    // 1/1 by the Sample branch when Sample is the default-selected variant — that
++    // was why the roll variant came back showing step=1/min=1 instead of 2/2.
++    var originalMin = parseFloat($qty.attr('data-order-min'));
++    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.data('min'));
++    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.attr('min')) || 1;
++    $qty.data('min', originalMin);
++
++    var originalStep = parseFloat($qty.attr('data-order-step'));
++    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.data('step'));
++    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.attr('step')) || 1;
++    $qty.data('step', originalStep);
+ 
+     // Sample request buttons are always available (hide/show is variant-driven only)
+     $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
+--- product-form-content.ORIGINAL.liquid	2026-06-30 20:14:43
++++ liquid/snippets__product-form-content.liquid	2026-06-30 20:15:57
+@@ -477,6 +477,8 @@
+                 type="text"
+                 step="{{ prods_quantity_order_units }}"
+                 min="{{ prods_quantity_order_min }}"
++                data-order-step="{{ prods_quantity_order_units }}"
++                data-order-min="{{ prods_quantity_order_min }}"
+                 max=""
+                 name="quantity"
+                 value="{{ prods_quantity_order_min }}"
+@@ -577,6 +579,8 @@
+                 type="text"
+                 step="{{ prods_quantity_order_units }}"
+                 min="{{ prods_quantity_order_min }}"
++                data-order-step="{{ prods_quantity_order_units }}"
++                data-order-min="{{ prods_quantity_order_min }}"
+                 max=""
+                 name="quantity"
+                 value="{{ prods_quantity_order_min }}"
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js.diff b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js.diff
new file mode 100644
index 00000000..8618de9a
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/custom.js.diff
@@ -0,0 +1,149 @@
+--- custom.ORIGINAL.js	2026-06-30 20:14:43
++++ custom.js	2026-06-30 20:15:30
+@@ -38,27 +38,93 @@
+     mm_refresh_quantity_increments()
+ }), jQuery(document).on("click", ".plus, .minus", async function(event) {
+     event.preventDefault();
+-    
+-    var a = jQuery(this).closest(".mm_quantity").find(".qty"),
+-        b = parseFloat(a.val()),
+-        c = parseFloat(a.attr("max")),
+-        d = parseFloat(a.attr("min")),
+-        e = a.attr("step");
+-    //b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals())), a.trigger("change")
+-     b && "" !== b && "NaN" !== b || (b = 0), "" !== c && "NaN" !== c || (c = ""), "" !== d && "NaN" !== d || (d = 0), "any" !== e && "" !== e && void 0 !== e && "NaN" !== parseFloat(e) || (e = 1), jQuery(this).is(".plus") ? c && b >= c ? a.val(c) : a.val((b + parseFloat(e)).toFixed(e.getDecimals())) : d && b <= d ? a.val(d) : b > 0 && a.val((b - parseFloat(e)).toFixed(e.getDecimals()))
+-     
+-     //alert();
+-     const quantity=jQuery(this).closest(".mm_quantity").find(".qty").val();
+-     jQuery(this).closest(".mm_quantity").find(".hquantity").attr('value',quantity);
+ 
+-     if($(this).hasClass('cart-page-quantity-value')){
+-      const variantId=jQuery(this).closest(".mm_quantity").find(".hquantity").attr('variantid');
+-      console.log('-----', variantId)
+-      await updateItem(variantId, quantity)
+-      window.location.href="/cart"
+-     }
+-    
+-     
++    // ===== 2026-06-30 qty-stepper repair (vp-dw-commerce) =====
++    // Prior code read step/min from the .qty element's live `step`/`min` attributes,
++    // but the variant-change handler (below) poisons those to 1/1 when the default-
++    // selected variant is the Sample — so on MOQ products (DWTT/Anna French, step=2,
++    // min=2) the + button could only go 1->2 and the floor was 1 instead of 2, and
++    // the original math line was left half-disabled (no a.trigger('change')), so the
++    // visible input often failed to reflect the change. This rewrite reads the ORDER
++    // step/min from the pristine data-order-step / data-order-min attributes the liquid
++    // renders straight from v_prods_quantity_order_units / v_prods_quantity_order_min
++    // (never mutated by JS), clamps correctly, and mirrors the hidden hquantity.
++    var $btn = jQuery(this);
++    try {
++      var $wrap = $btn.closest(".mm_quantity");
++      var a = $wrap.find(".qty");
++      if (!a.length) return;
++
++      // STEP = pristine order-units (fallback to step attr, then 1). Must be > 0.
++      var step = parseFloat(a.attr("data-order-step"));
++      if (!isFinite(step) || step <= 0) step = parseFloat(a.attr("step"));
++      if (!isFinite(step) || step <= 0) step = 1;
++
++      // MIN = pristine order-min (fallback to min attr, then step). Must be >= step floor.
++      var min = parseFloat(a.attr("data-order-min"));
++      if (!isFinite(min) || min <= 0) min = parseFloat(a.attr("min"));
++      if (!isFinite(min) || min <= 0) min = step;
++
++      // MAX only if a real numeric cap is present.
++      var maxRaw = a.attr("max");
++      var max = parseFloat(maxRaw);
++      var hasMax = (maxRaw != null && maxRaw !== "" && isFinite(max));
++
++      var decimals = String(step).getDecimals();
++      var current = parseFloat(a.val());
++      if (!isFinite(current)) current = min;
++
++      var next;
++      if ($btn.is(".plus")) {
++        next = current + step;
++        if (hasMax && next > max) next = max;
++      } else {
++        next = current - step;
++        if (next < min) next = min;   // never below the minimum order quantity
++      }
++      // Snap to the on-step grid anchored at min (typing 3 on a step-2 min-2 product -> 2, then +2 -> 4).
++      var gridSteps = Math.round((next - min) / step);
++      if (gridSteps < 0) gridSteps = 0;
++      next = min + gridSteps * step;
++      if (hasMax && next > max) next = max;
++
++      var nextStr = next.toFixed(decimals);
++      a.val(nextStr);
++
++      // Mirror the visible qty into the hidden hquantity that is actually submitted to cart.
++      $wrap.find(".hquantity").attr('value', nextStr).val(nextStr);
++
++      if ($btn.hasClass('cart-page-quantity-value')) {
++        var variantId = $wrap.find(".hquantity").attr('variantid');
++        await updateItem(variantId, nextStr);
++        window.location.href = "/cart";
++      }
++    } catch (err) {
++      if (window.console && console.warn) console.warn('qty stepper error:', err);
++    }
++});
++// Snap a manually-typed qty to the nearest valid on-step value (>= min) on blur/change.
++jQuery(document).on('change blur', '.mm_quantity .qty', function () {
++  try {
++    var $wrap = jQuery(this).closest('.mm_quantity');
++    var a = jQuery(this);
++    var step = parseFloat(a.attr('data-order-step'));
++    if (!isFinite(step) || step <= 0) step = parseFloat(a.attr('step'));
++    if (!isFinite(step) || step <= 0) step = 1;
++    var min = parseFloat(a.attr('data-order-min'));
++    if (!isFinite(min) || min <= 0) min = parseFloat(a.attr('min'));
++    if (!isFinite(min) || min <= 0) min = step;
++    var v = parseFloat(a.val());
++    if (!isFinite(v) || v < min) v = min;
++    var gridSteps = Math.round((v - min) / step);
++    if (gridSteps < 0) gridSteps = 0;
++    var snapped = min + gridSteps * step;
++    var snappedStr = snapped.toFixed(String(step).getDecimals());
++    if (a.val() !== snappedStr) a.val(snappedStr);
++    $wrap.find('.hquantity').attr('value', snappedStr).val(snappedStr);
++  } catch (err) {
++    if (window.console && console.warn) console.warn('qty snap error:', err);
++  }
+ });
+ 
+ async function updateItem(variantId, quantity,  properties={}){
+@@ -132,17 +198,22 @@
+ 
+     var $qty = $form.find('.mm_quantity .qty');
+     var $qtyWrapper = $form.find('.mm_quantity');
+-    var originalMin = $qty.data('min');
+-    var originalStep = $qty.data('step');
+ 
+-    if (!originalMin) {
+-      originalMin = parseFloat($qty.attr('min')) || 1;
+-      $qty.data('min', originalMin);
+-    }
+-    if (!originalStep) {
+-      originalStep = parseFloat($qty.attr('step')) || 1;
+-      $qty.data('step', originalStep);
+-    }
++    // 2026-06-30: read the TRUE minimum-order-quantity + step from the pristine
++    // data-order-min / data-order-step attributes the liquid renders straight from
++    // the v_prods_quantity_order_min / v_prods_quantity_order_units metafields.
++    // The old code cached these from the live step/min attrs, which get poisoned to
++    // 1/1 by the Sample branch when Sample is the default-selected variant — that
++    // was why the roll variant came back showing step=1/min=1 instead of 2/2.
++    var originalMin = parseFloat($qty.attr('data-order-min'));
++    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.data('min'));
++    if (!isFinite(originalMin) || originalMin <= 0) originalMin = parseFloat($qty.attr('min')) || 1;
++    $qty.data('min', originalMin);
++
++    var originalStep = parseFloat($qty.attr('data-order-step'));
++    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.data('step'));
++    if (!isFinite(originalStep) || originalStep <= 0) originalStep = parseFloat($qty.attr('step')) || 1;
++    $qty.data('step', originalStep);
+ 
+     // Sample request buttons are always available (hide/show is variant-driven only)
+     $form.find('.dl-sample-btn, .dl-second-sample-btn').show();
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/product-form-content.liquid.diff b/shopify/theme-fixes/custom-js-qty-2026-06-30/product-form-content.liquid.diff
new file mode 100644
index 00000000..56b2a0f2
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/product-form-content.liquid.diff
@@ -0,0 +1,20 @@
+--- product-form-content.ORIGINAL.liquid	2026-06-30 20:14:43
++++ liquid/snippets__product-form-content.liquid	2026-06-30 20:15:57
+@@ -477,6 +477,8 @@
+                 type="text"
+                 step="{{ prods_quantity_order_units }}"
+                 min="{{ prods_quantity_order_min }}"
++                data-order-step="{{ prods_quantity_order_units }}"
++                data-order-min="{{ prods_quantity_order_min }}"
+                 max=""
+                 name="quantity"
+                 value="{{ prods_quantity_order_min }}"
+@@ -577,6 +579,8 @@
+                 type="text"
+                 step="{{ prods_quantity_order_units }}"
+                 min="{{ prods_quantity_order_min }}"
++                data-order-step="{{ prods_quantity_order_units }}"
++                data-order-min="{{ prods_quantity_order_min }}"
+                 max=""
+                 name="quantity"
+                 value="{{ prods_quantity_order_min }}"
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/proof-harness.png b/shopify/theme-fixes/custom-js-qty-2026-06-30/proof-harness.png
new file mode 100644
index 00000000..4388b8bc
Binary files /dev/null and b/shopify/theme-fixes/custom-js-qty-2026-06-30/proof-harness.png differ
diff --git a/shopify/theme-fixes/custom-js-qty-2026-06-30/shot.cjs b/shopify/theme-fixes/custom-js-qty-2026-06-30/shot.cjs
new file mode 100644
index 00000000..78d39270
--- /dev/null
+++ b/shopify/theme-fixes/custom-js-qty-2026-06-30/shot.cjs
@@ -0,0 +1,14 @@
+const { chromium } = require(require('path').join(process.env.HOME, 'Projects/Designer-Wallcoverings/node_modules/playwright'));
+const path=require('path');
+(async()=>{
+  const b=await chromium.launch();const p=await(await b.newContext({viewport:{width:700,height:520}})).newPage();
+  await p.goto('file://'+path.join(__dirname,'harness.html'),{waitUntil:'load'});
+  await p.waitForTimeout(300);
+  await p.click('#moq .plus'); await p.waitForTimeout(80);
+  await p.click('#moq .plus'); await p.waitForTimeout(80); // MOQ now 6
+  await p.click('#std .plus'); await p.waitForTimeout(80); // std now 2
+  await p.screenshot({path:'proof-harness.png'});
+  console.log('screenshot saved: proof-harness.png');
+  console.log('MOQ qty=',await p.evaluate(()=>document.querySelector('#moq .qty').value),' STD qty=',await p.evaluate(()=>document.querySelector('#std .qty').value));
+  await b.close();
+})();

← b77fe370 DWTT qty stepper fix: repair +/- increment/decrement in cust  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-30T20:43:13 (5 files) — pending-approval/ ce69ce72 →