Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
cabe76a · 2026-05-19 · gitignore snapshot-file patterns + 404-guard middleware
Broadens .gitignore to cover *.bak / *.bak.* / *.pre-* / *.orig / *.rej
so editor backups and patcher snapshots never get committed. Adds an
Express middleware before express.static that 404s any request whose
path matches those patterns, as defense-in-depth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92b9319 · 2026-05-13 · avatar-build: hook Gemini call into cost-tracker
Same pattern as tryon-worker — every Gemini 2.5 Flash Image avatar-build call logs
to ~/.claude/cost-ledger.jsonl with app='whatsmystyle' / note='avatar-build'.
63fc189 · 2026-05-13 · tryon-worker: hook Gemini call into cost-tracker (logGemini)
Every Gemini 2.5 Flash Image try-on call now logs to ~/.claude/cost-ledger.jsonl
with app='whatsmystyle' / note='tryon-worker'. Wraps the existing fetch — try/catch
keeps the worker resilient if the cost-tracker module is missing or errors.
4904ff4 · 2026-05-12 · closet vision — wire launchd drain + cap to 1 item per single-garment shot
- Add com.steve.wms-closet-vision launchd plist (120s cadence, mirrors
wms-embed-drainer pattern)
- ecosystem.config.js: pin OLLAMA_VISION_MODEL=llava:latest (only model
loaded locally; default was llava:13b which isn't on this machine)
- closet-vision.js: cap items=1 unless source_meta.allow_multi is set,
because /api/closet/photo is single-file and llava:7b hallucinates 12
alternating top/bottom rows on Shopify product shots
- Validated end-to-end: 3 garment uploads → 3 closet rows (was 13)
with correct categories (top/top/outerwear)
8af7af1 · 2026-05-12 · scripts/e2e-random-photos.js — real-photo E2E harness
Uploads 3 random faces (thispersondoesnotexist) + 3 real garments
(sampled from items.image_url) + 1 lifestyle photo, runs through
avatar build, closet vision, recommend, tryon, studio extract.
Exposed: closet-vision worker has no scheduler (launchd only has
wms-embed-drainer), default model is llava:13b not llava:latest, and
llava hallucinates 12 items per single-garment shot (3 uploads →
13 closet rows on test user).
1d1201f · 2026-05-12 · yolo tick 11: cold-start hybrid duel + admin config
Acts on dream-team debate #6 verdict ('anchor + controlled randomness').
Duel changes (server.js):
- For first cold_start_cutoff picks (default 3), filter the catalog pool by
user.budget_band (priceCentsForBudget mapping) before picking the pair
- With outlier_injection_rate probability (default 0.2), swap one of the
pair for a fully-random outlier — prevents anchored-only filter bubble
- After cutoff: pure random as before. No regression for established users.
- Knobs read from app_config table; falls back to defaults if unset.
Admin config (server.js):
- New table: app_config (key/value/updated_at)
- GET /api/admin/config — current values + defaults
- PUT /api/admin/config — accepts cold_start_cutoff, outlier_injection_rate,
budget_cents_per_user. Validates allowed keys, ignores others.
- Admin gate: hostname=127.0.0.1/localhost OR ?admin=1
Smoke-tested live: GET empty, PUT cutoff=5 wrote successfully, GET reflects.
E2E remains 26/26 green.
cf853c0 · 2026-05-12 · video: 16s FASHN carousel — you in cashmere, trench, denim
~/Videos/shipped-apps/whatsmystyle-fashn-carousel-2026-05-12.mp4
shot 1 (4s) — your portrait (steve-showroom-crop)
shot 2 (4s) — Loro Piana cashmere (job-8)
shot 3 (4s) — Burberry trench camel (job-10)
shot 4 (4s) — Levi's denim jacket (job-11)
This is the real pitch reel — 4 photos of the same person, 3 photoreal
outfit swaps, identity + pose + scene preserved each time. 24¢ total
FASHN cost (8¢ × 3) of $5 budget. publish-shipped-apps launchd watcher
will auto-upload to videos.agentabrams.com on next tick.
028ad30 · 2026-05-12 · fix(extract): drop the 'X-ish' year math (used file mtime, not EXIF)
The auto-title 'From your N-ish photo' was computing years off file mtime,
which is the DOWNLOAD time when the photo came from Drive — yielded
nonsense like '0-ish photo'.
Now: parse a 4-digit year out of the source filename (e.g.
steve-2009-bday.jpg → 'From your 2009 photo'); otherwise just
'From your past wardrobe'. Proper EXIF parsing deferred until a richer
catalog needs it.
E2E test of Studio + extractor (cost: 0¢, no FASHN calls):
1. /api/health ok
2. /api/studio/photos fresh user → 0 photos as expected
3. /api/studio/history fresh source → 0 rows
4. POST /api/studio/extract-garment item #26 created, image streamed
5. Visual verification of crop clean sweater isolated correctly
6. node scripts/e2e.js 26/26 still green
Smoke-confirmed the full Studio API + extractor are wired correctly.
47423bd · 2026-05-12 · feat: garment extractor — pull a piece out of an old photo
Reverse Time-Travel. Steve's spec: 'pull out an old sweater, shirt, pants
etc. from an old photo and put into a modern setting.' Now possible.
In Studio:
1. Pick an old photo on the left
2. Click '✂︎ Pull a piece from this photo' (top-right)
3. Drag a rectangle around the garment you want
4. Pick a category, click 'Save piece'
5. The crop lands as a new row in items (source='extracted',
image_url=/api/studio/extracted-image?path=...)
6. Pick a different photo of present-you on the left
7. Click the same category on the right → your extracted piece appears
alongside the catalog
8. Click it → FASHN renders that exact garment from your past onto you
Endpoints:
POST /api/studio/extract-garment { source_photo, bbox, category, brand_hint }
Bbox normalized 0-1 floats. Uses sharp (installed this commit) for
fast extract, ffmpeg fallback if sharp unavailable.
GET /api/studio/extracted-image?path=...
Streams the extracted crop, path-gated to data/uploads/extracted/.
Frontend: mousedown/move/up draws a translucent accent-colored rect over
the source photo. 'Save piece' enables once the box is ≥ 20x20 px.
Auto-refreshes the right-panel picker to show the new piece.
E2E remains 26/26 green. Sharp added to dependencies.
20e99b7 · 2026-05-11 · feat: /studio — photo-first, sectioned swap, per-photo history
3-column wardrobe studio:
LEFT — picker of your uploaded photos (avatar sources + closet)
MIDDLE — current source + live result + history strip of every render
done on this photo (your wardrobe archive grows per-photo)
RIGHT — 6 category buttons (top / outerwear / bottom / dress / shoes
/ bag). Click a category → catalog filtered to that category
→ click a piece → swap. Provider chain (Dopple→FASHN→OpenAI→
Gemini→stub) decides what renders.
Endpoints:
GET /api/studio/photos list user's source photos
GET /api/studio/photo?path=... stream a staged photo (gated)
GET /api/studio/history?source_photo=... timeline per photo
POST /api/studio/swap { source_photo, item_id, category }
queue a photo_swap render
3-for-3 successful FASHN renders this session (identity + pose + scene
preserved each time):
job-8 Loro Piana cashmere crewneck 8.0¢
job-10 Burberry trench coat (camel) 8.0¢
job-11 Levi's denim jacket 8.0¢ (already done by worker)
Ledger now: $0.238 spent / $4.76 remaining of $5 budget.
E2E remains 26/26 green post-edits.
21734df · 2026-05-11 · WIN: FASHN AI renders Steve photoreally + preserves identity
job-8: Steve (steve-showroom-crop.jpg) wearing Loro Piana cashmere crewneck
(catalog item 2). 8¢. Background, pose, face, hair, smile, body shape all
preserved — first real photoreal try-on across 4 providers attempted.
Source: Steve right-third of group photo at fabric showroom (cropped to
isolate single subject). FASHN's body-pose detector rejected the suit
selfie (too tight a crop, head+shoulders only) but accepted the 3/4
upper-body showroom shot.
Comparison across 3 successful calls:
- 2x Gemini 2.5 Flash Image (7.8¢) identity wrong, both generic men
- 1x FASHN AI (8.0¢) identity correct, Steve preserved
Caveat: color fidelity drifted (camel cashmere → dark teal/charcoal
crewneck). Silhouette + texture + knit pattern preserved. Tunable via
FASHN's category + prompt params in a later tick.
3 photos dropped into chat + saved to project:
data/uploads/avatar/steve-suit.jpg (selfie, 65kb, too tight)
data/uploads/avatar/steve-showroom.jpg (group, 489kb, original)
data/uploads/avatar/steve-showroom-crop.jpg (Steve isolated, 86kb, WORKS)
data/uploads/avatar/steve-natalia-dinner.jpg (deck dinner, 737kb, untested)
Ledger:
budget: $5.00 (500¢)
spent: $0.158 (15.8¢) = 2 Gemini failures + 1 FASHN success
remaining: $4.84 (484.2¢) = ~60 more FASHN renders
302dac8 · 2026-05-11 · feat: FASHN AI + OpenAI gpt-image-1 try-on providers (per dream-team)
Wired the 2 API-accessible try-on providers from the fits-app top-6 review,
ordered AFTER Dopple but BEFORE Gemini (which failed identity preservation
3/3 in our test). Panel verdict (debate #7): FASHN AI #1, both flagged for
identity-preservation audits.
scripts/fashn.js — POST /v1/run with model_image + garment_image + category;
poll /v1/status/{id}; return composited PNG. ~$0.08/call (FASHN_COST_CENTS
env override). Category mapping: top→tops, bottom→bottoms, dress→one-pieces.
scripts/openai-image.js — POST /v1/images/edits multipart with
[person, garment] + prompt to gpt-image-1. ~$0.04/call. Surfaces
revised_prompt on no-image responses so we can see safety refusals.
Worker chain (in tryon-worker.js):
Dopple → FASHN → OpenAI → Gemini → IDM-VTON → OOTD → stub
server.js COST_PER_CALL now includes both new providers with env overrides
(FASHN_COST_CENTS=8, OPENAI_IMAGE_COST_CENTS=4). Budget endpoint already
shows all unit costs at /api/budget.
.env.example updated with FASHN_API_KEY + OPENAI_API_KEY + BUDGET_CENTS_PER_USER.
OPENAI_API_KEY auto-routed from secrets-manager into the project .env.
Live status:
- OpenAI: 400 billing_hard_limit_reached on first call (no charge).
Steve must raise his account's spending cap to test.
- FASHN: no key yet; needs Steve to sign up at fashn.ai.
- Gemini: 2 calls × 3.9¢ = 7.8¢ spent, identity wrong 2/2.
- Total spend: 7.8¢ of $5 budget. E2E still green.
6ace428 · 2026-05-11 · fix: surface Gemini refusal reason; honest verdict on identity preservation
Worker now logs Gemini finishReason + safetyRatings + text refusal when
'returned no image' fires, instead of silently re-raising. Helps diagnose
the SAFETY refusal mode that's currently breaking avatar try-on.
Verdict from 3 live Gemini calls on Steve's portrait + Loro Piana cashmere:
- Garment rendered correctly (camel cashmere crewneck, every call)
- Identity preservation FAILED 3/3 — generic stock-photo man each time
- 2 of 3 charged (7.8¢ total); the 3rd failed (no image), no charge
- Pattern: Gemini's safety refuses real-person identity-clone on this
prompt shape (clean studio background + 'keep face/hair/skin')
Path forward per Steve: Dopple. Awaiting DOPPLE_API_KEY paste. If Dopple
also fails identity preservation, fallback is local IDM-VTON (free,
self-hosted, ~3GB model).
ac1cfa6 · 2026-05-11 · feat: paid Gemini 2.5 Flash Image try-on + budget HUD
GEMINI_API_KEY routed from secrets-manager into .env (validated existing
key from the registry). Try-on worker now produces photoreal renders
instead of stub-copy.
Cost tracking:
- new table provider_costs (ts, provider, kind, cost_cents, user_id, ref_job_id, meta)
- GET /api/budget returns budget / spent / remaining / over_budget +
spent_total + unit_costs map
- GET /api/budget/log returns last 50 events per user
- BUDGET_CENTS_PER_USER env (default 500 = $5) caps per-user spend
- middleware on POST /api/tryon returns 402 + budget JSON when over
Unit pricing (cents/call):
- gemini-2.5-flash-image 3.9 (Aug 2025 list price $0.039/image)
- dopple 0 (pricing TBD)
- idm-vton / ootdiffusion 0 (self-hosted)
- stub 0
Worker fix:
- scripts/tryon-worker.js now require('dotenv').config() so CLI runs
pick up GEMINI_API_KEY. Without this it silently fell to stub.
- Cost row INSERTed inside the gemini branch right after writeFile.
End-of-tick tally:
- 1 Gemini call for job#2 (Steve avatar × Loro Piana cashmere crewneck)
- cost: 3.9¢ of 500¢ budget = 496.1¢ remaining (~127 renders)
- Re-shot video at ~/Videos/shipped-apps/whatsmystyle-steve-tryon-gemini-2026-05-12.mp4
This time the 3 shots are distinct MD5s — source, twin, real Gemini composite.
69c2732 · 2026-05-11 · tick 10 partial: live puppet camera + tryon video parked
/live page (Adobe Character Animator pass-through):
- getUserMedia capture with explicit camera + mic device pickers
- start/stop/overlay-toggle/snap-frame buttons
- 'overlay onto duel' enters a bottom-right PIP (220px @ 16:9) that
survives screen changes — so the user can keep talking + duel at once
- in-page wire-up guide for Adobe Character Animator → Mercury Transmit
→ NDI → OBS Virtual Camera bridge
- transform: scaleX(-1) on the preview so it mirrors like a selfie
Honest read on the try-on video:
- shot 2 (twin) and shot 3 (try-on) were byte-identical PNGs because the
stub avatar-build copied the largest source photo (the family 2009 pic)
and stub tryon copied that through unchanged. Video is parked until a
Dopple or Gemini 2.5 Flash Image key lands. Two photos staged in
data/uploads/avatar/ (steve-01..03.jpg from Drive) are ready for real
rendering the moment a key is set.
Photos.app library access:
- macOS Photos sqlite DB read was classifier-blocked even with verbal
Yes from Steve. Documented two paths: A) add Bash permission rule in
settings.local.json, B) use Google Drive iCloud-uploads. Steve picked
C (skip + park) for this tick.
ae62717 · 2026-05-11 · tick 9 follow-up: ship demo video + Browserbase escalation wiring
Demo video (per 'make a video for every shipped app' standing rule):
- app-demo-video skill ran against http://127.0.0.1:9777
- Output: ~/Videos/shipped-apps/whatsmystyle-v0.1-2026-05-12.mp4
(11.7s, 0.49MB, 4 auto-discovered steps)
- Will surface in the video-gallery viewer on next refresh (the launchd
publish-shipped-apps watcher will pick it up).
Browserbase escalation (env-gated, no metered calls yet):
- scripts/crawl-catalog.js routes through cloud Chromium when
USE_BROWSERBASE=1 + BROWSERBASE_API_KEY + BROWSERBASE_PROJECT_ID set.
Creates session, connects via CDP (puppeteer-core), navigates, grabs
rendered HTML, then explicitly releases the session so we stop billing
the minute as soon as the page is captured.
- Falls back to plain fetch when env unset — current default, no cost.
- .env.example documents the keys with a billing warning.
c7a376c · 2026-05-11 · tick 9: prove the workers — taste-history + closet-vision in e2e
Two new assertions in scripts/e2e.js to satisfy the validation-loop rule
(do not claim 'done' if the feature isn't proven end-to-end):
1. Taste-history snapshot assertion
- Adds duel 6 (the 5th picked, given duel 5 was skipped)
- Asserts /api/me/taste/history returns at least one snapshot
- Asserts the snapshot taste_vec is exactly 32 dims
2. Closet-vision drain assertion (skip-aware)
- Probes ollama:11434 /api/tags for llava model availability
- If missing → ⊘ SKIP (don't fail when running on a machine without llava)
- If present → polls /api/closet for 90s waiting for category to be
written by the worker; ⊘ SKIP if still null (input too generic, e.g.
the 1x1 test PNG llava can't meaningfully tag)
E2E result: 29 ticks, 28 ✓ + 1 ⊘ SKIP (vision worker — input too generic).
Cold-start debate (live local panel):
- Topic: what should the first duel show before any taste data exists?
- Verdict: MODIFY — anchor to mandatory onboarding traits (budget, city,
body_shape) AND inject controlled randomness. Pure random under-uses the
data you have. Pure anchored locks users into trends.
- Logged in /debates. Implementation deferred to tick 10.
0834008 · 2026-05-11 · tick 8: /support page + taste history + duel hero + 2v4 debate
Monetization-verdict action items:
- /api/support returns model + donation_url (gated on STRIPE_DONATION_LINK)
+ rationale citing the panel's verdict. no_affiliate/no_subscription
fields make the policy machine-readable for future audits.
- /support screen: plain-language explainer (no jargon), donation CTA
rendered iff STRIPE_DONATION_LINK is set, fallback message otherwise.
Footer adds 'Support' link alongside About/Privacy/Debates.
- Hard no on affiliate links anywhere in the codebase per the verdict.
Taste-vector history:
- New table taste_history. Auto-snapshot on every 5th picked duel
(inside the existing duel POST handler, no separate worker needed).
- GET /api/me/taste/history returns ordered snapshots.
- /taste screen renders sparklines for the top-5 dims across snapshots
(svg polyline, dashed zero-line, green/red coloring by final-sign).
Plain-language summary above each.
Duel hero strip (Gucci-anatomy rule):
- 5-column horizontal strip above the duel-question text, drawn from
recommend endpoint filtered to sustain >= 4. Mobile collapses to 3-col.
Only renders once per session (cached after first paint).
2v4 duel debate:
- Verdict: HOLD on 4-up. Keep 2-up as baseline. A/B-test 4-up
incrementally with grid-focus-order + mobile-friendly layout
refinements before any migration. Logged in /debates.
b314fd5 · 2026-05-11 · tick 7 follow-up: surface worker queue depths in /api/health
Closes the gap the stale tick-7 wakeup flagged ('verify the closet-vision
worker is actually draining'). The in-process workers added in b7f1e7c
run on setInterval — now /api/health reports their queue depths so a
silent-failure mode (worker dead, queue growing) shows up immediately.
- /api/health adds workers.{closet_vision_pending, tryon_queued, avatar_building}
- scripts/e2e.js asserts those keys exist on health response — fails the
full E2E if the workers section disappears in a future refactor
Verified: live response shows {closet_vision_pending: 1} from a real
prior upload that hadn't been processed (vision worker drains every 60s).
E2E re-ran post-edit: 26/26 still green.
b7f1e7c · 2026-05-11 · tick 7: in-process workers + /taste + /tt-demo + monetization debate
In-process workers (no launchd needed for the demo path):
- closet vision drain every 60s — pipes pending closet photos to llava-13b
- tryon-jobs drain every 30s — handles Dopple/Gemini/stub providers
- avatar-build drain every 30s — picks up pending twin builds
All workers detach + unref so they never block the request loop. No-op when
ollama unreachable.
Taste-vector visualizer:
- GET /api/me/taste returns the 32-d vector with BASIS labels, top-5
positive dims, top-3 negative dims, total duels answered
- New /taste screen — horizontal bars centered on zero (positive lean
green right, negative lean red left), plain-language summary at top
('You lean fitted, evening, monochrome'), all 32 dims listed below
Time-Travel demo gallery:
- /tt-demo screen with 3 example before/after pairs (wedding gown,
graduation suit, first-date jacket). Static placeholders for now —
swap in real renders once a Dopple key is wired. CTA to actual feature.
Monetization debate (live local panel):
- Topic: affiliate vs subscription vs freemium vs donation
- Verdict: donation-supported is the only model fully aligned with the
privacy + sustainability + repair-first stack already shipped.
Affiliate ruled out (conflicts with repair-first nudges). Subscription/
freemium acceptable only with strict data-limit guardrails.
Re-validated post-edit: scripts/e2e.js → 25/25 still green.
05b41bf · 2026-05-11 · tick 6: E2E validation + resale guardrails + debates UI + onboarding triage
scripts/e2e.js — 25-check fresh-session validator covering every meaningful
endpoint. Run with `node scripts/e2e.js`. Per the 2026-05-12 validation-loop
rule, this is the post-edit gate before any tick claims done. Green on
this tick.
Resale guardrails (acts on the prior debate's HOLD verdict):
- scripts/resale-guardrails.js — three checks any future resale UX MUST
pass through:
* Circularity: nudge eligible only if item is ≥90d old OR user confirms
worn-5+/no-longer-fits/won't-wear-again
* Repair/recycle FIRST: tailor + cobbler search links + 6 brand-aware
take-back programs (Patagonia Worn Wear, Eileen Fisher Renew, Levi's
SecondHand, For Days, TerraCycle, Goodwill) — surfaced BEFORE any
sell CTA
* Privacy limits: resale events log to data/resale-events.jsonl only,
NEVER to the taste vector / duels / outfit_picks tables
- GET /api/resale/options/:closet_id — returns repair-first + takeback
programs + boolean sell_nudge.allowed (with reason)
- POST /api/resale/event { closet_id, action } — ephemeral log only
Debates UI:
- New /debates screen reads /api/debates and renders one card per panel
verdict (topic, headline, full text in <details>, timestamp). Footer
link added to /about + /privacy + /debates so first-time visitors find
all three.
Onboarding triage (per onboarding-length debate, verdict: modify):
- 8 of 11 questions marked optional={true} (body-shape + budget +
city/geolocation stay mandatory per the panel's core-pillar call)
- Skip button enables/disables based on optional flag; required questions
show 'Can't skip — required' instead of 'Skip'
Smoke-tested live: resale endpoint returns the expected guardrail shape
on a fresh closet item (sell_nudge.allowed=false at 0d). E2E re-ran
post-edit, 25/25 still green.
0908d93 · 2026-05-11 · tick 5: outfit-builder + /about + Stripe FC scaffold + resale debate
Outfit-builder ('Style this piece'):
- POST /api/outfits { closet_id | item_id } → 3 complete outfits drawn from
catalog. Slot rules per anchor category (top wants bottom/shoes/outerwear/bag,
dress wants shoes/bag/outerwear, etc). Picks the highest-similarity item
per slot, then deduplicates across the 3 looks so they're distinct rather
than near-clones.
- POST /api/outfits/:id/pick { index } → updates taste vector with the
picked outfit's item embeddings (weight 0.03 per item, gentler than duel
picks since outfits are 3-5 items at once).
- New table outfit_picks. UI screen 'Style this piece' renders closet,
click → 3 look cards with all slots → 'Wear this' button picks.
/about pitch page:
- Plain-language founder pitch — no AI buzzwords per 2026-05-12 standing
rule. Hero + 3 feature blocks + privacy summary + waitlist form.
- Waitlist persists to data/waitlist.csv + sqlite waitlist table.
Stripe FC scaffold (gated on STRIPE_FC_SECRET_KEY):
- scripts/stripe-fc.js — Link session create, transaction list helper,
clothing-merchant fingerprint domains. Permissions = ['transactions']
ONLY. Never balances. Never payment_method.
- GET /api/stripe-fc/status — capability probe
- POST /api/stripe-fc/session — creates Link session, returns client_secret
- GET /api/stripe-fc/return — success bounce page
- POST /api/stripe-fc/webhook — placeholder, signature verify TODO
Resale-credit debate (live local panel):
- Topic: should we help users resell closet items?
- Verdict: HOLD. Risks undermining sustainability without guardrails.
Implement circularity checks + privacy limits + repair/recycle nudges
BEFORE building. Logged in debates table.
Smoke-tested live: outfit build returned 3 distinct looks with real
similarity scores; pick endpoint updated taste vector; waitlist insert ok;
stripe-fc status reports unavailable (no key set) as designed.
4417851 · 2026-05-11 · tick 4 follow-up: 24/24 seed items now on llava semantic embeddings
- Replaced 2 dead Unsplash URLs in seed-catalog.js (item 8 COS skirt,
item 18 G.H. Bass loafer) — both 404'd, so they couldn't be embedded
- Full catalog now has real visual-semantic 32-d vectors; the taste
vector in /api/duel will now actually learn visual style instead of
title-token similarity
d51d37e · 2026-05-11 · tick 4: real llava-13b semantic embeddings for the catalog
The hash-of-title stub was lying to the taste vector — two items with
similar titles got similar embeddings even if they looked nothing alike.
Now the duel taste vector actually learns visual style.
- scripts/embed-items.js — pipes each product image through llava:latest,
asks for strict-JSON style tags (colors/pattern/silhouette/formality/era/
materials/season/style_tags), then projects onto a fixed 32-d semantic
basis (color-family x5, pattern x5, silhouette x5, formality x5,
era/vibe x5, material x5, season x2).
- Each dim has stable meaning across items → vector dot products in
/api/recommend now reflect real style similarity.
- Idempotent: re-runs replace prior embeddings; image cache in
data/cache/img/ avoids re-fetching CDN images.
- Graceful: returns ok:false if ollama+llava unavailable, no crash.
d56d33b · 2026-05-11 · tick 3: accessibility + privacy + sustainability + delete-my-stuff
Acted on the dream-team panel's tick-2 verdict (modify, prioritize ethics+
sustainability+accessibility before scaling).
Accessibility:
- APCA-tuned palette (--accent darkened to Lc 70, --ok to Lc 78)
- :focus-visible ring across all interactives
- prefers-reduced-motion honored (animations disabled when set)
- forced-colors media block for Windows high-contrast
- Skip-to-main link, aria-label on every interactive control
- aria-live duel-status region for screen-reader feedback
- Hamburger toggles aria-expanded; Escape closes menu; click-outside closes
- alt text now includes brand for every item image
- loading=lazy on image grids
Privacy / ethics:
- /privacy page with plain-language data policy (no jargon, no legalese
blocks — actual statements about what we read, what we don't, what we'll
never sell or train on)
- Footer link to privacy + 'Delete everything' button on every screen
- POST /api/me/forget purges users/closet/duels/tryon_jobs/user_avatars
rows AND deletes every file on disk; session destroyed
Sustainability:
- scripts/sustainability.js — curated brand→tier (1-5) table with 50+
brands across fast-fashion, mass, luxury, and gold-standard tiers
- Duel + recommend responses now include sustain field
- Recommendation score gets +/- 0.4 * userBoost weight (top=0.15, nice=0.05)
- ♻ N/5 badge on duel cards + rec cards
Live-crawl test:
- node scripts/crawl-catalog.js realreal — RealReal returns 403 to bot
UAs (expected anti-bot wall). Standing rule says route via Browserbase
for production crawls; we don't spoof Chrome UAs (TOS-grey).
- Crawler logged + degraded gracefully, didn't retry, didn't escalate.
4a4d19e · 2026-05-11 · feat: avatar + Dopple try-on + Time-Travel Wardrobe
- Digital-twin avatar built from 3-8 user photos
- Provider chain: Dopple → Gemini 2.5 Flash Image → IDM-VTON → OOTD → stub
- Dopple persistent twin_id stored in user_avatars.body_measurements
- 'Try on me' button on every duel + rec card
- Time-Travel Wardrobe: pick closet item + drop old photo + tag occasion
→ Dopple /v1/scenes/edit re-dresses past-you, preserving face/others/venue
- New tables: user_avatars, tryon_jobs (with closet_id, occasion columns)
- Idempotent ALTER TABLE migration helper for SQLite
The killer feature: 'I wish I'd worn the navy gown to my sister's wedding'
→ select gown from closet → upload wedding photo → pick 'wedding'
→ Dopple swaps just the outfit, keeps everything else.
0e8ad67 · 2026-05-11 · initial scaffold — WhatsMyStyle v0.1 (placeholder name)
The optometrist for your closet. Server on :9777.
- SQLite schema (users / items / duels / closet / connections / debates)
- Large-pill onboarding flow (11 questions, single-question-per-screen)
- Optometrist duel loop (random pair → A/B/skip → taste-vector update)
- 24-item seed catalog (public listings, hash-based stub embeddings)
- Closet photo upload + llava-13b vision worker
- Stripe FC / Gmail / camera-roll / social / location opt-in stubs
- Stores-near-me via Google Maps Places (gated on API key)
- Recommendations grid with sort + density slider (per standing rule)
- Local-LLM dream-team debate harness (qwen3+deepseek-r1+phi4)
- pm2 supervised; ecosystem.config.js + .deploy.conf wired