[object Object]

← back to Eur Recrawl

Price basis RESOLVED (retail=trade/0.65/0.85=x1.810, verified vs canary EUR-71216 $590.05); login delegated to claude-eur; await authed session

54ef49f37c2b9cbf04ec447dc6002916c476d031 · 2026-07-30 18:22:18 -0700 · Steve

Files touched

Diff

commit 54ef49f37c2b9cbf04ec447dc6002916c476d031
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 30 18:22:18 2026 -0700

    Price basis RESOLVED (retail=trade/0.65/0.85=x1.810, verified vs canary EUR-71216 $590.05); login delegated to claude-eur; await authed session
---
 PLAN.md      |  6 ++++++
 reset-dg.mjs | 66 ++++++++++++++++++++++++++++++++++--------------------------
 2 files changed, 43 insertions(+), 29 deletions(-)

diff --git a/PLAN.md b/PLAN.md
index 21494bd..cc3c3e2 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -37,3 +37,9 @@ Scaffold + target list built (safe/local). Authenticated crawler = next, gated o
 4. **Cloudflare feasibility unproven** — "Playwright passes CF naturally" is an assumption; CF Bot Management fingerprints automation. Needs a real authenticated-session test, not the curl inference.
 
 **Loop state: BLOCKED on Steve for the price basis (#1) — cannot build the pricing step until the logged-in number's meaning + DW's charge formula are pinned.**
+
+## UPDATE 2026-07-30 — price basis RESOLVED + login delegated
+- **Price basis SOLVED (verified live):** retail = trade_cost / 0.65 / 0.85 (= ×1.810). Confirmed against canary EUR-71216: live roll $590.05 = trade $326.00 × 1.810, Sample $4.25 preserved. (Prior work TK-00113, re-verified here.) Cody FIX-FIRST #1 is closed.
+- **Live write path SOLVED:** bespoke productVariantsBulkCreate (API 2024-10, inventoryPolicy CONTINUE, tracked=false) adds a sellable roll to an already-active sample-only product without disturbing the Sample. Canary proven.
+- **Login delegated:** sibling agent `claude-eur` owns the Playwright trade-LOGIN flow (Osborne + DG Cloudflare) and will drop an authed storage-state at `.auth/osborne.json` + note where per-SKU trade price lives. This terminal (vp-dw-commerce) owns: targets.csv → crawl w/ that session → parse trade price → ×1.810 → PG stage → gated productVariantsBulkCreate write. Standing down on login (avoid clobber/lockout).
+- **Remaining before writes:** (a) authed session handoff; (b) per-SKU trade-price parse; (c) 729 shared-mfr_code colorway handling (capture per-colorway); (d) dry-run then Steve-gated write.
diff --git a/reset-dg.mjs b/reset-dg.mjs
index 26e35ff..e8c0fee 100644
--- a/reset-dg.mjs
+++ b/reset-dg.mjs
@@ -1,46 +1,54 @@
 // reset-dg.mjs — complete the DG consumer-account password reset (Steve-initiated).
+// Engine-selectable to get past Cloudflare:  ENGINE=webkit|firefox|chrome  (default webkit)
 // pw read from /tmp/dg-newpw.txt (never inline). Token from the info@ reset email.
-import { chromium } from 'playwright';
+import { chromium, firefox, webkit } from 'playwright';
 import fs from 'node:fs';
 import path from 'node:path';
 
 const pw = fs.readFileSync('/tmp/dg-newpw.txt', 'utf8').trim();
 const email = 'info@designerwallcoverings.com';
 const url = 'https://www.designersguild.com/en-us/password-reset/l181?token=68DA5BFD-EAFA-48D6-BC0A-EDBCD830AA5EE9A50F873C1CCCB98E74';
+const ENGINE = (process.env.ENGINE || 'webkit').toLowerCase();
 
-const ctx = await chromium.launchPersistentContext(path.join(process.cwd(), '.auth', 'dg'), {
-  headless: false, channel: 'chrome', viewport: { width: 1440, height: 900 },
-});
+const engines = { webkit, firefox, chrome: chromium };
+const drv = engines[ENGINE] || webkit;
+const authDir = path.join(process.cwd(), '.auth', `dg-${ENGINE}`);
+console.log(`engine: ${ENGINE}`);
+
+const launchOpts = { headless: false, viewport: { width: 1440, height: 900 } };
+if (ENGINE === 'chrome') launchOpts.channel = 'chrome';
+const ctx = await drv.launchPersistentContext(authDir, launchOpts);
 const page = ctx.pages()[0] || await ctx.newPage();
 await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch((e) => console.log('nav', e.message));
-// Wait out the Cloudflare "security verification" interstitial (real Chrome clears it).
+
+console.log('  >> If a Cloudflare "verify you are human" check shows, CLICK IT in the window. <<');
 let ready = false;
-for (let i = 0; i < 20; i++) {
+for (let i = 0; i < 80; i++) { // up to ~4 min
   await page.waitForTimeout(3000);
-  if (await page.$('#new-password')) { ready = true; break; }
+  if (await page.$('#new-password').catch(() => null)) { ready = true; break; }
   const t = await page.title().catch(() => '');
-  if (i % 3 === 0) console.log(`  waiting for reset form... (${(i + 1) * 3}s, title="${t.slice(0, 40)}")`);
+  if (i % 4 === 0) console.log(`  waiting for reset form... (${(i + 1) * 3}s, title="${t.slice(0, 40)}")`);
 }
-console.log(ready ? '  reset form ready' : '  reset form still not present (Cloudflare may need a hand in the window)');
+console.log(ready ? '  reset form ready' : '  reset form never appeared — Cloudflare not cleared on this engine.');
 
-async function tryFill(sel, val, label) {
-  const el = await page.$(sel);
-  if (!el) { console.log(`${label}: field not found`); return false; }
-  try { await el.fill(val); console.log(`${label}: filled`); return true; }
-  catch (e) { console.log(`${label}: fill error ${e.message.slice(0, 50)}`); return false; }
+if (ready) {
+  await page.waitForTimeout(1500);
+  // token-reset modal only needs the two password fields + SAVE (do NOT touch #password-reset-email).
+  const fill = async (sel, val, label) => {
+    try { await page.locator(sel).first().fill(val, { timeout: 15000 }); console.log(`${label}: filled`); }
+    catch (e) { console.log(`${label}: ${e.message.slice(0, 45)}`); }
+  };
+  await fill('#new-password', pw, 'new-password');
+  await fill('#new-password-confirm', pw, 'confirm-password');
+  // click the SAVE button inside the visible reset modal
+  const save = await page.locator('button:has-text("SAVE")').first();
+  if (await save.count().catch(() => 0)) { await save.click({ timeout: 8000 }).catch((e) => console.log('save click:', e.message.slice(0, 40))); console.log('SAVE clicked'); }
+  else { await page.keyboard.press('Enter').catch(() => {}); }
+  await page.waitForTimeout(6000);
+  const body = await page.evaluate(() => (document.body ? document.body.innerText : '')).catch(() => '');
+  console.log('post-save url:', page.url());
+  console.log('result:', body.replace(/\s+/g, ' ').slice(0, 300));
+  await page.screenshot({ path: 'probe-reset.png' }).catch(() => {});
 }
-await tryFill('#password-reset-email', email, 'reset-email');
-await tryFill('#new-password', pw, 'new-password');
-await tryFill('#new-password-confirm', pw, 'confirm-password');
-
-const save = await page.$('button:has-text("SAVE")');
-if (save) { await save.click().catch(() => {}); console.log('SAVE clicked'); }
-else { await page.keyboard.press('Enter').catch(() => {}); console.log('submitted via Enter'); }
-await page.waitForTimeout(6000);
-
-const body = await page.evaluate(() => (document.body ? document.body.innerText : '')).catch(() => '');
-console.log('post-save url:', page.url());
-console.log('result:', body.replace(/\s+/g, ' ').slice(0, 300));
-await page.screenshot({ path: 'probe-reset.png' }).catch(() => {});
-await page.waitForTimeout(1500);
-await ctx.close();
+await page.waitForTimeout(1500).catch(() => {});
+await ctx.close().catch(() => {});

← 9834b9e auto-save: 2026-07-30T18:20:08 (5 files) — login.mjs probe-d  ·  back to Eur Recrawl  ·  BREAKTHROUGH: authed Osborne PRICE LISTS -> April-2026 US tr d728197 →