← back to Claude Webdev Accelerator

clients/estimate-instant-contrarian-handoff.md

54 lines

# estimate-instant — contrarian handoff (from the yielded accelerator session)

Two Claude sessions built `estimate-instant` concurrently on 2026-07-15/16. This
session **yielded** to the active one (Steve's call) after running the playbook's
verify + contrarian gate. Captured here so the fixes aren't lost. Checked against
the active session's HEAD `646c3eb` — **all 5 remain open**.

## Verified working (this session, hand-checked)
- Roll-math `estimate()`: 120"×96" Heirloom Damask → 2 rolls / $192 / 26% waste ✓;
  grasscloth 200"×108" → 3 rolls / $384 / 25% ✓; too-tall + zero-input guards fire ✓.
- Embeddable widget `embed.js` (iframe + auto-height postMessage) — shipped, matches
  the brief's 200+-site embed thesis.

## STATUS: all 5 fixes APPLIED + browser-verified — commit `fb752d2` (2026-07-16). Section below kept for the record.

## Contrarian verdict: FIX FIRST — 5 open items (highest leverage first)

1. **Leads PII exposed in public mode** — `server.js:57` `if (!BASIC_AUTH) return true`.
   `/api/leads` + `/admin` are gated only when `BASIC_AUTH` is set. On any embed host
   that runs the estimator open (the whole point of the widget), the leads store is
   world-readable. Fix: a separate always-on `ADMIN_AUTH` (default `admin:DW2024!`)
   that gates `/api/leads` + `/admin` independent of the public-preview `BASIC_AUTH`.

2. **postMessage targets `'*'`** — `public/index.html:165`. Send height to the known
   host origin instead. Pass it in: embed.js `f.src = origin + '/?embed=1&host=' +
   encodeURIComponent(location.origin)`; index reads `host` and targets it.

3. **Unit toggle silently mis-scales** — `public/index.html:128` only relabels ft/in;
   the typed value is unchanged, so toggling ft→in feeds e.g. `12` back through the
   inch path. Fix: convert the current `#w`/`#h` values in place on toggle (×12 / ÷12),
   or clear them.

4. **Half-drop math is an approximation stated with false confidence** —
   `server.js:96` docks `repeat/2` from usable roll length. The defensible
   never-short model is per-strip: `cutLen += repeat/2` before rounding up to the
   full repeat. Minimum honest fix if not reworking: say so in the disclaimer
   ("half-drop / large-repeat patterns — add ~10–15%").

5. **Single "wall width" invites under-ordering a room** — `public/index.html:70`.
   Add a hint: "total width of all walls to cover (linear footage)". One sentence
   prevents every non-trade user from ordering for their widest wall only.

## New surface to gate (added by the active session, needs its own review)
- Shopify **"Buy N rolls" checkout CTA** hands off to the **live** Shopify checkout
  (TEST banner + stand-in-variant logic present). Live checkout = customer-facing /
  money → verify the variant mapping and keep it Steve-gated before any public embed.

## Data note (already handled by the active session)
- Real roll specs now pull via `scripts/pull-rollspecs.sql` from `dw_unified`. Seed
  `data/rolls.json` remains the labeled-representative fallback. `dw_unified`
  wallcovering roll metafields are messy free-text (e.g. `length:"Sisal"`,
  `width:"36 inches (trim to 34\")"`) — the SQL must normalize width/length/repeat,
  not trust the raw metafield strings.