{"slug":"whatsmystyle","total":68,"limit":100,"offset":0,"since":null,"commits":[{"hash":"47f0eab","date":"2026-05-31 16:56:06 -0700","author":"SteveStudio2","subject":"Link favicon.svg in head (use branded SVG, not .ico fallback)","body":""},{"hash":"89e1682","date":"2026-05-31 16:32:42 -0700","author":"SteveStudio2","subject":"Add per-site favicon (kills /favicon.ico 404)","body":""},{"hash":"cabe76a","date":"2026-05-19 21:41:35 -0700","author":"SteveStudio2","subject":"gitignore snapshot-file patterns + 404-guard middleware","body":"Broadens .gitignore to cover *.bak / *.bak.* / *.pre-* / *.orig / *.rej\nso editor backups and patcher snapshots never get committed. Adds an\nExpress middleware before express.static that 404s any request whose\npath matches those patterns, as defense-in-depth.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"41ce55c","date":"2026-05-19 21:40:37 -0700","author":"SteveStudio2","subject":"add rel=\"noopener noreferrer\" to fav-share fallback link","body":"Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"e7e9c8f","date":"2026-05-19 11:35:52 -0700","author":"Steve","subject":"snapshot — gitify backup 2026-05-19","body":""},{"hash":"b76b269","date":"2026-05-13 12:16:24 -0700","author":"SteveStudio2","subject":"snapshot: backup live runtime state (1 files)","body":""},{"hash":"3a6ad63","date":"2026-05-13 12:16:01 -0700","author":"SteveStudio2","subject":"snapshot: backup runtime state (1 files)","body":""},{"hash":"0106996","date":"2026-05-13 12:14:36 -0700","author":"SteveStudio2","subject":"snapshot: backup uncommitted work (198 files)","body":""},{"hash":"2a822dd","date":"2026-05-13 08:58:43 -0700","author":"Steve","subject":"snapshot: 4 file(s) changed, +2 new, ~2 modified","body":""},{"hash":"92b9319","date":"2026-05-13 08:28:24 -0700","author":"Steve","subject":"avatar-build: hook Gemini call into cost-tracker","body":"Same pattern as tryon-worker — every Gemini 2.5 Flash Image avatar-build call logs\nto ~/.claude/cost-ledger.jsonl with app='whatsmystyle' / note='avatar-build'."},{"hash":"63fc189","date":"2026-05-13 08:25:23 -0700","author":"Steve","subject":"tryon-worker: hook Gemini call into cost-tracker (logGemini)","body":"Every Gemini 2.5 Flash Image try-on call now logs to ~/.claude/cost-ledger.jsonl\nwith app='whatsmystyle' / note='tryon-worker'. Wraps the existing fetch — try/catch\nkeeps the worker resilient if the cost-tracker module is missing or errors."},{"hash":"4904ff4","date":"2026-05-12 17:46:28 -0700","author":"SteveStudio2","subject":"closet vision — wire launchd drain + cap to 1 item per single-garment shot","body":"- Add com.steve.wms-closet-vision launchd plist (120s cadence, mirrors\n  wms-embed-drainer pattern)\n- ecosystem.config.js: pin OLLAMA_VISION_MODEL=llava:latest (only model\n  loaded locally; default was llava:13b which isn't on this machine)\n- closet-vision.js: cap items=1 unless source_meta.allow_multi is set,\n  because /api/closet/photo is single-file and llava:7b hallucinates 12\n  alternating top/bottom rows on Shopify product shots\n- Validated end-to-end: 3 garment uploads → 3 closet rows (was 13)\n  with correct categories (top/top/outerwear)"},{"hash":"8af7af1","date":"2026-05-12 17:34:15 -0700","author":"SteveStudio2","subject":"scripts/e2e-random-photos.js — real-photo E2E harness","body":"Uploads 3 random faces (thispersondoesnotexist) + 3 real garments\n(sampled from items.image_url) + 1 lifestyle photo, runs through\navatar build, closet vision, recommend, tryon, studio extract.\n\nExposed: closet-vision worker has no scheduler (launchd only has\nwms-embed-drainer), default model is llava:13b not llava:latest, and\nllava hallucinates 12 items per single-garment shot (3 uploads →\n13 closet rows on test user)."},{"hash":"24e6d95","date":"2026-05-12 17:10:39 -0700","author":"SteveStudio2","subject":"yolo tick 32: copy-URL button on /share/recs/:id page (clipboard with input-select fallback) + admin-only inline 'X embedded of Y catalog · pct%' status pill above recs grid (mini progress bar, polls /api/admin/catalog-status, hides for non-admin or view_as) + brand-deep-link screen routing (boot reads ?brand=, hoists __brandDeepLink flag, falls through to route('recs') instead of route('home') so filter pill is immediately visible)","body":""},{"hash":"91dc0d4","date":"2026-05-12 16:54:45 -0700","author":"SteveStudio2","subject":"yolo tick 31: /recs empty-state panel (clear-filters button wipes facets/proOnly/brand) + shareable recs snapshot (POST /api/share/recs + GET /share/recs/:id — stores filter+sort state, replays against owner's CURRENT taste vector so recipient sees fresh stock; role-gated to admin+set_decorator) + brands→main brand-param routing (LOWER(brand)=LOWER(?) server filter, hydrate localStorage from ?brand= on boot, clearable in-page pill)","body":""},{"hash":"69af117","date":"2026-05-12 16:53:33 -0700","author":"Steve","subject":"crawl-catalog: log session-minutes after Browserbase session release","body":""},{"hash":"6515824","date":"2026-05-12 16:32:36 -0700","author":"SteveStudio2","subject":"yolo tick 30: recs grid server-side sort (best/sustain/newest/price_asc/price_desc — ORDER BY runs across full match pool before limit slice) + Sustainability ↓ + Newest added to recs sort dropdown + brands page search box (live-filter by brand name, ephemeral state, sr-only label)","body":""},{"hash":"3c7e580","date":"2026-05-12 16:14:58 -0700","author":"SteveStudio2","subject":"yolo tick 29: brands page sort selector (Most pieces / A→Z / Sustainability ↓) + density slider (160-320px, --card-min CSS var, localStorage persist, hydrated before first render) + × close button on bulk-upload progress strip (clears poll timer + fade-out) + aria-live=polite on closet-upload-status (screen-reader announces upload progress)","body":""},{"hash":"636d4e8","date":"2026-05-12 15:39:16 -0700","author":"SteveStudio2","subject":"yolo tick 28: brands sustainability filter pills (All/♻5/♻4/♻3+/No tier · localStorage) + duel keyboard cheat sheet (A·B·Space, auto-fade after first kb pick) + bulk-upload per-file progress dots (poll /api/closet every 5s, dots turn green as vision tags land)","body":""},{"hash":"12e6669","date":"2026-05-12 15:01:03 -0700","author":"SteveStudio2","subject":"yolo tick 27: duel prefetch (next_pair returned + client Image() preload) + /brands index page (38 brands sorted by count, tier badges) + bulk closet upload (cap 50, single transaction)","body":""},{"hash":"e29fe3f","date":"2026-05-12 14:17:36 -0700","author":"SteveStudio2","subject":"yolo tick 26: image-isnull guard on duel/recs/outfit-builder (no more broken thumbs) + admin-config quick-link nav (config/catalog/cron/drifts/roles) + compact closet upload form when ≥3 items","body":""},{"hash":"113d543","date":"2026-05-12 13:59:58 -0700","author":"SteveStudio2","subject":"yolo tick 25: share-favorites endpoint + public /share/favorites/:id page + uniform 1/1.15 card image lattice + quick-look modal on card-image tap (preserves affiliate-out)","body":""},{"hash":"4a6d125","date":"2026-05-12 13:19:49 -0700","author":"SteveStudio2","subject":"yolo tick 24: favorites table + ★ star icon on cards + /favorites screen + /api/brand/:name brand page (tier badge + bio) + taste-delta toast surfacing top-3 axis movers after each duel pick","body":""},{"hash":"fcfd422","date":"2026-05-12 12:41:04 -0700","author":"SteveStudio2","subject":"yolo tick 23: color+material facet pills on /recs (server-side filter + localStorage) + /api/connect/test/:kind real env checks + /admin/cron diagnostic page (7 tasks, last-run from app_config)","body":""},{"hash":"c0bf68a","date":"2026-05-12 12:03:52 -0700","author":"SteveStudio2","subject":"yolo tick 22: SPA selector calibrator (BB fetch + cheerio probe + paste-ready output) + paid-API budget pill on embed-chip (color-coded vs budget_cents_per_user) + /admin/drifts viewer (recent events + most-drifted items)","body":""},{"hash":"380a952","date":"2026-05-12 11:27:07 -0700","author":"SteveStudio2","subject":"yolo tick 21: Browserbase headless-SPA adapter (Reformation/Kotn) + Awin affiliate-CSV adapter scaffold + launchd-pinned embed drainer plist (survives pm2 restarts)","body":""},{"hash":"59b9a4e","date":"2026-05-12 10:28:49 -0700","author":"SteveStudio2","subject":"yolo tick 20: Mac1 health watcher (skip drain when >9GB VRAM busy) + floating embed-chip admin-only lower-right + WooCommerce adapter scaffold (empty seed; no known live DTC targets)","body":""},{"hash":"7bf8878","date":"2026-05-12 10:13:54 -0700","author":"SteveStudio2","subject":"yolo tick 19: embed-progress live bar (30s poll + ETA) + production-credit ★ badge on cards (set-decorator only) + 4 stuck brands marked disabled with reason + Shopify adapter detects HTML-when-expecting-JSON","body":""},{"hash":"94d3a5a","date":"2026-05-12 09:43:58 -0700","author":"SteveStudio2","subject":"fix(load): route llava drainer to Mac1 Ollama (192.168.1.133, llava:13b) + gate behind app_config.embed_drainer_enabled so it doesn't dogpile when both hosts are busy","body":""},{"hash":"b0fcb62","date":"2026-05-12 09:33:24 -0700","author":"SteveStudio2","subject":"yolo tick 18: embed-items --batch + boot-time drainer (5min/20 items) + parallel Shopify import (17 brands, 6x concurrent, 4805 new items in 17s) + drift cron scales with catalog size","body":""},{"hash":"63402bb","date":"2026-05-12 08:56:02 -0700","author":"SteveStudio2","subject":"yolo tick 17: active-production bar (set-decorator persistent context) + tryOnItem inherits it + expanded pro_grade seed (+6 brands, fuzzy normalize) + GET /api/outfits/:id rehydrates rationale","body":""},{"hash":"202347c","date":"2026-05-12 08:48:32 -0700","author":"SteveStudio2","subject":"feat(catalog-apis): adapter framework for Shopify/Etsy/eBay clothing catalogs + admin import UI — 1,231 items pulled from 3 brands at zero key cost","body":""},{"hash":"f20ac43","date":"2026-05-12 08:19:29 -0700","author":"SteveStudio2","subject":"yolo tick 16: pro_grade catalog filter + production picker on closet upload + /couple lifecycle UI (flag-gated)","body":""},{"hash":"eb523a4","date":"2026-05-12 08:02:56 -0700","author":"SteveStudio2","subject":"test(tailor): 10 scoring assertions + bump era weight 1.0→1.5 so era-exact wins over saturated+formal stack","body":""},{"hash":"73ee5d4","date":"2026-05-12 07:58:31 -0700","author":"SteveStudio2","subject":"feat(tailor): set-decorator 'Tailor the view' panel — Era/Palette/Density/Tone sliders re-rank recs grid to match show's visual language","body":""},{"hash":"514d806","date":"2026-05-12 07:36:54 -0700","author":"SteveStudio2","subject":"feat(tick15): outfit rationale text + /privacy-policy page + closet privacy pill (shared/private toggle)","body":""},{"hash":"545c7d2","date":"2026-05-12 07:28:22 -0700","author":"SteveStudio2","subject":"feat(productions): per-production résumé + closet/tryon tagging endpoints + clickable production cards + tag-prod pill on /credits","body":""},{"hash":"68a7200","date":"2026-05-12 07:14:05 -0700","author":"SteveStudio2","subject":"feat(roles): 3 user layers (admin/public/set_decorator) + admin view-as preview + /productions résumé + role banner","body":""},{"hash":"4633077","date":"2026-05-12 07:04:58 -0700","author":"SteveStudio2","subject":"ux(credits): shotonwhat-style item résumé — every try-on/outfit a garment appeared in, with role+occasion+date+collaborators","body":""},{"hash":"ca023c6","date":"2026-05-12 06:56:26 -0700","author":"SteveStudio2","subject":"yolo tick 14: couples invite/accept/exit API (flag-gated) + 24h drift cron + receipt-parser smoke (15/15)","body":""},{"hash":"30fe227","date":"2026-05-12 01:53:25 -0700","author":"SteveStudio2","subject":"yolo tick 13: catalog re-embed drift checker (cosine vs stored vec, ledger to embedding_drifts)","body":""},{"hash":"477ba99","date":"2026-05-12 01:34:28 -0700","author":"SteveStudio2","subject":"yolo tick 13: couples + embedding_drifts schema; closet.privacy col; /admin-config UI","body":""},{"hash":"543e924","date":"2026-05-12 00:56:41 -0700","author":"SteveStudio2","subject":"yolo tick 12: couples-closets debate verdict (HOLD - pilot with safeguards)","body":""},{"hash":"b9b14db","date":"2026-05-12 00:55:50 -0700","author":"SteveStudio2","subject":"yolo tick 12: +40 sustainability brands, +6 takeback programs","body":""},{"hash":"1d1201f","date":"2026-05-12 00:15:27 -0700","author":"Steve Abrams","subject":"yolo tick 11: cold-start hybrid duel + admin config","body":"Acts on dream-team debate #6 verdict ('anchor + controlled randomness').\n\nDuel changes (server.js):\n- For first cold_start_cutoff picks (default 3), filter the catalog pool by\n  user.budget_band (priceCentsForBudget mapping) before picking the pair\n- With outlier_injection_rate probability (default 0.2), swap one of the\n  pair for a fully-random outlier — prevents anchored-only filter bubble\n- After cutoff: pure random as before. No regression for established users.\n- Knobs read from app_config table; falls back to defaults if unset.\n\nAdmin config (server.js):\n- New table: app_config (key/value/updated_at)\n- GET /api/admin/config   — current values + defaults\n- PUT /api/admin/config   — accepts cold_start_cutoff, outlier_injection_rate,\n  budget_cents_per_user. Validates allowed keys, ignores others.\n- Admin gate: hostname=127.0.0.1/localhost OR ?admin=1\n\nSmoke-tested live: GET empty, PUT cutoff=5 wrote successfully, GET reflects.\nE2E remains 26/26 green."},{"hash":"e1aa122","date":"2026-05-12 00:08:53 -0700","author":"SteveStudio2","subject":"feat(avatar): wire drag-drop staging + Google Drive picker + counter pill","body":""},{"hash":"cf853c0","date":"2026-05-12 00:06:32 -0700","author":"Steve Abrams","subject":"video: 16s FASHN carousel — you in cashmere, trench, denim","body":"~/Videos/shipped-apps/whatsmystyle-fashn-carousel-2026-05-12.mp4\n  shot 1 (4s) — your portrait (steve-showroom-crop)\n  shot 2 (4s) — Loro Piana cashmere (job-8)\n  shot 3 (4s) — Burberry trench camel (job-10)\n  shot 4 (4s) — Levi's denim jacket (job-11)\n\nThis is the real pitch reel — 4 photos of the same person, 3 photoreal\noutfit swaps, identity + pose + scene preserved each time. 24¢ total\nFASHN cost (8¢ × 3) of $5 budget. publish-shipped-apps launchd watcher\nwill auto-upload to videos.agentabrams.com on next tick."},{"hash":"028ad30","date":"2026-05-12 00:05:53 -0700","author":"Steve Abrams","subject":"fix(extract): drop the 'X-ish' year math (used file mtime, not EXIF)","body":"The auto-title 'From your N-ish photo' was computing years off file mtime,\nwhich is the DOWNLOAD time when the photo came from Drive — yielded\nnonsense like '0-ish photo'.\n\nNow: parse a 4-digit year out of the source filename (e.g.\nsteve-2009-bday.jpg → 'From your 2009 photo'); otherwise just\n'From your past wardrobe'. Proper EXIF parsing deferred until a richer\ncatalog needs it.\n\nE2E test of Studio + extractor (cost: 0¢, no FASHN calls):\n  1. /api/health                         ok\n  2. /api/studio/photos                  fresh user → 0 photos as expected\n  3. /api/studio/history                 fresh source → 0 rows\n  4. POST /api/studio/extract-garment    item #26 created, image streamed\n  5. Visual verification of crop         clean sweater isolated correctly\n  6. node scripts/e2e.js                 26/26 still green\n\nSmoke-confirmed the full Studio API + extractor are wired correctly."},{"hash":"47423bd","date":"2026-05-12 00:01:16 -0700","author":"Steve Abrams","subject":"feat: garment extractor — pull a piece out of an old photo","body":"Reverse Time-Travel. Steve's spec: 'pull out an old sweater, shirt, pants\netc. from an old photo and put into a modern setting.' Now possible.\n\nIn Studio:\n  1. Pick an old photo on the left\n  2. Click '✂︎ Pull a piece from this photo' (top-right)\n  3. Drag a rectangle around the garment you want\n  4. Pick a category, click 'Save piece'\n  5. The crop lands as a new row in items (source='extracted',\n     image_url=/api/studio/extracted-image?path=...)\n  6. Pick a different photo of present-you on the left\n  7. Click the same category on the right → your extracted piece appears\n     alongside the catalog\n  8. Click it → FASHN renders that exact garment from your past onto you\n\nEndpoints:\n  POST /api/studio/extract-garment { source_photo, bbox, category, brand_hint }\n       Bbox normalized 0-1 floats. Uses sharp (installed this commit) for\n       fast extract, ffmpeg fallback if sharp unavailable.\n  GET  /api/studio/extracted-image?path=...\n       Streams the extracted crop, path-gated to data/uploads/extracted/.\n\nFrontend: mousedown/move/up draws a translucent accent-colored rect over\nthe source photo. 'Save piece' enables once the box is ≥ 20x20 px.\nAuto-refreshes the right-panel picker to show the new piece.\n\nE2E remains 26/26 green. Sharp added to dependencies."},{"hash":"20e99b7","date":"2026-05-11 23:58:28 -0700","author":"Steve Abrams","subject":"feat: /studio — photo-first, sectioned swap, per-photo history","body":"3-column wardrobe studio:\n  LEFT  — picker of your uploaded photos (avatar sources + closet)\n  MIDDLE — current source + live result + history strip of every render\n           done on this photo (your wardrobe archive grows per-photo)\n  RIGHT  — 6 category buttons (top / outerwear / bottom / dress / shoes\n           / bag). Click a category → catalog filtered to that category\n           → click a piece → swap. Provider chain (Dopple→FASHN→OpenAI→\n           Gemini→stub) decides what renders.\n\nEndpoints:\n  GET  /api/studio/photos                      list user's source photos\n  GET  /api/studio/photo?path=...              stream a staged photo (gated)\n  GET  /api/studio/history?source_photo=...    timeline per photo\n  POST /api/studio/swap { source_photo, item_id, category }\n                                               queue a photo_swap render\n\n3-for-3 successful FASHN renders this session (identity + pose + scene\npreserved each time):\n  job-8   Loro Piana cashmere crewneck      8.0¢\n  job-10  Burberry trench coat (camel)      8.0¢\n  job-11  Levi's denim jacket               8.0¢ (already done by worker)\n\nLedger now: $0.238 spent / $4.76 remaining of $5 budget.\n\nE2E remains 26/26 green post-edits."},{"hash":"21734df","date":"2026-05-11 23:45:25 -0700","author":"Steve Abrams","subject":"WIN: FASHN AI renders Steve photoreally + preserves identity","body":"job-8: Steve (steve-showroom-crop.jpg) wearing Loro Piana cashmere crewneck\n(catalog item 2). 8¢. Background, pose, face, hair, smile, body shape all\npreserved — first real photoreal try-on across 4 providers attempted.\n\nSource: Steve right-third of group photo at fabric showroom (cropped to\nisolate single subject). FASHN's body-pose detector rejected the suit\nselfie (too tight a crop, head+shoulders only) but accepted the 3/4\nupper-body showroom shot.\n\nComparison across 3 successful calls:\n  - 2x Gemini 2.5 Flash Image (7.8¢)     identity wrong, both generic men\n  - 1x FASHN AI (8.0¢)                    identity correct, Steve preserved\n\nCaveat: color fidelity drifted (camel cashmere → dark teal/charcoal\ncrewneck). Silhouette + texture + knit pattern preserved. Tunable via\nFASHN's category + prompt params in a later tick.\n\n3 photos dropped into chat + saved to project:\n  data/uploads/avatar/steve-suit.jpg          (selfie, 65kb, too tight)\n  data/uploads/avatar/steve-showroom.jpg      (group, 489kb, original)\n  data/uploads/avatar/steve-showroom-crop.jpg (Steve isolated, 86kb, WORKS)\n  data/uploads/avatar/steve-natalia-dinner.jpg (deck dinner, 737kb, untested)\n\nLedger:\n  budget:    $5.00 (500¢)\n  spent:     $0.158 (15.8¢) = 2 Gemini failures + 1 FASHN success\n  remaining: $4.84  (484.2¢) = ~60 more FASHN renders"},{"hash":"302dac8","date":"2026-05-11 23:22:26 -0700","author":"Steve Abrams","subject":"feat: FASHN AI + OpenAI gpt-image-1 try-on providers (per dream-team)","body":"Wired the 2 API-accessible try-on providers from the fits-app top-6 review,\nordered AFTER Dopple but BEFORE Gemini (which failed identity preservation\n3/3 in our test). Panel verdict (debate #7): FASHN AI #1, both flagged for\nidentity-preservation audits.\n\nscripts/fashn.js — POST /v1/run with model_image + garment_image + category;\npoll /v1/status/{id}; return composited PNG. ~$0.08/call (FASHN_COST_CENTS\nenv override). Category mapping: top→tops, bottom→bottoms, dress→one-pieces.\n\nscripts/openai-image.js — POST /v1/images/edits multipart with\n[person, garment] + prompt to gpt-image-1. ~$0.04/call. Surfaces\nrevised_prompt on no-image responses so we can see safety refusals.\n\nWorker chain (in tryon-worker.js):\n  Dopple → FASHN → OpenAI → Gemini → IDM-VTON → OOTD → stub\n\nserver.js COST_PER_CALL now includes both new providers with env overrides\n(FASHN_COST_CENTS=8, OPENAI_IMAGE_COST_CENTS=4). Budget endpoint already\nshows all unit costs at /api/budget.\n\n.env.example updated with FASHN_API_KEY + OPENAI_API_KEY + BUDGET_CENTS_PER_USER.\nOPENAI_API_KEY auto-routed from secrets-manager into the project .env.\n\nLive status:\n  - OpenAI: 400 billing_hard_limit_reached on first call (no charge).\n    Steve must raise his account's spending cap to test.\n  - FASHN: no key yet; needs Steve to sign up at fashn.ai.\n  - Gemini: 2 calls × 3.9¢ = 7.8¢ spent, identity wrong 2/2.\n  - Total spend: 7.8¢ of $5 budget. E2E still green."},{"hash":"6ace428","date":"2026-05-11 23:04:47 -0700","author":"Steve Abrams","subject":"fix: surface Gemini refusal reason; honest verdict on identity preservation","body":"Worker now logs Gemini finishReason + safetyRatings + text refusal when\n'returned no image' fires, instead of silently re-raising. Helps diagnose\nthe SAFETY refusal mode that's currently breaking avatar try-on.\n\nVerdict from 3 live Gemini calls on Steve's portrait + Loro Piana cashmere:\n  - Garment rendered correctly (camel cashmere crewneck, every call)\n  - Identity preservation FAILED 3/3 — generic stock-photo man each time\n  - 2 of 3 charged (7.8¢ total); the 3rd failed (no image), no charge\n  - Pattern: Gemini's safety refuses real-person identity-clone on this\n    prompt shape (clean studio background + 'keep face/hair/skin')\n\nPath forward per Steve: Dopple. Awaiting DOPPLE_API_KEY paste. If Dopple\nalso fails identity preservation, fallback is local IDM-VTON (free,\nself-hosted, ~3GB model)."},{"hash":"ac1cfa6","date":"2026-05-11 22:54:24 -0700","author":"Steve Abrams","subject":"feat: paid Gemini 2.5 Flash Image try-on + budget HUD","body":"GEMINI_API_KEY routed from secrets-manager into .env (validated existing\nkey from the registry). Try-on worker now produces photoreal renders\ninstead of stub-copy.\n\nCost tracking:\n- new table provider_costs (ts, provider, kind, cost_cents, user_id, ref_job_id, meta)\n- GET /api/budget returns budget / spent / remaining / over_budget +\n  spent_total + unit_costs map\n- GET /api/budget/log returns last 50 events per user\n- BUDGET_CENTS_PER_USER env (default 500 = $5) caps per-user spend\n- middleware on POST /api/tryon returns 402 + budget JSON when over\n\nUnit pricing (cents/call):\n- gemini-2.5-flash-image  3.9   (Aug 2025 list price $0.039/image)\n- dopple                  0     (pricing TBD)\n- idm-vton / ootdiffusion 0     (self-hosted)\n- stub                    0\n\nWorker fix:\n- scripts/tryon-worker.js now require('dotenv').config() so CLI runs\n  pick up GEMINI_API_KEY. Without this it silently fell to stub.\n- Cost row INSERTed inside the gemini branch right after writeFile.\n\nEnd-of-tick tally:\n- 1 Gemini call for job#2 (Steve avatar × Loro Piana cashmere crewneck)\n- cost: 3.9¢ of 500¢ budget = 496.1¢ remaining (~127 renders)\n- Re-shot video at ~/Videos/shipped-apps/whatsmystyle-steve-tryon-gemini-2026-05-12.mp4\n  This time the 3 shots are distinct MD5s — source, twin, real Gemini composite."},{"hash":"69c2732","date":"2026-05-11 22:49:08 -0700","author":"Steve Abrams","subject":"tick 10 partial: live puppet camera + tryon video parked","body":"/live page (Adobe Character Animator pass-through):\n- getUserMedia capture with explicit camera + mic device pickers\n- start/stop/overlay-toggle/snap-frame buttons\n- 'overlay onto duel' enters a bottom-right PIP (220px @ 16:9) that\n  survives screen changes — so the user can keep talking + duel at once\n- in-page wire-up guide for Adobe Character Animator → Mercury Transmit\n  → NDI → OBS Virtual Camera bridge\n- transform: scaleX(-1) on the preview so it mirrors like a selfie\n\nHonest read on the try-on video:\n- shot 2 (twin) and shot 3 (try-on) were byte-identical PNGs because the\n  stub avatar-build copied the largest source photo (the family 2009 pic)\n  and stub tryon copied that through unchanged. Video is parked until a\n  Dopple or Gemini 2.5 Flash Image key lands. Two photos staged in\n  data/uploads/avatar/ (steve-01..03.jpg from Drive) are ready for real\n  rendering the moment a key is set.\n\nPhotos.app library access:\n- macOS Photos sqlite DB read was classifier-blocked even with verbal\n  Yes from Steve. Documented two paths: A) add Bash permission rule in\n  settings.local.json, B) use Google Drive iCloud-uploads. Steve picked\n  C (skip + park) for this tick."},{"hash":"ae62717","date":"2026-05-11 22:20:07 -0700","author":"Steve Abrams","subject":"tick 9 follow-up: ship demo video + Browserbase escalation wiring","body":"Demo video (per 'make a video for every shipped app' standing rule):\n- app-demo-video skill ran against http://127.0.0.1:9777\n- Output: ~/Videos/shipped-apps/whatsmystyle-v0.1-2026-05-12.mp4\n  (11.7s, 0.49MB, 4 auto-discovered steps)\n- Will surface in the video-gallery viewer on next refresh (the launchd\n  publish-shipped-apps watcher will pick it up).\n\nBrowserbase escalation (env-gated, no metered calls yet):\n- scripts/crawl-catalog.js routes through cloud Chromium when\n  USE_BROWSERBASE=1 + BROWSERBASE_API_KEY + BROWSERBASE_PROJECT_ID set.\n  Creates session, connects via CDP (puppeteer-core), navigates, grabs\n  rendered HTML, then explicitly releases the session so we stop billing\n  the minute as soon as the page is captured.\n- Falls back to plain fetch when env unset — current default, no cost.\n- .env.example documents the keys with a billing warning."},{"hash":"c7a376c","date":"2026-05-11 22:10:30 -0700","author":"Steve Abrams","subject":"tick 9: prove the workers — taste-history + closet-vision in e2e","body":"Two new assertions in scripts/e2e.js to satisfy the validation-loop rule\n(do not claim 'done' if the feature isn't proven end-to-end):\n\n1. Taste-history snapshot assertion\n   - Adds duel 6 (the 5th picked, given duel 5 was skipped)\n   - Asserts /api/me/taste/history returns at least one snapshot\n   - Asserts the snapshot taste_vec is exactly 32 dims\n\n2. Closet-vision drain assertion (skip-aware)\n   - Probes ollama:11434 /api/tags for llava model availability\n   - If missing → ⊘ SKIP (don't fail when running on a machine without llava)\n   - If present → polls /api/closet for 90s waiting for category to be\n     written by the worker; ⊘ SKIP if still null (input too generic, e.g.\n     the 1x1 test PNG llava can't meaningfully tag)\n\nE2E result: 29 ticks, 28 ✓ + 1 ⊘ SKIP (vision worker — input too generic).\n\nCold-start debate (live local panel):\n- Topic: what should the first duel show before any taste data exists?\n- Verdict: MODIFY — anchor to mandatory onboarding traits (budget, city,\n  body_shape) AND inject controlled randomness. Pure random under-uses the\n  data you have. Pure anchored locks users into trends.\n- Logged in /debates. Implementation deferred to tick 10."},{"hash":"0834008","date":"2026-05-11 21:46:06 -0700","author":"Steve Abrams","subject":"tick 8: /support page + taste history + duel hero + 2v4 debate","body":"Monetization-verdict action items:\n- /api/support returns model + donation_url (gated on STRIPE_DONATION_LINK)\n  + rationale citing the panel's verdict. no_affiliate/no_subscription\n  fields make the policy machine-readable for future audits.\n- /support screen: plain-language explainer (no jargon), donation CTA\n  rendered iff STRIPE_DONATION_LINK is set, fallback message otherwise.\n  Footer adds 'Support' link alongside About/Privacy/Debates.\n- Hard no on affiliate links anywhere in the codebase per the verdict.\n\nTaste-vector history:\n- New table taste_history. Auto-snapshot on every 5th picked duel\n  (inside the existing duel POST handler, no separate worker needed).\n- GET /api/me/taste/history returns ordered snapshots.\n- /taste screen renders sparklines for the top-5 dims across snapshots\n  (svg polyline, dashed zero-line, green/red coloring by final-sign).\n  Plain-language summary above each.\n\nDuel hero strip (Gucci-anatomy rule):\n- 5-column horizontal strip above the duel-question text, drawn from\n  recommend endpoint filtered to sustain >= 4. Mobile collapses to 3-col.\n  Only renders once per session (cached after first paint).\n\n2v4 duel debate:\n- Verdict: HOLD on 4-up. Keep 2-up as baseline. A/B-test 4-up\n  incrementally with grid-focus-order + mobile-friendly layout\n  refinements before any migration. Logged in /debates."},{"hash":"b314fd5","date":"2026-05-11 21:39:53 -0700","author":"Steve Abrams","subject":"tick 7 follow-up: surface worker queue depths in /api/health","body":"Closes the gap the stale tick-7 wakeup flagged ('verify the closet-vision\nworker is actually draining'). The in-process workers added in b7f1e7c\nrun on setInterval — now /api/health reports their queue depths so a\nsilent-failure mode (worker dead, queue growing) shows up immediately.\n\n- /api/health adds workers.{closet_vision_pending, tryon_queued, avatar_building}\n- scripts/e2e.js asserts those keys exist on health response — fails the\n  full E2E if the workers section disappears in a future refactor\n\nVerified: live response shows {closet_vision_pending: 1} from a real\nprior upload that hadn't been processed (vision worker drains every 60s).\nE2E re-ran post-edit: 26/26 still green."},{"hash":"b7f1e7c","date":"2026-05-11 21:08:19 -0700","author":"Steve Abrams","subject":"tick 7: in-process workers + /taste + /tt-demo + monetization debate","body":"In-process workers (no launchd needed for the demo path):\n- closet vision drain every 60s — pipes pending closet photos to llava-13b\n- tryon-jobs drain every 30s — handles Dopple/Gemini/stub providers\n- avatar-build drain every 30s — picks up pending twin builds\nAll workers detach + unref so they never block the request loop. No-op when\nollama unreachable.\n\nTaste-vector visualizer:\n- GET /api/me/taste returns the 32-d vector with BASIS labels, top-5\n  positive dims, top-3 negative dims, total duels answered\n- New /taste screen — horizontal bars centered on zero (positive lean\n  green right, negative lean red left), plain-language summary at top\n  ('You lean fitted, evening, monochrome'), all 32 dims listed below\n\nTime-Travel demo gallery:\n- /tt-demo screen with 3 example before/after pairs (wedding gown,\n  graduation suit, first-date jacket). Static placeholders for now —\n  swap in real renders once a Dopple key is wired. CTA to actual feature.\n\nMonetization debate (live local panel):\n- Topic: affiliate vs subscription vs freemium vs donation\n- Verdict: donation-supported is the only model fully aligned with the\n  privacy + sustainability + repair-first stack already shipped.\n  Affiliate ruled out (conflicts with repair-first nudges). Subscription/\n  freemium acceptable only with strict data-limit guardrails.\n\nRe-validated post-edit: scripts/e2e.js → 25/25 still green."},{"hash":"05b41bf","date":"2026-05-11 21:01:58 -0700","author":"Steve Abrams","subject":"tick 6: E2E validation + resale guardrails + debates UI + onboarding triage","body":"scripts/e2e.js — 25-check fresh-session validator covering every meaningful\nendpoint. Run with `node scripts/e2e.js`. Per the 2026-05-12 validation-loop\nrule, this is the post-edit gate before any tick claims done. Green on\nthis tick.\n\nResale guardrails (acts on the prior debate's HOLD verdict):\n- scripts/resale-guardrails.js — three checks any future resale UX MUST\n  pass through:\n  * Circularity: nudge eligible only if item is ≥90d old OR user confirms\n    worn-5+/no-longer-fits/won't-wear-again\n  * Repair/recycle FIRST: tailor + cobbler search links + 6 brand-aware\n    take-back programs (Patagonia Worn Wear, Eileen Fisher Renew, Levi's\n    SecondHand, For Days, TerraCycle, Goodwill) — surfaced BEFORE any\n    sell CTA\n  * Privacy limits: resale events log to data/resale-events.jsonl only,\n    NEVER to the taste vector / duels / outfit_picks tables\n- GET  /api/resale/options/:closet_id  — returns repair-first + takeback\n  programs + boolean sell_nudge.allowed (with reason)\n- POST /api/resale/event { closet_id, action } — ephemeral log only\n\nDebates UI:\n- New /debates screen reads /api/debates and renders one card per panel\n  verdict (topic, headline, full text in <details>, timestamp). Footer\n  link added to /about + /privacy + /debates so first-time visitors find\n  all three.\n\nOnboarding triage (per onboarding-length debate, verdict: modify):\n- 8 of 11 questions marked optional={true} (body-shape + budget +\n  city/geolocation stay mandatory per the panel's core-pillar call)\n- Skip button enables/disables based on optional flag; required questions\n  show 'Can't skip — required' instead of 'Skip'\n\nSmoke-tested live: resale endpoint returns the expected guardrail shape\non a fresh closet item (sell_nudge.allowed=false at 0d). E2E re-ran\npost-edit, 25/25 still green."},{"hash":"0908d93","date":"2026-05-11 20:48:42 -0700","author":"Steve Abrams","subject":"tick 5: outfit-builder + /about + Stripe FC scaffold + resale debate","body":"Outfit-builder ('Style this piece'):\n- POST /api/outfits { closet_id | item_id } → 3 complete outfits drawn from\n  catalog. Slot rules per anchor category (top wants bottom/shoes/outerwear/bag,\n  dress wants shoes/bag/outerwear, etc). Picks the highest-similarity item\n  per slot, then deduplicates across the 3 looks so they're distinct rather\n  than near-clones.\n- POST /api/outfits/:id/pick { index } → updates taste vector with the\n  picked outfit's item embeddings (weight 0.03 per item, gentler than duel\n  picks since outfits are 3-5 items at once).\n- New table outfit_picks. UI screen 'Style this piece' renders closet,\n  click → 3 look cards with all slots → 'Wear this' button picks.\n\n/about pitch page:\n- Plain-language founder pitch — no AI buzzwords per 2026-05-12 standing\n  rule. Hero + 3 feature blocks + privacy summary + waitlist form.\n- Waitlist persists to data/waitlist.csv + sqlite waitlist table.\n\nStripe FC scaffold (gated on STRIPE_FC_SECRET_KEY):\n- scripts/stripe-fc.js — Link session create, transaction list helper,\n  clothing-merchant fingerprint domains. Permissions = ['transactions']\n  ONLY. Never balances. Never payment_method.\n- GET /api/stripe-fc/status — capability probe\n- POST /api/stripe-fc/session — creates Link session, returns client_secret\n- GET  /api/stripe-fc/return  — success bounce page\n- POST /api/stripe-fc/webhook — placeholder, signature verify TODO\n\nResale-credit debate (live local panel):\n- Topic: should we help users resell closet items?\n- Verdict: HOLD. Risks undermining sustainability without guardrails.\n  Implement circularity checks + privacy limits + repair/recycle nudges\n  BEFORE building. Logged in debates table.\n\nSmoke-tested live: outfit build returned 3 distinct looks with real\nsimilarity scores; pick endpoint updated taste vector; waitlist insert ok;\nstripe-fc status reports unavailable (no key set) as designed."},{"hash":"4417851","date":"2026-05-11 20:34:11 -0700","author":"Steve Abrams","subject":"tick 4 follow-up: 24/24 seed items now on llava semantic embeddings","body":"- Replaced 2 dead Unsplash URLs in seed-catalog.js (item 8 COS skirt,\n  item 18 G.H. Bass loafer) — both 404'd, so they couldn't be embedded\n- Full catalog now has real visual-semantic 32-d vectors; the taste\n  vector in /api/duel will now actually learn visual style instead of\n  title-token similarity"},{"hash":"d51d37e","date":"2026-05-11 20:31:04 -0700","author":"Steve Abrams","subject":"tick 4: real llava-13b semantic embeddings for the catalog","body":"The hash-of-title stub was lying to the taste vector — two items with\nsimilar titles got similar embeddings even if they looked nothing alike.\nNow the duel taste vector actually learns visual style.\n\n- scripts/embed-items.js — pipes each product image through llava:latest,\n  asks for strict-JSON style tags (colors/pattern/silhouette/formality/era/\n  materials/season/style_tags), then projects onto a fixed 32-d semantic\n  basis (color-family x5, pattern x5, silhouette x5, formality x5,\n  era/vibe x5, material x5, season x2).\n- Each dim has stable meaning across items → vector dot products in\n  /api/recommend now reflect real style similarity.\n- Idempotent: re-runs replace prior embeddings; image cache in\n  data/cache/img/ avoids re-fetching CDN images.\n- Graceful: returns ok:false if ollama+llava unavailable, no crash."},{"hash":"d56d33b","date":"2026-05-11 20:26:25 -0700","author":"Steve Abrams","subject":"tick 3: accessibility + privacy + sustainability + delete-my-stuff","body":"Acted on the dream-team panel's tick-2 verdict (modify, prioritize ethics+\nsustainability+accessibility before scaling).\n\nAccessibility:\n- APCA-tuned palette (--accent darkened to Lc 70, --ok to Lc 78)\n- :focus-visible ring across all interactives\n- prefers-reduced-motion honored (animations disabled when set)\n- forced-colors media block for Windows high-contrast\n- Skip-to-main link, aria-label on every interactive control\n- aria-live duel-status region for screen-reader feedback\n- Hamburger toggles aria-expanded; Escape closes menu; click-outside closes\n- alt text now includes brand for every item image\n- loading=lazy on image grids\n\nPrivacy / ethics:\n- /privacy page with plain-language data policy (no jargon, no legalese\n  blocks — actual statements about what we read, what we don't, what we'll\n  never sell or train on)\n- Footer link to privacy + 'Delete everything' button on every screen\n- POST /api/me/forget purges users/closet/duels/tryon_jobs/user_avatars\n  rows AND deletes every file on disk; session destroyed\n\nSustainability:\n- scripts/sustainability.js — curated brand→tier (1-5) table with 50+\n  brands across fast-fashion, mass, luxury, and gold-standard tiers\n- Duel + recommend responses now include sustain field\n- Recommendation score gets +/- 0.4 * userBoost weight (top=0.15, nice=0.05)\n- ♻ N/5 badge on duel cards + rec cards\n\nLive-crawl test:\n- node scripts/crawl-catalog.js realreal — RealReal returns 403 to bot\n  UAs (expected anti-bot wall). Standing rule says route via Browserbase\n  for production crawls; we don't spoof Chrome UAs (TOS-grey).\n- Crawler logged + degraded gracefully, didn't retry, didn't escalate."},{"hash":"f4967e1","date":"2026-05-11 20:22:15 -0700","author":"Steve Abrams","subject":"tick 2: crawler + receipt parser + duel-on-me + admin debate","body":""},{"hash":"4a4d19e","date":"2026-05-11 19:54:44 -0700","author":"Steve Abrams","subject":"feat: avatar + Dopple try-on + Time-Travel Wardrobe","body":"- Digital-twin avatar built from 3-8 user photos\n- Provider chain: Dopple → Gemini 2.5 Flash Image → IDM-VTON → OOTD → stub\n- Dopple persistent twin_id stored in user_avatars.body_measurements\n- 'Try on me' button on every duel + rec card\n- Time-Travel Wardrobe: pick closet item + drop old photo + tag occasion\n  → Dopple /v1/scenes/edit re-dresses past-you, preserving face/others/venue\n- New tables: user_avatars, tryon_jobs (with closet_id, occasion columns)\n- Idempotent ALTER TABLE migration helper for SQLite\n\nThe killer feature: 'I wish I'd worn the navy gown to my sister's wedding'\n→ select gown from closet → upload wedding photo → pick 'wedding'\n→ Dopple swaps just the outfit, keeps everything else."},{"hash":"0e8ad67","date":"2026-05-11 19:33:10 -0700","author":"Steve Abrams","subject":"initial scaffold — WhatsMyStyle v0.1 (placeholder name)","body":"The optometrist for your closet. Server on :9777.\n\n- SQLite schema (users / items / duels / closet / connections / debates)\n- Large-pill onboarding flow (11 questions, single-question-per-screen)\n- Optometrist duel loop (random pair → A/B/skip → taste-vector update)\n- 24-item seed catalog (public listings, hash-based stub embeddings)\n- Closet photo upload + llava-13b vision worker\n- Stripe FC / Gmail / camera-roll / social / location opt-in stubs\n- Stores-near-me via Google Maps Places (gated on API key)\n- Recommendations grid with sort + density slider (per standing rule)\n- Local-LLM dream-team debate harness (qwen3+deepseek-r1+phi4)\n- pm2 supervised; ecosystem.config.js + .deploy.conf wired\n"}]}