← back to CelebritySignatures
wear: fix stale ink-color fallback in reconcile-wear-orders.mjs + its test
8322ca0e39eedc0036bf4ac13fa2a520af72863a · 2026-09-09 20:16:44 -0700 · Steve
test/wear-artwork-flow.test.mjs (committed 07:58 today) asserted the old
fixed #f5f3ee recolor tint. Commit 18cca456 (16:13 today) deliberately
changed server.js's dark-cloth fallback to #ffffff to match the on-shirt
preview's inkHexFor() exactly — but the test was never updated, so it was
failing on HEAD (verified via git blame: the two commits are unrelated,
the test simply predates the ink-choice feature).
Root-caused by reading both implementations rather than guessing:
server.js's #ffffff is correct/intentional (matches public/wear.html's
inkHexFor, "dark cloth, no pick → white"); the old #f5f3ee was pre-feature
scaffolding. So the fix is NOT touching server.js — it's:
1. scripts/reconcile-wear-orders.mjs: renamed recolorSignatureToLight() ->
recolorSignatureInk(imageUrl, hex) and added safeInkHex(), mirroring
server.js's current signature/cache-key/fallback exactly (was still on
the pre-buyer-choice, fixed-#f5f3ee version — the two producers had
drifted out of parity since the ink-color feature landed).
2. test/wear-artwork-flow.test.mjs: updated the two stale pixel assertions
from #F5F3EE(FF/00) to #FFFFFF(FF/00) to match current intended
behavior.
Verified: both producer subtests (success + reconciler) now pass; node
--check clean. Did not touch server.js, public/index.html, or the
unrelated wear-accessories.test.mjs failures (separate, pre-existing,
in another session's active hat/accessory work).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Files touched
M scripts/reconcile-wear-orders.mjsM test/wear-artwork-flow.test.mjs
Diff
commit 8322ca0e39eedc0036bf4ac13fa2a520af72863a
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 20:16:44 2026 -0700
wear: fix stale ink-color fallback in reconcile-wear-orders.mjs + its test
test/wear-artwork-flow.test.mjs (committed 07:58 today) asserted the old
fixed #f5f3ee recolor tint. Commit 18cca456 (16:13 today) deliberately
changed server.js's dark-cloth fallback to #ffffff to match the on-shirt
preview's inkHexFor() exactly — but the test was never updated, so it was
failing on HEAD (verified via git blame: the two commits are unrelated,
the test simply predates the ink-choice feature).
Root-caused by reading both implementations rather than guessing:
server.js's #ffffff is correct/intentional (matches public/wear.html's
inkHexFor, "dark cloth, no pick → white"); the old #f5f3ee was pre-feature
scaffolding. So the fix is NOT touching server.js — it's:
1. scripts/reconcile-wear-orders.mjs: renamed recolorSignatureToLight() ->
recolorSignatureInk(imageUrl, hex) and added safeInkHex(), mirroring
server.js's current signature/cache-key/fallback exactly (was still on
the pre-buyer-choice, fixed-#f5f3ee version — the two producers had
drifted out of parity since the ink-color feature landed).
2. test/wear-artwork-flow.test.mjs: updated the two stale pixel assertions
from #F5F3EE(FF/00) to #FFFFFF(FF/00) to match current intended
behavior.
Verified: both producer subtests (success + reconciler) now pass; node
--check clean. Did not touch server.js, public/index.html, or the
unrelated wear-accessories.test.mjs failures (separate, pre-existing,
in another session's active hat/accessory work).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
scripts/reconcile-wear-orders.mjs | 32 ++++++++++++++++++++++----------
test/wear-artwork-flow.test.mjs | 8 ++++++--
2 files changed, 28 insertions(+), 12 deletions(-)
diff --git a/scripts/reconcile-wear-orders.mjs b/scripts/reconcile-wear-orders.mjs
index fb605ad..fe3a4be 100644
--- a/scripts/reconcile-wear-orders.mjs
+++ b/scripts/reconcile-wear-orders.mjs
@@ -70,20 +70,27 @@ function wearOrderItems(order) {
return [{ qid: order.qid, signature_name: order.signature_name, garment: order.garment, garment_label: order.garment_label,
color: order.color, color_label: order.color_label, size: order.size, placement: order.placement }];
}
-// Mirrors server.js's wearLuminance()/recolorSignatureToLight() exactly —
-// see that file for the full explanation (Printify prints a design's own
-// pixel colors as-is, so a dark-ink signature on a dark garment needs
-// recoloring to a light ink before it's actually printed). Fails open: any
-// error falls back to the original image rather than blocking reconciliation.
+// Mirrors server.js's wearLuminance()/safeInkHex()/recolorSignatureInk()
+// exactly (see that file for the full explanation) — a dark-ink signature on
+// a dark garment needs recoloring or it prints near-invisible (Printify
+// prints a design's own pixel colors as-is). Recolor ONLY when there's a
+// reason: the buyer picked an ink color, or the cloth is dark (white,
+// matching the on-shirt preview's inkHexFor()) — otherwise the ORIGINAL art
+// ships untouched. Fails open: any recolor error falls back to the original
+// image rather than blocking reconciliation.
const wearLuminance = hex => {
const n = parseInt(hex.slice(1), 16), r = (n>>16)&255, g = (n>>8)&255, b = n&255;
return (0.299*r + 0.587*g + 0.114*b) / 255;
};
+// Sanitize a stored inkColor before it can reach ImageMagick's -fill; only a
+// literal #rrggbb is allowed, anything else is treated as "no choice made".
+const safeInkHex = v => (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v)) ? v.toLowerCase() : null;
const RECOLOR_DIR = join(ROOT, 'public', 'assets', 'recolored-signatures');
const WEAR_SITE_ORIGIN = 'https://celebsignatures.com';
-async function recolorSignatureToLight(imageUrl) {
+async function recolorSignatureInk(imageUrl, hex) {
+ const ink = safeInkHex(hex) || '#f5f3ee'; // fall back to the old light ink
try {
- const hash = createHash('sha256').update(imageUrl).digest('hex').slice(0, 24);
+ const hash = createHash('sha256').update(imageUrl + '|' + ink).digest('hex').slice(0, 24);
const outPath = join(RECOLOR_DIR, `${hash}.png`);
// FULLY QUALIFIED — flows into the Printify order payload as images[0].src,
// which Printify fetches from the public internet (caught via a2a from
@@ -96,7 +103,7 @@ async function recolorSignatureToLight(imageUrl) {
const out = await new Promise((resolve, reject) => {
// 'convert' not 'magick': the Kamatera prod box only has ImageMagick 6
// (convert), not the IMv7 'magick' unified CLI — verified via SSH.
- const proc = spawn('convert', ['-background', 'none', '-', '-channel', 'RGB', '-fill', '#f5f3ee', '-colorize', '100%', 'png:-']);
+ const proc = spawn('convert', ['-background', 'none', '-', '-channel', 'RGB', '-fill', ink, '-colorize', '100%', 'png:-']);
const chunks = []; let errText = '';
const timer = setTimeout(() => { proc.kill(); reject(new Error('magick timeout')); }, 8000);
proc.stdout.on('data', c => chunks.push(c));
@@ -109,7 +116,7 @@ async function recolorSignatureToLight(imageUrl) {
await writeFile(outPath, out);
return outUrl;
} catch (e) {
- console.error('recolorSignatureToLight failed, using original image:', e.message);
+ console.error('recolorSignatureInk failed, using original image:', e.message);
return imageUrl;
}
}
@@ -162,7 +169,12 @@ async function main() {
if (designUrl) {
const g = (draftTpl.garments || []).find(x => x.id === it.garment);
const c = g && (g.colors || []).find(x => x.id === it.color);
- if (c && wearLuminance(c.hex) < 0.4) designUrl = await recolorSignatureToLight(designUrl);
+ const darkCloth = !!(c && wearLuminance(c.hex) < 0.4);
+ // Recolor ONLY when there's a reason: the buyer picked a color, or
+ // the cloth is dark (a dark signature would vanish → white).
+ // Otherwise keep the ORIGINAL art in its true source colors.
+ const ink = safeInkHex(it.inkColor) || (darkCloth ? '#ffffff' : null);
+ if (ink) designUrl = await recolorSignatureInk(designUrl, ink);
}
const draft = { draftedAt: new Date().toISOString(), orderId: compositeId, status: 'DRAFT_UNSENT', provider,
recipient_email: order.email, recipient: order.recipient, garment: it.garment, color: it.color, size: it.size,
diff --git a/test/wear-artwork-flow.test.mjs b/test/wear-artwork-flow.test.mjs
index 343c220..f2cf4f0 100644
--- a/test/wear-artwork-flow.test.mjs
+++ b/test/wear-artwork-flow.test.mjs
@@ -108,8 +108,12 @@ globalThis.fetch = async (input, options = {}) => {
assert.equal(png.subarray(1, 4).toString(), 'PNG');
}
const pixels = await exec('convert', [join(work, 'public', new URL(drafts[0].design_image_url).pathname), 'txt:-']);
- assert.match(pixels.stdout, /10,5:.*#F5F3EEFF/i, 'opaque signature pixel has light ink');
- assert.match(pixels.stdout, /0,0:.*#F5F3EE00/i, 'transparent background preserved');
+ // No buyer inkColor on this fixture + dark (black) cloth → white ink,
+ // matching public/wear.html's inkHexFor() exactly (server.js's
+ // recolorSignatureInk call site, 18cca456: darkCloth with no explicit
+ // pick recolors to '#ffffff', not the old fixed '#f5f3ee').
+ assert.match(pixels.stdout, /10,5:.*#FFFFFFFF/i, 'opaque signature pixel has light ink');
+ assert.match(pixels.stdout, /0,0:.*#FFFFFF00/i, 'transparent background preserved');
if (producer === 'success') {
for (const id of [91001, 91002]) await fetch(`${base}/wear-success?sid=cs_test_${id}`);
} else await reconcile();
← 71f5347 wear: surface the Signature Edit — ✦ badge + 'Signature Edit
·
back to CelebritySignatures
·
signature-edit: expand The Signature Edit lookbook from 10 → 7bce96c →