← back to Pattern Vault
fernwick: add uploader health-check monitor + record the first 3 published designs
9efb375ee886be14c1d19d549d353ad3516e1d5e · 2026-07-08 08:41:47 -0700 · Steve Abrams
The whole pipeline was silently broken for a long time (0 uploads) with TWO invisible
failures — expired session AND Spoonflower moving the upload page — and nothing caught it.
- check_health.js: read-only monitor that verifies (a) session valid (not bounced to /login)
and (b) the /artists/designs/new file input is present. Exits 0=HEALTHY / 2=DEGRADED with a
reason. Catches both silent-failure classes; publishes nothing. Verified HEALTHY.
- daily_run.sh: runs check_health as a pre-flight every run, logs uploader-health + writes
health.json to the run dir — so a silent break is now loud in cron.out.log.
- spoonflower-uploads.json: recorded the first 3 real uploads (22712805/06/09, Fernwick Studio,
PRIVATE) that the direct upload_sf.js run didn't log.
Files touched
M data/spoonflower-uploads.jsonA whimsical-compare/daily/scripts/check_health.jsM whimsical-compare/daily/scripts/daily_run.sh
Diff
commit 9efb375ee886be14c1d19d549d353ad3516e1d5e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 8 08:41:47 2026 -0700
fernwick: add uploader health-check monitor + record the first 3 published designs
The whole pipeline was silently broken for a long time (0 uploads) with TWO invisible
failures — expired session AND Spoonflower moving the upload page — and nothing caught it.
- check_health.js: read-only monitor that verifies (a) session valid (not bounced to /login)
and (b) the /artists/designs/new file input is present. Exits 0=HEALTHY / 2=DEGRADED with a
reason. Catches both silent-failure classes; publishes nothing. Verified HEALTHY.
- daily_run.sh: runs check_health as a pre-flight every run, logs uploader-health + writes
health.json to the run dir — so a silent break is now loud in cron.out.log.
- spoonflower-uploads.json: recorded the first 3 real uploads (22712805/06/09, Fernwick Studio,
PRIVATE) that the direct upload_sf.js run didn't log.
---
data/spoonflower-uploads.json | 146 +++++-------------------
whimsical-compare/daily/scripts/check_health.js | 64 +++++++++++
whimsical-compare/daily/scripts/daily_run.sh | 7 ++
3 files changed, 99 insertions(+), 118 deletions(-)
diff --git a/data/spoonflower-uploads.json b/data/spoonflower-uploads.json
index 6c6b147..42c9c84 100644
--- a/data/spoonflower-uploads.json
+++ b/data/spoonflower-uploads.json
@@ -1,119 +1,29 @@
-{
- "shop": "Fernwick Studio",
- "handle": "fernwick_studio",
- "account": "900854229",
- "login": "info@printmurals.com",
- "_seller_account_going_forward": "info@designerwallcoverings.com — 2026-07-07: ALL future uploads switch to this account; info@printmurals.com becomes a designer persona. This record is the initial printmurals/Fernwick batch (account 900854229).",
- "uploaded_at": "2026-07-02",
- "state": "Private (not for sale — payout/tax setup is the remaining switch)",
- "designs": [
- {
- "n": 1,
- "id": "22668196",
- "title": "Arden Botanical — Heritage Trellis",
- "url": "https://www.spoonflower.com/artists/designs/22668196",
- "proofed": true
- },
- {
- "n": 2,
- "id": "22668205",
- "title": "Nocturne Bloom — Dark Moody Floral",
- "url": "https://www.spoonflower.com/artists/designs/22668205",
- "proofed": true
- },
- {
- "n": 3,
- "id": "22668206",
- "title": "Gilded Bough — Heritage Noir",
- "url": "https://www.spoonflower.com/artists/designs/22668206",
- "proofed": true
- },
- {
- "n": 4,
- "id": "22668210",
- "title": "Rosewater Trellis — Heritage Soft",
- "url": "https://www.spoonflower.com/artists/designs/22668210",
- "proofed": false,
- "hold_reason": "4-way mirror repeat fault (graphic-designer HOLD)"
- },
- {
- "n": 5,
- "id": "22668217",
- "title": "Amethyst Nocturne — Moody Bloom",
- "url": "https://www.spoonflower.com/artists/designs/22668217",
- "proofed": true
- },
- {
- "n": 6,
- "id": "22668219",
- "title": "Emerald Nocturne — Camellia",
- "url": "https://www.spoonflower.com/artists/designs/22668219",
- "proofed": true
- },
- {
- "n": 7,
- "id": "22668220",
- "title": "Atomic Meadow — Mid-Century Retro",
- "url": "https://www.spoonflower.com/artists/designs/22668220",
- "proofed": false,
- "hold_reason": "column-to-tile-edge seam misalignment (graphic-designer HOLD)"
- },
- {
- "n": 8,
- "id": "22668245",
- "title": "Tide Lines — Coastal Stripe",
- "url": "https://www.spoonflower.com/artists/designs/22668245",
- "proofed": true
- },
- {
- "n": 9,
- "id": "22668247",
- "title": "Folk Tile — Block Print Geometry",
- "url": "https://www.spoonflower.com/artists/designs/22668247",
- "proofed": true
- },
- {
- "n": 10,
- "id": "22668250",
- "title": "Calico Vine — Heritage Ditsy",
- "url": "https://www.spoonflower.com/artists/designs/22668250",
- "proofed": true
- },
- {
- "n": 11,
- "id": "22668252",
- "title": "Cottage Wash — Painterly Floral",
- "url": "https://www.spoonflower.com/artists/designs/22668252",
- "proofed": true
- },
- {
- "n": 12,
- "id": "22668255",
- "title": "Indigo Trellis — Heritage Vine",
- "url": "https://www.spoonflower.com/artists/designs/22668255",
- "proofed": true
- },
- {
- "n": 13,
- "id": "22668266",
- "title": "Crimson Nocturne — Moody Rose",
- "url": "https://www.spoonflower.com/artists/designs/22668266",
- "proofed": true
- },
- {
- "n": 14,
- "id": "22668271",
- "title": "Teal Nocturne — Magnolia Bloom",
- "url": "https://www.spoonflower.com/artists/designs/22668271",
- "proofed": true
- }
- ],
- "proofing": {
- "reviewed_by": "graphic-designer agent",
- "date": "2026-07-02",
- "proofed": 12,
- "held": 2,
- "method": "Spoonflower digital proofing (For Sale > Proof > guided repeat review > Looks Good)"
+[
+ {
+ "id": "22712805",
+ "title": "Fern Wink — Rainbows",
+ "url": "https://www.spoonflower.com/artists/designs/22712805",
+ "account": "info@designerwallcoverings.com",
+ "shop": "Fernwick Studio",
+ "private": true,
+ "uploadedAt": "2026-07-08T15:40:50.287Z"
},
- "remaining_switch": "Account verification (tax ID + payout) required before any design can go For Sale — Steves manual step. Product coverage (fabric, wallpaper, home goods) is automatic once verified."
-}
\ No newline at end of file
+ {
+ "id": "22712806",
+ "title": "Clover Trellis — Rolling Hills",
+ "url": "https://www.spoonflower.com/artists/designs/22712806",
+ "account": "info@designerwallcoverings.com",
+ "shop": "Fernwick Studio",
+ "private": true,
+ "uploadedAt": "2026-07-08T15:40:50.287Z"
+ },
+ {
+ "id": "22712809",
+ "title": "Foxglove Ditty — Garden Snails",
+ "url": "https://www.spoonflower.com/artists/designs/22712809",
+ "account": "info@designerwallcoverings.com",
+ "shop": "Fernwick Studio",
+ "private": true,
+ "uploadedAt": "2026-07-08T15:40:50.287Z"
+ }
+]
\ No newline at end of file
diff --git a/whimsical-compare/daily/scripts/check_health.js b/whimsical-compare/daily/scripts/check_health.js
new file mode 100644
index 0000000..125e5ce
--- /dev/null
+++ b/whimsical-compare/daily/scripts/check_health.js
@@ -0,0 +1,64 @@
+#!/usr/bin/env node
+/**
+ * Fernwick → Spoonflower UPLOADER HEALTH CHECK (read-only, publishes nothing).
+ *
+ * Born 2026-07-07: the pipeline generated designs daily but published 0 for a long
+ * time and NOTHING noticed — two independent silent failures (a) the stored session
+ * expired, (b) Spoonflower moved the upload page to /artists/designs/new so the file
+ * input was "not found". This check catches BOTH classes before a real run:
+ * - session VALID? (logged in, not bounced to /login)
+ * - upload page REACHABLE? (the /artists/designs/new file input is present)
+ * Exits 0 = HEALTHY, 2 = DEGRADED (with a reason). Wire to a canary/cron to alert.
+ *
+ * node check_health.js # human-readable
+ * node check_health.js --json # {healthy, session, uploadPage, reason}
+ */
+const { chromium } = require('/Users/macstudio3/.npm-global/lib/node_modules/playwright');
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+
+const STATE = process.env.SF_STATE || path.join(os.homedir(), '.config/pattern-vault/sf-state.json');
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/126 Safari/537.36';
+const JSON_OUT = process.argv.includes('--json');
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+(async () => {
+ const out = { healthy: false, session: false, uploadPage: false, reason: '', checkedAt: new Date().toISOString() };
+ if (!fs.existsSync(STATE)) { out.reason = 'no session file at ' + STATE; return done(out); }
+
+ let b;
+ try {
+ b = await chromium.launch({ headless: true });
+ const ctx = await b.newContext({ storageState: STATE, userAgent: UA });
+ const pg = await ctx.newPage();
+
+ // 1. session valid?
+ await pg.goto('https://www.spoonflower.com/en/dashboard', { waitUntil: 'domcontentloaded', timeout: 45000 }).catch(() => {});
+ await sleep(3000);
+ if (pg.url().includes('/login')) { out.reason = 'session expired (bounced to /login) — run capture_sf_session.js --headful'; return done(out, b); }
+ out.session = true;
+
+ // 2. upload page reachable? (the exact thing that silently rotted)
+ await pg.goto('https://www.spoonflower.com/artists/designs/new', { waitUntil: 'domcontentloaded', timeout: 45000 }).catch(() => {});
+ await sleep(4000);
+ if (pg.url().includes('/login')) { out.reason = 'upload page bounced to /login'; return done(out, b); }
+ const fi = await pg.waitForSelector('input[type=file]', { state: 'attached', timeout: 8000 }).catch(() => null);
+ if (!fi) { out.reason = 'upload page has NO file input — Spoonflower may have moved it again (recalibrate openUploader URLs/selectors)'; return done(out, b); }
+ out.uploadPage = true;
+
+ out.healthy = true;
+ out.reason = 'session valid + upload page reachable';
+ return done(out, b);
+ } catch (e) {
+ out.reason = 'check error: ' + e.message;
+ return done(out, b);
+ }
+})();
+
+async function done(out, b) {
+ if (b) await b.close().catch(() => {});
+ if (JSON_OUT) console.log(JSON.stringify(out));
+ else console.log(`${out.healthy ? '✅ HEALTHY' : '❌ DEGRADED'} — ${out.reason} (session=${out.session} uploadPage=${out.uploadPage})`);
+ process.exit(out.healthy ? 0 : 2);
+}
diff --git a/whimsical-compare/daily/scripts/daily_run.sh b/whimsical-compare/daily/scripts/daily_run.sh
index 669c6e8..cdb7588 100755
--- a/whimsical-compare/daily/scripts/daily_run.sh
+++ b/whimsical-compare/daily/scripts/daily_run.sh
@@ -46,6 +46,13 @@ SEED_BASE=$(( NOW_EPOCH % 1000000 ))
mkdir -p "$STATE/$TODAY"
MANIFEST="$STATE/$TODAY/manifest.json"
+# 0. pre-flight uploader health (born 2026-07-07 — the pipeline was silently broken for a long
+# time: the stored session expired AND Spoonflower moved the upload page to /artists/designs/new,
+# and NOTHING noticed). This read-only check surfaces both failure classes in every run's log.
+HEALTH=$(node "$HERE/check_health.js" --json 2>/dev/null || echo '{"healthy":false,"reason":"health check crashed"}')
+echo "uploader-health: $HEALTH"
+echo "$HEALTH" > "$STATE/$TODAY/health.json"
+
# 1. generate
python3 "$HERE/gen_daily.py" --n $N --date "$TODAY" --seed-base "$SEED_BASE" > "$STATE/$TODAY/gen.stdout" 2> "$STATE/$TODAY/gen.stderr" || true
[ -f "$MANIFEST" ] || { echo "no manifest — gen failed"; cat "$STATE/$TODAY/gen.stderr" | tail; exit 1; }
← 6603026 auto-save: 2026-07-08T08:34:49 (1 files) — package-lock.json
·
back to Pattern Vault
·
fernwick: surface uploader-DEGRADED in the daily email (both 3f91374 →