Skip to content

Add end-to-end Outfits experience with demo mode - #7

Draft
bwu640 wants to merge 7 commits into
tandpfun:mainfrom
bwu640:add-outfits-experience
Draft

Add end-to-end Outfits experience with demo mode#7
bwu640 wants to merge 7 commits into
tandpfun:mainfrom
bwu640:add-outfits-experience

Conversation

@bwu640

@bwu640 bwu640 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Adds a complete Outfits feature: backend outfits API with atomic filesystem persistence and crash-safe boot recovery, an in-app Outfits gallery, builder, and viewer, unified backend persistence for gallery edits, and an OpenAI-free demo mode so the app is fully explorable without an API key or reference photo.

Behavior

  • Adds a Wardrobe/Outfits navigation switch.
  • Builds outfits from library garments with occasion, style, setting, and notes inputs.
  • Supports create, save, edit, delete, and generation flows.
  • Generates a modeled photo with OpenAI when configured, or a local flat-lay preview in demo mode.
  • Persists gallery metadata edits and deletes to data/library.json; legacy localStorage edits migrate on load.
  • Includes empty, loading, error, unconfigured, and onboarding states.
  • Supports nested-path preview deployments with relative static assets and a deployment-aware API prefix.

Testing

  • npm run check passes.
  • 19 Vitest tests cover outfit normalization, version migration, CRUD persistence, disk/status reconciliation, preview composition, prompt building, and transparent framing.
  • Demo-mode APIs were smoke-tested for config, list, create, generate, patch, delete, image serving, and wardrobe persistence.
  • Interactive browser QA passed on desktop and 390 px mobile with no horizontal overflow.

Generated by Computer

Computer and others added 7 commits July 22, 2026 15:59
Build the missing Outfits feature so the generate-outfits skill's output
is finally usable in the app, and make the whole app explorable without an
OpenAI key.

- Add outfits store (normalization, filesystem CRUD, crash-safe atomic
  writes) shared with the import pipeline via a new fs-utils module.
- Add /api/import/outfits routes (list/create/read/update/delete/generate)
  plus outfit image serving, wired into the existing Vite-middleware backend.
- Outfit image generation uses OpenAI when configured and falls back to a
  local sharp flat-lay composite (demo mode) otherwise.
- Add an Outfits gallery, outfit builder, and detail viewer in the app with
  a top-level Wardrobe/Outfits nav, empty/loading/error/unconfigured states,
  and onboarding copy.
- Persist gallery edits to library.json through a new wardrobe PATCH route,
  migrating and retiring the old localStorage-only edits.
- Add scripts/seed-demo.mjs and a seed:demo script for a no-key demo wardrobe.
- Add Vitest with tests for outfit normalization/persistence and pipeline
  helpers; check now runs tests before build.
Set Vite base to './' so built index.html references JS/CSS with
relative URLs, fixing 404s when dist is served from a nested path.
Runtime /api fetches are unaffected, preserving dev/preview backend
path handling.
Add src/api.js with apiUrl() that prefixes root-relative /api and
/_ipx requests with a deploy proxy prefix. deploy_website rewrites the
literal __PORT_4173__ token to the proxy path when hosting dist from a
nested URL; locally the token is untouched, so requests stay same-origin
and dev/preview are unchanged. Wire every wardrobe, Outfits, import, and
image URL through the helper.
The Outfits card art, card placeholders, garment picker thumbnails, and
viewer piece thumbnails rendered /api/... paths via plain <img> tags,
bypassing apiUrl() and 404ing on nested hosted deployments. Route every
outfit image src (including cache-busting query strings) through
apiUrl(). Extract pure normalizePrefix/joinApiPath helpers and add tests
covering local same-origin, path/full-URL proxy prefixes, query-string
preservation, and no double-prefixing.
The Vite middleware backend and filesystem persistence only run under
dev/preview, so static hosts have no /api. Add a VITE_DATA_MODE=browser
build that serves the same /api/import/... shapes from a bundled demo
JSON (base64 data URLs) plus localStorage, with client-side canvas
outfit previews. Persistence is per-browser only and documented as such.

- api.js: DATA_MODE flag + apiFetch dispatching to in-browser adapter
- data-browser.js: localStorage-backed adapter for wardrobe/outfits CRUD
- demo-fixtures.mjs: shared garment/outfit fixtures for both seeders
- seed-demo-static.mjs: emits public/demo-data.json for the static build
- vercel.json + build:vercel script; README deployment + limitation notes
Introduce server-side Vercel functions for the deployed app so real garment
photos, outfits, generated cutouts/previews, and a private model reference
photo persist durably across devices via Vercel Blob (with a local filesystem
fallback for dev/tests). All routes are gated by a shared app passcode that is
stored only in env and exchanged for an HttpOnly, Secure, SameSite=Lax
HMAC-signed session cookie; login attempts are rate-limited per instance.

- Extract the OpenAI/image pipeline into scripts/wardrobe-core.mjs, shared by
  the Vite middleware and the new backend to avoid duplication.
- api/_lib: keys (path-traversal-safe normalization), storage (Blob + local),
  auth (passcode/session/rate limit), http adapters, and a transport-agnostic
  router that runs generation synchronously (serverless-safe).
- api functions: authenticated import catch-all, auth login/logout/session,
  and one-at-a-time bulk-import. Blob URLs are never exposed; assets stream
  through authenticated functions.
- Frontend: passcode login gate + logout, 401 handling in apiFetch. The
  browser demo build is untouched (no backend, no gate).
- scripts/bulk-import.mjs CLI for seeding garments and the reference photo.
- Cost guardrails: 4MB upload cap, 5 garments/outfit, 500-item wardrobe.
- Tests for auth, storage-key normalization, key builders, and API/bulk-import
  validation; production build wired via vercel.json + build:production.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All Blob objects are written with access:'private' and read back through
the authenticated SDK get() by pathname, so no Blob URL, token, or store
id is ever returned to the client. Auth resolves to Vercel OIDC
(VERCEL_OIDC_TOKEN + BLOB_STORE_ID) when present, otherwise a read-write
token. On Vercel with no private store configured, data access fails
closed with an exposable 503 instead of silently using the ephemeral
filesystem. Config reporting no longer leaks the internal model-reference
pathname. Adds tests asserting private access, OIDC threading, and
fail-closed getStore.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant