MoodCanvas is a client-only interior design assistant. A user uploads one photo (JPG/PNG, camera capture supported) of an empty or near-empty room and chats through a guided flow to:
- analyze the room, 2) explore 10 full-style renders (gallery), 3) compare A/B mini-variants for up to 3 favorites, and 4) get 3 hero renders (Smart Mixed) plus a 60-30-10 palette, 5 quick wins, and a mini shopping list (neutral specs only). All AI calls use Google Gemini with Bring-Your-Own-Key (BYOK). No backend; data persists locally.
 
Goals (MVP)
- Single-photo based advisory for empty rooms; secondary cases (near-empty) should still work.
 - “Chat-style, linear” UX where each step appends a card; strict backtrack: changing an earlier answer removes subsequent cards.
 - Inspiration Pulse inside the analysis: top usage candidates + style fit.
 - 10-style gallery (1 image per style) with pool=5 parallel generation; order by fit score desc, no “recommended” badge.
 - Post-selection: A/B mini-variants for up to 3 styles (Smart Mixed axes from analysis) after a confirmation step.
 - Final: 3 hero renders (Smart Mixed) after a confirmation step.
 - Mini shopping list = 3 impact items from analysis + 2 function staples, no prices.
 - Strict JSON analysis via Gemini (response schema enforced).
 - Dark, relaxing UI (Plum–Peach palette) with Tailwind Play CDN; system sans fonts.
 
Non-goals (MVP)
- No user accounts; no server storage; no exports (ZIP/PDF/HTML) yet.
 - No multi-language UI (English only); internal units in meters only.
 - No accessibility hard targets (postponed).
 - No PWA/offline installation; GitHub Pages static hosting only.
 - No seed control/deduplication for renders; no price or shop links in the mini list.
 
- Bedroom, Home-Office, Kids Room, Guest Room, Living Room, Dining Room, Hobby/Studio, Fitness/Yoga, Library/Reading, Music/Recording, Walk-in Closet, Storage/Utility, plus free-text “Other”.
 
- Client-only web app: privacy-forward, easy hosting (GitHub Pages), zero backend complexity.
 - BYOK (Gemini): user supplies API key; no server secrets. Key stored only in localStorage.
 - Models: 
gemini-2.5-flashfor analysis;gemini-2.5-flash-imagefor renders. Balanced cost/latency. - Strict JSON mode for analysis: 
responseMimeType:"application/json"+responseSchema→ stable, machine-consumable output. - Storage: IndexedDB via tiny 
idblib; Hybrid model (projects, events, media, artifacts). Local only. - Styling: Tailwind compiled locally (CLI) into static CSS; Plum–Peach dark palette; system sans.
 - Hosting: GitHub Pages; CSP meta (loose MVP) embedded in 
index.html. 
Run npm run build:css after editing HTML/JS classes to regenerate styles/app.css.
- 
Key handling:
- BYOK banner (“Get your key… Google AI Studio. The key is stored only in your browser”).
 - Store in localStorage; Settings modal offers “Remove key from this device”.
 - No validation calls; optional superficial prefix hint (keys often start 
AIza…). - Send key in header 
x-goog-api-key(not query). 
 - 
Images & metadata:
- Allow JPG/PNG. Accept EXIF orientation; do not strip metadata for MVP; send “as-is”.
 - Resize before upload: long side ≤ 2048 px (q≈0.9).
 - No additional consent gate (info lives in Help/README).
 
 - 
CSP (loose MVP):
default-src 'self'; img-src 'self' blob: data:; connect-src https://generativelanguage.googleapis.com; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'self'; 
Stores (via idb):
- projects 
{ id, name, createdAt, updatedAt, settings { units:"m", theme:"plum-peach" }, caps { perProjectMB:150 } } - events (append-only timeline) 
{ id, projectId, type, payload, createdAt }Types:upload_image,analysis_done,gallery_generated,ab_generated,hero_generated,selection_changed,warning,error, etc. - media (blobs & thumbs) 
{ id, projectId, kind:"input|render|thumb", bytes|blob, mime, width, height, relatedId, createdAt }Import: store original + 512px thumb. Renders: store full + thumb. - artifacts (JSON/text) 
{ id, projectId, kind:"analysis|prompts|palette|quickwins|shoppinglist", json, createdAt } 
Storage caps & cleanup
- Output JPEG q≈0.85; thumbs 384px.
 - Per-project cap 150 MB, global cap 600 MB.
 - LRU auto-delete oldest full-res renders when over cap; thumbs remain; 30s undo toast.
 
Cards appear top→down; editing an earlier card removes all later cards.
- 
UploadCard
<input type="file" accept="image/jpeg,image/png" capture="environment">- Shows selected image preview.
 - Minimal import normalization: fix EXIF orientation, store original + 512 thumb.
 
 - 
KeyBanner (only if no key is present)
- Text: “Get your key… Google AI Studio. The key is stored only in your browser.” (link).
 - Paste field to save key. No validation.
 
 - 
Inspiration Pulse + Function/Scope Quick Pick
- One Single-Analysis call (strict JSON) returns 
usage_candidates. - Show top 3 as an “Inspiration” bubble.
 - User explicitly selects intended use and scope (1–4) (short picker).
 - Low scale confidence → small non-blocking warning banner.
 
 - One Single-Analysis call (strict JSON) returns 
 - 
AnalysisCard
- Renders JSON summary: 
photo_findings,palette_60_30_10,quick_wins(top 5),styles_top10order and scores. 
 - Renders JSON summary: 
 - 
StyleGalleryCard
- Generates 10 full renders (one per style) in parallel with pool=5; default size 1536×1024.
 - User can adjust count (6/8/10) and size (1024/1536) before running.
 - Tiles ordered by fit_score desc; corner chip shows style name.
 - Per-tile soft fail → small error chip + Retry.
 
 - 
SelectionCard
- Select up to 3 favorites.
 
 - 
A/B Mini-Variants
- Confirmation card shows “N favorites × 2 images”.
 - Generate per favorite 2 images using smart_mixed_axes from analysis; pool=5.
 
 - 
HeroRenderCard (final)
- Confirmation card (“3 images @ 1536×1024”).
 - Generate 3 hero renders (Smart Mixed).
 - Derive 60-30-10 palette, 5 Quick Wins, and mini shopping list here.
 
 - 
QuickWins & Mini List
- Show 5 actionable items (concise rules, e.g., distances in meters).
 - Mini list (5 items) = 3 highest-impact + 2 function staples; no prices.
 
 - 
Settings modal
 
- “Remove key from this device”.
 
Image viewer: tapping any image opens it in a new tab (full size).
- 
Analysis:
POST v1beta/models/gemini-2.5-flash:generateContentcontents: user text prompt (spec below) + inlineData image (base64, ≤2048 px long side).generationConfig:responseMimeType: "application/json"responseSchema: ANALYSIS_SCHEMA(see “Strict JSON schema”).
 - 
Renders (gallery, A/B, hero):
gemini-2.5-flash-image(image-to-image)- Inputs: original image + per-style/variant prompt from 
render_gallery[](or templates below). - Concurrency: 5 at a time.
 - Errors: per-tile retry button; analysis errors show a single toast with Retry.
 
 - Inputs: original image + per-style/variant prompt from 
 
Timeouts & retries (Safe Defaults)
- Analysis timeout 45s; Render timeout 120s.
 - Retry up to 2× on 429 / 5xx / network with 500ms/1500ms backoff.
 - AbortController cancels in-flight calls if a prior step changes.
 
Single-Analysis (user content) Use the version shared earlier (“ROLE: Interior design analyst for empty rooms…”) including context, inputs, and output rules.
- Language: English; units meters.
 - Geometry: treat camera pose + envelope as fixed (unless scope=4).
 - Return only JSON (no prose) because strict mode is used.
 
Render templates
- 10 styles: Scandi, Japandi, Modern Minimal, Contemporary Cozy, Mid-Century, Industrial Soft, Boho, Rustic, Mediterranean, Art-Deco.
 - Base constraints for image-to-image: keep camera pose and room envelope; keep doors, windows, and entries exactly where they appear in the source photo; respect intervention_scope; reflect palette_60_30_10 subtly; avoid logos/text; photorealistic lighting.
 - A/B Mini: vary along 
smart_mixed_axes.axisAvsaxisBas specified. - Hero (3): Smart Mixed (A, B, and best-of-both).
 
The approved ANALYSIS_SCHEMA (JSON Schema 2020-12) is part of the app and sent to Gemini in responseSchema.
- Required sections: 
usage_candidates,photo_findings,palette_60_30_10,constraints,quick_wins,styles_top10,smart_mixed_axes,negative_prompts,safety_checks,render_gallery. - Enumerations and exact counts are now handled at the prompt/UX level (schema only asserts basic shapes) to avoid overwhelming Gemini with state explosion.
 - Numeric ranges and string patterns (confidence 0-1, hex codes, etc.) are enforced via prompt instructions and client-side validation instead of schema constraints.
 scale_guessesallowsnullfor width/depth/height with explicitconfidencevianullable: trueon numeric fields (no range bounds in-schema).
- 
Theme: Dark Plum–Peach
bg #392338,surface #3F2840,surface2 #462E49,text #EDEDED,textMuted #CFC7D2,accent #FFCFA4,accent2 #FF947F,cta #C1264E.
 - 
Tailwind: Play CDN; inline
tailwind.configextends the above tokens; border radiusxl2. - 
Components: Header, HomeGrid, KeyBanner, ChatTimeline (cards listed above), Modals (Settings, Error).
 
- No key: show BYOK banner with AI Studio link; block calls; everything else visible.
 - Low scale confidence: show non-blocking warning; proceed.
 - Analysis invalid: strict JSON mode prevents schema drift; if API error, show retry toast.
 - Gallery tile fails: show per-tile error chip + Retry; other tiles continue.
 - Rate limit spikes: rely on per-tile behavior + two backoff retries; no global pause in MVP.
 - Network loss: calls fail; show retry.
 - Storage cap exceeded: auto-purge oldest full-res renders (thumbs remain) + 30s undo.
 - Backtrack: editing any earlier card removes later cards and cancels in-flight requests.
 
- Mobile-priority: Chrome (Android) and Safari (iOS) prioritized; desktop browsers “best effort” (latest Chrome/Edge/Firefox/Safari).
 - Camera capture relies on 
<input capture="environment">(browser support varies; gracefully falls back to picker). 
- 
Analysis Strict-JSON Harness (in-browser):
- Given a room photo and a valid hard-coded key, when calling analysis, then the response parses and validates against 
ANALYSIS_SCHEMAwith Ajv → PASS. - Negative tests: extra fields → FAIL; wrong enum → FAIL; non-10 
styles_top10length → FAIL. - Edge: 
scale_guessesnullvalues + low confidence should PASS. 
 - Given a room photo and a valid hard-coded key, when calling analysis, then the response parses and validates against 
 - 
Prompt sanity: snapshots of rendered prompts for each style to avoid accidental drift in future edits.
 - 
Render pipeline smoke: with mocks (if key absent) ensure UI handles tiles loading, success, and per-tile failure states.
 
- GitHub Pages, single 
index.htmlentry (root), relative asset paths. - Add 
.nojekyllto avoid Jekyll processing. - Keep CSP meta in 
index.html. 
- Do exactly one prioritized task per iteration. Before/after: run relevant checks (build/lint/tests or in-browser harness).
 - When adding/updating tests, include a brief “why this test matters” note to guide future changes.
 - Before adding functionality, search the codebase (ripgrep) to confirm it’s missing; if present, prefer refactor over re-implementation.
 - After each iteration, add a concise update to 
docs/implementation-progress.md(what changed, decisions, follow-ups). - Prefer CI-friendly, non-interactive commands/reporters where possible so runs can be automated later.