[object Object]

← back to Dw Photo Capture

color-widen: add min param — guarantee >=N results via nearest-neighbor fallback when the band is thin

0d6c355e88bfec17d40393b871ce4a35b48de1e5 · 2026-07-16 11:40:44 -0700 · Steve

Files touched

Diff

commit 0d6c355e88bfec17d40393b871ce4a35b48de1e5
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 16 11:40:44 2026 -0700

    color-widen: add min param — guarantee >=N results via nearest-neighbor fallback when the band is thin
---
 server.js | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/server.js b/server.js
index def8fff..09443b6 100644
--- a/server.js
+++ b/server.js
@@ -1059,24 +1059,27 @@ const appHandler = (req, res) => {
       if (!hex) return send(res, 400, { err: 'hex required (#rrggbb)' }, CORS);
       const k = Math.max(1, Math.min(parseInt(p.k, 10) || 240, 400));
       const ceil = Math.max(5, Math.min(Number(p.ceiling) || 15, 25)); // % ≈ ΔE76 ceiling
+      const min = Math.max(0, Math.min(parseInt(p.min, 10) || 0, 400)); // guarantee at least this many
       try {
         const idx = loadColorIndex();               // cached, loaded once
         const target = hexToLab(hex);
-        const within = [];
-        for (let i = 0; i < idx.length; i++) {
+        const scored = [];                           // ΔE for EVERY product, so we can
+        for (let i = 0; i < idx.length; i++) {       // fall back to nearest-N when the band is thin
           const it = idx[i];
           const dl = it.l - target.l, da = it.a - target.a, db = it.b - target.b;
-          const de = Math.sqrt(dl * dl + da * da + db * db);
-          if (de <= ceil) within.push({ it, de });
+          scored.push({ it, de: Math.sqrt(dl * dl + da * da + db * db) });
         }
-        within.sort((x, y) => x.de - y.de);          // nearest first
-        const results = within.slice(0, k).map(w => ({
+        scored.sort((x, y) => x.de - y.de);          // nearest first
+        let sel = scored.filter(s => s.de <= ceil);  // within the 15% band
+        const withinCeiling = sel.length;
+        if (sel.length < min) sel = scored.slice(0, min); // thin band → expand to the nearest `min`
+        const results = sel.slice(0, k).map(w => ({
           handle: w.it.h, title: w.it.t, vendor: w.it.v,
           hex: w.it.x, image: w.it.i, delta_e: Math.round(w.de * 10) / 10
         }));
         return send(res, 200, {
-          ok: true, hex, tolerance_pct: ceil / 100,
-          delta_e_ceiling: ceil, total_in_tolerance: within.length, results
+          ok: true, hex, tolerance_pct: ceil / 100, delta_e_ceiling: ceil, min,
+          total_in_tolerance: withinCeiling, returned: results.length, results
         }, CORS);
       } catch (e) {
         return send(res, 503, { ok: false, err: 'color index unavailable', results: [] }, CORS);

← e2c9e01 add /apps/color-widen route: tunable ΔE band (default 15%) f  ·  back to Dw Photo Capture  ·  color-index: carry product_type through builder + both /apps 8112eaf →