← back to Wallco Ai

TODO.md

162 lines

# wallco-ai TODO

Surfaced to CNCP at http://localhost:3333 (via /api/tasks reading this file).
Format — `## Section` headers + `- [ ] task` / `- [x] done`. CNCP picks
up unchecked rows automatically.

## designs.json duplicate-row defect — FIXED locally, deduped (2026-06-21, commit f55cefc, prod rebuild+deploy Steve-gated)
- [x] Root cause: base table `all_designs` (under the `spoon_all_designs` VIEW, a plain `SELECT FROM all_designs`, no JOIN/UNION) physically stores ~2 byte-identical rows per id — 69,399 rows / 35,192 unique. Builder faithfully emitted every physical row → designs.json doubled (53,385 raw rows / 27,165 unique ids; 26,220 pure-dup rows). Safety proof: 34,207/34,207 dup ids fully identical across every column, 0 differ → dedup provably lossless.
- [x] Fix (scripts/refresh_designs_snapshot.py): `SELECT DISTINCT ON (id)` collapses the fan-out at emission; catastrophe guard now compares UNIQUE-id coverage (not raw rows) so a legit 2x→1x dedup passes while a sparse-PG regen still aborts; byte guard now compares bytes-per-RAW-ROW (not total bytes) so dedup passes while a field-strip clobber still aborts; added WARN if any prior unique id disappears.
- [x] Validated LOCALLY (scratch dirs only, prod designs.json untouched): 53,385→27,165 raw rows, 27,165→27,165 unique ids (0 dropped/0 added), 0 payload mismatches, published render set 6,264→6,264 unchanged. Guard regression: legit dedup PASS, sparse-PG regen ABORT, field-strip clobber ABORT.
- [x] Prod rebuild done on Mac2 (2026-06-21 ~20:00): `python3 scripts/refresh_designs_snapshot.py` → 27,166 rows, NO ABORT, guards passed. `node -c server.js` clean. Proven strict superset of prod: 27,166 unique vs prod 26,282 (0 dropped, 884 net new), raw/unique ratio 1.0, 0 local_path leak. Backups: local `data/designs.json.bak-prededuploy-20260621-195901` + both prod copies `/root/public-projects/{wallco-ai,wallpapersback}/data/designs.json.bak-prededuploy-20260621-195901`.
- [x] **wallco.ai is RETIRED.** Prod inspection: nginx serves `location / { return 410; }` (deliberate, backup `wallco.ai.bak-410-*` + `wallco.ai.bak-pre-wpb-20260621`), pm2 `wallco-ai` process REMOVED. wallco.ai → wallpapersback migration was done deliberately earlier 2026-06-21 (matches Steve's "everything on wallpapersback"). `./deploy-kamatera.sh` to wallco.ai is OBSOLETE — skipped (DTD 3/3 verdict A). wallpapersback.com (pm2 id373, port 9835) is the SOLE live storefront.
- [ ] **STEVE — DECISION NEEDED before re-deploy.** Deployed deduped snapshot to wallpapersback → its `/api/designs` `total` DROPPED 11,818 → 5,909, so per Steve's hard "don't shrink the served total / abort+rollback" rule I ROLLED BACK (live restored to 11,818, md5 `5966fffd…`, pm2 online, homepage+image 200). Root cause: the server computes `total = DESIGNS.filter(designHasImage).length` where `DESIGNS` is NOT deduped by id, so the doubled snapshot counted (and the grid SERVED) every design as TWO identical cards (proven: page1 = 60 items / 30 unique ids, each ×2). So 11,818 = 5,909 unique designs double-counted; the dedup loses ZERO unique designs (0 unique image-bearing ids dropped, 884 gained). The fork is what "lossless" means: (A) unique-design reading — re-deploy the deduped snapshot, 5,909 is the honest count, double-cards were a bug; OR (B) literal served-total reading — keep rollback. Better long-term fix is at the SERVER: dedupe `DESIGNS` by id in-memory at boot (in BOTH wallpapersback AND wallco-ai code) so the count is honest regardless of snapshot, then deploy. NOT re-deployed pending Steve's call.
- [ ] OPTIONAL durable cleanup (separate, destructive, Steve-gated): de-dupe the `all_designs` base table + add UNIQUE/PK on id so future builds can't re-double even without DISTINCT ON.

## Seam joint-heal — element-copy method + four-horsemen gate (2026-06-12, DTD verdict C, Steve-gated decision)
- [x] Root cause of the rejected smudge variants CONFIRMED: old heal_band_mid AVERAGED L/R columns across the seam → blurred band = the smudge Steve rejected all session. Built scripts/element_copy_heal.py as the replacement: CRISP wrap-edge palette-reconcile (writes only dominant-palette colors on disagreeing wrap columns; NO averaging, NO drawn line). Committed 0fdef02.
- [x] First element-copy attempt (half-period-roll patch-copy) zeroed the seam scan to 0.0 PASS but VISIBLY transplanted a block / sliced the frog's face on 2766 — rejected on inspection. DTD verdict C: interior mid-lines (W/2,H/2) are arbitrary sample lines, NOT real tile joins; never heal them. Rewrote to wrap-edge-only.
- [x] Wrap-edge v2 PASSES four-horsemen elegance on 2766: no smudge, no slice, two-tone engraving crispness preserved, frog intact. Before/after at /tmp/beforeafter_2766_wrap.png; edge zoom /tmp/wrapedge_zoom_2766.png.
- [x] DIAGNOSTIC (blocking): ALL 9 Wave-1 roots are SINGLE-SUBJECT and do NOT tile (autocorrelation ~0/-negative; none >0.3 repeat threshold). The wrap heal is elegant but only moves the scan 9.34→8.86 — stays WARN, because the subject body genuinely does not continue across the tile wrap. Cannot reach the EDGE-SEAMLESS PASS gate without visibly damaging the subject.
- [x] Loop kept STOPPED (/tmp/seam-joint-heal.STOP present, runner not running). NOTHING inserted, NOTHING published (0/13 old smudge variants published). Curator filter NOT repointed (no v2 variants exist — correct under verdict).
- [ ] **STEVE — DECISION NEEDED:** these 9 roots can't be both scan-PASS AND elegant (they don't tile). Pick: (A) relax the EDGE-SEAMLESS gate for confirmed non-tiling single-subject roots — ship the crisp wrap-edge heal at WARN (Codex's dissent; requires you to OK relaxing a hard gate), OR (B) route these to regen-until-clean / a true seamless re-roll instead of joint-heal (joint-heal is for tiles with localized joints, which these are not). I did NOT resume the loop pending your call.

## Generator-health regression — batch child timeout (2026-06-01, DIAGNOSED + LOCAL FIX, deploy Steve-gated)
- [x] Root cause: `0/N survived the quality gate (code null)` was NOT a gate rejection. The batch child SIGTERM-timed-out mid-flight. Batch route budgeted 150s/render (`Math.max(180000, n*150000)`) but comfy polls up to 15min/render (commit 86d053ad). Every multi-render comfy batch was killed before finishing; runs 27/28/30 hit their exact timeouts (3/10/30 min) and died at code=null. 5 designs (10251-10255) DID land in prod PG, correctly unpublished — the run rows lied.
- [x] Fix (commit 3eeffee3, server.js + lib/{ghost,composition,subject}-detector.js): (1) per-render budget 150s→18min +5min slack, cap 4h (env `WALLCO_BATCH_PER_RENDER_MS`); (2) code=null signal-kill distinguished from real gate-reject(code 1), reported accurately; (3) reconciler falls back to harvesting `✓ #<id>` lines when killed-child never prints the `Created…IDs:` summary; (4) boot reaper closes runs stuck in `running` >30min (8 prod orphans incl pm2-wipe #29); (5) retired gemini-2.0-flash (404 since 2026-06-01) → gemini-2.5-flash in the 3 in-gen detectors (matches b25dd485).
- [ ] DEPLOY Steve-gated: `./deploy-kamatera.sh` from ~/Projects/wallco-ai. On boot the reaper closes the 8 stuck `running` rows (#1,2,21-25,29). node -c clean. No publish/override touched.
- [x] Migrated remaining retired 2.0-flash refs → gemini-2.5-flash (LOCAL, not yet deployed): scripts/bleed_guard.js + scripts/rate_design.js (URL model now env-overridable `GEMINI_VISION_MODEL` default 2.5-flash); scripts/audit-{dogs-breed,stoned-animals-mood,cohort-generic,drunk-animals-abstraction}.js (`AUDIT_MODEL` default 2.5-flash). ALSO caught 6 live `model:'gemini-2.0-flash'` vision call sites in server.js that the prior note missed (regenerate-reverse describe + ghost detect + opacity verify; interior-designer critique; cataloging; style analysis) — all → 2.5-flash, the same model the rest of wallco's vision path uses. node -c passed on all 7 files. Not deployed (Steve gates deploys).

## Prompt-vs-image rating (2026-06-01, Steve-approved both gated steps)
- [x] Migration: user_prompt_match (smallint) + user_prompt_verdict (text) + user_prompt_rated_at (timestamptz). LOCAL via CREATE OR REPLACE VIEW (spoon_all_designs is a view over all_designs locally); PROD via ALTER TABLE spoon_all_designs (it's a real table on prod — topology drift). Both applied + verified.
- [x] prompt-rate API: PG is source of truth (list reads cols; POST writes PG-first strict, merge-from-PG, then JSONL audit append). is_published never touched (verified live before=after).
- [x] Backfill JSONL→PG (scripts/backfill_prompt_match.js): 0 rows applied — ledger was empty (no ratings recorded yet).
- [x] learn_from_votes.js reads user_prompt_match: excludes verdict='reject' / match<=2 from the learned recipe; adds (match-3)*0.2 rank bonus (NULL→neutral).
- [x] Deployed via ./deploy-kamatera.sh. Live smoke: /designs?sort=color & ?sort=style = 200; /admin/prompt-vs-image gated 404 (no auth) / 200 (admin); prompt-rate/list returns real prompts + new cols, no 500.

## 2026-05-30 session — CURRENT

- [x] **ROOM MOCKUPS at scale — SHIPPED LIVE, FREE.** Re-rendered 4,763 published-without-room designs with the FREE local PIL compositor (`scripts/pil-room-composite.py`, full-bleed quad onto `_template_blank_living_room.png` + perspective warp + light blend) — 21 min, **$0** (vs ~$750 Gemini cloud, Steve vetoed). Rsynced 5 GB → prod `data/rooms`, inserted 284 new `wallco_rooms` rows (other ~4,479 already had rows w/ stale `room_mockups` → PNG overwrite auto-updated). Verified 8 designs across ID range live (PNG 200, 1 deduped room). Graphic-designer + UX agents drove QA (full-bleed, no white-frame, no blob).
- [x] **Room PDP dual-path resolver — fixed + live (prod 911ccd1 / Mac2 43012d9d).** `resolveRoomUrl`/`resolveDesignRooms` — rooms resolve from the wallco_rooms TABLE (durable) via data/rooms OR legacy /uploads image_path, applied to /api/rooms+gallery, PDP "See in a room" figure, tearsheet, brief. Restored ~1,217 legacy designs regressed earlier same day. /admin/rooms list filters removed_at (78c7a75).
- [x] Pulled 8 BAD damask room renders (oversized/micro, misrepresented product) — reversible soft-delete, Steve-approved.
- [ ] **Room pipeline Phase 2 (gated, when ready):** the room-setting-app Gemini engine still has flat/scale/single-template issues for NEW cloud renders — prompt-harden (no-frame), scale-normalize (analyze-pattern route exists, caller hardcodes 27"), hue→room template. See `~/.claude/yolo-queue/ROOM-PIPELINE-ROOTCAUSE.md`. NOT urgent — the free PIL path covers the catalog.
- [~] **Orphan room renders — FIXED LOCALLY 2026-06-01.** Census corrected the stale "~114": actually **141 gallery rows** missing the canonical `data/rooms/design_<id>_<rt>.png` render (resolver checks canonical only, ignores image_path). ALL 141 were **copy-fixable** from existing upload PNGs (lossless `shutil.copy2`, NO regen) — restored on Mac2, `MISSING_gallery 141→0`, rooms serve HTTP 200; **104 are published live designs** that were missing their "See it in a room". Reusable: `/tmp/orphan_strict.py` (census) + `/tmp/orphan_fix.py` (copy upload→canonical). **STILL OPEN (Steve-gated):** prod wallco.ai still 404s these — `data/rooms/` is deploy-excluded, so push via a **targeted additive rsync of just the 141** (`--files-from=/tmp/orphan-rooms-filelist.txt`, no `--delete`, no restart — resolver reads files live). Command handed to Steve 2026-06-01; classifier blocks autonomous prod write.
- [~] **Security (Kamatera compromise 2026-05-30):** wallco-ai stripped of hardcoded dw_admin DSN (c07228b8) + security-monitor cron (4d989e2e). **Designer-Wallcoverings code hygiene DONE 2026-05-30** — 25 source files stripped across nested DW-Programming (3e3124d, 16 files) + nested shopify (0f0a91a, 2 files) + parent (06d3d7a, 7 files), plus 7 stale .pyc untracked via new __pycache__ gitignore rule. All dw_admin pw literals gone from `git grep` (literal redacted from this TODO row to keep gitleaks happy). Patterns covered: bare DSN, export PGPASSWORD, inline PGPASSWORD='..' psql, DB_PASS="..", README.md env-example. Host + DB preserved in every DSN per secret-strip convention. **STILL OPEN (Steve-gated):** rotate the dw_admin pw itself via `~/Projects/_shared/docs/pg-rotation-runbook.md` — once rotated, these scripts inherit the new pw via env without further code changes. **Queued 2026-06-01** in `~/Projects/secrets-manager/ROTATION-CHECKLIST.md` (item #1, alongside the 2 Gemini key mints).
- [x] SSH auth to Kamatera fixed (rotated key not matched by IP in ~/.ssh/config → added IP to Host block + ssh-add).
- [x] Auto-resume: canonical daemon `com.steve.claude-auto-resume` (pid 3452) is the real one; global kill-switch `~/.claude-resume-disabled` is ON to stop self-trigger in phrase-quoting sessions (removed my redundant claude-resume-all.sh dup).

## Open from 2026-05-26 session

- [~] **DOGS COLLECTION** — generation + curator UI both DONE; awaiting Steve curation. Verified 2026-05-31 via live DB: 220/220 designs across 11 breeds (20 each) — beagle, bulldog, dachshund, french-bulldog, german-shepherd, german-shorthaired-pointer, golden-retriever, labrador-retriever, pit-lab-mix, poodle, rottweiler. 17 already published (Steve curated up); **203 still unpublished awaiting hot-or-not pass.** Generator: scripts/generate-dogs.js (commit 3b8204fd). Curator UI: /admin/dogs-curator (commits d46222f1 + 46a8f148 — has the date+time chip per standing rule). Endpoint /api/admin/dogs/list at server.js:1248. DTD verdict (unanimous 3/3): 11 groups = AKC top-10 + pit-lab-mix ("Labrabull"). Category convention `dogs · <breed-slug>`. Engine = Gemini repeat-v2 (buildRepeatPrompt + composition gate ≥3 instances). **NEXT (Steve-gated):** open /admin/dogs-curator, hot-or-not the 203, then flip published + deploy.
- [x] **INCIDENT RESOLVED 2026-05-26 — wallco.ai catalog collapsed to 157 served.** Root cause: `server.js` recolor-bake spawns `refresh_designs_snapshot.py` ON PROD, which regenerates designs.json from prod's near-empty PG (~350 rows; the real ~50k catalog lives in Mac2 PG), and `setInterval(load(true),30000)` reloads the gutted manifest. data/generated (85,840 files) was intact — only the manifest died; CF served stale 200s masking it. Fix: catastrophe guard in refresh script (refuse <50% shrink, commit bf7915c8) + deploy step 2c ships it (commit 6421b5e1) + Steve generalized the allowlist to prefix-match. Restored via ./deploy-kamatera.sh → /health 157 → 13,446 (13,444 served). Memory: feedback_wallco_prod_pg_sparse_designs_json_clobber.
- [x] fliepaper-bugs Gemini remakes — PUBLISHED + UPSCALED 2026-05-26. Steve approved "the PASS + the cabinet murals". Published 11: PASS #43215 (firefly-nocturne midnight-flora, seamless_tile) + 5 stag-beetle specimen plates #43230-43234 (mural_panel) + scarab ×2 (#43226/43229) + luna-moth ×3 (#43219/43221/43223). The 5 stag-beetle murals UPSCALED 1024→4096px via Real-ESRGAN x4plus (27.3" framed-panel print), TIFs rebuilt at 4K, shipped as `_4x` files, live on prod (images 200, 27-32MB each). Remaining: 16 EDGES_FAIL remakes + 9 unpicked WARN still is_published=FALSE — leave culled unless Steve names ids. 8x (~9ft wall-scale) available via a 2nd ESRGAN pass if wanted.
- [x] Scale the edge-heal to the 630 non-ghost edges_only cohort — DONE (Steve-approved 2026-05-26). `scripts/edge_seam_heal.py --all` ran 630/630, 0 err, 24.8s, $0. Combined mids+edges feather (np.roll offset-heal for the wrap seam PLUS the mid_seam_heal center-band blur — the "edges_only" rows carried WARN-range mids). Realized PASS rate ~72% (sampled 36/50): ≈454 reach edges-agent PASS, ≈176 land WARN (residual edge ΔE 5-9, mids fixed clean <2 — feathered down from FAIL but not to PASS; a 12px band can't fully blend a wide edge discontinuity). All 630 stay is_published=FALSE. snapshot allowlist includes 'pil-edge-heal'. (cohort 1,064 total; 428 BLEED_GHOST excluded.)
- [ ] **NEXT (needs Steve): promote the edge-heal PASSes + deploy.** Promotion must gate on a fresh per-row edges-agent scan (only verdict=PASS flips is_published=TRUE), then `./deploy-kamatera.sh`. ~454 promotable. Optional: a WARN-targeted second pass with a wider BAND_PX/SIGMA to recover some of the 176 — risks a visible soft stripe, so sample-gate it first.
- [x] Address the 2,883 catastrophic-defect cohort (both edges AND mids broken) — DONE 2026-05-26. `edge_seam_heal.py --grid both` already ran the whole cohort: 2,800 sources marked EDGE_HEALED_BY, 2,807 `pil-edge-heal` children exist, all `is_published=FALSE` (correctly gated). Verified via `--grid both --all --dry-run` → "targets: 0" (idempotency guard `notes NOT LIKE '%EDGE_HEALED_BY:%'` confirms nothing un-healed remains). The children now fold into the SAME gated promotion as the edges_only PASSes (see the tick above): they need a fresh per-row edges-agent PASS scan before any `is_published=TRUE` flip + deploy. No bulk heal left to run.
- [x] WARN cohort (11,947) classified — DO NOT bulk-heal. 67% mids-only / 30% both / 2% edges-only. But the cohort is already fully unpublished (0/200 still published — bleed_guard culled it since the AM sweep) and ≥24% carry a BLEED_GHOST verdict the mid-seam-blend can't fix. Worse: the pil-* bleed_guard exemption (68b78757) means a heal of a ghost-defective WARN would skip ghost-recheck and could auto-publish the defect. Recovery (if ever) = ghost-removal FIRST (opacity-snap / Gemini img-to-img), then edges-heal, then re-gate — a real project, not a $0 sweep. Leaving the cohort culled.
- [x] Deploy heal results to Kamatera — DONE. Two ./deploy-kamatera.sh runs: first shipped code + 1.4 GB PNGs but designs.json was missing pil-mid-heal (refresh script allowlist gap, fixed in e189ef09); second shipped the corrected snapshot. Prod /health count 3,516 → 5,864 (+2,348 edges-PASS heals live). 4,981 WARN-tier heals stayed is_published=FALSE per the strict gate.
- [x] Wire the /admin/elements drawer "Use as reference → / Blend 2 → / Compose N →" CTA to actual Gemini img-to-img edit — ALREADY DONE. Frontend wiring shipped in a7c545b3 (bundled with the security commit), backend `/api/admin/elements/compose` in d5dc8323 (real Gemini fuse, smoke-tested with #54724 ref + #54725 blend). End-to-end: confirm-prompt → optional art-director hint → POST `{ids,hint}` → loading state → success alert + opens new design in new tab. Verified 2026-05-30: `node -c server.js` clean, POST {ids:[]} on local 127.0.0.1:9905 returns 400 (past admin gate + body validator firing). The TODO row was stale.

## Architecture / dev hygiene

- [x] Audit server.js for duplicate routes — CLEAN. Zero true method+path conflicts. The /api/design/:id/fix shadow stayed deleted (68b78757); the 4 grep "dupes" were legit GET+POST pairs (/api/bg-textures, /api/generator/recipes, /login) + the leftover removed-route comment string.
- [x] passesAestheticGate diagnosis — NOT over-rejecting. Only ~435 dropped (PG published 6,299 → prod DESIGNS 5,864), the expected neon/rainbow filter. The "7,790" worry was a stale "Designs loaded: 3516" reading from an early restart; post-deploy count is 5,864. All 2,348 published mid-heal rows have a dominant_hex so they don't hit the null-hex neon fallback.
- [x] (Optional) Expose aesthetic-gate-filtered designs in /admin/edges-review so an admin can see the ones the gate hides — DONE (already shipped — TODO row was stale). Backend `/api/admin/edges-review/aesthetic-gated` at server.js:2921 (read-only, re-runs `passesAestheticGate` over data/designs.json, returns `gate_reasons[]` + `hue_count` + `dominant_hex` + `created_at` per row, isAdmin-gated). Frontend tab "Aesthetic-gated (hidden)" in public/admin/edges-review.html:206, endpoint registered at :274, gate_reasons render branch at :360-364 with ⊘ prefix glyph. Verified live: pm2 log shows `[aesthetic-gate] dropped 7 designs at load (neon/multi-hue/rainbow)` — note "~435" count was stale; most neon/rainbow rows were culled long ago, current drop count is 7. Tab persists via localStorage like the other source tabs.

## Pre-existing pile (not from this session, but visible)

- [x] **/admin/bleed-review surface** built 2026-05-31 (commit b48dba41) — server.js routes + public/admin/bleed-review.html, sort+density+date+time per standing rules, 200/page pagination, bulk Remove (action=bad) + Publish-anyway (action=live force:true). Linked from #admin-nav as 🩸 Bleed Review. Verified end-to-end on local 127.0.0.1:9905.
- [x] **Triaged the BLEED_GHOST cohort 2026-06-01** — real cohort is 7,875 (not 749). Report `BLEED_GHOST_TRIAGE.md` (commit ea882b74). Compliance clean: 0 BLOCK live. Resolved the 850 live-but-removed contradiction (BLEED_GHOST_OK + is_published=true + user_removed=true) by unpublishing — PG flipped, snapshot rebuilt, local reloaded, live prod 404. Remaining: 1,580 clean-but-unpublished review pool (needs settlement gate before publish) + 3 live-REGEN — both Steve-in-loop via /admin/bleed-review.
- [ ] **Remaining ~15,400 unpublished** (16,160 total archived − 749 bleed) are mostly EDGES_FAIL (covered by /admin/edges-review) or otherwise gate-held. Bulk-triage outside the bleed-review scope; consider extending /admin/status-browser if a single triage surface is wanted.
- [ ] **~307 luxe-curator roots** awaiting Steve hot-or-not (was stated as "912 gemini-luxe-v2 rows" — that's the per-row count; the curator surface groups them into 307 roots each with multiple variants). Queue file `data/luxe-curator-queue.jsonl` has 924 entries lifetime. Surface: /luxe-curator (UI), /api/luxe-curator (data, server.js:19401). Per `feedback_luxe_curator_steve_in_loop`. Steve-in-loop, not autonomous.

## Done this session (for context — CNCP shows checked items too)

- [x] Run 6-lens edges-agent sweep across all 27,267 published designs (35.6s, 41% fail rate)
- [x] Unpublish 11,274 edges-fail designs + flag with EDGES_FAIL notes
- [x] Build /admin/edges-review red-outlined chip viewer
- [x] Remake all 36 fliepaper-bugs via Gemini 2.5 Flash Image (~$1.44, scrubbed category leak)
- [x] Scaffold elements skill at ~/.claude/skills/elements/
- [x] Bake 15,231 alpha-trimmed motif + 512px ref derivatives (~5.7 GB, 67/s, $0)
- [x] Build /admin/elements browse-first library UI (per ui-ux-designer panel)
- [x] 20-sample mid-seam-heal experiment (FAIL → 9 PASS + 11 WARN + 0 FAIL, avg ΔE 17.1 → 5.2)
- [x] Mid-heal 7,329 SDXL mids-only-fail designs via PIL Gaussian-band-blur (121.3s, $0, 0 errors, 50-sample verification 0 FAIL)
- [x] Cleanup: bleed_guard pil-* exemption + delete dead /api/design/:id/fix shadow route
- [x] Memory codified — "only clean designs welcome" — gates are authoritative, no bulk rollbacks
- [x] **geometric-block-leaves REROLLED 2026-05-27 (DTD verdict C: programmatic SVG tessellation).** Old SDXL batch (38, broken seams + 9-color) stays flagged bad_batch. New: 19 tiles via scripts/gen-geom-svg-tessellate.js (navy #16203c on cream #f2e9d3, 2 inks exactly, seamless-by-construction � verified pixel-exact periodic + 2-color; .svg master kept for 5400px re-raster). ids 53769-53791, is_published=FALSE, AWAITING CURATION at /admin/drunk-curator?category=geometric-block-leaves.

## Batch-regen of 284 settlement-blocked designs (launched 2026-06-01, Steve-approved)
- RUNNING: scripts/batch-regen-settlement.js (PID-tracked, nohup) — 4-gate (seam+color+concept+SETTLEMENT-OK) regen, CONCURRENCY=2, MAX_ROLLS=20, GEN_BACKEND=replicate.
- Winners promoted STAGED only (is_published=FALSE via /api/seam-debug/heal/approve); originals→user_removed (recoverable). NOTHING goes live.
- Ledger: data/batch-regen-settlement-ledger.jsonl · live log: /tmp/batch-regen-settlement.live.log
- Resumable: re-run the same command; already-terminal designs skip.
- Fixed mid-launch: settlement_postgen_vision_check.js used RETIRED gemini-2.0-flash (404) → gemini-2.5-flash (was fail-closing every roll).
- Expected ~37h wall-clock under N=2 (rolls ~100-300s concurrent), est. $17 worst-case if many hit the 20-roll cap.
- TODO for Steve after run: review staged winners in curator, then decide publish (Steve-gated). Damask bucket (Merian-insect) expected to mostly ABANDON on legal grounds — that's correct.

## New original Monterey/California-coast scenic murals — STAGED for review (2026-06-04, Steve-approved one-off)
- [x] Built scripts/generate-monterey-mural-batch.js (commit bb95dd7) — dedicated one-off batch generator, NOT the all-night night-builder, NOT the (stopped) dog pipeline.
- [x] 8 FRESH landmark subjects (Bixby Bridge, Pfeiffer Beach Keyhole Arch, Point Lobos, 17-Mile Drive cypress tunnel, Big Sur panorama, Old Fishermans Wharf, McWay Falls, Garrapata bluff) — NOT reprocessings of existing Lone Cypress/Cannery Row/Point Pinos/Carmel Mission.
- [x] Real natural-textile ground injected per CLAUDE.md rule #7 (grasscloth/sisal/arrowroot/linen/etc.) — fixes the night-builder monterey track's flat-colorway-ground gap.
- [x] Generated 24 on Mac2 ComfyUI (tailscale 100.65.187.120:8188), kind=mural_panel 144x108, is_published=FALSE. Settlement gate verified (lock OK + inline check + post-gen visual). 0 fails.
- [x] Fixed inline-settlement false-block: abaca ("banana-stalk fiber") substrate was tripping Part-B; now strips texture/substrate vocabulary before the match (commit da2cce1). Regenerated the 2 blocked combos.
- [x] Visual self-review (contact sheet) → culled 1 broken-composition roll (#57631 blank-plate defect) + 1 accidental dup (#57667 from a require()-triggered run). Final 24 staged clean.
- [ ] **STEVE: hot-or-not review at /admin/design-curator?category=monterey-mural** — pick keepers, then publish is Steve-gated (settlement override + deploy). Nothing is live; fully reversible.

## Seam-Fix Curator (2026-06-11) — heal seams of GOOD roots, keep round-1 intact
- [x] New variant source: SEAM-FIXED copies of good drunk-animals roots (replaces the luxe de Gournay/1838 aesthetic transform Steve called "too childlike"). Same design, same aesthetic — only the tile EDGES healed so it tiles seamlessly.
- [x] scripts/seam-fix-variants.py — 2 composition-preserving fixers per root (v1 fix-seam shift-feather, v2 per-box circular-wrap heal), re-scored with seam-defect-boxes, landed as parent_design_id=root, is_published=FALSE. ComfyUI reroll deliberately excluded (DTD 3/0 — a reroll drifts composition).
- [x] /api/seam-fix-curator + /api/seam-fix-curator/pick (server.js) — pick publishes the chosen child, leaves the ROOT untouched (round-1 sacred). Validates child is a real seam-fix child before publish.
- [x] public/seam-fix-curator.html — luxe-style per-root picker, 2x2-tile preview reveals the seam, before/after seam scores + verdict per tile.
- [x] Sample landed: 5 roots (2810,2778,43186,43198,2662), all FAIL gate. v2 per-box heal wins every time (FAIL→WARN/PASS, 50-82% lower overall_max). Children #57869-57878, all is_published=FALSE. Commit fe0b0b1.
- [x] SCALE-UP (2026-06-11, Steve-approved): seam-scanned all 1304 eligible published drunk-animals roots (parent NULL, has path, not user_removed, needs_fixing NULL) → 228 PASS / 684 WARN / 392 FAIL = **1076 flagged**. Generated v1+v2 candidates for the **80 worst-FAIL roots** (worst-first, overall_max 20.5–56.0) → 160 children #57880-58039, ALL is_published=FALSE. Roots + prior 5 picks untouched. PIL only, no API cost. Commit 0236a2b.
- [ ] **STEVE: confirm to continue** — **996 flagged roots remain** (312 FAIL + 684 WARN). Did the first 2 batches (80) per the >200 cap rule; awaiting Steve OK before generating candidates for the rest (in ~25-40-root batches).
- [ ] **STEVE: pick at http://127.0.0.1:9905/seam-fix-curator.html** — 80 undecided rows pending (160 candidates). Click the cleanest seam-fix per root (publishes the LOCAL child + leaves root). Nothing live yet; no deploy. Prod publish/deploy is Steve-gated.

## 2026-06-11 — Steve feedback: "weird smudges... use textured naturals... not so busy.. more open"
- [x] DIAGNOSED smear: force-edge-seamless.py / seam-fix v1/v2 get a clean wrap by MIRROR-AVERAGING a boundary strip → that ghost band IS the "weird smudges" (visible on 2662_edgeforce.png L/R/bottom edges). RETIRED post-hoc strip-smearing as a visible-output technique.
- [x] CONFIRMED native-seamless IS available: ComfyUI SeamlessTile + MakeCircularVAE (circular-pad) on Mac1 192.168.1.133:8188 AND 100.94.103.98:8188. Same mechanism generate_designs.js uses for WALLCO_FORCE_TILEABLE. Zero post-hoc smear by construction.
- [x] Built scripts/native-seamless-sample.py (DTD verdict A, unanimous — standalone, generate_designs.js untouched). Builds open/natural-ground prompt, drives ComfyUI circular-pad directly, inserts children (parent_design_id=root, is_published=FALSE), verifies each with verify-edge-seamless.py.
- [x] SANITY SAMPLE generated: 5 heterogeneous roots → children #58050(2662/grasscloth WARN), #58051(54266/raffia WARN), #58052(54076/linen FAIL), #58053(54610/sisal WARN), #58054(53896/paperweave WARN). NO smear on any (raw ComfyUI bytes, no numpy touch).
- [ ] **STEVE: eyeball the sample before scaling.** Findings: (1) NO smudge — fixed. (2) Best two (#58053 elephants, #58054 red panda) NAIL the open/clean/sparse brief, subject preserved. (3) Two drifted to ABSTRACT (#58050 giraffe→waves, #58051 frog→leaf-blades) — SDXL lost subject under the heavy open+flat+ground prompt. (4) Natural-FIBRE TEXTURE did NOT render — SDXL paints a flat solid ground, not woven grasscloth/linen; real fibre needs compositing onto a real DW natural-material SKU (elements/seamless-bg-swap path), NOT prompting SDXL. (5) Edge gate not all-PASS: literal line_diff near-zero (native wrap clean) but the 2x2 tiled cross-seam flags real subject-sliced-at-seam structure.
- [ ] **NEXT (pending Steve OK on direction):** (a) add img2img/ref-anchor or stronger subject-weight to stop abstract drift; (b) composite onto real natural-ground SKU for true fibre texture instead of prompting it; (c) re-roll the survivors' approach across the rest. Do NOT scale the current prompt as-is.

## REDO (2026-06-11, Steve "redo them") — sample BLOCKED on missing anchor
- [x] Built scripts/make-fibre-ground.py — deterministic SEAMLESS natural-fibre swatch generator (grasscloth/raffia/linen/sisal/paperweave), wraps by construction (edge ΔE 0.00). FIXES the real-fibre-ground gap: composite of root onto these swatches reads as GENUINE woven fibre (validated on #54266→raffia). No local DW fibre swatch library existed; this is it.
- [x] Built scripts/seamless-bg-swap-file.py — file-input wrapper around seamless-bg-swap.py so the deterministic real-fibre composite runs on a fresh motif PNG, not just a DB id.
- [x] Built scripts/native-seamless-redo.py — img2img native-seamless orchestrator (VAEEncode root + circular-pad, subject-anchored from root latent) → real-fibre composite. Sacred roots, is_published=FALSE.
- [x] PROVED the blocker: txt2img circular-pad PASSES edges cleanly (3.18/2.18). But (a) txt2img has NO subject anchor → drift; (b) img2img anchors subject but breaks native-seamless to WARN AND can't open the busy root. NO ControlNet model + NO IPAdapter on Mac1 ComfyUI = no anchor usable at denoise-1.0 (the only strength that PASSES seamless).
- [x] **STEVE — BLOCKED, decision needed:** redo sample CANNOT hit all 4 goals (subject survives + open + real fibre + native-seamless PASS) with current Mac1 ComfyUI nodes. MINIMUM MISSING = an SDXL canny/lineart ControlNet model on Mac1 (~2.5GB download to Mac1, Steve-gated). With it: txt2img denoise-1.0 (PASSES seamless) steered by root edge-map (preserves subject) + open prompt (controls density) → all 4. The real-fibre composite half is DONE and validated. Probes 58056/58057 removed (FALSE + user_removed). Did NOT ship another drifted/WARN sample.

## CONTROLNET REDO (2026-06-11, Steve approved CN download) — sample landed, MIXED result
- [x] ControlNet installed + verified: `controlnet-canny-sdxl-1.0.safetensors` (2.30GB / 2,502,139,104 bytes) at `/Users/steveabramsdesignsgmail.com/Projects/ComfyUI/models/controlnet/` on Mac1. ComfyUI 0.21.1 lists it in /object_info/ControlNetLoader — no restart needed. Native Canny preprocessor node present.
- [x] Built scripts/controlnet-anchored-redo.py — DTD-verdict (3/3): canny CN fed a CROPPED single focal motif of the root on a blank canvas (not the full busy root) at strength 0.5, end 0.55, so subject identity survives while layout opens. Circular-pad SeamlessTile+MakeCircularVAE txt2img denoise 1.0 = zero smear. Real-fibre composite. Sacred roots, is_published/user_removed FALSE, parent=root, explicit id (no seq USAGE), image_url inline (table in no-replica-identity publication → UPDATE/DELETE error). Commit on master.
- [x] 5-root sanity sample landed (children #10000011-15, ALL is_published=FALSE, curator-mode): edge line_diff small everywhere (ZERO smear), cross-seam is the WARN driver (motif copies touch the tile boundary). 10000014 elephant=PASS both axes (cs ~1). 10000011 giraffe + 10000015 panda = strong visuals (subject recovered, open even, real fibre) but WARN from thin line-art crossing the seam. 10000012 frog (49.8% cov, busy/abstract) + 10000013 orangutan (real white horizontal seam + fragmented) = FAIL the brief.
- [ ] **STEVE: eyeball /tmp/cnar_contact_5.png + per-root tiles.** 1 clean PASS, 2 strong near-miss WARN, 2 fails. The architecture WORKS (elephant proves all-four-goals are co-achievable). To chase PASS on the other 4 I can add a seam-retry loop (re-roll seed until motif-only passes cross-seam) + tighter-crop/smaller-motif so copies stay off edges — but each re-run INSERTS new rows and CANNOT delete the prior ones (all_designs has no replica identity/PK in dw_unified_pub, so dw_admin can only INSERT). Decision is yours: accept near-miss WARNs, or direct a re-roll (which roots / grounds / tighter-crop). Did NOT auto-double the orphan rows before your eyeball.
- [ ] **DB hygiene note (Steve):** all_designs in dw_unified_pub has NO replica identity + NO primary key → no UPDATE/DELETE possible for dw_admin. Orphan curator rows 10000010-17 (is_published=FALSE, user_removed=FALSE, not in live catalog, harmless) are un-removable by me. If you want them cleaned, that's a Steve-gated ALTER TABLE / superuser delete.

## Seam-fix reroll-2 (54076 orangutan + 54266 frog) — STOP/REPORT, both unrecoverable (2026-06-11)
- [x] Built scripts/controlnet-anchored-reroll-2.py — scope-LOCKED to the 2 Steve-approved roots (refuses any other id), seam-retry loop that re-rolls the SEED + regenerates the motif-only tile and verifies edge-seamless IN-MEMORY on the raw array (edgeverify.verify), proceeds only on PASS-both-axes; ONE DB INSERT per root max (entire retry runs at image/file level — orphan-min for the no-replica-identity table). Added gen_textled_seamless to the redo module + scripts/vision-contains-local.py (FREE Mac2 llava backend).
- [x] First run (canny path): BOTH cleared edges easily — 10000016 (54076, linen, PASS h/v, cov 17%) + 10000017 (54266, raffia, PASS h/v, cov 29%), 1 insert each. BUT visual review: subject NOT recovered — 10000016 orangutan reads as arm-in-a-glass, 10000017 is all-over glassware with NO frog. Clean wallpaper, wrong brief.
- [x] Root cause (conclusive across 3 trials, 24+ attempts): in BOTH roots the animal is a MINORITY structure entangled with dominant glassware (wine-glass rim / champagne coupes); the detail-density focal crop lands on GLASS, so the canny anchor imports glassware, not the animal.
- [x] Added Gemini/llava subject-recovery gate to the loop (Gemini is 429 over monthly spend-cap → routed to FREE local llava; verified elephant=yes, subjectless-glassware=no). DTD verdict A (3/3) said drop canny → text-led: tested, FAILED — pure text-led produced busy/dense (40-63% cov) edge-FAILING tiles, 0/12 cleared (canny was also holding openness+wrap). Synthesis weak-canny + subject-gate: canny gives clean PASS-both-axes tiles (54076 PASS/23%, 54266 PASS/18%) but the subject-gate correctly REJECTS every one (no animal) → both STOP at the 6-retry cap with **0 rows inserted** (orphan-min held; max id still 10000017).
- [ ] **STEVE — DECISION: both roots are structurally unrecoverable by this pipeline.** The canny crop region in 54076/54266 contains glassware, not the animal, so no clean-seamless roll ever has a recognizable subject; dropping canny loses the wrap+openness. Eyeball **/tmp/cnar_reroll_2.png**. Options: (A) accept 10000016/10000017 as clean-but-subjectless wallpaper (NOT what you asked); (B) hand-author an animal reference for these two (img2img/IPAdapter on a clean elephant-style motif, ignore the glassware root entirely); (C) retire 54076/54266 from the seam-fix redo set — the architecture is proven on animal-forward roots (elephant 54610=PASS), it just can't rescue a root where the animal was never the dominant structure. Did NOT ship a WARN, did NOT expand scope, did NOT double the orphan rows (0 inserts this session beyond the first 2).

## OVERNIGHT SEAM-FIX RUNNER (2026-06-11, launched ~19:04 PDT) — autonomous, DTD-gated, NOTHING publishes
- [x] Built scripts/overnight-seam-loop.sh — detached overnight runner that drives scripts/seam-wave.sh wave-by-wave all night, with DTD (codex+qwen+rule-vote, majority, ties fail-safe PAUSE) as the CONTINUE/PAUSE decider between waves. bash-3.2 compatible. Commits f1efeda (loop) + 81fefac (single-log-sink fix).
- [x] SAFETY: nothing publishes (only is_published=FALSE candidates via the gated seam-wave.sh); never touches settlement/deploy/push; roots sacred (generator INSERTs new id, parent=root). DATABASE_URL self-resolves inside the generator; loop never echoes secrets.
- [x] SELF-GATING (no Wave-1 collision): waits — never runs a wave — until (manifest populated) AND (generator present) AND (ComfyUI idle). At launch ComfyUI was busy with Wave-1's own job, so the loop is correctly HOLDING OFF; it fires the first real wave when ComfyUI goes idle.
- [x] 5 STOP CONDITIONS: (a) 07:00 PDT 2026-06-12 (epoch 1781272800); (b) manifest exhausted (0 remaining); (c) kill switch /tmp/seam-overnight.STOP; (d) DTD votes PAUSE; (e) 3 consecutive zero-PASS waves.
- [x] PASS rate computed from data/seam-fix-queue.jsonl records in each wave's time window (edge_seamless==true || edge_verdict=="PASS" / distinct roots attempted). CNCP heartbeat per wave (best-effort; CNCP was down at launch — ignored).
- [x] Manifest = 3 roots (43199, 54385, 55272) = 1 wave at WAVE_SIZE=20. Generator = scripts/controlnet-anchored-reroll-2.py. Logfile /tmp/seam-overnight.log. PID in /tmp/seam-overnight.pid. Reparented to launchd (PID 1) — survives the launching session.
- [ ] **STEVE (morning):** read /tmp/seam-overnight.log — total waves, total PASS candidates added, why it stopped. Then curate the new is_published=FALSE candidates in the seam-fix curator (http://127.0.0.1:9905/seam-fix-curator.html). Kill switch any time: touch /tmp/seam-overnight.STOP.

## REPLACE COLORS → NEW SKU, 3 METHODS ON ONE SURFACE (2026-06-12) — local only, nothing deployed
- [x] FIXED AI-recolor publish-gate bug: POST /api/design/:id/recolor INSERT was is_published=TRUE (auto-publishing) → now FALSE + carries parent_design_id (server.js ~25439, ~25463). Was an orphan-and-publish double bug.
- [x] METHOD 3 (preset colorways) shipped: GET /api/colorways/presets + POST /api/colorways/preset (src/colorways.js). Remaps a design's 2 dominant tones → a COLORWAY_PAIRS preset, builds an ink_map, reuses promoteToSpoonRow → ONE is_published=FALSE child SKU. Preset list sourced from scripts/colorway-variants.py --list-presets (single source); tones via --detect.
- [x] LINEAGE FIX: promoteToSpoonRow PG SELECT lacked LIMIT 1 — spoon_all_designs is INSERT-only (dup id rows) so row_to_json emitted 2 lines → JSON.parse threw → srcInPg=false → child lost parent_design_id. Added LIMIT 1; lineage now populates (verified children of 12 → parent_design_id=12).
- [x] UI: all 3 methods on the design-edit recolor-wheel admin block — swatch dot-wheel (Color tab, method 1), hex/AI recolor (method 2), new preset chips (method 3). One surface.
- [x] PROVED locally: M1 swatch save → child 58125; M3 preset → child 58124 (both design 12, parent_design_id=12, is_published=f). M3 design 11 → 58122, M1 design 11 → 58123 (pre-lineage-fix, parent NULL — local test artifacts). All 4 children is_published=f, none on published grid.
- [ ] **STEVE:** METHOD 2 (AI recolor) gate fix verified by code only — could NOT exercise end-to-end because Gemini is over its monthly spend cap (HTTP 429 from /api/design/:id/recolor). Re-test the recolor button once the cap is lifted to confirm the live child lands FALSE.
- [ ] **STEVE (gated):** none of this is deployed. When ready, ./deploy-kamatera.sh ships server.js + src/colorways.js + scripts/colorway-variants.py. Publishing any preset/recolor child to live = explicit admin click (colorways publish) — still Steve-gated.