Dw Photo Capture
Search the build
-
e9448322026-07-28 auto-save: 2026-07-28T04:27:17 (1 files) — data/color-dots.json -
20856852026-07-28 auto-save: 2026-07-28T03:57:08 (1 files) — data/color-dots.json -
7f05d6c2026-07-27 auto-save: 2026-07-27T04:17:53 (1 files) — data/color-dots.json -
d407efc2026-07-27 auto-save: 2026-07-27T03:47:45 (1 files) — data/color-dots.json -
210a2792026-07-26 auto-save: 2026-07-26T04:40:35 (1 files) — data/color-dots.json -
80387932026-07-26 auto-save: 2026-07-26T03:40:20 (1 files) — data/color-dots.json -
89699fd2026-07-25 auto-save: 2026-07-25T04:33:37 (1 files) — data/color-dots.json -
77c6b642026-07-25 auto-save: 2026-07-25T03:33:15 (1 files) — data/color-dots.json -
d7be4c62026-07-24 auto-save: 2026-07-24T04:25:19 (1 files) — data/color-dots.json -
98b67d22026-07-24 auto-save: 2026-07-24T03:55:12 (1 files) — data/color-dots.json -
d17f80f2026-07-23 auto-save: 2026-07-23T04:18:33 (1 files) — data/color-dots.json -
c643cf02026-07-23 auto-save: 2026-07-23T03:48:26 (1 files) — data/color-dots.json -
214f7fb2026-07-22 auto-save: 2026-07-22T04:41:22 (1 files) — data/color-dots.json -
22fe1cb2026-07-22 auto-save: 2026-07-22T03:41:10 (1 files) — data/color-dots.json -
52d98a42026-07-21 chore: lint (drop unused HSL l), refactor (familyOf hue comment, hexToRgb param), v1.5.1 (session close) -
f7337962026-07-21 auto-save: 2026-07-21T04:35:41 (1 files) — data/color-dots.json -
904218b2026-07-21 auto-save: 2026-07-21T03:35:30 (1 files) — data/color-dots.json -
d684bcb2026-07-20 auto-save: 2026-07-20T04:29:52 (1 files) — data/color-dots.json -
2a2122c2026-07-20 auto-save: 2026-07-20T03:59:45 (1 files) — data/color-dots.json -
e0020542026-07-19 shop-by-color: data-driven ΔE-collapsed color dots (real DB hexes, ≥20 patterns, qty-ordered) -
a9c6a112026-07-19 chore: version-up v1.5.0 (session close — shop-by-color product_type) -
8112eaf2026-07-17 color-index: carry product_type through builder + both /apps/color-index and /apps/color-widen results (shop-by-color type checkboxes) -
0d6c3552026-07-16 color-widen: add min param — guarantee >=N results via nearest-neighbor fallback when the band is thin -
e2c9e012026-07-16 add /apps/color-widen route: tunable ΔE band (default 15%) for the Shop-by-Color wheel at /pages/colors -
5d305fe2026-07-15 color-index: exclude sample-only ($4.25) products — require has_product_variant so shop-by-color never links to a sample-only PDP (harm-reduction until vendor costs are sourced) -
42148682026-07-15 add nightly color-index refresh script + launchd plist (DRAFT — install gated): build from live shopify_products, rsync json to Kamatera (endpoint hot-reloads) -
91ce1712026-07-15 deps: add pg so build-color-index runs standalone (needed for scheduled rebuild) -
61eee412026-07-15 color-index: build from live shopify_products (active + online_store_published) — drop archived/unpublished handles so PDP color grid never links to 404s or dead images -
dd6e4162026-07-13 chore: session close snapshot (visual-search) -
2212c212026-07-13 nightly embed top-up script + plist draft (coverage anti-decay: both embedders resumable + index hot-reload) -
bdba9e22026-07-13 visual-search: path-(a) Kamatera self-host bundle (lite venv, no torch; pm2 unit reading local dw_unified) — verified 71MB footprint -
fcb1edb2026-07-13 visual-search: bind VS_HOST=0.0.0.0 so Kamatera pairs-well can reach the CLIP index over the tailnet (100.82.17.107:9914) -
95931a92026-07-13 auto-save: 2026-07-13T00:51:43 (2 files) — visual-search/__pycache__/search_service.cpython-312.pyc visual-search/search_service.py -
a0e586b2026-07-13 visual-search: make bind host configurable via VS_HOST (default 127.0.0.1, unchanged); enables opt-in tailnet exposure for remote consumers -
2890b8f2026-07-12 auto-save: 2026-07-12T23:51:20 (5 files) — visual-search/search_service.py visual-search/__pycache__/embed_shopify_gap.cpython-312.pyc visual-search/__pycache__/search_service.cpython-312.pyc visual-search/com.steve.visual-search.plist visual-search/embed_shopify_gap.py -
ca63a1e2026-07-12 auto-save: 2026-07-12T23:21:12 (1 files) — visual-search/.gitignore -
c913d7d2026-07-09 auto-save: 2026-07-09T22:42:31 (5 files) — screenrecord/debug-log.jsonl screenrecord/rec/run3/page@ea8fb1218f5b7b345af268e4e545b083.mp4 screenrecord/rec/run3/page@ea8fb1218f5b7b345af268e4e545b083.webm screenrecord/rec/run4/page@96324b4cfafc83a80afe7cd203ccecb3.mp4 screenrecord/rec/run4/page@96324b4cfafc83a80afe7cd203ccecb3.webm -
7c5ada12026-07-09 auto-save: 2026-07-09T22:12:24 (1 files) — package-lock.json -
bb7bf3e2026-07-09 chore: v1.4.0 (session close) — GCV+Gemini OCR fusion, WALLPAPER FileMaker lookup (dash-agnostic + partial search), save-every-scan, iOS freeze fixes (decode-free photo pipeline + 250x memory cut) -
6aa4a182026-07-09 auto-save: 2026-07-09T19:41:55 (8 files) — screenrecord/debug-log.jsonl screenrecord/DEBUG-REPORT.md screenrecord/rec/run0_v103/ screenrecord/rec/run1_v103/ screenrecord/rec/run2_v103/ -
30757b52026-07-09 auto-save: 2026-07-09T16:41:19 (5 files) — schu-candidates.tsv schu-publish.mjs schu-apply-out.json schu-publish-audit.jsonl schu-settlement-hold.json -
d52d7652026-07-09 auto-save: 2026-07-09T16:11:12 (2 files) — schu-candidates.tsv schu-publish.mjs -
8ef6e5f2026-07-09 auto-save: 2026-07-09T09:09:49 (1 files) — visual-search/__pycache__/ -
602bb7f2026-07-09 PDP color-dot color-index: /apps/color-index endpoint + generator (catalog-wide 10% CIELAB tolerance) -
41d871a2026-07-09 CLIP /similar: DW-safe guard — only return storefront products w/ handle + DW-hosted image; drop wallquest.com hotlinks (broken imgs + private-label leak) + handleless entries; backfill to keep strip full -
0bd91872026-07-09 iOS OOM-freeze root cause (found via mobile screenrecord 5x): app fetched /api/twil = 36MB/88,319 items on EVERY page load (default filter=twil) and render() built a DOM card for all 88k → browser hit 13GB and crashed the tab, starving the device so photo capture froze. Fix: RENDER_CAP=500 cards max + 'narrow your search' note; defer the 36MB loadTwil() off page-load (lazy on skip/TWIL-chip) -
036d1302026-07-09 auto-save: 2026-07-09T08:39:40 (2 files) — public/index.html screenrecord/ -
ab7c20f2026-07-08 Scan upload-progress (final DTD FIX-THEN-SHIP): identify-multi POST via XHR shows '⬆ Uploading photo… n%' then '🔎 Identifying…' — kills the dead-looking wait on a big raw-photo cellular upload that a user would misread as a freeze -
dd49fa42026-07-08 iOS freeze — close the contrarian gap: ALL photo flows now decode-free. downscale() itself = FileReader raw (fixes Add New SKU / Update SKU / gallery-batch, which still froze via createImageBitmap that v99 proved unsafe). Server normalizes at every ingest: create-item + /api/photo + /api/photos via normB64 (HEIC→JPEG for Shopify; box IM has libheif). Scan regression green -
71b85742026-07-08 iOS freeze — eliminate ALL client-side image decode (DTD verdict B): fbRead uses FileReader.readAsDataURL (raw file, zero decode/canvas/bitmap), server normalizeImage() (ImageMagick: any format→auto-orient→≤1600px JPEG) handles it before OCR + visual. decodeQR capped at 1000px (a full-12MP getImageData is a ~48MB array that froze iOS). Verified: raw 3000px photo → resolves CHC217203. /5x 10/10 -
d5ff7da2026-07-08 iOS photo-processing freeze fix: downscale() now uses createImageBitmap (off-main-thread, low-memory decode) instead of <img>+canvas.toDataURL which OOM-hangs iOS on a 12MP camera photo; cap 1200px + q0.82. fbRead shows '📷 Processing photo…' immediately + guards a null (undecodable) read -
ef6656d2026-07-08 Fix iOS camera freeze on Look Up SKU: stop auto-clicking the BACK capture input after the front photo — iOS Safari LOCKS UP when a capture-input .click() fires from a change-event (no fresh user gesture). Now highlights the Back tile + prompts a tap; front-only Identify also enabled -
6eb90132026-07-08 FM lookup dash-agnostic + printed-code-first: wallpaper-details tries raw+dashless forms on both combo-sku and Mfr-Pattern (CHC-217203 ⇄ CHC217203 auto), and the scan keys the FM lookup off the PRINTED OCR code first (the model# on the label) plus the resolved dw_sku -
c473b212026-07-08 dwphoto: add SSRF-guarded CORS-scoped /apps/similar proxy route → loopback CLIP :9914 (shipped GATED-STEP-1) -
06f58392026-07-08 visual-search /similar: also return storefront handle (LEFT JOIN shopify_products) so PDP cards link to real product pages -
3e6d84f2026-07-08 Save EVERY scan (Steve): identify-multi now persists the raw photo(s) + OCR candidates + engines + resolved identity to data/scan-log.jsonl (append-only, async best-effort — never breaks a scan). New /api/scans (newest-first, filter=unresolved) + /scans review page: photo ⇄ what OCR read ⇄ resolved SKU, so mis-reads are visible and the corpus improves the app. Verified: scan saved, image served, GCV+Gemini fusion recorded -
51cf1882026-07-08 OCR: multi-engine fusion for the back label — backOcrFused() runs every available engine (GCV→Gemini→macVision) and unions candidate codes so the catalog partial-search has more shots on goal; literal-transcriber engines lead. GCV is now plug-and-play (set GCV_API_KEY → auto-fuses). Scan read-line shows which engine(s) ran + per-scan cost -
c68081a2026-07-08 visual-search: add POST /similar (SKU->CLIP visual neighbors, color+style soft re-rank, lazy CLIP load) -
a7f63742026-07-08 Scan lookup driven by the OCR'd label code: a VISUAL-only identify no longer beats the printed model# — the wallpaper-file lookup now prefers the OCR'd code (so 217500 gets searched even when identify 'found' a visual match), and every scan shows '📄 read: <codes>' so mis-reads are visible on-device. No-code-read state guides toward a clearer label photo -
5cea2c82026-07-08 Scan fixes: (1) scrub DW's OWN header from identification — our phone 888-373-4564 (+ fragments/phone-shaped tokens) never becomes a candidate code, and 'Designer Wallcoverings' never read as the pattern name; model# is what runs down the SKU. (2) partial search now fires AUTOMATICALLY when a scan doesn't confidently match (uses the OCR'd code via back_read). (3) live search-as-you-type (debounced 300ms + race-guard) in the manual search box -
8837b5f2026-07-08 Scan: partial/fuzzy wallpaper-file lookup — new /api/wallpaper-search substring-matches any part of combo sku / Mfr Pattern / JS Pattern / Internal Description (FileMaker *q* OR-across-fields), with OCR trailing-char trim tolerance (217500→21750→CHC-21750-style), grouped+coalesced per SKU. Manual search box in the scan modal + auto partial-fallback when exact identify misses; tap a candidate → full detail card -
c3d84292026-07-08 Scan → show WALLPAPER FileMaker details: new /api/wallpaper-details (queries 'Basic List of Fields' by combo sku→Mfr Pattern, coalesces non-empty specs across the mega-table's multiple rows, portal:[] so no stall) + loadWallpaperDetails() renders a spec card (Mfr#/Collection/Width/Repeat/Retail/Net/Cost/Roll/Min/Border/Price-code/Line/SKU) under every scan result. 58k+ SKUs carry rich FM specs; sparse scraped lines show what FM has -
d4db33e2026-07-08 doc: cross-reference the shared Date WP Sample Sent writer (mark-received + print-sticker both close the open-sample request via this field) — surfaced by contrarian gate during /5x -
b56afa12026-07-08 auto-save: 2026-07-08T11:35:28 (1 files) — package-lock.json -
66895a72026-07-08 chore: v1.2.0 → v1.3.0 (session close — feed + discontinued-successor features) -
5f86fc52026-07-08 auto-save: 2026-07-08T11:05:21 (1 files) — data/build.json -
ba637862026-07-08 Add discontinued-SKU resolver → red card → live successor link (local only) -
140c9942026-07-08 gitignore the runtime all-dw feed cache (data/all-catalog.json) -
a4417e32026-07-08 browse whole dw_unified catalog via all-dw feed (was Fentucci-only); disk-cache + Fentucci fallbacks -
62ce2a42026-07-08 Incoming queue: best-effort image enrichment — pass 2 in the background refresh resolves CATALOG-miss samples via identifyUnified (full 267k crossref + 173k shopify_products), concurrency-bounded (6 workers), serve-stale-while-enriching. 0/40 -> 16/40 (40%) thumbnails + real pattern names; also populates dw_sku/mfr_sku so scan-match highlights more incoming cards -
999db252026-07-08 Check Sample In: 'Received' button stamps Date WP Sample Sent=today (Steve's receive-stamp field) via POST /api/mark-received (fmSet), drops the card from the incoming queue; scan-match surfaces a one-tap 'Check in — received today'. Input-guarded (400 on missing recordId/bad json), no live write on my tests -
427db512026-07-07 Check Sample In: incoming-queue panel — shows vendor-ordered+not-sent samples (recent 120d) with spec info, cached server-side + 8-min background refresh (FM find is a ~30s whole-file scan, so cache→instant 0.15s). Scan flags the matching incoming card. Images best-effort from active catalog -
2d627ac2026-07-07 Check Sample In → incoming queue: /api/incoming-samples finds vendor-ordered+not-sent samples in FileMaker (the arriving ones), enriched with catalog image+spec. fmFind gains portal:[] to suppress heavy portalData (fixed the FM timeout) -
17b5c682026-07-07 Local aug-model deploy pipeline (mac_deploy_embed): embed full catalog with dw_clip_ft_aug on Mac MPS → Postgres COPY file for Kamatera local ingest (gate-respecting: no Mac→prod tunnel). Reuses 90k cache, 3-state load (ok/dead-404/transient-retry) so rate-limits never false-mark images dead. Resumable -
2acc59c2026-07-07 auto-save: 2026-07-07T13:30:45 (1 files) — visual-search/train/__pycache__/finetune_aug.cpython-312.pyc -
e56063d2026-07-07 Look Up SKU pill: camera-first pure-identify flow — shoot FRONT pattern → auto-prompts/opens BACK label (the text/SKU) → runs identify-multi (visual+OCR+code fusion) and shows the SKU. Skips the sample check-in (that's the separate Check Sample In pill) -
27b572c2026-07-07 Fix aug-training crash (cannot write empty image): guard augment against tiny/empty crops + wrap JPEG recompress + AugSet falls back to a neighbor on a corrupt cache image (never crashes the run) -
a376a2d2026-07-07 DEVICE-TEST: multi-photo tip — take 2-3 pattern shots for visual-ID (app fuses views; single shot ~28% top-1, multi-view is the reliable path) -
de2aca32026-07-07 robustness_fusion: note CDN rate-limiting makes the A/B unreliable; fusion verified directly (3-view demo promotes the corroborated match) -
6cdd9d92026-07-07 Multi-photo visual-ID fusion: identify-multi now fuses up to 5 views (fronts[]) — a product matching across multiple photos gets a corroboration boost and outranks spurious single-view matches; Add/Update flows send all captured photos. Independent lever on the 28% single-shot problem (complements the augmentation retrain) -
724673e2026-07-07 auto-save: 2026-07-07T13:00:27 (1 files) — visual-search/train/__pycache__/finetune_aug.cpython-312.pyc -
33641ca2026-07-07 eval_aug: A/B degraded→clean image retrieval (base vs current FT vs augmented) → recommend deploy only if the aug model beats current top-1 -
54e085f2026-07-07 Augmentation re-fine-tune: warm-start from dw_clip_ft.pt + heavy handheld-photo augmentation (rotate/crop/blur/glare/JPEG) on the image side → teach degraded→clean matching (targets the 28% real-photo top-1) -
6a137d92026-07-07 5x contrarian gate: honest re-scope — FT visual-ID validated on the REAL task (degraded phone-like image→image) = only 28% top-1 (n=40), thresholds overlap; clean-image self-match hid this. App still safe (label-OCR primary + confidence+2nd-push gates). Real fix = augmentation re-train (surfaced, gated). Camera/iOS-Safari still untested (headless can't reach getUserMedia) -
8694d872026-07-07 5x: 2 consecutive clean sweeps (0 app defects) post FT-model + dedup — six-way + endpoints + flow + dedup + calibrate all green; sole non-green is the M3 no-auth harness artifact (authed load 0.24s) -
828996f2026-07-07 Contrarian gate fix (dedup): add _addSession guard before every DOM write (no stale warning/switch into a reopened modal) + require a non-null product_id before offering 'Update instead' (was binding null pid → wrong-product write) -
cf2ea4a2026-07-07 Add-mode visual dedup: new-SKU pattern photo → fine-tuned visual-ID; if it confidently matches an existing catalog pattern, warn (non-blocking) + offer one-tap 'Update it instead' (resolves via mfr then dw_sku). Prevents duplicate SKUs -
7d4ccb62026-07-07 Device-test checklist for the iPhone-Safari real-device pass (camera-first, specs, mics, 10/6 gallery, calibrate-once measure, live Update + 2nd-push confirm, video transcode) -
ec02bd72026-07-07 Wire in fine-tuned DW CLIP: sanity-check picks best epoch (ep4, 52.9% held-out recall@1 vs 23.0% base — 2.3x, no overfit); load dw_clip_ft.pt in search_service + embed_catalog (both sides); catalog re-embedding on FT model (311k, snapshot saved for rollback) -
5673cad2026-07-06 Contrarian gate fix: _updateNeedsConfirm invariant — ANY ambiguous visual bind (background OR mid-Push) requires a 2nd Push before a LIVE write; label OCR + chip-tap are explicit (no gate). Plus session checks after resolve/visual awaits in addExtract -
1570feb2026-07-06 yolo hardening (8 fixes from adversarial review): C1 visual bind requires confirm before live push · C2 no auto-bind on low-confidence · W1 escape all $$ fields in staging SQL · W2 downscale onerror+timeout (no HEIC hang) · W3 create-item/photo/photos drain+413 · W4 session token guards stale async rebind · S1 calibration viewport check · S2 poll waits for all expected videos -
a2500f82026-07-06 Calibrate-once measurement: card sizes px/in for the device once → stored in localStorage → every later scan just needs the goods box, no card. Zoom locked so focal length stays constant. Recalibrate button to re-anchor. -
8cff6c42026-07-06 Camera-unavailable UX: detect Chrome/3rd-party iOS browsers (no getUserMedia) and tell user to open in Safari, instead of a cryptic error -
b0a3ecc2026-07-06 Handoff doc: how to wire the fine-tuned DW CLIP weights into visual search (sanity-check, re-embed both sides, gate the reindex, rollback) -
da94b6f2026-07-06 Card-free measurement groundwork: lock camera zoom + /caps capability probe (reports focusDistance/zoom/motion support per device) to pick the right no-reference scale method -
b87d4022026-07-06 Visual pattern-ID in capture flow (front photo → identify-multi → bind SKU by design, even with unreadable label; tappable candidate chips) + live on-camera dimension readout (credit-card scale reference → real W×L overlaid live, writes to specs) -
5126f022026-07-06 Overnight DW CLIP fine-tune pipeline (MPS): build_manifest + resumable 256px downloader + open_clip contrastive fine-tune + overnight orchestrator -
942ce162026-07-06 5x: 2 consecutive clean sweeps (0 app defects) — six-way + functional + headless-flow all green; 2 caught items proven harness artifacts -
aa3a6ce2026-07-06 Video transcode poll: /api/media-status reads Shopify video processing state; client polls after upload until every clip is READY (or fails), updating the note in-place -
4a51d5b2026-07-06 Camera-first capture flow + 10-photo/6-video gallery: pill→camera→details-after-scan; Add mode creates draft, Update mode resolves existing SKU (shopifySearch fallback) and pushes photos+videos LIVE to Shopify; /api/resolve-product endpoint -
29237c62026-07-06 Sticker loop web side LIVE: web_print_request field placed + FM_PRINT_FLAG_FIELD configured on Kamatera; dryRun-verified Print writes sent-date + sku/address flag -
c0cf2c52026-07-06 Add 3 photo slots to Add-item (Front·pattern / Back·label / Detail): label photo drives spec extraction, all 3 attach as ordered Shopify product images (front primary) -
eae81b82026-07-06 Add per-field 🎤 mic buttons on every Add-item field (dictate straight into Mfr#/Vendor/VID/Color/Name/Price) + prominent Voice-fill button -
28573392026-07-06 FileMaker sticker setup: STEP 1 now requires placing web_print_request on the target layout (Data API can only write layout-present fields) -
d548ec82026-07-06 Generalize spec capture to any spec sheet or sample: read product material (Wallcovering/Fabric/Trim/…), drive Shopify product_type + metafield off it, persist full spec set to new_items_staging.specs jsonb -
3fcbec52026-07-06 Capture full wallcovering spec set on scan (repeat/width/roll length/match/substrate/how-sold/collection/price code) → metafields + read-only specs panel in add-item -
264f5cc2026-07-06 feat: max-info extract on scan + voice-only-as-needed. /api/extract: Gemini pulls EVERY label field (vendor, mfr, pattern, colorway, collection, width, length, repeat, content, price) + fuzzy-maps vendor. Add-item photo auto-prefills all fields. Then voice ONLY as needed: if a required field (mfr/vendor) is still blank, the app SPEAKS the prompt (‘Enter vendor name’) via TTS + listens, while the dropdown stays usable + the field is highlighted. Verified extract: WDW2310/Winfield/Compass Clay. -
496dc6c2026-07-06 chore: embed-daemon.sh — resumable batch loop (20k chunks + auto-reload vsearch index), run via nohup + @reboot cron on Kamatera for reboot-resume. Replaces bare nohup embed. (pm2 wouldn't register the bash daemon reliably.) -
2e4814d2026-07-06 feat: onload chooser — 3 white-on-black pills (Add New SKU / Update SKU / Check Sample In), centered 3 rows. Routes: Add→new-item modal, Update→live scan, Check→front/back identify. ⌂ home button reopens; skip→ goes to catalog. Verified on mobile viewport, 0 pageerrors. -
94d5ee92026-07-06 5x: app clean across 2 sweeps — M1/M2/B4 pass, 85 tests, both new modals open, 163-vendor dropdown, 0 console errors, all new endpoints live. M3 flake = harness no-auth + batch load (networkidle settles 1.24s with auth). WebKit skip = install tooling. No app fixes needed. -
9a593762026-07-06 feat: add new SKU to dw_unified + Shopify (draft) with vendor/vid dropdown -
ac1d6d02026-07-06 feat: front+back identify UI + QR/vendor clues. #fbModal captures back label + front pattern; client jsQR decodes QR silently (extracts SKU token, never navigates); server fuses code+QR+vendor+visual. QR token filter requires a digit (skips domain words). Vendor name off the label used to weight visual + fill primary. Verified: QR URL→TR2581→RHT264/Seabrook. -
023d1b22026-07-06 feat: CLIP visual search + multi-photo front/back fusion identify -
873541c2026-07-06 feat: visual identify — Gemini compares the ACTUAL user photo against candidate catalog IMAGES (not typed attributes) and floats the true pixel-match to the front, or honestly reports no-match. Adds fetchImageB64 + geminiVisualMatch; /api/recognize returns a visual{} block. ~$0.0036/recognize (6 imgs). Correctly rejected 5 non-matching grasscloths in test. -
11691f42026-07-06 feat: Option B — Mac-side sticker watcher (use both). sticker-watcher.js polls FileMaker for web_print_request-flagged records, fires the existing print script via AppleScript do-script (combo-sku param), clears the flag; reuses fm-client.js, loads FM creds from filemaker-mcp/.env. launchd plist staged (not started — needs field + real print-script names). Doc: arm ONLY ONE of A/B (double-print race); DTD favored A, B built as the no-FileMaker-window option. -
8d68c8e2026-07-06 docs: precise 2-step FileMaker setup (create web_print_request field + Web Sticker Poller script) — Data API can't create schema/scripts; server pre-staged to activate on field creation. Includes Mac-side AppleScript-watcher alternative. -
5e674582026-07-06 5x: report — desktop + mobile Chrome PASS (0 console errors), lazy-thumb fix (414→4 reqs), M3=harness auth artifact. WebKit engine install broken on this box (tooling, not app). -
060b63d2026-07-06 5x sweep: lazy-load grid thumbnails (IntersectionObserver) — grid used CSS background-images (can't use loading=lazy) so a big filter loaded 100s of images at once. Now bg applies only on-visible: +414 requests after load → +4. Big mobile win; networkidle settles 2.3s. No console errors. -
77542212026-07-06 fix: print-sticker no longer hangs + go-live hardening -
1f9cc3b2026-07-06 docs: FileMaker sticker setup — flag field + OnTimer poller (reuse existing print/stamp scripts) + FM_WRITE gate. Deployed read-only FM creds to Kamatera; sample-request lookup live (Maya50162→Haley McKinley w/ image), print-sticker dry-run until flag field designated. -
7f00ab42026-07-06 feat: sample-request sticker popup + scan cost throttle -
31dbd122026-07-06 feat: FileMaker sample-request lookup + print-sticker handoff -
a9856732026-07-06 feat: best-ID identify-first against unified catalog — identifyUnified(code) resolves a scanned code via dw_sku_crossref (267k, shares FileMaker's Mfr Pattern/combo sku vocabulary) to canonical {internal_sku, mfr_sku, vendor} + best-effort image/title from shopify_products. New GET /api/identify-unified. Verified: TR2581→RHT264/Seabrook, 83615→VIC261B w/ image. -
5483a672026-07-06 feat: hone in on the text — OCR now reads a centered, upscaled ROI (guide-box region, 5:2, up to 2x) instead of the whole frame, so small printed SKUs read sharper; full frame retained for logo/pattern match -
9277da42026-07-06 fix: dw_unified 'match design' works on Kamatera — drop non-existent min_variant_price column from unifiedSimilar (Kamatera schema differs from mac2); app now returns similar products. DW_UNIFIED_DB accepts a full postgres:// URI (app runs as root, no PG role). Verified: grasscloth swatch → 48 products live. -
1ffca172026-07-06 feat: structured field auto-detect (sku#/model#/name/color) + live green highlight + per-vendor spec learning -
761b3562026-07-06 feat: front side auto-matches the design — hold steady on the pattern face → auto-fires /api/recognize (reuses the match path) instead of only prompting a flip; resets on code/open/close -
91923282026-07-06 feat: auto front/back side detection on scan — analyzeOcr classifies label(back) vs pattern-face(front) from OCR density ($0, per-frame); live scanner shows '🎨 flip to the label' on the pattern side. +5 tests (79 total) -
b7eee402026-07-06 chore: photocapture LIVE on Kamatera at photo.designerwallcoverings.com (pm2 dwphoto :9912, gemini engine, CF-proxied + LE cert); retire Mac tunnel -
d3b12d12026-07-06 chore: Kamatera deploy artifacts — .deploy.conf (gemini engine, /healthz smoke), .env.example, DEPLOY.md runbook -
75c75e32026-07-06 feat: pluggable vision engines (macvision|gemini|gcv), runtime-selectable + engine=all — enables Linux/Kamatera hosting -
7bb65212026-07-06 docs: expose photocapture publicly via Cloudflare Tunnel at photo.designerwallcoverings.com (Mac-bound; CF edge TLS for camera) -
ea390e52026-07-03 chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew) -
70f9dc02026-06-26 auto-save: 2026-06-26T10:45:01 (2 files) — OVERNIGHT_LEDGER.md package-lock.json -
fbc4b682026-06-26 chore: lint clean, refactor (none needed), v1.1.0 (session close) -
06f58bc2026-06-26 chore: v1.1.0 — 🎨 Recognize Pattern feature (session close) -
6407d112026-06-26 test: lock parseOcrRows barcode branches + overnight wind-down note (cycle 23) -
25e95322026-06-26 test: cover nmfr + brandRe normalizers (cycle 22) -
2e6d31c2026-06-26 test: cover mfrToHouseSku resolver (cycle 21) -
93cd3e42026-06-26 auto-save: 2026-06-26T08:43:59 (1 files) — data/build.json -
eb1e5462026-06-26 test: reject O/0 OCR fallback (real collisions) + lock verbatim-code contract (cycle 20) -
e3483ef2026-06-26 observability: add /api/stats; backlog O/0 OCR fallback with evidence (cycle 19) -
c3b1b212026-06-26 auto-save: 2026-06-26T08:13:39 (2 files) — data/build.json data/vendor_profiles.json -
4d8fe6d2026-06-26 robustness: atomic saveJSON so a crash/ENOSPC can't corrupt persisted data (cycle 18) -
d9df0cb2026-06-26 test: lock candidate-order contract scanResolve relies on (cycle 17 audit) -
5980db12026-06-26 auto-save: 2026-06-26T07:13:19 (2 files) — data/build.json data/vendor_profiles.json -
830b25f2026-06-26 fuzzy: ambiguity guard so a partial read can't bind the wrong vendor (cycle 16) -
f35ef392026-06-26 security: fix /api/learn prototype-pollution DoS (cycle 15) -
bf76d612026-06-26 auto-save: 2026-06-26T06:43:07 (1 files) — data/build.json -
a3b00cc2026-06-26 harden: prefix support gate so one bad scan can't corrupt learned prefixes (cycle 14) -
3eaa90d2026-06-26 auto-save: 2026-06-26T06:12:56 (1 files) — data/build.json -
ad48b182026-06-26 Recognize Pattern: strip -SAMPLE suffix from result SKUs (2,900 sample-variant rows showed sample SKU on card) -
f050ab32026-06-26 test: fuzzyVendor edge guards + disk-full debug note (cycle 13) -
0801a442026-06-26 test: fingerprintVendor edge cases + reject OCR-confidence-threshold (cycle 12) -
8f32cd42026-06-26 auto-save: 2026-06-26T05:12:44 (1 files) — data/build.json -
bfd82492026-06-26 scan UX: tentative 'hold steady' cue + reject Swift low-light pre-pass (cycle 11) -
367d1252026-06-26 auto-save: 2026-06-26T02:08:48 (2 files) — data/build.json data/vendor_profiles.json -
2c6e0522026-06-26 test: harden analyzeOcr against adversarial OCR inputs (cycle 10, brainstorm) -
f6b8dec2026-06-26 review: fix 3 findings from session code-review (cycle 9) -
a412dce2026-06-26 auto-save: 2026-06-26T01:38:39 (1 files) — data/build.json -
64ab5082026-06-26 scan: background logo-ID when a code locks but OCR names no vendor (cycle 8) -
d76cc5a2026-06-26 identify: per-request model toggle (moondream fast path) + API.md (cycle 7) -
934a7582026-06-26 auto-save: 2026-06-26T00:38:23 (2 files) — data/build.json data/recents.json -
bf7279e2026-06-26 test: analyzeOcr/vendor unit tests (overnight cycle 6) -
6264e282026-06-26 Recognize Pattern refactor: dedupe SQL term-match, single-trigger visual rerank, shared image-pipe helper, reuse one fingerprint canvas -
94dce122026-06-25 ledger: switch overnight loop to NEVER-STOP idea-adding mode (Steve) -
2a4ae142026-06-25 identify: logo-fingerprint tiebreaker (DTD 3/3 verdict A) -
edd68a92026-06-25 Add 🎨 Recognize Pattern: VLM reads a swatch, finds this + visually-similar items across the whole unified DB -
39a065c2026-06-25 learn: surface logo fingerprint + flag rejected logos (overnight cycle 4) -
dfefc4a2026-06-25 Scan UX: distinct BARCODE lock label (overnight cycle 3) -
b10f11c2026-06-25 Overnight cycle 1-2: selfcheck pass + draft gated weekly vendor-refresh launchd -
3c0bcb12026-06-25 Logos: tighten matcher + validate fingerprints (DTD-A verdict) -
33731b62026-06-25 auto-save: 2026-06-25T23:37:59 (8 files) — build_logo_fingerprints.py build_vendor_logos.py data/build.json data/logos/fabricut.jpg data/vendor_logos.json -
85dccb32026-06-25 Harvest vendor logos from Shopify Files → ground logo recognition -
d30783b2026-06-25 Scan: recognize barcodes (exact SKU) + logos/typeface (local VLM) -
8fae1142026-06-25 auto-save: 2026-06-25T23:07:50 (4 files) — bin/ocr bin/ocr.swift data/build.json server.js -
5ebe4e62026-06-25 Learn how each vendor labels their samples (180 vendor profiles) -
47a5ecf2026-06-25 Ask-the-SKU: voice Q&A about a product via local Ollama ($0) -
fc5196a2026-06-25 auto-save: 2026-06-25T18:36:37 (1 files) — data/recents.json -
3948d6a2026-06-25 Voice search: mic button → speak vendor/SKU/what-to-look-for → search -
610fc302026-06-25 Scan audio: outcome-based — yoo-hoo on resolve, 'uh-oh' on no-match -
a045b5f2026-06-25 Scan: brand-aware fast-lock + RED locked-in state -
89ca6322026-06-25 Scan: audible 'yoo-hoo' cue on code lock-in (v41) -
b9742402026-06-25 Auto-refresh mfr# index: hourly incremental (8-18) + nightly full rebuild -
dcb3fda2026-06-25 auto-save: 2026-06-25T17:36:07 (2 files) — build_mfr_index.py refresh_mfr_index.py -
47fbc5a2026-06-25 Add manufacturer-SKU resolver: scanned vendor codes resolve to DW house SKUs -
8ddb73e2026-06-25 Scan: code numbers ONLY — drop name/prose candidates -
3046f622026-06-25 Scan ranks by font size first (largest letters), WD/Winfield Thybony prefix -
e48f07d2026-06-25 auto-save: 2026-06-25T16:05:06 (2 files) — data/build.json photo-app-qr.png -
9a72bcb2026-06-25 Add dev-only ESLint static gate + remove dead tcase helper -
4ba1fb12026-06-25 Fix front-camera scan: mirror-retry (ocrSmart) recovers backwards-text reads on iOS selfie cam; wired into live + snap scan -
3355c702026-06-25 Camera chooser: back/front toggle (header + live-scan flip), persisted, drives scanner + all capture inputs; fix getLS->LS init crash -
f482ee72026-06-25 Multi-pass OCR scan: try every candidate (DW SKU + mfr# + name) across TWIL then all-Shopify -
7a111392026-06-25 Video uploads: 🎥 Video in gallery → /api/video runs the Shopify staged-upload + productCreateMedia(VIDEO) pipeline (multipart to GCS), 150MB cap. Verified end-to-end + cleaned test -
38324932026-06-25 Crop tool: add aspect-ratio presets (Free / 1:1 / 4:3 / 3:4 / 16:9) — ratio-locked, anchor-based corner resize -
f97337a2026-06-25 Add ✂️ Crop tool to photo editor (drag corners/move, dim outside, bakes into final) + default landing = 🧵 All TWIL + Any-SKU auto-falls-back to all-Shopify (mfr#/DW SKU) when not in TWIL index -
a2e7b322026-06-25 Add 🧵 All TWIL chip (every Fentucci/grasscloth item, not just photos-needed) via /api/twil; confirm 🌐 All Shopify gives access to every vendor's SKUs. GRS-27080 = numbering gap (never assigned) -
ae60fb42026-06-25 auto-save: 2026-06-25T09:33:15 (1 files) — server.js -
ae3352b2026-06-25 Live SKU scanner: HTTPS listener (:9891, self-signed cert) enables getUserMedia; 🔢 Scan streams the camera, OCRs each frame, LOCKS IN when a SKU repeats 2x -> auto-lookup. Falls back to tap-snap over HTTP -
5047a832026-06-25 App starts collapsed (clean landing until filter/search/scan) + re-crawl spreadsheet -> 203 GRS items (DWNAT->GRS col-W backfill) -
54495aa2026-06-25 auto-save: 2026-06-25T09:03:03 (1 files) — data/build.json -
b34052e2026-06-25 Camera SKU scan: 🔢 Scan button → macOS Vision OCR (/api/ocr, local/free) reads a printed mfr#/SKU off a sample book, extracts the SKU token, and jumps to that product in Any-SKU search. bin/ocr Swift binary -
f61b6332026-06-25 Desktop-controlled remote shutter (DTD-C): /desk control page + /cam phone live-camera page, paired via SSE+POST over the tailnet. Desktop SHOOT -> SSE shoot event -> phone captures native-res frame from getUserMedia -> uploads via existing /api/photo -> desktop previews. iOS-hardened (playsinline/muted, 3-tier facingMode, auto-reconnect). No new Shopify auto-write; attach stays operator-triggered. -
8ca15e92026-06-25 auto-save: 2026-06-25T08:32:54 (1 files) — server.js -
8cdab9b2026-06-25 Add /selfcheck self-check: server-side health report (catalog/index/queue/token/data) + auto-cleans stale photo refs (kills the dead-link class); HTML report; version pill links to it. selfcheck.sh runs server + deep Playwright UI crawl -
f90170b2026-06-25 Make PWA icon + manifest public (no auth) so iOS Add-to-Home-Screen fetches them; app routes stay gated -
989aa832026-06-25 Installable PWA: app icon (gold DW/camera) + web manifest + apple-touch-icon + standalone meta — Add to Home Screen now gives a real app icon + fullscreen -
4e6ec3f2026-06-25 Multi-photo add: ➕ Multiple in the Photos gallery batch-appends several shots at once (keeps featured + existing images); /api/photos endpoint, never-upscale each, 12/batch cap -
a4af4402026-06-25 All GRS- sold PER YARD: flipped 133 products (variant Roll->Per Yard, unit_of_measure->Priced Per Yard, dwc.order_unit->Yard); now 174/174 uniform. createFromSheet defaults to per-yard for future creates. Price unchanged (already per-yard) -
3c7d0a22026-06-25 Add ⭐ Favorites + 🕘 Recent views: star any card to pin SKUs you update often; recents auto-log on every upload (server-stored, shared across phone+office) -
3cbfbc02026-06-24 All-Shopify search covers collections too: parallel product + collection-products query, merged/deduped, across every vendor; results carry their collections -
418e9722026-06-24 Add collections to search: each product carries its Shopify collection titles; lookup matches collection name + cards show 📚 collection chips -
087b3862026-06-24 Create 22 net-new GRS drafts (full sheet info, photo->live) + per-SKU photo gallery: 🖼 Photos shows every image with ★ Feature / ✕ Remove / 📷 Add (keep existing). /api/images,/api/image/delete,/api/image/feature -
62e3b372026-06-24 auto-save: 2026-06-24T18:28:52 (1 files) — data/queue.json -
6a655b42026-06-24 Add end-to-end test for All-Shopify search + keep/replace photo paths (11/11 pass; throwaway draft, prod-clean teardown) -
7e0a77d2026-06-24 Add ✨ New (N) filter chip (just the net-new sheet SKUs to create) + fix doubled name==color titles; verified create-on-photo end-to-end (Cremona Parchment created live w/ full info + deleted) -
06a420e2026-06-24 auto-save: 2026-06-24T17:58:43 (3 files) — data/build.json public/index.html test-allshopify.js -
708fac72026-06-24 All-Shopify cards: per-card 'Replace all — make this the only image' toggle (DTD verdict C); default keeps existing imagery, explicit opt-in to wipe -
05042e02026-06-24 Any-SKU: include all 170 TWIL-sheet GRS items; photographing a not-yet-in-Shopify one (22 net-new, deduped by GRS+mfr) creates the product from sheet info + photo and goes live; existing-SKU updates preserve room shots (keep_images) -
b4532b22026-06-24 Add 'All Shopify' button: live store-wide SKU search to update any product's photo from phone (non-destructive — keeps existing images) -
a2403b92026-06-24 Add 'Any SKU' lookup: search all 555 Fentucci products by SKU/model#/name to add or replace any product's photo (in-memory catalog index + /api/lookup) -
89c56862026-06-24 Fix grid density slider + editor preview-equals-export bake -
daf466a2026-06-24 auto-save: 2026-06-24T16:58:22 (3 files) — public/index.html server.js data/build.json -
571e9412026-06-24 auto-save: 2026-06-24T16:28:13 (4 files) — data/queue.json ref-roll.jpg screenshot-roll.png screenshot-sheet.png -
444a6832026-06-24 Send Cache-Control: no-store on HTML so browsers never serve a stale page (was hiding the photo editor after deploy) -
4b82a742026-06-24 Roll: add Curl intensity slider + cycle Flat→Tube→Sheet (curled-sheet variant reveals paper's cream back, like the PR roll shot) -
b6eb1622026-06-24 Add 🗞 Roll button: wrap captured pattern around a tube (cylinder shading + sheen + shadow) like the PR roll shot -
9e8d4992026-06-24 auto-save: 2026-06-24T15:58:04 (2 files) — data/queue.json screenshot-editor.png -
dcdf1c32026-06-24 Add Photoshop-style photo editor (brightness/contrast/saturation/hue + color-balance wheel) with review-before-upload step -
ea8d5f12026-06-24 auto-save: 2026-06-24T15:27:54 (2 files) — public/index.html server.js -
2b07e492026-06-24 DW Pattern Photo Capture: mobile-first (iPhone/iPad) camera worklist for 179 photo-less Fentucci patterns; snaps save locally + attach to Shopify draft (filename=DW SKU, leak-safe); basic-auth, sort+density, client-side downscale
Authors
- Steve231
Agents used
- code-reviewer1
- vp-engineering1
Skills used
- /color-dots20
- /learn16
- /ocr12
- /apps8
- /identify8
- /rec7
- /index6
- /vendor5
- /color-index4
- /page4
- /similar4
- /recognize4
- /queue4
- /photo3
- /photos3
- /back3
- /model3
- /stats3
- /recents3
- /contrast3
- /color-widen2
- /debug-log2
- /run32
- /run42
- /twil2
- /scans2
- /train2
- /print-sticker2
- /name2
- /color2
Creative ideas + design notes
e002054 · 2026-07-19 · shop-by-color: data-driven ΔE-collapsed color dots (real DB hexes, ≥20 patterns, qty-ordered)
- scripts/build-color-dots.cjs: greedy CIELAB ΔE76 clustering (DTD verdict A) of product_colors (live-truth gated) into visually-unique dots; keep clusters ≥20 patterns, order by summed count desc; nearest-Pantone naming + LAB-chroma family bucketing. 146 dots covering 41,907/43,284 products. - server.js: public GET /apps/color-dots serving data/color-dots.json (hot-reload on mtime, same CORS posture as /apps/color-index). - refresh-color-index.sh: build + rsync color-dots.json alongside the index.
602bb7f · 2026-07-09 · PDP color-dot color-index: /apps/color-index endpoint + generator (catalog-wide 10% CIELAB tolerance)
New public (CORS-guarded, allowlisted) endpoint POST /apps/color-index on dwphoto:
takes {hex,k}, returns catalog-wide products within a perceptual 10% tolerance
(CIELAB deltaE76, named COLOR_INDEX_TOLERANCE_PCT=0.10 -> deltaE ceiling 10),
nearest-first, public-safe fields only. Backed by a static color-index.json
(one dominant color per ACTIVE product w/ precomputed LAB, 60,960 products) built
by scripts/build-color-index.cjs from dw_unified.product_colors -- no DB at request
time. Index loaded once + mtime-hot-reload. Staged/local; do NOT deploy publicly
without Steve's go.
Verified: forest #3C5B49 -> 97 in-tolerance products, all returned deltaE<=6.3.
ba63786 · 2026-07-08 · Add discontinued-SKU resolver → red card → live successor link (local only)
/api/discontinued?q=<sku> resolves discontinued/deleted SKUs dash/punct-insensitively (CHC-216830 == CHC216830 == CHC-216830-SAMPLE) and links the live successor via the COPY-OF mfr hint + sibling harvest + active title match, ported from substitutefinder find.py. Scanner lookup falls back to it on 0 active hits and shows a red successor card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9a59376 · 2026-07-06 · feat: add new SKU to dw_unified + Shopify (draft) with vendor/vid dropdown
- /api/vendors: 162 real vendors + vids from vendor_catalog for the dropdown. - /api/create-item: dryRun PREVIEW (default) → commit creates a Shopify DRAFT product (never auto-published — go-live stays gated) + a dw_unified new_items_staging row (additive table, does NOT touch canonical catalog); dedups on mfr#. - '➕ New item' modal: photo(optional)+mfr+vendor dropdown(auto-fills vid)+name+color+price → preview → 'Create draft'. Verified: 162 vendors, dryRun preview of Phillip Jeffries draft.
023d1b2 · 2026-07-06 · feat: CLIP visual search + multi-photo front/back fusion identify
- visual-search/embed_catalog.py: batch CLIP-embeds (ViT-B-32/laion2b) the 316k catalog images → image_embeddings (bytea), resumable, ~19 img/s. Full batch running. - visual-search/search_service.py: in-RAM brute-force cosine service (no pgvector needed), pm2 dwphoto-vsearch :9914 — photo → visually-nearest catalog products. - /api/identify-multi: fuses ALL clues — BACK label OCR→code→crossref (exact) + FRONT pattern CLIP visual search; returns primary + confidence + corroborated flag when both agree. - Fixed Node→python Content-Length (chunked body read as empty). Verified: WDW2310 code + grasscloth visual 0.91 live.
7754221 · 2026-07-06 · fix: print-sticker no longer hangs + go-live hardening
- ROOT CAUSE: fmUpdate's pre-read (fmGet) stalls in-request on records with big portals. fm() now has a 12s AbortController timeout (no FM call can hang a request); added fmSet (direct PATCH, no pre-read). print-sticker: dryRun returns the intended write with ZERO FM calls; live uses fmSet. - todayMDY() now computes the Pacific date (Kamatera is UTC) so the stamp never records tomorrow. - Deployed; writes stay dryRun (FM_WRITE unset) until Steve flips it on his business data.
7f00ab4 · 2026-07-06 · feat: sample-request sticker popup + scan cost throttle
- #sampleModal popup (reuses .hidden modal pattern): shows identified item + image + client (who ordered the sample) + client/vendor dates; Print SKU / Print address buttons → /api/print-sticker. Auto-fires from applyScan on a resolved scan (best id: barcode > matched sku > mfr code). - Cost throttle: scanTick slows 600ms→1300ms when the engine is cloud-only (no macvision), and idle-pauses after 60s with tap-to-resume — caps Gemini spend on a forgotten-open scanner.
31dbd12 · 2026-07-06 · feat: FileMaker sample-request lookup + print-sticker handoff
- fm-client.js: FileMaker Cloud (Claris/Cognito) client mirroring filemaker-mcp — idToken→session auth, fmFind/fmGet/fmUpdate (dryRun-gated); guarded require so missing dep/creds degrade gracefully. - /api/sample-request: identify-first (unified) → find most-recent OPEN sample request in FileMaker (today for client filled + vendor-order filled + WP-sample-sent empty), OR(combo sku, Mfr Pattern), sort client-ordered desc. Returns identity+client name+dates. Verified: AV-3X17-S→Haley McKinley. - /api/print-sticker: flags the record (FM_PRINT_FLAG_FIELD) for the Mac poller; dryRun unless FM_WRITE=1. - dep amazon-cognito-identity-js.
1ffca17 · 2026-07-06 · feat: structured field auto-detect (sku#/model#/name/color) + live green highlight + per-vendor spec learning
- analyzeOcr returns fields{sku,model,name,color,barcode}: classifyFields pulls the human-facing
fields from the label read (Color: label + color-word lexicon, Pattern/Name label, secondary code),
strips a trailing colorway from the name (Compass Clay→Compass). Flows through every engine.
- Live camera: green SKU#/Model#/Name/Color chips light up + fill as each is detected (sticky).
- /api/learn now saves each vendor's colors/names + SKU shape (WDW2310→AAA####) — teaches the reader
each vendor's format; client sends detected name/color on resolve. Barcode stays ground truth.
- +6 tests (85 total, all green). Verified via Gemini: WDW2310/Compass/Clay.
b7eee40 · 2026-07-06 · chore: photocapture LIVE on Kamatera at photo.designerwallcoverings.com (pm2 dwphoto :9912, gemini engine, CF-proxied + LE cert); retire Mac tunnel
- .deploy.conf port 9890→9912 (9890 belongs to goldleafwallpaper-website on Kamatera) - ACCESS.md: document the two-place setup, IP-specific nginx listen gotcha, DNS-01 cert via cloudflare-dw.ini
75c75e3 · 2026-07-06 · feat: pluggable vision engines (macvision|gemini|gcv), runtime-selectable + engine=all — enables Linux/Kamatera hosting
- OCR + logo-identify + pattern-recognize now route through resolveEngines(): macvision
(Apple Vision bin/ocr + local Ollama), gemini (2.5-flash, OCR+identify), gcv (Cloud Vision OCR).
- /api/ocr,/api/identify,/api/recognize take {engine:...}; engine=all fans out to every
available engine and returns by_engine + per-engine cost_usd. Default macvision on macOS, gemini elsewhere.
- Backward-compatible: single-engine responses keep the same top-level shape; 74/74 tests green.
- Verified end-to-end: both engines extract WDW2310->Winfield Thybony from a label.
fbc4b68 · 2026-06-26 · chore: lint clean, refactor (none needed), v1.1.0 (session close)
Session quality gate: ESLint 0 errors across session files (server.js, public/index.html, public/learn.html, test-analyze-ocr.js), javascript-pro refactor pass found the code clean (no churn), 74/74 unit tests green. Minor bump for the session's backward-compatible feature additions (logo learning, /api/identify, /api/stats, fingerprint tiebreaker, barcode/QR, model toggle, bgIdentify).
6407d11 · 2026-06-26 · test: lock parseOcrRows barcode branches + overnight wind-down note (cycle 23)
Covered the last untested parse branches: barcode line WITH symbology (sym captured), WITHOUT symbology (sym omitted), and non-numeric height → 0. Noted .sym is parsed-but- unused downstream (deferred, no consumer). 71→74 tests, lint clean. The pure-helper test space is now exhausted; added an honest 'Overnight wind-down note' to the ledger summarizing the 23-cycle run (74 tests, 4 hardenings, 6 evidence-based rejections, a recovered disk-full emergency, ~$0.006 total cost) and flagging the only Steve-gated item left: the staged weekly vendor-refresh launchd job. $0 (local).
25e9532 · 2026-06-26 · test: cover nmfr + brandRe normalizers (cycle 22)
Two untested load-bearing normalizers: - nmfr (mfr dedup key: strips ONE trailing T, uppercases, trims so WOS3467T == WOS3467). A regression here would defeat dedup and let duplicate products be created. 5 guards: trailing-T strip, no-T-unchanged, trim+upper+strip, only-one-T-removed, null→'' no-throw. - brandRe (vendor name → flexible OCR-detection regex source). 5 guards: multiword → \s*-joined caps, single ≥4-char word kept, single <4-char → null, 2-char word dropped, no-alpha → null. Behavior verified empirically before asserting. 61→71 tests, lint clean. $0 (local).
2e6d31c · 2026-06-26 · test: cover mfrToHouseSku resolver (cycle 21)
mfrToHouseSku — the core 'scanned vendor code → DW house SKU' resolver (exact normalized lookup + bounded ±1-trailing-char fuzzy + length guards) — was untested; a regression there would silently break scan resolution. Added 7 guards via an injected controlled MFR_INDEX fixture (not the real 225k file, so deterministic): exact match, case/dash/space normalization, <4-char→null, no-match→null, +1-trailing-char fuzzy recovery (WDW23107 matches WDW2310), and the fuzzy length guard (5-char code won't fuzzy-match). Behavior verified empirically before asserting — my initial expectations were wrong and the code was right (e.g. WDW23106 matches WDW2310 by prefix, not WDW23105). 54→61 tests, lint clean. $0 (local).
eb1e546 · 2026-06-26 · test: reject O/0 OCR fallback (real collisions) + lock verbatim-code contract (cycle 20)
Ran the collision gate the backlog required before building the O/0+I/1 variant fallback: checked 225,846 real catalog codes (mfr_index + profile examples) and found 14 collision groups where genuinely DIFFERENT products are pure O/0 or I/1 variants of each other — RI5112 vs R15112, MI725 vs M1725, etc. A blind variant fallback could therefore resolve a scan to a real-but-WRONG product. REJECTED with evidence (saved shipping a subtly-wrong feature). The correct, safe behavior is to search exactly what OCR read — analyzeOcr preserves codes verbatim with no silent O↔0 / I↔1 normalization. Locked that contract with 3 regression tests (RI5112 and R15112 stay distinct; letter-O+I code unchanged). 51→54 tests, lint clean. $0 (local).
e3483ef · 2026-06-26 · observability: add /api/stats; backlog O/0 OCR fallback with evidence (cycle 19)
Investigated the OCR O-vs-0 / I-vs-1 normalization idea empirically: the ambiguity is REAL (of 1033 sample catalog codes, 122 contain a letter-O and 47 contain both O and 0, e.g. SNPBOBJML11I3, BMGI006). But a recovery fallback touches the hot resolve path and carries a false-match risk that can't be tested headlessly (resolve hits live endpoints), so I BACKLOGGED it with the evidence + a safe design (exact-first, variant-only-on-miss, capped) rather than ship speculative hot-path complexity. Shipped the clean additive win instead: GET /api/stats — a read-only observability snapshot for the self-teaching scanner (version, vendor count, seeded, fingerprinted, total learned scans, last-learn timestamp, recents, catalog indexed, vision model). Auth-gated, in-memory, zero hot-path risk. Verified: 200 with creds / 401 without; lint + 51 tests green. $0 (local).
4d8fe6d · 2026-06-26 · robustness: atomic saveJSON so a crash/ENOSPC can't corrupt persisted data (cycle 18)
Brainstorm idea #2. saveJSON (the shared write primitive for progress/recents/favorites/ vendor_profiles) did a plain writeFileSync — a crash or disk-full mid-write could leave a truncated, corrupt file, losing all 180 learned vendor profiles. Especially live given we hit ENOSPC this very night. Now it serializes to a temp file then renameSync over the target (atomic on POSIX), and on failure removes the partial temp and rethrows (same API). One change to the shared primitive protects every JSON persistence path. Proved with the REAL saveJSON source: a forced write failure left the original file intact with 0 stale temps; normal /api/learn still persists (200), file stays valid JSON. lint + 51 tests green, selfcheck 200. $0 (local).
d9df0cb · 2026-06-26 · test: lock candidate-order contract scanResolve relies on (cycle 17 audit)
Audited resolution idea #3 (multi-candidate → different products). Found scanResolve's first-hit-wins is ALREADY correct because analyzeOcr orders candidates strongest-first: barcode > brand-prefixed code > larger-font > generic. Verified empirically (WDW2310 beats XQ5599 at equal font height; a barcode beats a 200-height OCR code; CD2222 beats AB1111 on font size). No code change — locked the contract with 3 regression tests so a future analyzeOcr change can't silently break scanResolve's reliance on the ordering. Deferred known-minor (logged): scanResolve runs all candidates through the TWIL catalog before any through Shopify, so a weak TWIL hit can beat a barcode that only resolves in Shopify — narrow edge, deliberate source preference, revisit only if seen in real use. 48→51 tests, lint clean. $0 (local).
830b25f · 2026-06-26 · fuzzy: ambiguity guard so a partial read can't bind the wrong vendor (cycle 16)
Two parts this cycle: 1. Proto-key sweep (follow-up to cycle 15): grepped every OBJ[userInput]= site — only two, both in /api/learn. The vendor-key path was fixed cycle 15; the prefix-key path (prof.prefixes[pre]) is safe by construction (pre is uppercase-only + underscore- stripped, so never __proto__ or lowercase constructor). Confirmed live: skus deriving CONSTRUCTOR/PROTOTYPE/TOSTRING all → 200, server stays up. Defer-with-evidence, no change. 2. Fuzzy-collision audit found REAL collisions among the 180 vendor names: Alan Campbell ~ Nina Campbell (0.33), Retro Walls ~ Rebel Walls (0.30) — genuinely different vendors. A partial VLM read like 'Campbell' could fuzzy-match the WRONG one and corrupt its profile via /api/learn. Added an ambiguity guard: if a genuinely-different vendor (different normalized name, so same-brand spellings don't count) is within 0.08 of the winner → return null instead of guessing. Verified: CAMPBELL→null, NINA CAMPBELL→Nina Campbell, BRUNSWIG→Brunschwig still corrects. 46→48 tests, lint clean, live identify no-regression. $0 (local).
f35ef39 · 2026-06-26 · security: fix /api/learn prototype-pollution DoS (cycle 15)
Investigating brainstorm idea #4 found a REAL, reproducible denial-of-service. POSTing
{vendor:'__proto__'} to /api/learn crashed the whole process: VENDOR_PROFILES['__proto__']
returns Object.prototype (truthy), so 'const prof = VENDOR_PROFILES[vendor] || ...' binds
prof to Object.prototype, then prof.prefixes[pre] dereferences undefined → uncaught
TypeError → process exit (launchd then auto-restarted it). One authenticated request — and
bgIdentify auto-calls /api/learn with VLM-derived vendor names — could repeatedly kill the
scanner.
Fix: reject the dangerous prototype keys (__proto__/constructor/prototype) with 400; no real
vendor name is one. Verified: pre-fix reproduced (HTTP 000 + crash in dwphoto.err.log);
post-fix all 3 payloads → 400 'invalid vendor', server stays up (selfcheck 200), normal
learn still 200. lint + 46 tests green. $0 (local).
a3b00cc · 2026-06-26 · harden: prefix support gate so one bad scan can't corrupt learned prefixes (cycle 14)
Brainstorm idea #1 (learning-loop robustness). profileToLex used ALL prefix keys regardless of support — so a single /api/learn scan (count 1) immediately injected a full-weight prefix into the ranking lexicon. For a scan-CREATED vendor, one bad scan could bias future ranking. Now a prefix needs >=2 supporting scans before it enters the lexicon; catalog-built prefixes (builder already keeps n>=3) are unaffected, and the prefix data is still stored (just gated until confirmed — auto-activates on a 2nd scan). Real-data impact verified: of 291 prefixes, exactly 1 is gated (WC count-1, a test artifact on Phillip Jeffries which still resolves via DWJP); 0 vendors lose matching. Exposed profileToLex to the test harness + 5 gate tests (count-1 gated→null, count-2 included, mixed confirmed-matches/unconfirmed-ignored, numeric_share still matches). 41→46 tests, lint clean, restart 200. $0 (local).
f050ab3 · 2026-06-26 · test: fuzzyVendor edge guards + disk-full debug note (cycle 13)
Cycle blocked mid-run by a FULL Data volume (ENOSPC — harness writes cmd stdout to /tmp on /, so every Bash call failed). Debugged: 460G volume 100% full, home=138G (Projects 83G, Ollama 45G/16 models), ~250G in sudo-gated areas (surfaced to Steve, NOT auto-deleted — won't blind-rm his data/models). Free space recovered 124Mi→4.7Gi as macOS reclaimed purgeable space. Did a disk-light refinement (pure tests, no server restart, no OCR scratch): 5 fuzzyVendor edge guards — empty/<4-char/null never throw → null; SCHUMACKER→Schumacher, Phillip Jefries→Phillip Jeffries resolve. 36→41 tests, lint clean. Logged a GATED backlog item for Steve to review disk consumers. $0 (local).
0801a44 · 2026-06-26 · test: fingerprintVendor edge cases + reject OCR-confidence-threshold (cycle 12)
Brainstorm (qwen3:14b) → triaged 5: rejected 3 as already-done/misconceived (font smoothing, prefix cache, logo ROTATION — fingerprints are text not pixels), investigated + rejected-with-evidence the OCR confidence threshold (Vision returns confidence 1.000 or NO read across all blur/noise tiers — there's no low-confidence band to threshold), and shipped the safe test-coverage pick. Added 4 fingerprintVendor guards: empty + <4-char reads → null; punctuation/case normalized before matching; and the CRITICAL one — a rejected logo (logo_valid=false, e.g. Versace's mislabeled 'Savannah') is NEVER matched, proving the validation gate keeps mislabels out of vendor resolution. 32→36 tests, lint clean. $0 (local brainstorm + tests).
bfd8249 · 2026-06-26 · scan UX: tentative 'hold steady' cue + reject Swift low-light pre-pass (cycle 11)
Two brainstorm-pool items resolved this cycle: - REJECTED (with evidence) the Swift grayscale/contrast pre-pass for low-light labels. A/B test: macOS Vision reads even a 4-level-contrast label (text 56 vs bg 60, near- invisible) perfectly RAW, and an autocontrast pre-pass changed nothing. Vision already normalizes contrast better than a naive pre-pass — building one is risk for zero gain. - SHIPPED a tiny UX cue: when a read is not topStrong (needs a 2nd matching frame to lock), the live overlay shows '👀 hold steady · <code>' instead of '👀', coaching the user to keep the camera still. Strong reads still lock instantly with no extra prompt. Verified: inline JS valid, lint + 32 tests green, page 200. $0 (local).
2c6e052 · 2026-06-26 · test: harden analyzeOcr against adversarial OCR inputs (cycle 10, brainstorm)
First brainstorm-mode cycle. A local qwen3:8b proposed 5 refinements; triaged — 2 were already shipped (prefix-boost, fast-lock), 2 deferred to backlog (UX confidence overlay, Swift grayscale pre-pass), and the test-coverage one was the safe high-value pick. Added 11 regression guards locking the ranking contract under degraded/adversarial OCR: 3-char + no-digit tokens excluded; dashed code is a candidate but not strong; <4-char barcode ignored; two brand names → first lexicon match wins (deterministic); lowercase brand still detected; duplicate code de-duped; two barcodes → both candidates, barcode forces topStrong. All behaviors pre-verified correct first (no bug found — pure hardening). 21→32 tests, lint clean. $0 (local LLM brainstorm + local tests).
f6b8dec · 2026-06-26 · review: fix 3 findings from session code-review (cycle 9)
Independent code-reviewer pass over the session diff — no CRITICAL. Fixed 3 real
robustness/security findings:
- /api/learn: cap vendor length at 120 chars. brandRe(vendor) is recompiled into the
lexicon on every learn and tested against OCR text every ~600ms; an oversized vendor
name (body allows 64KB) could hang the scanner. Now returns 400.
- build_logo_fingerprints validation: require len(nr) >= 4 before the containment check,
so a 3-char VLM read ('Sch') can't false-validate a logo via 'sch' in 'schumacher'.
- checkAuth: split the Basic-auth credential on the FIRST colon only (RFC allows colons
in passwords) instead of String.split(':') which truncates. Default pw unaffected.
Verified: lint + 21 unit tests green; auth good→200 / bad→401; oversized vendor→400;
normal learn→200. Two latent MINORs documented + deferred in the ledger. $0 (local).
64ab508 · 2026-06-26 · scan: background logo-ID when a code locks but OCR names no vendor (cycle 8)
When the scanner locks a code but OCR couldn't read the brand, the captured frame is
now threaded into applyScan and a non-blocking bgIdentify() fires /api/identify (VLM
logo recognition) in the background — the SKU resolve already ran, so this never blocks
it. If the logo names a vendor, it's surfaced ('🏷 Looks like X') and bound to the
locked SKU via /api/learn so the vendor profile learns. Fires from both the live scanner
(passes the locked frame) and the snap input (passes the photo). Only triggers when OCR
found no vendor — no extra VLM calls on the common path.
Verified: lint + 21 unit tests green, page serves 200, inline JS valid. $0 (local).
d76cc5a · 2026-06-26 · identify: per-request model toggle (moondream fast path) + API.md (cycle 7)
/api/identify can now trade accuracy for speed per request. ollamaVision() gains a
model arg; a VISION_MODELS allow-list maps {fast:true} or {model:'moondream'|...} to a
faster local model (anything off-list falls back to default — no arbitrary model pulls).
The chosen model is echoed in the response. Default stays qwen2.5vl:7b (most accurate).
Verified: default qwen2.5vl 21.5s (resolves Kravet), {fast:true} moondream 12.2s (~2x
faster, lower accuracy as documented); lint + 21 unit tests green. Added API.md
documenting /api/ocr, /api/identify + the model toggle table, and the learn surface.
$0 (local VLM).
bf7279e · 2026-06-26 · test: analyzeOcr/vendor unit tests (overnight cycle 6)
21 assertions over the pure OCR/vendor functions. The harness slices the REAL function source out of server.js (parseOcrRows, analyzeOcr, fuzzyVendor, fingerprintVendor + the VENDOR_LEX/STRONG_PFX/CODE_TOKEN consts) and builds a module via new Function(...) that returns the names — so it tests the shipping code, not copies. Coverage: height parsing + barcode tagging; brand-on-swatch vendor detection; barcode ground-truth ordering + forced topStrong; GRS- prefix strength; weak-read non-strength; empty input; fuzzy misspelling resolution; fingerprint resolve + null. Wired into 'npm test' and the selfcheck unit-test layer. Found + fixed 2 harness bugs (eval-const scope leak, comment-line over-grab) before landing. $0 (local).
2a4ae14 · 2026-06-25 · identify: logo-fingerprint tiebreaker (DTD 3/3 verdict A)
DTD panel (Claude+Codex+Qwen, unanimous A) ruled the 20 validated reference-logo fingerprints should improve /api/identify as a TIEBREAKER ONLY — not a global blended score (B would bias toward the 20 fingerprinted vendors over the 160 without). New fingerprintVendor() compares the live VLM read (brand text + typeface) to the stored validated fingerprints, and is consulted ONLY when the name match (lexicon + fuzzy) returns null. Response now carries match_via: name|fingerprint. Verified: clean 'PHILLIP JEFFRIES' still resolves via=name (no regression); the real fingerprintVendor source resolves Mind the Gap + Ralph Lauren from their fingerprints and returns null for an unknown brand. $0 (local VLM + Shopify read; $0.003 DTD).
39a065c · 2026-06-25 · learn: surface logo fingerprint + flag rejected logos (overnight cycle 4)
Each /learn card now shows how the VLM read that vendor's reference logo — a green '🏷 reads X · typeface' for validated logos, and a red '⚠ logo rejected — read as X' for the 6 logos the validation pass dropped (Versace→Savannah, Hollywood→Hollywood Reporter, Mid Century→20th Century Fox, etc.) so Steve can review the misfires at a glance. Reads logo_valid/logo_reads already in /api/vendor-profiles. $0 (local).
dfefc4a · 2026-06-25 · Scan UX: distinct BARCODE lock label (overnight cycle 3)
A barcode read is an EXACT SKU, not an OCR guess — so on a barcode lock the live scanner now shows '▍▍ BARCODE · <sku>' instead of the generic '🔒 LOCKED', so the user knows it's ground-truth. Uses the d.barcode field already returned by /api/ocr. $0 (local).
b10f11c · 2026-06-25 · Overnight cycle 1-2: selfcheck pass + draft gated weekly vendor-refresh launchd
- Cycle 1: ran project selfcheck after the session's changes — lint clean, all server-side checks ok, 0 dead links / console errors. No regression. - Cycle 2 (GATED): drafted weekly vendor-learning refresh to pending-approval and staged the plist (NOT installed — scheduled jobs are Steve-gated). vp-engineering verdict APPROVE. Keeps vendor profiles + logos + fingerprints fresh as the catalog grows.
3c0bcb1 · 2026-06-25 · Logos: tighten matcher + validate fingerprints (DTD-A verdict)
Executes the unanimous DTD verdict to fix logo-data quality before building on it. - build_vendor_logos.py: require the file to BE a logo (name/alt) + anchored keymatch + MIN_CONF 0.7; 26->19 candidate matches (drops loose cross-matches). - build_logo_fingerprints.py: VALIDATE each logo against its own VLM read-back — reject any whose logo_reads isn't this vendor (whole-word: containment OR >=2 shared significant words, single-word vendors on exact). Caught Versace->Savannah, Hollywood->'The Hollywood Reporter', Mid Century->'20th Century Fox', Telefina. Result: 20 trusted logos, 6 rejected (logo_url stripped so nothing downstream trusts them). Start of overnight officer-yolo run; ledger in OVERNIGHT_LEDGER.md. $0 (local + Shopify read).
85dccb3 · 2026-06-25 · Harvest vendor logos from Shopify Files → ground logo recognition
The brand-logo app on the store keeps vendor logos in Shopify Files (e.g.
maxwell-logo-black.png). Pull them to ground the VLM instead of guessing cold:
- build_vendor_logos.py: pages Files via SHOPIFY_DRAFT_TOKEN (only token with
read_files), fuzzy-matches filename→vendor, downloads to data/logos/, writes
data/vendor_logos.json + stamps profiles[vendor].logo_url. 26 vendors matched.
- build_logo_fingerprints.py: runs local qwen2.5vl over each reference logo once
and stores a canonical {logo_desc, typeface, reads_as, colors} into the profile
— auto-populating the LLM's per-vendor logo knowledge. Background, idempotent.
- server.js: fuzzyVendor() (normalized Levenshtein over known vendor names) so a
VLM brand guess resolves to a real vendor — 'BRUNSWIG & FILS' → 'Brunschwig &
Fils'. Wired into /api/identify.
- /learn now shows each vendor's reference logo thumbnail.
Verified: 26 logos harvested + shown; identify fuzzy-resolves misspelled brands;
fingerprinter reads Kravet/Phillip Jeffries/Ralph Lauren correctly. Known: loose
filename match mispaired Versace (logo reads 'Savannah') — matched_on recorded for
review. $0 (Shopify read + local VLM).
d30783b · 2026-06-25 · Scan: recognize barcodes (exact SKU) + logos/typeface (local VLM)
Two new visual signals beyond printed-text OCR:
- BARCODES/QR: bin/ocr.swift now runs VNDetectBarcodes in the same Vision pass and
emits 'BC<tab>payload<tab>symbology'. analyzeOcr treats a barcode payload as ground
truth — it outranks every OCR'd code and forces topStrong, so a barcoded sample locks
instantly. Returns { barcode } too. Zero added cost (same image pass).
- LOGO + TYPESETTING: /api/identify sends the frame to local qwen2.5vl (Ollama, $0) and
returns { brand, confidence, logo, typeface, code }, cross-checked against the learned
LEXICON for a canonical vendor. A '🏷 Logo' button in the live scanner runs it on demand
(it's ~seconds, not per-frame) for swatches whose brand text won't OCR cleanly; if the
VLM also reads a code it routes straight into the normal resolve.
Reuses the existing OLLAMA_URL (voice feature). Verified: Code128 'WDW2310' →
barcode ground-truth lock; a serif Brunschwig & Fils label → brand+typeface+code read.
$0 (local Vision OCR + local VLM).
5ebe4e6 · 2026-06-25 · Learn how each vendor labels their samples (180 vendor profiles)
The scanner's vendor knowledge was 5 hardcoded entries; now it learns every vendor's sample-labeling pattern from two sources: - build_vendor_profiles.py: one Shopify bulk export grouped by vendor induces a per-vendor profile — brand-name regex, manufacturer-code prefix histogram, numeric-share, example codes. Learns from the MANUFACTURER code (what's printed on the swatch), not the DW house SKU. 180 vendors profiled from 131k products. - /api/learn: each scan that resolves to a real product folds the on-swatch code prefix back into that vendor's profile (learned_scans++), persisted + live. server.js merges seed VENDOR_LEX + learned profiles into a runtime LEXICON that analyzeOcr consults, so brand detection + SKU-prefix boosting + fast-lock now cover all 180 vendors. profileToLex handles prefixed AND numeric-coded vendors. /learn viewer (admin, basic-auth) shows what's been learned per vendor — prefixes, examples, code-source (mfr vs house), private-label flag, scan count, updated time. Verified: Scalamandre (learned-only, not seeded) → vendor detected + topStrong; /api/learn write persists + rebuilds lexicon. $0 (local + Shopify read).
47a5ecf · 2026-06-25 · Ask-the-SKU: voice Q&A about a product via local Ollama ($0)
- server /api/ask: pulls a product's facts (basics + spec-like metafields, prefers the main non-sample variant) and asks local Ollama (qwen2.5:latest, 127.0.0.1) - gallery '🎤 Ask' button: speak a question about the open SKU -> answer shown in a banner + spoken back via speechSynthesis; prompt() fallback where no SpeechRecognition - OLLAMA_URL/OLLAMA_MODEL env-overridable; 30s timeout, graceful 'AI unreachable'
3948d6a · 2026-06-25 · Voice search: mic button → speak vendor/SKU/what-to-look-for → search
Web Speech API (webkitSpeechRecognition) mic in the header; transcribes a spoken query, strips lead-ins (show me/find/look for), and runs it through all-Shopify search (resolves vendors, product types, titles, SKUs + vendor codes via the mfr index). Graceful fallback to the keyboard mic where SpeechRecognition is absent.
610fc30 · 2026-06-25 · Scan audio: outcome-based — yoo-hoo on resolve, 'uh-oh' on no-match
Moved the success chime from lock-in to the resolve branch (only plays when the code actually finds a product) and added a distinct low descending square-wave 'uh-oh' when a code locks but matches nothing (or nothing is read). Refactored into a shared tone() player.
a045b5f · 2026-06-25 · Scan: brand-aware fast-lock + RED locked-in state
- OCR now detects the vendor name printed on the swatch (Winfield Thybony,
Fentucci, Phillip Jeffries, Thibaut/Anna French, Schumacher) and boosts the
code whose prefix matches that brand, so the largest-font SKU wins.
- Emits topStrong: a known-prefix code, or brand-name-present + font-dominant
code, is safe to lock on the FIRST read instead of waiting for 2 frames.
- Live scanner locks fast on strong reads, turns the whole frame + status pill
RED ('locked in'), holds a beat, then resolves — 'once locked... go.'
89ca632 · 2026-06-25 · Scan: audible 'yoo-hoo' cue on code lock-in (v41)
Web Audio two-note falling minor third (G5->E5) plays the instant a code locks, alongside the existing haptic vibrate. AudioContext unlocked on the scan-open tap (iOS gesture requirement). Zero asset, offline, $0.
b974240 · 2026-06-25 · Auto-refresh mfr# index: hourly incremental (8-18) + nightly full rebuild
- refresh_mfr_index.py: incremental merge of only products updated since last build (seconds, $0) so same-day-imported SKUs are scannable within the hour - build_mfr_index.py: stamp _meta.built_at for the incremental baseline - launchd/: com.steve.dwphoto-mfr-refresh (hourly 8am-6pm incremental) + com.steve.dwphoto-mfr-rebuild (3am full rebuild) — both bootstrapped + verified
47fbc5a · 2026-06-25 · Add manufacturer-SKU resolver: scanned vendor codes resolve to DW house SKUs
- build_mfr_index.py: Shopify bulk-op export of every product's manufacturer_sku (custom + dwc) -> normalized vendor-code index (131,516 products, 224,704 keys) - server.js: load data/mfr_index.json; shopifySearch() rewrites a single code-like query to its DW house SKU via normalized lookup (handles dashes/dots/compound) + tight ±1-char fuzzy for DW's dropped-trailing-char codes - fixes scans of vendor codes (WDW-2290 -> CHC-217000, WHF3846.WT.0 -> DWKK-156194) that Shopify can't search because the code lives only in a metafield - mfr_index.json (38MB, regenerable) gitignored
8ddb73e · 2026-06-25 · Scan: code numbers ONLY — drop name/prose candidates
Per Steve: never scan names or marketing text, only code numbers (SKU/model/lot). /api/ocr now returns only alphanumeric-with-digit tokens, ranked largest-font first then vendor-prefix score. A page with no code returns no candidates (no name fallback). Verified: WDW2310 -> ['WDW2310']; logo-only -> [].
3046f62 · 2026-06-25 · Scan ranks by font size first (largest letters), WD/Winfield Thybony prefix
- bin/ocr.swift: emit normalized boundingBox height per line (height<TAB>text) - server.js /api/ocr: rank candidates by font height first (largest printed text tried first), then SKU-likeness; add WD-prefix score (Winfield WDW2310) + WINFIELD/THYBONY stopwords - verified: synthetic WDW2310 sample -> top candidate; ref-roll height-ordered
9a72bcb · 2026-06-25 · Add dev-only ESLint static gate + remove dead tcase helper
- package.json: dev-only eslint deps (zero runtime deps preserved) - eslint.config.mjs: no-undef as error (the rule that catches the getLS class of crash), idiomatic catch-bindings silenced, selfcheck-ui browser globals - selfcheck.sh: run static lint before the browser click-through - server.js: drop unused tcase() helper (dead code, lint-found)
89c5686 · 2026-06-24 · Fix grid density slider + editor preview-equals-export bake
- Density slider: set grid-template-columns directly on <main> as repeat(N,1fr) via applyDensity(), removing reliance on var() inside repeat() (older iOS Safari ignores that); add change-event backstop; apply on restore. - Editor bake: clamp after each filter stage (brightness/contrast/saturation/ hue) to match CSS/SVG semantics so the saved JPEG equals the on-screen preview (was drifting up to 67/255 on saturated pixels). - FLAT preview now renders from bakeAdjust() pixels instead of a CSS element filter + soft-light cast, so preview === export for the color-wheel path too (was diverging up to 62/255). Removed now-dead curFilter(). - openEditor: add img.onerror toast so a failed Shopify proxy-image load no longer hangs silently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
File tree
- .deploy.conf
- .env.example
- .gitignore
- 5x/REPORT.md
- 5x/sweep-1.md
- 5x/sweep-2.md
- ACCESS.md
- API.md
- DEPLOY.md
- DEVICE-TEST.md
- FILEMAKER-STICKER-SETUP.md
- OVERNIGHT_LEDGER.md
- bin/ocr
- bin/ocr.swift
- build_logo_fingerprints.py
- build_mfr_index.py
- build_sheet_grs.py
- build_vendor_logos.py
- build_vendor_profiles.py
- create_22_new.py
- data/build.json
- data/color-dots.json
- data/favorites.json
- data/logos/1838wallcoverings.png
- data/logos/andrewmartin.png
- data/logos/architecturalfabrics.png
- data/logos/architecturalwallcoverings.png
- data/logos/armanicasa.png
- data/logos/coleson.png
- data/logos/fabricut.jpg
- data/logos/hollywoodacoustical.png
- data/logos/hollywoodwallcoverings.png
- data/logos/kravet.jpg
- data/logos/kravet.png
- data/logos/leejofa.jpg
- data/logos/midcenturymodern.jpg
- data/logos/mindthegap.png
- data/logos/ninacampbell.jpg
- data/logos/ninacampbellwallcoverings.jpg
- data/logos/nlxl.jpg
- data/logos/phillipjeffries.png
- data/logos/ralphlauren.png
- data/logos/ralphlaurenfabric.png
- data/logos/rebelwalls.jpg
- data/logos/schumacher.png
- data/logos/telefinafinefabrics.png
- data/logos/thibaut.png
- data/logos/versace.png
- data/logos/williammorris.png
- data/queue.json
- data/recents.json
- data/sheet_grs.json
- data/vendor_logos.json
- data/vendor_profiles.json
- deploy/launchd/com.steve.color-index-rebuild.plist
- eslint.config.mjs
- fm-client.js
- grs_per_yard.py
- launchd/com.steve.dwphoto-mfr-rebuild.plist
- launchd/com.steve.dwphoto-mfr-refresh.plist
- launchd/com.steve.dwphoto-sticker-watcher.plist
- launchd/com.steve.dwphoto-vendor-refresh.plist
- package-lock.json
- package.json
- photo-app-qr.png
- public/cam.html
- public/caps.html
- public/desk.html
- public/icon-180.png
- public/icon-512.png
- public/index.html
- public/learn.html
- ref-roll.jpg
- refresh_mfr_index.py
- schu-apply-out.json
- schu-publish-audit.jsonl
- schu-publish.mjs
- schu-settlement-hold.json
- screenrecord/DEBUG-REPORT.md
- screenrecord/debug-log.jsonl
- screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.mp4
- screenrecord/rec/run0/page@8d672a8c348229a87b60bc50cf0f16a4.webm
- screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.mp4
- screenrecord/rec/run0/page@baf635327b1dd2ad57d25686a3348dc6.webm
- screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.mp4
- screenrecord/rec/run0/page@c02a42ee275506e6376d31fad467949b.webm
- screenrecord/rec/run0_v103/page@8c4e4c64f0efacceccc8ec61cfc18404.mp4
- screenrecord/rec/run0_v103/page@8c4e4c64f0efacceccc8ec61cfc18404.webm
- screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.mp4
- screenrecord/rec/run1/page@045d6c3359d0ee4c5008a03e3e743927.webm
- screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.mp4
- screenrecord/rec/run1/page@65767f2cbeed44e6f0746eb7b331ac58.webm
- screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.mp4
- screenrecord/rec/run1/page@759aba46dc4a16b4a16bd7623b33e392.webm
- screenrecord/rec/run1_v103/page@2d36e7ee18fbf7abd4adf8890a223e9f.mp4
- screenrecord/rec/run1_v103/page@2d36e7ee18fbf7abd4adf8890a223e9f.webm
- screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.mp4
- screenrecord/rec/run2/page@4c646c54fc7d2b27396bde8641b14e2b.webm
- screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.mp4
- screenrecord/rec/run2/page@e9ba48f157a7481ee9e0122f0faf25ae.webm
- screenrecord/rec/run2_v103/page@792e03857e8e203189e3eda19d9f4e04.mp4
- screenrecord/rec/run2_v103/page@792e03857e8e203189e3eda19d9f4e04.webm
- screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.mp4
- screenrecord/rec/run3/page@4802028714f9cea97f12f6938a7a7d33.webm
- screenrecord/rec/run3/page@ea8fb1218f5b7b345af268e4e545b083.mp4
- screenrecord/rec/run3/page@ea8fb1218f5b7b345af268e4e545b083.webm
- screenrecord/rec/run3_v103/page@aeae63853bf8608b4c9f7c1345580f85.mp4
- screenrecord/rec/run3_v103/page@aeae63853bf8608b4c9f7c1345580f85.webm
- screenrecord/rec/run4/page@96324b4cfafc83a80afe7cd203ccecb3.mp4
- screenrecord/rec/run4/page@96324b4cfafc83a80afe7cd203ccecb3.webm
- screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.mp4
- screenrecord/rec/run4/page@b2f4018983a23e276abe379a34736903.webm
- screenrecord/rec/run4_v103/page@6bdbb0935be81eb1898f9e213bbef949.mp4
- screenrecord/rec/run4_v103/page@6bdbb0935be81eb1898f9e213bbef949.webm
- screenrecord/run-tests-v103-mobile.js
- screenrecord/run-tests.js
- screenshot-editor.png
- screenshot-roll.png
- screenshot-sheet.png
- scripts/build-color-dots.cjs
- scripts/build-color-index.cjs
- scripts/refresh-color-index.sh
- selfcheck-ui.cjs
- selfcheck.sh
- server.js
- sticker-watcher.js
- test-allshopify.js
- test-analyze-ocr.js
- visual-search/.gitignore
- visual-search/__pycache__/embed_shopify_gap.cpython-312.pyc
- visual-search/__pycache__/search_service.cpython-312.pyc
- visual-search/__pycache__/search_service.cpython-314.pyc
- visual-search/com.steve.visual-search-embed-nightly.plist
- visual-search/com.steve.visual-search.plist
- visual-search/deploy-kamatera/requirements-lite.txt
- visual-search/deploy-kamatera/setup.sh
- visual-search/embed-daemon.sh
- visual-search/embed-nightly-mac2.sh
- visual-search/embed_catalog.py
- visual-search/embed_shopify_gap.py
- visual-search/search_service.py
- visual-search/train/.gitignore
- visual-search/train/HANDOFF.md
- visual-search/train/__pycache__/finetune.cpython-312.pyc
- visual-search/train/__pycache__/finetune_aug.cpython-312.pyc
- visual-search/train/build_manifest.sh
- visual-search/train/download.py
- visual-search/train/eval_aug.py
- visual-search/train/finetune.py
- visual-search/train/finetune_aug.py
- visual-search/train/mac_deploy_embed.py
- visual-search/train/robustness_fusion.py
- visual-search/train/robustness_test.py
- visual-search/train/run_overnight.sh
- visual-search/train/sanity_check.py