Dw Photo Capture

repo: ~/Projects/dw-photo-capture · 231 commits · 2 in last 24h, 15 in last 7d · █▅▅▅▅▅▅

Search the build

231 commits indexed

  1. e944832 2026-07-28 auto-save: 2026-07-28T04:27:17 (1 files) — data/color-dots.json
  2. 2085685 2026-07-28 auto-save: 2026-07-28T03:57:08 (1 files) — data/color-dots.json
  3. 7f05d6c 2026-07-27 auto-save: 2026-07-27T04:17:53 (1 files) — data/color-dots.json
  4. d407efc 2026-07-27 auto-save: 2026-07-27T03:47:45 (1 files) — data/color-dots.json
  5. 210a279 2026-07-26 auto-save: 2026-07-26T04:40:35 (1 files) — data/color-dots.json
  6. 8038793 2026-07-26 auto-save: 2026-07-26T03:40:20 (1 files) — data/color-dots.json
  7. 89699fd 2026-07-25 auto-save: 2026-07-25T04:33:37 (1 files) — data/color-dots.json
  8. 77c6b64 2026-07-25 auto-save: 2026-07-25T03:33:15 (1 files) — data/color-dots.json
  9. d7be4c6 2026-07-24 auto-save: 2026-07-24T04:25:19 (1 files) — data/color-dots.json
  10. 98b67d2 2026-07-24 auto-save: 2026-07-24T03:55:12 (1 files) — data/color-dots.json
  11. d17f80f 2026-07-23 auto-save: 2026-07-23T04:18:33 (1 files) — data/color-dots.json
  12. c643cf0 2026-07-23 auto-save: 2026-07-23T03:48:26 (1 files) — data/color-dots.json
  13. 214f7fb 2026-07-22 auto-save: 2026-07-22T04:41:22 (1 files) — data/color-dots.json
  14. 22fe1cb 2026-07-22 auto-save: 2026-07-22T03:41:10 (1 files) — data/color-dots.json
  15. 52d98a4 2026-07-21 chore: lint (drop unused HSL l), refactor (familyOf hue comment, hexToRgb param), v1.5.1 (session close)
  16. f733796 2026-07-21 auto-save: 2026-07-21T04:35:41 (1 files) — data/color-dots.json
  17. 904218b 2026-07-21 auto-save: 2026-07-21T03:35:30 (1 files) — data/color-dots.json
  18. d684bcb 2026-07-20 auto-save: 2026-07-20T04:29:52 (1 files) — data/color-dots.json
  19. 2a2122c 2026-07-20 auto-save: 2026-07-20T03:59:45 (1 files) — data/color-dots.json
  20. e002054 2026-07-19 shop-by-color: data-driven ΔE-collapsed color dots (real DB hexes, ≥20 patterns, qty-ordered)
  21. a9c6a11 2026-07-19 chore: version-up v1.5.0 (session close — shop-by-color product_type)
  22. 8112eaf 2026-07-17 color-index: carry product_type through builder + both /apps/color-index and /apps/color-widen results (shop-by-color type checkboxes)
  23. 0d6c355 2026-07-16 color-widen: add min param — guarantee >=N results via nearest-neighbor fallback when the band is thin
  24. e2c9e01 2026-07-16 add /apps/color-widen route: tunable ΔE band (default 15%) for the Shop-by-Color wheel at /pages/colors
  25. 5d305fe 2026-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)
  26. 4214868 2026-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)
  27. 91ce171 2026-07-15 deps: add pg so build-color-index runs standalone (needed for scheduled rebuild)
  28. 61eee41 2026-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
  29. dd6e416 2026-07-13 chore: session close snapshot (visual-search)
  30. 2212c21 2026-07-13 nightly embed top-up script + plist draft (coverage anti-decay: both embedders resumable + index hot-reload)
  31. bdba9e2 2026-07-13 visual-search: path-(a) Kamatera self-host bundle (lite venv, no torch; pm2 unit reading local dw_unified) — verified 71MB footprint
  32. fcb1edb 2026-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)
  33. 95931a9 2026-07-13 auto-save: 2026-07-13T00:51:43 (2 files) — visual-search/__pycache__/search_service.cpython-312.pyc visual-search/search_service.py
  34. a0e586b 2026-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
  35. 2890b8f 2026-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
  36. ca63a1e 2026-07-12 auto-save: 2026-07-12T23:21:12 (1 files) — visual-search/.gitignore
  37. c913d7d 2026-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
  38. 7c5ada1 2026-07-09 auto-save: 2026-07-09T22:12:24 (1 files) — package-lock.json
  39. bb7bf3e 2026-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)
  40. 6aa4a18 2026-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/
  41. 30757b5 2026-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
  42. d52d765 2026-07-09 auto-save: 2026-07-09T16:11:12 (2 files) — schu-candidates.tsv schu-publish.mjs
  43. 8ef6e5f 2026-07-09 auto-save: 2026-07-09T09:09:49 (1 files) — visual-search/__pycache__/
  44. 602bb7f 2026-07-09 PDP color-dot color-index: /apps/color-index endpoint + generator (catalog-wide 10% CIELAB tolerance)
  45. 41d871a 2026-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
  46. 0bd9187 2026-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)
  47. 036d130 2026-07-09 auto-save: 2026-07-09T08:39:40 (2 files) — public/index.html screenrecord/
  48. ab7c20f 2026-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
  49. dd49fa4 2026-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
  50. 71b8574 2026-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
  51. d5ff7da 2026-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
  52. ef6656d 2026-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
  53. 6eb9013 2026-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
  54. c473b21 2026-07-08 dwphoto: add SSRF-guarded CORS-scoped /apps/similar proxy route → loopback CLIP :9914 (shipped GATED-STEP-1)
  55. 06f5839 2026-07-08 visual-search /similar: also return storefront handle (LEFT JOIN shopify_products) so PDP cards link to real product pages
  56. 3e6d84f 2026-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
  57. 51cf188 2026-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
  58. c68081a 2026-07-08 visual-search: add POST /similar (SKU->CLIP visual neighbors, color+style soft re-rank, lazy CLIP load)
  59. a7f6374 2026-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
  60. 5cea2c8 2026-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
  61. 8837b5f 2026-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
  62. c3d8429 2026-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
  63. d4db33e 2026-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
  64. b56afa1 2026-07-08 auto-save: 2026-07-08T11:35:28 (1 files) — package-lock.json
  65. 66895a7 2026-07-08 chore: v1.2.0 → v1.3.0 (session close — feed + discontinued-successor features)
  66. 5f86fc5 2026-07-08 auto-save: 2026-07-08T11:05:21 (1 files) — data/build.json
  67. ba63786 2026-07-08 Add discontinued-SKU resolver → red card → live successor link (local only)
  68. 140c994 2026-07-08 gitignore the runtime all-dw feed cache (data/all-catalog.json)
  69. a4417e3 2026-07-08 browse whole dw_unified catalog via all-dw feed (was Fentucci-only); disk-cache + Fentucci fallbacks
  70. 62ce2a4 2026-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
  71. 999db25 2026-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
  72. 427db51 2026-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
  73. 2d627ac 2026-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)
  74. 17b5c68 2026-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
  75. 2acc59c 2026-07-07 auto-save: 2026-07-07T13:30:45 (1 files) — visual-search/train/__pycache__/finetune_aug.cpython-312.pyc
  76. e56063d 2026-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)
  77. 27b572c 2026-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)
  78. a376a2d 2026-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)
  79. de2aca3 2026-07-07 robustness_fusion: note CDN rate-limiting makes the A/B unreliable; fusion verified directly (3-view demo promotes the corroborated match)
  80. 6cdd9d9 2026-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)
  81. 724673e 2026-07-07 auto-save: 2026-07-07T13:00:27 (1 files) — visual-search/train/__pycache__/finetune_aug.cpython-312.pyc
  82. 33641ca 2026-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
  83. 54e085f 2026-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)
  84. 6a137d9 2026-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)
  85. 8694d87 2026-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)
  86. 828996f 2026-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)
  87. cf2ea4a 2026-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
  88. 7d4ccb6 2026-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)
  89. ec02bd7 2026-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)
  90. 5673cad 2026-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
  91. 1570feb 2026-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
  92. a2500f8 2026-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.
  93. 8cff6c4 2026-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
  94. b0a3ecc 2026-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)
  95. da94b6f 2026-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
  96. b87d402 2026-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)
  97. 5126f02 2026-07-06 Overnight DW CLIP fine-tune pipeline (MPS): build_manifest + resumable 256px downloader + open_clip contrastive fine-tune + overnight orchestrator
  98. 942ce16 2026-07-06 5x: 2 consecutive clean sweeps (0 app defects) — six-way + functional + headless-flow all green; 2 caught items proven harness artifacts
  99. aa3a6ce 2026-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
  100. 4a51d5b 2026-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
  101. 29237c6 2026-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
  102. c0cf2c5 2026-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)
  103. eae81b8 2026-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
  104. 2857339 2026-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)
  105. d548ec8 2026-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
  106. 3fcbec5 2026-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
  107. 264f5cc 2026-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.
  108. 496dc6c 2026-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.)
  109. 2e4814d 2026-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.
  110. 94d5ee9 2026-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.
  111. 9a59376 2026-07-06 feat: add new SKU to dw_unified + Shopify (draft) with vendor/vid dropdown
  112. ac1d6d0 2026-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.
  113. 023d1b2 2026-07-06 feat: CLIP visual search + multi-photo front/back fusion identify
  114. 873541c 2026-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.
  115. 11691f4 2026-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.
  116. 8d68c8e 2026-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.
  117. 5e67458 2026-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).
  118. 060b63d 2026-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.
  119. 7754221 2026-07-06 fix: print-sticker no longer hangs + go-live hardening
  120. 1f9cc3b 2026-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.
  121. 7f00ab4 2026-07-06 feat: sample-request sticker popup + scan cost throttle
  122. 31dbd12 2026-07-06 feat: FileMaker sample-request lookup + print-sticker handoff
  123. a985673 2026-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.
  124. 5483a67 2026-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
  125. 9277da4 2026-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.
  126. 1ffca17 2026-07-06 feat: structured field auto-detect (sku#/model#/name/color) + live green highlight + per-vendor spec learning
  127. 761b356 2026-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
  128. 9192328 2026-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)
  129. 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
  130. d3b12d1 2026-07-06 chore: Kamatera deploy artifacts — .deploy.conf (gemini engine, /healthz smoke), .env.example, DEPLOY.md runbook
  131. 75c75e3 2026-07-06 feat: pluggable vision engines (macvision|gemini|gcv), runtime-selectable + engine=all — enables Linux/Kamatera hosting
  132. 7bb6521 2026-07-06 docs: expose photocapture publicly via Cloudflare Tunnel at photo.designerwallcoverings.com (Mac-bound; CF edge TLS for camera)
  133. ea390e5 2026-07-03 chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)
  134. 70f9dc0 2026-06-26 auto-save: 2026-06-26T10:45:01 (2 files) — OVERNIGHT_LEDGER.md package-lock.json
  135. fbc4b68 2026-06-26 chore: lint clean, refactor (none needed), v1.1.0 (session close)
  136. 06f58bc 2026-06-26 chore: v1.1.0 — 🎨 Recognize Pattern feature (session close)
  137. 6407d11 2026-06-26 test: lock parseOcrRows barcode branches + overnight wind-down note (cycle 23)
  138. 25e9532 2026-06-26 test: cover nmfr + brandRe normalizers (cycle 22)
  139. 2e6d31c 2026-06-26 test: cover mfrToHouseSku resolver (cycle 21)
  140. 93cd3e4 2026-06-26 auto-save: 2026-06-26T08:43:59 (1 files) — data/build.json
  141. eb1e546 2026-06-26 test: reject O/0 OCR fallback (real collisions) + lock verbatim-code contract (cycle 20)
  142. e3483ef 2026-06-26 observability: add /api/stats; backlog O/0 OCR fallback with evidence (cycle 19)
  143. c3b1b21 2026-06-26 auto-save: 2026-06-26T08:13:39 (2 files) — data/build.json data/vendor_profiles.json
  144. 4d8fe6d 2026-06-26 robustness: atomic saveJSON so a crash/ENOSPC can't corrupt persisted data (cycle 18)
  145. d9df0cb 2026-06-26 test: lock candidate-order contract scanResolve relies on (cycle 17 audit)
  146. 5980db1 2026-06-26 auto-save: 2026-06-26T07:13:19 (2 files) — data/build.json data/vendor_profiles.json
  147. 830b25f 2026-06-26 fuzzy: ambiguity guard so a partial read can't bind the wrong vendor (cycle 16)
  148. f35ef39 2026-06-26 security: fix /api/learn prototype-pollution DoS (cycle 15)
  149. bf76d61 2026-06-26 auto-save: 2026-06-26T06:43:07 (1 files) — data/build.json
  150. a3b00cc 2026-06-26 harden: prefix support gate so one bad scan can't corrupt learned prefixes (cycle 14)
  151. 3eaa90d 2026-06-26 auto-save: 2026-06-26T06:12:56 (1 files) — data/build.json
  152. ad48b18 2026-06-26 Recognize Pattern: strip -SAMPLE suffix from result SKUs (2,900 sample-variant rows showed sample SKU on card)
  153. f050ab3 2026-06-26 test: fuzzyVendor edge guards + disk-full debug note (cycle 13)
  154. 0801a44 2026-06-26 test: fingerprintVendor edge cases + reject OCR-confidence-threshold (cycle 12)
  155. 8f32cd4 2026-06-26 auto-save: 2026-06-26T05:12:44 (1 files) — data/build.json
  156. bfd8249 2026-06-26 scan UX: tentative 'hold steady' cue + reject Swift low-light pre-pass (cycle 11)
  157. 367d125 2026-06-26 auto-save: 2026-06-26T02:08:48 (2 files) — data/build.json data/vendor_profiles.json
  158. 2c6e052 2026-06-26 test: harden analyzeOcr against adversarial OCR inputs (cycle 10, brainstorm)
  159. f6b8dec 2026-06-26 review: fix 3 findings from session code-review (cycle 9)
  160. a412dce 2026-06-26 auto-save: 2026-06-26T01:38:39 (1 files) — data/build.json
  161. 64ab508 2026-06-26 scan: background logo-ID when a code locks but OCR names no vendor (cycle 8)
  162. d76cc5a 2026-06-26 identify: per-request model toggle (moondream fast path) + API.md (cycle 7)
  163. 934a758 2026-06-26 auto-save: 2026-06-26T00:38:23 (2 files) — data/build.json data/recents.json
  164. bf7279e 2026-06-26 test: analyzeOcr/vendor unit tests (overnight cycle 6)
  165. 6264e28 2026-06-26 Recognize Pattern refactor: dedupe SQL term-match, single-trigger visual rerank, shared image-pipe helper, reuse one fingerprint canvas
  166. 94dce12 2026-06-25 ledger: switch overnight loop to NEVER-STOP idea-adding mode (Steve)
  167. 2a4ae14 2026-06-25 identify: logo-fingerprint tiebreaker (DTD 3/3 verdict A)
  168. edd68a9 2026-06-25 Add 🎨 Recognize Pattern: VLM reads a swatch, finds this + visually-similar items across the whole unified DB
  169. 39a065c 2026-06-25 learn: surface logo fingerprint + flag rejected logos (overnight cycle 4)
  170. dfefc4a 2026-06-25 Scan UX: distinct BARCODE lock label (overnight cycle 3)
  171. b10f11c 2026-06-25 Overnight cycle 1-2: selfcheck pass + draft gated weekly vendor-refresh launchd
  172. 3c0bcb1 2026-06-25 Logos: tighten matcher + validate fingerprints (DTD-A verdict)
  173. 33731b6 2026-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
  174. 85dccb3 2026-06-25 Harvest vendor logos from Shopify Files → ground logo recognition
  175. d30783b 2026-06-25 Scan: recognize barcodes (exact SKU) + logos/typeface (local VLM)
  176. 8fae114 2026-06-25 auto-save: 2026-06-25T23:07:50 (4 files) — bin/ocr bin/ocr.swift data/build.json server.js
  177. 5ebe4e6 2026-06-25 Learn how each vendor labels their samples (180 vendor profiles)
  178. 47a5ecf 2026-06-25 Ask-the-SKU: voice Q&A about a product via local Ollama ($0)
  179. fc5196a 2026-06-25 auto-save: 2026-06-25T18:36:37 (1 files) — data/recents.json
  180. 3948d6a 2026-06-25 Voice search: mic button → speak vendor/SKU/what-to-look-for → search
  181. 610fc30 2026-06-25 Scan audio: outcome-based — yoo-hoo on resolve, 'uh-oh' on no-match
  182. a045b5f 2026-06-25 Scan: brand-aware fast-lock + RED locked-in state
  183. 89ca632 2026-06-25 Scan: audible 'yoo-hoo' cue on code lock-in (v41)
  184. b974240 2026-06-25 Auto-refresh mfr# index: hourly incremental (8-18) + nightly full rebuild
  185. dcb3fda 2026-06-25 auto-save: 2026-06-25T17:36:07 (2 files) — build_mfr_index.py refresh_mfr_index.py
  186. 47fbc5a 2026-06-25 Add manufacturer-SKU resolver: scanned vendor codes resolve to DW house SKUs
  187. 8ddb73e 2026-06-25 Scan: code numbers ONLY — drop name/prose candidates
  188. 3046f62 2026-06-25 Scan ranks by font size first (largest letters), WD/Winfield Thybony prefix
  189. e48f07d 2026-06-25 auto-save: 2026-06-25T16:05:06 (2 files) — data/build.json photo-app-qr.png
  190. 9a72bcb 2026-06-25 Add dev-only ESLint static gate + remove dead tcase helper
  191. 4ba1fb1 2026-06-25 Fix front-camera scan: mirror-retry (ocrSmart) recovers backwards-text reads on iOS selfie cam; wired into live + snap scan
  192. 3355c70 2026-06-25 Camera chooser: back/front toggle (header + live-scan flip), persisted, drives scanner + all capture inputs; fix getLS->LS init crash
  193. f482ee7 2026-06-25 Multi-pass OCR scan: try every candidate (DW SKU + mfr# + name) across TWIL then all-Shopify
  194. 7a11139 2026-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
  195. 3832493 2026-06-25 Crop tool: add aspect-ratio presets (Free / 1:1 / 4:3 / 3:4 / 16:9) — ratio-locked, anchor-based corner resize
  196. f97337a 2026-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
  197. a2e7b32 2026-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)
  198. ae60fb4 2026-06-25 auto-save: 2026-06-25T09:33:15 (1 files) — server.js
  199. ae3352b 2026-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
  200. 5047a83 2026-06-25 App starts collapsed (clean landing until filter/search/scan) + re-crawl spreadsheet -> 203 GRS items (DWNAT->GRS col-W backfill)
  201. 54495aa 2026-06-25 auto-save: 2026-06-25T09:03:03 (1 files) — data/build.json
  202. b34052e 2026-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
  203. f61b633 2026-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.
  204. 8ca15e9 2026-06-25 auto-save: 2026-06-25T08:32:54 (1 files) — server.js
  205. 8cdab9b 2026-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
  206. f90170b 2026-06-25 Make PWA icon + manifest public (no auth) so iOS Add-to-Home-Screen fetches them; app routes stay gated
  207. 989aa83 2026-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
  208. 4e6ec3f 2026-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
  209. a4af440 2026-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)
  210. 3c7d0a2 2026-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)
  211. 3cbfbc0 2026-06-24 All-Shopify search covers collections too: parallel product + collection-products query, merged/deduped, across every vendor; results carry their collections
  212. 418e972 2026-06-24 Add collections to search: each product carries its Shopify collection titles; lookup matches collection name + cards show 📚 collection chips
  213. 087b386 2026-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
  214. 62e3b37 2026-06-24 auto-save: 2026-06-24T18:28:52 (1 files) — data/queue.json
  215. 6a655b4 2026-06-24 Add end-to-end test for All-Shopify search + keep/replace photo paths (11/11 pass; throwaway draft, prod-clean teardown)
  216. 7e0a77d 2026-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)
  217. 06a420e 2026-06-24 auto-save: 2026-06-24T17:58:43 (3 files) — data/build.json public/index.html test-allshopify.js
  218. 708fac7 2026-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
  219. 05042e0 2026-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)
  220. b4532b2 2026-06-24 Add 'All Shopify' button: live store-wide SKU search to update any product's photo from phone (non-destructive — keeps existing images)
  221. a2403b9 2026-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)
  222. 89c5686 2026-06-24 Fix grid density slider + editor preview-equals-export bake
  223. daf466a 2026-06-24 auto-save: 2026-06-24T16:58:22 (3 files) — public/index.html server.js data/build.json
  224. 571e941 2026-06-24 auto-save: 2026-06-24T16:28:13 (4 files) — data/queue.json ref-roll.jpg screenshot-roll.png screenshot-sheet.png
  225. 444a683 2026-06-24 Send Cache-Control: no-store on HTML so browsers never serve a stale page (was hiding the photo editor after deploy)
  226. 4b82a74 2026-06-24 Roll: add Curl intensity slider + cycle Flat→Tube→Sheet (curled-sheet variant reveals paper's cream back, like the PR roll shot)
  227. b6eb162 2026-06-24 Add 🗞 Roll button: wrap captured pattern around a tube (cylinder shading + sheen + shadow) like the PR roll shot
  228. 9e8d499 2026-06-24 auto-save: 2026-06-24T15:58:04 (2 files) — data/queue.json screenshot-editor.png
  229. dcdf1c3 2026-06-24 Add Photoshop-style photo editor (brightness/contrast/saturation/hue + color-balance wheel) with review-before-upload step
  230. ea8d5f1 2026-06-24 auto-save: 2026-06-24T15:27:54 (2 files) — public/index.html server.js
  231. 2b07e49 2026-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

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

Commits with substantial prose (≥120 chars) — the rationale behind each move.

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

155 files tracked. Click any to browse the source at HEAD.

Other build journals

← Dw Pairs Well  ·  all 4 projects  ·  Dw Pitch Followup →

Export

commits.csv · feed.atom · project.json · commits.json

rendered in 1ms