# MUST OBEY — Mobile-first build requirements This app's PRIMARY surface is a mobile phone. Build it impeccably on mobile FIRST, then verify on tablet and desktop. Treat the rules below as non-negotiable hard constraints, not suggestions. ## Viewports to verify (every screen, every state) - 320 px, 360 px, 375 px, 390 px, 414 px, 480 px - 768 px, 834 px (iPad portrait / Pro 11) - 1024 px, 1280 px, 1440 px, 1920 px, 2560 px - Plus: 200% browser zoom, landscape orientation on every mobile width, iPhone with safe-area insets visible ## Hard layout rules - Mobile-first CSS. Default styles target mobile; `@media (min-width: ...)` for larger viewports. - Use `dvh` and `svh` instead of `vh` for full-height surfaces (iOS Safari URL-bar bug). - Use `clamp()` for fluid typography across all viewports. - Prefer container queries (`@container`) over media queries for component-level responsiveness. - Use `min(100%, ...)` widths so content never overflows. Zero horizontal overflow at any viewport. - Add `` to every page. - Apply `padding: max(safe-area-inset-X, fallback)` on every edge-bleeding container so notched iPhones in landscape never clip content. - Wide tables and code blocks scroll INSIDE their container (`overflow-x: auto`), never push the body. - Use `background-attachment: scroll` on mobile, not `fixed` (iOS Safari repaint bug). - Avoid `backdrop-filter` on animated elements. Use it sparingly on static surfaces only. - **Canvas Scaling**: Canvases must dynamically scale with window resize events and properly handle high-DPI screens (`window.devicePixelRatio`). Set physical dimensions (`canvas.width`/`canvas.height`) using pixel ratio and render relative to this grid, using CSS to control responsive viewport scaling. - **Robust Storage**: Every access to `localStorage`/`sessionStorage` (especially `JSON.parse` of loaded state or writes) MUST be wrapped in a `try-catch` block to handle disabled storage, private browsing mode, quota limits, or corrupted JSON gracefully. Fall back to a robust in-memory object store. ## Touch & accessibility - Tap targets ≥ 44 × 44 px on touch (Apple HIG). Increase to 48 px under `@media (hover: none) and (pointer: coarse)`. - All interactive controls reachable by keyboard with a visible focus ring; respect `:focus-visible`. - Color contrast ≥ 4.5:1 for body text, 3:1 for UI components. - All images have meaningful `alt`. Decorative images use `alt=""`. - Respect `prefers-reduced-motion: reduce` — zero animation durations under that query. - Forms validate inline; error messages are specific, not "Invalid input". - Modals: focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, focus restored on close. ## Performance bar (Lighthouse mobile, throttled 3G/4G) - LCP < 2.5 s · INP < 200 ms · CLS < 0.1 - JS bundle gzip < 200 KB mobile-first; lazy-load non-critical screens via `React.lazy` / dynamic imports. - No render-blocking resources above the fold. - Images: WebP/AVIF preferred, `loading="lazy"`, explicit `width`/`height` attributes (zero CLS), `srcset` for retina. - Videos: `preload="metadata"`, low-resolution poster, max 720p mobile fallback. Never autoplay with audio. - Fonts: `font-display: swap`; preload only the one used above the fold. - Smooth scroll honoured via CSS `scroll-behavior: smooth` with reduced-motion fallback. ## Pre-ship mobile checklist (the deployer MUST verify before declaring done) 1. Open at 375 px in DevTools — every screen scrolls vertically only; zero horizontal scroll. 2. Browser zoom 200% — layout reflows without overlap. 3. iPhone Safari with the URL bar visible AND landscape — no content under the home indicator; no notch clipping. 4. iPad portrait (768 px) and landscape (1024 px) — no awkward gaps; tablet-specific breakpoints land cleanly. 5. Tap every interactive element with a thumb at real-device size — every target is easy to hit. 6. `prefers-reduced-motion: reduce` — every transition / animation skips cleanly, scroll-behavior becomes instant. 7. Lighthouse mobile score ≥ 90 across all 4 categories. 8. Zero `console.error` and zero CLS shift in real-device testing on a mid-tier Android (e.g. Pixel 6a) and an iPhone SE. --- The original template starts below. All rules above apply on TOP of whatever this template specifies. --- # Style My Room ## 1. Project **Style My Room** is a fast, opinionated room-styling tool for the person standing in front of an empty corner with their phone in one hand and a tab open to a furniture site in the other. They are not hiring an interior designer. They are not going to spend two months on a moodboard. They want six honest design directions for THIS room — same window, same sofa, same paint, same floorboards — in the time it takes to make a coffee. Drop a photo of the corner. Drop two inspiration shots (a Pinterest screenshot, a magazine page, a friend's living room you photographed politely on the way out). Pick a vibe or let the app suggest six. In about twenty-five seconds you get back six redesigns rendered into your actual room at 4K — Scandinavian, Japandi, mid-century, maximalist, gallery-wall eclectic, biophilic — each with the rug, the lamp, the plant, the throw, and the art the model added, itemised, priced, and linked to current retailers. You see what the room would look like before you spend four hundred dollars on the wrong rug. **The 30-second demo** that earns the tap on the landing page: visitor uploads a photo of an empty corner in their living room (window on the left, beige sofa on the right, terracotta tile floor, white walls). They drop two reference shots — a Pinterest screenshot of a Japandi nook and a magazine page with a tall fiddle-leaf fig. They tap **Style my room**. In about twenty-five seconds the tool returns six 4K images of the same corner — same window light, same sofa, same terracotta floor, same white walls, the existing radiator under the window untouched — each restyled in a distinct direction. Below each image: a shopping list. A Calligaris-style boucle armchair at €489. A Hay lamp at €189. A 6 ft Areca palm at €78. A 160×230 cm wool rug at €329. Tap any item: opens the retailer page, current price, in-stock status, shipping estimate, all surfaced via grounded search at the moment the shopping list was generated. The hero capability that makes this possible — and that did not exist before Google I/O 2026 — is **Nano Banana Pro** (model ID `gemini-3-pro-image`) with its **14-reference style guide** ingestion. Old room-styling tools either generated a beautiful fantasy room that bore no relationship to yours, or warped your real photo into something unrecognisable. Nano Banana Pro can take your room photo, your two inspiration boards, and twelve more reference images for the chosen vibe (curated by the app, not the user — the user only uploads two) — and produce a 4K image that preserves the structural elements of your room (window frame, radiator, sofa profile, paint colour, floor) while restyling only the empty volume. This is the post-I/O 2026 capability that turns an entire category of consumer-AI tool from interesting demo to actual purchase- decision support. **Tagline:** _Drop a photo of the empty corner. Get back six 4K redesigns in your actual room — with a real shopping list._ ## 2. Target audience This is a **jobs-to-be-done** template, not a persona template. The universal job: *"I have a space I don't know what to do with. Show me what could work HERE, before I spend money."* The target audience is anyone with that job, including but not limited to: - A renter staring at a corner of their living room that has been empty for fourteen months - A first-time homeowner who just closed on a one-bedroom and is paralysed by the empty rooms - A parent whose kid just moved out, looking at the spare bedroom and asking what it should become - Anyone with a 'dead zone' in their floor plan — the awkward triangle under the stairs, the alcove beside the chimney, the bay window with bad proportions - Someone shopping for one big-ticket piece (rug, sofa, dining table) who wants to see it in their actual room before clicking buy - A small landlord prepping a furnished short-let listing who needs to see what minimal restyling would lift the place - An interior-design student building a portfolio of room variations for the same shell - A real-estate agent staging a listing virtually — same photographed empty room, six staged variants for the listing carousel - An adult moving back in with a parent who needs to make a childhood bedroom feel like a 35-year-old's home - A couple combining two flats into one and trying to find a visual common ground The /19 gallery promise is universal: if you have an empty space and a phone, this works for you. Not "a 34-year-old design- conscious renter in Brooklyn" — anyone. ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Six directions, not one.** A single AI render is a guess. Six side-by-side, with the same window, same sofa, same floor, lets the user *see what they actually like* by comparing — Scandinavian-quiet vs maximalist-loud, Japandi-warm vs gallery- wall-busy. Decision-making is comparative; the tool serves comparison. - **Your actual room, not a fantasy.** The model preserves the structural elements: window, door, radiator, paint, floor, fixed furniture you marked as 'keep'. Only the empty volume gets styled. A small **"What was generated vs photographed"** overlay on each image shades the generated pixels at 30% opacity so the user can verify the structure is honoured. - **Shopping list with current prices, not vibes.** Each item in each rendered image is itemised with category, dimensions, a short visual description, a price, and a link to a current retailer surfaced via grounded search. Prices are scraped fresh at generation time (not cached from training data); links are marked with the retrieval timestamp. - **Hard rule: keep the bones.** Windows, doors, structural walls, paint colour, and fixed furniture the user marked as 'keep' are NEVER altered unless the user opts in via an explicit "imagine a different paint colour too" toggle. Most users do not have permission to repaint, and the default respects that. - **Cost is the gating fact.** A six-direction generation at 4K costs about $0.36 on the Gemini side. We surface that. The shopping list grounded-search adds another ~$0.03. You're paying for designer-quality images of your real room for the price of a pack of gum. - **Honest about what AI can and can't see.** The model cannot tell the floorboards are slightly warped, can't see the electrical outlet behind the desk, can't smell the damp under the radiator. The shopping list is suggestions, not a guarantee the items will fit. The "before you buy" panel reminds users to measure twice with their actual phone. - **Save and refine.** Each rendered direction becomes a saved variation. Users can tap **"keep the rug, change everything else"** and the next generation locks the rug and rerolls the rest — the model accepts the chosen image as one of its references, with a 'lock' marker on the rug. ## 4. Features to build - **One-photo room input** — drop or capture a photo of the empty corner / bedroom / living-room area; the app accepts JPEG, HEIC, PNG up to 20 MB - **Up to 2 user reference photos** — Pinterest screenshots, magazine pages, photos of friends' rooms; the user uploads their inspiration - **Up to 12 curated reference images per vibe** — the app holds small style libraries per direction (Scandinavian, Japandi, etc.) internally; only the user's room + 2 user references are surfaced; the other 12 are passed silently to Nano Banana Pro as part of the 14-reference style guide - **Six design directions per generation** — Scandinavian, Japandi, mid-century, maximalist, gallery-wall eclectic, biophilic; the user can swap any one for one of: industrial, bohemian, coastal, art-deco, brutalist-soft, monochrome, dark-academia, country- modern, minimalist-warm - **Room-element preservation** — the user taps fixed elements in their photo (window, sofa, radiator, floor) to mark them 'keep'; the system instruction passes the mask description to Nano Banana Pro - **Generated-vs-photographed overlay** — toggle that shades the generated pixels at 30% opacity, leaving the photographed pixels fully visible; the visual proof that the bones are kept - **Shopping list per direction** — itemised list under each image with category (rug, lamp, plant, art, throw, side-table, chair), short visual description, dimensions estimate, price, retailer, link, and a 'fits-your-space-?' note if the item's typical dimensions might not fit the room's measured volume - **Grounded retailer search** — every shopping-list item is resolved via grounded search at generation time; sources are cited in the UI by retailer name with the retrieval timestamp - **Vibe picker with thumbnails** — six default vibes; the user can swap any one with a long-press - **Room measurement assistant** — the user can optionally tap three points in the photo (floor near, floor far, ceiling) and enter the rough wall-to-wall measurement in feet or metres; the app calculates room volume and uses it to flag oversized items - **Save and refine — keep-this-element loop** — tap an item in any rendered direction → "lock the rug, reroll everything else"; the locked direction becomes a reference for the next call - **Compare two directions side by side** — pick any two of the six → see them stacked with shopping-list diff (what's the same, what's different, total price delta) - **Export** — a single rendered direction at 4K as PNG or JPEG; the shopping list as PDF (with retailer links) or CSV (for importing into a budget spreadsheet) - **Save room project** — the user's room photo + chosen directions + locked elements + shopping list — auto-saved per user; multiple rooms supported (Living Room, Office Corner, Spare Bedroom) - **Re-render with a single change** — once a direction is saved, the user can edit a single field ("make it darker", "replace the rug with a flatweave", "swap the palm for a snake plant") and the app re-renders that one direction with the change applied; this is one Nano Banana Pro call with the prior image as the primary reference - **Live image-generation status** — a thinking indicator that names what the model is doing ("reading your room…", "matching the light from your window…", "placing the chair…", "selecting the rug…") so the 25-second wait feels meaningful - **No-paint default; opt-in 'imagine paint changes too'** — the app does NOT change the wall paint unless the user explicitly toggles on "imagine different paint too". This is the hard rule visualised: most users are renting and cannot repaint. ## 4b. Required Gemini capabilities + backend services **This template's intelligence comes from the Gemini capabilities below. Wire them up explicitly — don't substitute generic LLM calls.** ### Gemini capabilities (the load-bearing intelligence) - **Nano Banana Pro 14-reference style guide ingestion** (model `gemini-3-pro-image`) — THE hero capability. Up to 14 reference images per call: 1 (the user's room photo, the structural anchor) + up to 2 user-supplied inspiration references + up to 11 curated references the app passes silently for the chosen style direction. The model preserves the structural elements from the room photo and restyles the unfurnished space. 4K output. **Legible in-image text is supported but is NOT used here** — we don't want text inside the rendered room; turn the feature OFF via the system instruction. Six directions ⇒ six parallel Nano Banana Pro calls per "Style my room" tap, fanned out server-side and gathered. **Pricing:** $2.00 input / $12.00 output per 1M tokens (post-I/O 2026 pricing). A 4K render is ~$0.06 per direction; six directions ≈ $0.36. **Replaces** the pre-I/O Nano Banana 2 approach which could not honour structural fidelity at this level. - **Room scene understanding + style direction planning** (`gemini-3.5-flash`, thinkingLevel `medium`) — before fanning out the six image-gen calls, one fast text call reads the user's room photo + 2 inspiration photos and produces a `RoomStylePlan` object: detected room type, detected fixed elements (window, sofa, radiator, etc.), inferred light direction and time-of-day, inferred existing palette, six chosen style directions (drawn from the user's pick + sensible defaults), and a per-direction prompt seed that will be fed into the six Nano Banana Pro calls. **This is the planner.** It is also the call that runs grounded search via a separate call for shopping-list retailer prices. - **Shopping-list resolution via grounded search** (`gemini-3.5-flash`, thinkingLevel `low`, tool `google_search`) — once the six images are back, a per-direction call generates the itemised shopping list. For each item, the call resolves "a 160×230 cm wool boucle rug in the style of the Hay Dogger, neutral cream colourway, available now at major retailers in " against grounded search. Returns retailer name, price, link, in-stock status, retrieval timestamp. **Critical:** `responseSchema` and `google_search` cannot be combined in the same Gemini call. The model emits the JSON for `ShoppingList` in the text body and the server parses it. Citation URLs come from `response.groundingMetadata.groundingChunks[].web.uri`. - **Single-change re-render** (`gemini-3-pro-image`) — when the user edits one field ("replace the rug with a flatweave"), one Nano Banana Pro call runs with the prior rendered image as the primary reference plus a short instruction about the one change. This is a 'change only this' use of the multi-reference feature rather than a six-way fan-out. - **Lock-element refinement loop** (`gemini-3-pro-image`) — when the user locks the rug or the chair from a chosen direction and asks to reroll the rest, the locked element from the chosen image becomes one of the 14 references with a system- instruction note: "the rug visible in reference image #3 must be preserved in the output". This works because Nano Banana Pro's reference handling now respects per-reference role markers (post-I/O 2026 feature). - **Hero illustration generation** (`gemini-3.1-flash-image`, Nano Banana 2) — generates the landing screen's hero illustration (a photographic-looking shot of a tastefully styled empty corner before / after). Uses the cheaper Nano Banana 2 for chrome assets; reserves Pro for actual user-room renders. ### Capabilities NOT used in this template (and why) - **Live API** (`gemini-3.1-flash-live-preview`) — not used. This is a render-and-decide tool, not a conversational one. Voice input would feel forced. - **Managed Agents** (`antigravity-preview-05-2026`) — not used in v1. A future v2 could use a Managed Agent to actually place the orders ("buy the rug for me"), but v1 stops at the shopping list. Note this in the roadmap section of the capabilities panel. - **Computer Use** (`gemini-2.5-computer-use-preview-10-2025`) — not used. Browser automation against retailers is brittle, ToS- risky, and unnecessary for a 'show me what could work' tool. - **TTS** (`gemini-3.1-flash-tts-preview`) — not used in the core flow. An accessibility add-on could read shopping lists aloud, but this is not the hero experience. - **Gemini Omni Flash** (announced at I/O 2026, dev API not yet available as of 2026-06-01) — flagged in the capabilities panel as "video walkthrough — coming when the API opens"; do NOT wire it. When the API opens, the v2 roadmap is: a 10-second video pan around the rendered room. ### Backend services - **Auth — Required.** Firebase Auth with Google sign-in (auto- provisioned by AI Studio Build). **Apple sign-in is optional but user-configured**: it requires an Apple Developer account, Service ID, Key ID, and private key wired into the Firebase Auth console. Magic-link email is supported for share-this-room flows; requires sender domain authorisation in Firebase Auth. - **Database — Required.** Firestore for `users`, `rooms`, `generations`, `directions`, `shopping_lists`, `locked_elements`, `shared_links`. Auto-provisioned by AI Studio Build. - **File storage — Required.** Firebase Storage for the user's room photos, their 2 inspiration references, and the rendered 4K direction images. **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console and wire the bucket name before the first photo upload. 4K PNGs are large (~6 MB each); plan for ~36 MB per six-direction generation per user. - **Server functions — Required.** Cloud Run server functions (auto-provisioned + free 2-app deploy from AI Studio Build, a post-I/O 2026 perk). All Gemini API calls happen server-side; the API key lives in Secrets Manager, never in the client bundle. The six-direction fan-out is a single Cloud Run invocation that calls Nano Banana Pro six times in parallel with a 30-second wall-clock timeout. - **Workspace integration — Optional.** Post-I/O 2026, AI Studio Build apps can read+write Sheets/Drive/Docs directly without an OAuth handshake. We use this **only** for the "export shopping list to a Google Sheet" feature. Optional; v1 ships PDF and CSV export and treats the Sheet export as a 'rolling-out' bonus. - **Email — Required (transactional).** Magic-link share emails for "send my styled room to a friend"; optional weekly digest ("your saved rooms — prices that changed this week" — v2 roadmap, not v1). - **Payments — Not needed for v1.** Free for personal use. The hosting + Gemini API costs are on the deployer's bill; the cost-breakdown section is explicit. - **External APIs:** Gemini API for all intelligence. No other required external API beyond Gemini. The shopping list links are unaffiliated — the app does not run an affiliate scheme; if the user wants affiliate revenue, they wire it via a custom retailer-link rewriter (documented in the remix notes). **Environment variables:** every secret (Gemini API key, Firebase service-account JSON) lives in environment variables — never in client bundle. Include a `.env.example`. **Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete my room' inside the UI · explicit opt-in for any analytics · the user's room photos and rendered images are never used to train any model (we use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms) · room shares are explicit per-room and revocable. **Read this first — prompt-craft rules that apply to every call in this template:** 1. **Name the model variant explicitly** in every Gemini API call. Do not let the agent pick the model. See the per-call matrix below. 2. **Pin `thinkingLevel` explicitly** per call where applicable. Image-generation calls (Nano Banana Pro, Nano Banana 2) do NOT take `thinkingConfig`; omit the field entirely. 3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schemas are below. **Convert the Zod schema to Gemini's `Schema` type via the SDK helper** before passing to `responseSchema` — do NOT pass raw Zod. **Numeric `min`/`max` constraints are documentation only inside `responseSchema`; clamp on the server after the response arrives.** 4. **Pin the system instruction separately** from user input. Use the `systemInstruction` field for persona + behavioural rules; use `contents` for user input. Never concatenate. 5. **Pre-declare tools as an enable/disable list** per call. The matrix below names which tools are enabled per call. Tools NOT listed for a call should be disabled. 6. **Grounded search + `responseSchema` are mutually exclusive.** The shopping-list call uses `google_search` and emits JSON in the text body; the server parses it. Citations come from `response.groundingMetadata.groundingChunks[].web.uri`. 7. **Multi-image input uses the Files API.** Photos are uploaded to Firebase Storage, then server-side re-uploaded to the Gemini Developer API Files API; the resulting `files/*` resource name (e.g. `files/abc123xyz`) is passed via `fileData: { fileUri, mimeType }`. Do NOT pass Firebase Storage public URLs directly to `generateContent` — the Gemini API does not fetch them server-side. `gs://` URIs belong to Vertex AI / Cloud Storage — a different surface, not accepted by the Developer API. 8. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract: ```typescript function safeExtractJSON(raw: string): T { const clean = raw.replace(/```json\s*|```/gi, '').trim(); const s = clean.indexOf('{'); const e = clean.lastIndexOf('}'); if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response'); return JSON.parse(clean.slice(s, e + 1)) as T; } ``` 9. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing. ### Per-call model + tools matrix | Call | Model | thinkingLevel | Tools enabled | |------|-------|---------------|---------------| | Room scene + style direction planner → `RoomStylePlan` | `gemini-3.5-flash` | medium | (none) | | Six-direction image render (×6, fanned out in parallel) | `gemini-3-pro-image` | n/a | (none — multi-image reference is built in to the call body) | | Single-direction re-render after a 'change only this' edit | `gemini-3-pro-image` | n/a | (none) | | Lock-element reroll | `gemini-3-pro-image` | n/a | (none) | | Shopping list resolution (×6, one per direction) | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Hero / empty-state illustration | `gemini-3.1-flash-image` | n/a | (none) | *Note for builders:* image-generation calls do not accept `thinkingConfig` — the `n/a` cells in this matrix are documentation only; do not serialise them into the request body. Grounded search calls emit JSON in the text body — `responseSchema` and `google_search` cannot be combined in the same Gemini call; parse the JSON server-side and read citation URLs from `response.groundingMetadata.groundingChunks[].web.uri`. ### Primary structured-output schemas (seed verbatim in the prompt) ```typescript import { z } from "zod"; const StyleDirection = z.enum([ "scandinavian", "japandi", "mid_century", "maximalist", "gallery_wall_eclectic", "biophilic", "industrial", "bohemian", "coastal", "art_deco", "brutalist_soft", "monochrome", "dark_academia", "country_modern", "minimalist_warm", ]); const RoomType = z.enum([ "living_room", "bedroom", "kitchen", "dining_room", "home_office", "hallway", "entryway", "kids_room", "studio_apartment", "balcony", "alcove", "stairs_under", "bay_window", "other", ]); const LightDirection = z.enum([ "north_indirect", "south_direct", "east_morning", "west_evening", "unknown", "no_natural_light", ]); const FixedElement = z.object({ element: z.enum([ "window", "door", "radiator", "fireplace", "built_in_shelf", "fixed_lighting", "structural_pillar", "floor_finish", "wall_paint", "ceiling_beam", "sofa_keep", "bed_keep", "desk_keep", "wardrobe_keep", "other_user_marked_keep", ]), approximate_position_in_image: z.string(), // "left-third, lower-half" description: z.string(), // "single sash window, white frame, no curtains" must_preserve: z.boolean(), // true by default for windows/doors/radiators }); const RoomStylePlan = z.object({ detected_room_type: RoomType, detected_room_type_confidence: z.number().min(0).max(1), detected_light_direction: LightDirection, detected_existing_palette: z.array(z.string()), // ["warm white walls", "terracotta tile floor", "beige boucle sofa"] fixed_elements: z.array(FixedElement), estimated_floor_area_m2: z.number().nullable(), // if the user gave any measurement user_reference_photos_inferred_directions: z.array(StyleDirection), // up to 2, what the user's refs hint at six_chosen_directions: z.array(StyleDirection).length(6), per_direction_prompt_seeds: z.array(z.object({ direction: StyleDirection, one_line_intent: z.string(), // "soft, light, neutrals, one plant, no clutter" must_keep_elements: z.array(z.string()), // names of fixed elements to honour palette_hint: z.array(z.string()), // ["bone white", "pale oak", "moss green"] items_to_add: z.array(z.string()), // categories, not products: ["rug", "armchair", "floor_lamp", "plant", "art"] forbidden_items: z.array(z.string()), // ["acrylic furniture", "neon sign"] })), flags_for_user_review: z.array(z.object({ field_path: z.string(), reason: z.string(), })), }); const ShoppingItem = z.object({ item_id: z.string(), category: z.enum([ "rug", "armchair", "side_chair", "side_table", "coffee_table", "floor_lamp", "table_lamp", "pendant_light", "plant", "planter", "wall_art", "throw", "cushion", "curtains", "bookshelf", "storage_basket", "mirror", "tray", "vase", "candle_holder", "other", ]), visual_description: z.string(), // "low-pile wool boucle rug, cream, ~160×230 cm" estimated_dimensions: z.object({ width_cm: z.number().nullable(), depth_cm: z.number().nullable(), height_cm: z.number().nullable(), }), retailer_name: z.string(), retailer_country: z.string(), // ISO-3166 alpha-2 product_url: z.string().url(), price_value: z.number(), price_currency: z.string(), // ISO-4217 price_currency_symbol: z.string(), in_stock: z.boolean(), shipping_estimate_days_low: z.number().nullable(), shipping_estimate_days_high: z.number().nullable(), fits_estimated_space: z.enum(["yes", "tight", "no", "unknown"]), fits_estimated_space_note: z.string().nullable(), // "for a 12 m² room, a 160×230 rug works; verify the door swing" retrieved_at_iso: z.string(), source_citation_index: z.number(), // index into the groundingChunks array }); const ShoppingList = z.object({ direction: StyleDirection, generation_id: z.string(), items: z.array(ShoppingItem), total_low: z.number(), // sum of in-stock items at lowest config total_high: z.number(), currency: z.string(), currency_symbol: z.string(), user_region_iso: z.string(), retrieved_at_iso: z.string(), not_a_substitute_for_measuring_disclaimer_required: z.boolean(), }); const RenderedDirection = z.object({ direction: StyleDirection, generation_id: z.string(), image_uri: z.string(), // Files API `files/*` resource name of the 4K render image_width: z.number(), image_height: z.number(), prompt_seed_used: z.string(), reference_images_used: z.array(z.object({ role: z.enum([ "user_room_anchor", "user_inspiration", "curated_style_reference", "locked_element_reference", ]), uri: z.string(), })), preserved_elements_described: z.array(z.string()), // ["the window on the left", "the beige boucle sofa", "the terracotta tile floor"] generated_elements_described: z.array(z.string()), // ["wool boucle rug", "boucle armchair", "fiddle-leaf fig", "floor lamp", "wall art"] generation_seconds: z.number(), shopping_list: ShoppingList, }); type RoomStylePlan = z.infer; type ShoppingList = z.infer; type RenderedDirection = z.infer; ``` ### Common failure modes (and how to avoid them) - **The model rebuilds the entire room** — the most common failure mode. Nano Banana Pro, without an explicit instruction to preserve structural elements, will happily replace your terracotta floor with polished concrete and your beige sofa with a green velvet one. The system instruction for every render call must explicitly list the elements to preserve from `RoomStylePlan. fixed_elements`, by approximate-position-in-image and description, and must state the negative constraint "do not alter these elements". The 30% overlay tool is the user-side proof; the system instruction is the model-side discipline. - **Paint colour changes silently** — even when the user does not toggle 'imagine paint changes too', the model sometimes shifts the wall colour by a step. The system instruction must include the detected wall colour as a `must_preserve` `wall_paint` element. If the user toggles paint changes on, this constraint is relaxed and the system instruction explicitly invites a new palette. - **The fixed sofa gets restyled** — if the user marks the existing sofa as 'keep', it must appear unchanged in every direction, even when the direction (e.g. maximalist) would otherwise want a velvet replacement. The system instruction lists the sofa as `must_preserve: true` and the model is told to style AROUND it. - **Six directions look the same** — if the system instructions for the six calls are too similar, the model produces six near- identical images. The planner call's `per_direction_prompt_seed. one_line_intent` must produce maximally-distinct briefs: a Scandinavian-quiet direction and a maximalist-loud direction must read as different briefs in the prompt, not just different labels. Server-side, check the six prompt seeds for n-gram similarity and re-plan if any two are too close. - **Shopping list pretends to source items that don't exist** — Nano Banana Pro draws a rug that doesn't match any real product. The shopping-list grounded-search call cannot find the exact rug; the model fabricates a price and a URL. The system instruction must allow the call to surface the closest available product and to flag the visual mismatch ("the rendered rug doesn't have a 1:1 retail match; this is the closest current product"). The `fits_estimated_space_note` and the `not_a_substitute_for_measuring_disclaimer_required` flags carry the honesty. - **Prices are stale** — without grounded search the model returns prices from training data, which are months old and often wrong. The shopping-list call MUST use `google_search`. Without it, reject the response. Surface the `retrieved_at_iso` timestamp on every price. - **Grounded search + responseSchema confused** — if a builder pastes a `responseSchema` onto the shopping-list call, the Gemini API will refuse. The matrix is explicit: the shopping- list call emits JSON in the text body and the server parses it. - **The user's region is wrong** — if the user is in Lisbon and the shopping list returns prices in USD from a US retailer with no shipping to Portugal, the result is useless. The planner call reads the user's Firebase Auth region (or asks explicitly); the shopping-list call's system instruction passes the `user_region_iso` and requires the retailer's shipping to cover that region. - **The 4K image is enormous** — 4K PNGs from Nano Banana Pro are ~6 MB each. Cache them under Firebase Storage with appropriate TTL and serve the gallery view via on-demand resized derivatives (1024px) to avoid 36 MB downloads. - **Single-change re-render goes too far** — when the user asks "replace the rug with a flatweave", the model occasionally redraws the entire room. The single-change system instruction must pin the prior image as primary reference and constrain the change to the named element. - **Locked element drifts** — when the user locks a rug and rerolls everything else, the rug in the new images shifts in colour or pattern. The system instruction must explicitly mark the locked reference with role `locked_element_reference` and demand a pixel-honest reproduction of the locked element. - **Aspect-ratio mismatch** — the user's photo is portrait 3:4; the model outputs 16:9. Pass the input image's aspect ratio to the render call's `aspectRatio` parameter and require the output to match. - **The mobile build app delay** — AI Studio Build's mobile app is rolling out as of 2026-06-01; not every visitor has it. The template ships as a responsive web app; the mobile app remix path is mentioned in section 11 with a "rolling out" caveat. ### Negative constraints (hard rules) - Do NOT alter the structural elements of the room (window, door, radiator, structural walls, fixed lighting, ceiling beams) under any direction. These are listed verbatim in every render call's system instruction. - Do NOT change the wall paint colour unless the user has explicitly toggled "imagine different paint too" on. The default is OFF. - Do NOT change the floor finish unless the user has explicitly toggled "imagine different flooring too" on. The default is OFF. - Do NOT change any furniture the user has marked as 'keep'. Restyle around it. - Do NOT generate text inside the rendered room. Nano Banana Pro CAN render legible text but we don't want signs, posters with letters, or book spines with brand names — they read as AI artefacts. The system instruction says "no legible text in the rendered image; book spines and posters appear as textured / abstract surfaces". - Do NOT generate humans, pets, or limbs in the rendered room. The room is the subject. No staged figures. - Do NOT generate commercial branding in the rendered room (no Coca-Cola cans, no Apple laptops, no specific recognisable product labels). Generic shapes only. - Do NOT cite shopping items the model did not retrieve via grounded search. `source_citation_index` must point to a real index in `groundingMetadata.groundingChunks`. The server validates set membership. - Do NOT cite shopping items as 'in stock' without grounded-search confirmation. Default to `unknown` when the source page does not state stock; do not pretend to know. - Do NOT use prices from training data. Every price comes from a grounded-search citation with a `retrieved_at_iso` within the current generation session. - Do NOT recommend items that exceed the user's measured space. When `estimated_floor_area_m2` is set and a rug or chair would not fit, set `fits_estimated_space` to `"tight"` or `"no"`. - Do NOT recommend items shipped from a region that doesn't serve the user's region. - Do NOT auto-place any orders. v1 stops at the shopping list. - Do NOT use the user's room photos, inspiration photos, or rendered images to train or fine-tune any model. Use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The capabilities-info panel says this in plain English. - Do NOT moralise about consumption, sustainability scores, or the user's stylistic choices. Show what they ask for. (A separate 'show me the sustainable / second-hand option' filter is in the v2 roadmap; do not retrofit it into the default flow.) - Do NOT claim a rug, lamp, or item "perfectly fits your room". The model has not measured your floor. Use "looks proportional for the rendered space" / "may be tight — measure your floor". - Do NOT prompt the user to repaint. The default is paint- preservation. The opt-in toggle exists, but the language never encourages permanent alteration. ### Per-call `systemInstruction` strings Use these as the literal `systemInstruction` field for each Gemini API call the built app makes. They complement the series-wide rules already uploaded as the global instructions file (`00-series-instructions.txt`). ### Call: Room scene + style direction planner → `RoomStylePlan` Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are the planner for a six-direction room-restyling tool. You receive: - One photograph of the user's room (the "anchor" — the structural reality that must be preserved in all renders). - Up to two user-supplied inspiration photographs (Pinterest screenshots, magazine pages, friends' rooms). - The user's selected style directions (between 0 and 6 picks; fill the rest from your best six defaults for the detected room type). - The user's region (ISO-3166 alpha-2), used downstream for shopping-list retailer matching. - An optional rough floor-area measurement in square metres. Your job: 1. Read the anchor photo and identify the detected room type, the detected light direction (where the light source is in the image — left, right, above; and whether the light is direct or indirect; warm or cool), the detected existing palette (3-6 short descriptors of the room as it stands), and the fixed elements present. 2. Identify every fixed element that the rendered images must preserve. Windows, doors, radiators, fireplaces, built-in shelves, structural pillars, the floor finish, the wall paint, ceiling beams — all preserved by default. The user may have marked specific furniture as 'keep' (a sofa, a bed, a desk, a wardrobe); these are also preserved. 3. Read the user's inspiration photos and infer which style directions they hint at (e.g. a soft-light moodboard with blonde wood and white linen hints at Scandinavian or Japandi). 4. Pick six final style directions for this user's room. Diverse — do not pick six minor variations of the same vibe. Honour the user's explicit picks if any. Fill the rest from the room type's sensible defaults. 5. For EACH of the six directions, produce a per-direction prompt seed: a one-line intent, the must-keep elements (named verbatim), a palette hint of 3-5 specific colours, the categories of items to add to the rendered scene (rug, armchair, floor lamp, plant, etc.), and a short list of forbidden items for this direction (e.g. "no acrylic furniture in a Japandi render", "no neon signage in a Scandinavian render"). Hard rules: - One single window or door is enough fixed-element evidence to treat the room as 'has a real window / has a real door' — these are always must_preserve. - The wall paint and the floor finish are must_preserve=true by default, even when no specific colour is identified. - The six chosen directions must be visually distinct. If two prompt seeds share more than half their items_to_add list or more than half their palette_hint, regenerate one of them with more contrast. - per_direction_prompt_seed.items_to_add is a list of categories (rug, lamp, plant), NEVER specific products or brands. Brand resolution happens in the shopping-list step, AFTER rendering. - Confidence: detected_room_type_confidence < 0.7 → flag the room type in flags_for_user_review with a one-sentence reason. - If the user did not supply a measurement, set estimated_floor_area_m2 to null. Do NOT estimate from the photo; foreshortening makes it unreliable. Output ONLY the RoomStylePlan JSON matching the provided schema. No commentary. JSON only. ``` --- ### Call: Six-direction image render (one of six, fanned out) Model: `gemini-3-pro-image` · n/a · Tools: (none — multi-image reference built in) ``` You are Nano Banana Pro. You generate ONE photographic-quality 4K image of the user's actual room, restyled in a single specified style direction. You receive up to 14 reference images: - Reference image #1: the user's room (the anchor). MUST be preserved structurally — window, door, radiator, structural walls, paint colour, floor finish, fixed lighting, and any furniture the planner has named as 'keep' must appear in the output with their position, dimensions, colour, and material unchanged. - Reference images #2-#3: up to two user-supplied inspiration photographs (Pinterest screenshots, magazine pages, friend's room). Use them for *vibe* — mood, palette intent, density, formality — NOT for literal element copying. - Reference images #4-#14: curated style references for the chosen direction, supplied by the app. Use them for material, texture, and composition cues. - (If a locked-element reroll) one of the references is marked role: "locked_element_reference" — its named element must be reproduced pixel-honestly in the output. The system instruction also receives, as text: - direction: one of the StyleDirection enum values - one_line_intent: the planner's one-line brief - must_keep_elements: a list of structural / fixed-furniture elements to preserve, each with a short description - palette_hint: 3-5 colour names - items_to_add: categories to populate the empty volume - forbidden_items: categories or specific things to avoid Hard rules: - The output must be the user's room — same window position and size, same door if visible, same radiator if visible, same paint colour, same floor finish, same light direction and warmth, same fixed furniture marked 'keep'. The 30% overlay the user will toggle on top of this image must show the fixed pixels untouched. - The output must NOT contain legible text. No book spines with brand names, no posters with letters, no signs, no labels. Book spines appear as textured / abstract surfaces. - The output must NOT contain humans, pets, or limbs. - The output must NOT contain commercial branding. - The output must NOT change the wall paint colour unless the caller has explicitly passed `imagine_paint_changes: true`. - The output must NOT change the floor finish unless the caller has explicitly passed `imagine_floor_changes: true`. - The output must be photographic — soft, natural light from the detected direction, slight imperfection, real-paper texture on any visible paper, real-wood grain on any visible wood. Avoid the glossy 'AI render' look. - The aspect ratio of the output matches the aspect ratio of the anchor image (reference #1). - The items added must match the items_to_add list and must avoid the forbidden_items list. If a forbidden category appears in a reference image, do NOT carry it through to the output. Style direction notes: - scandinavian: blonde wood, white linen, one plant, neutral palette, generous negative space, low-pile flatweave rug - japandi: warm minimal, dark woods + pale walls, ceramic vessels, ikebana-style single branch, low furniture profile - mid_century: walnut frames, tapered legs, brass accents, one bold textile, low slung armchair - maximalist: layered textures, multiple plants, pattern-on- pattern (controlled), warm colour temperature, gallery wall - gallery_wall_eclectic: a curated wall of 6-10 framed pieces of varying sizes (no legible text in the art), a tall plant, a reading chair - biophilic: 3-5 plants of varying sizes, woven materials, soft earthy palette, terracotta accents - industrial: matte metal, leather, exposed bulb fixtures (warm light), a single statement plant - bohemian: layered rugs, woven baskets, low cushions, warm wood, a hanging plant or two - coastal: pale linen, weathered wood, ceramic vessels, no literal seashells / nautical clichés, a soft striped textile - art_deco: brass, velvet, geometric textiles, a fluted vessel, a green plant for contrast - brutalist_soft: matte concrete tones softened with boucle, one sculptural lamp, a single fern - monochrome: tonal palette across one colour family, varying textures, one statement plant - dark_academia: deep green or oxblood textiles, dark wood shelving (no legible book titles), a leather chair, a tall plant - country_modern: pale washed wood, neutral linens, a single bouquet of dried flowers, woven basket - minimalist_warm: bone-white, oak, one chair, one rug, one plant — and stop Output: a single image, 4K resolution, aspect ratio matching the anchor. No commentary. ``` --- ### Call: Shopping list resolution (one of six, per direction) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You receive one rendered direction image (the result of a Nano Banana Pro call), the prompt seed used to generate it, the user's region (ISO-3166), and an optional measured floor area in square metres. Your job: produce a concrete, itemised shopping list for the items added to that rendered image, sourced via grounded web search at current prices, in stock where confirmable, shipped to the user's region. Procedure: 1. Identify the items visible in the rendered image that match the planner's items_to_add list (rug, armchair, floor lamp, plant, art, etc.). Estimate dimensions and material from what is visible. 2. For each item, run a grounded web search to find a current product at a major retailer that ships to the user's region and matches the visual description closely. Prefer retailers the user can verify (well-known furniture sites, big-box home stores, plant retailers); do NOT cite obscure or suspicious sites. 3. For each found item, record: category, visual description, estimated dimensions, retailer name, retailer country, product URL, price (value + currency code + symbol), in- stock status if confirmable, shipping estimate if shown, whether the item fits the estimated_floor_area_m2 (yes, tight, no, unknown), a 'fits' note where relevant, and the retrieval timestamp. 4. Compute total_low (sum of in-stock items at default config) and total_high (sum including all items). Hard rules: - Use `google_search` grounding for every item. Do NOT answer from training-data memory; prices and availability change too fast. - Output the response as JSON in the text body (NOT via `responseSchema` — these cannot be combined in one call). Server-side: parse the JSON and read citation URLs from `response.groundingMetadata.groundingChunks[].web.uri`. Do NOT include URLs inside the JSON body for the source_citation_index — surface them via source_citation_index pointing into the groundingChunks array. - Every price must come from a grounded-search citation. No citation, no price. - If you cannot find a close match for an item, surface the closest available product and note the visual mismatch in the visual_description ("rendered rug has a tighter weave than this product; closest current match available"). - If the user's measured floor area is set, evaluate fits_estimated_space: a 200×300 rug in a 12 m² room is 'tight'; in a 6 m² room is 'no'. - Items must ship to the user's region. If the only matches are outside the user's region, surface them with a note ("ships from US, not currently shipping to Portugal — listed for reference") and set in_stock: false with a fits note. - Set not_a_substitute_for_measuring_disclaimer_required to true on every shopping list — the user has not measured every dimension of every item against their room. - Do NOT recommend items with commercial branding from the rendered image. The render had no branding; the shopping list must not invent any. - Do NOT moralise about consumption, sustainability, or the user's stylistic choices. - Do NOT include affiliate codes or referral parameters in the product_url. JSON shape (emit in the text body): { "direction": "", "generation_id": "", "items": [ { "item_id": "", "category": "rug", "visual_description": "...", "estimated_dimensions": { "width_cm": 160, "depth_cm": 230, "height_cm": null }, "retailer_name": "...", "retailer_country": "PT", "product_url": "https://...", "price_value": 329.00, "price_currency": "EUR", "price_currency_symbol": "€", "in_stock": true, "shipping_estimate_days_low": 3, "shipping_estimate_days_high": 7, "fits_estimated_space": "yes", "fits_estimated_space_note": null, "retrieved_at_iso": "", "source_citation_index": 0 } ], "total_low": 1085.00, "total_high": 1414.00, "currency": "EUR", "currency_symbol": "€", "user_region_iso": "PT", "retrieved_at_iso": "", "not_a_substitute_for_measuring_disclaimer_required": true } No commentary outside the JSON. ``` --- ### Call: Single-direction re-render after 'change only this' edit Model: `gemini-3-pro-image` · n/a · Tools: (none) ``` You receive ONE prior rendered direction image (the user's currently selected version), the user's room anchor image, the direction's prior prompt seed, and ONE specific change request in natural language (e.g. "replace the rug with a flatweave", "make the wall art smaller", "swap the palm for a snake plant", "warmer light"). Your job: produce a new image that is identical to the prior rendered image EXCEPT for the named change. The rest of the scene — sofa position, rug placement if not the change target, plant species if not the change target, light direction, window, door, paint, floor — stays pixel-honest to the prior rendered image. Hard rules: - The prior rendered image is the primary reference. Treat it as the canonical scene. - The anchor image is the secondary reference. Use it to re-verify the preserved structural elements. - Apply ONLY the requested change. Do not redraw the entire scene. - All the rules from the original render call apply: no text, no humans, no commercial branding, preserve structural elements, photographic quality, aspect ratio matches anchor. - If the change is ambiguous ("make it darker"), interpret as shifting the palette one step darker — not as turning off the light. Output: a single 4K image. No commentary. ``` --- ### Call: Lock-element reroll Model: `gemini-3-pro-image` · n/a · Tools: (none) ``` You receive the user's room anchor image, the planner's per- direction prompt seed, and ONE locked element — a specific item from a previously-rendered direction that the user wants preserved in the new rendering. The locked element is supplied as one of the reference images with the role "locked_element_reference" and a short description of which item is locked (e.g. "the cream wool rug in the centre of the floor", "the brass floor lamp to the left of the sofa"). Your job: produce a new image in the requested direction that preserves both: - The structural elements from the anchor (window, door, radiator, paint, floor, marked-keep furniture). - The locked element from the locked-element reference, pixel- honestly: same dimensions, same colour, same material, same position relative to other anchor elements. Everything else in the empty volume is restyled per the direction's prompt seed. Hard rules: - The locked element must be reproduced as faithfully as the structural anchor elements. Same colour, same texture, same shape, same position. - All other rules from the original render call apply. Output: a single 4K image. No commentary. ``` --- ### Call: Hero / empty-state illustration generation Model: `gemini-3.1-flash-image` · n/a · Tools: (none) ``` You generate a single photographic-quality image for the landing screen, the empty-room placeholder, or the 'try with a sample room' starter. Use the cheaper Nano Banana 2 (Flash Image) for chrome — the Pro model is reserved for the user's actual room renders. Prompt anchors that work well: - "a bright minimalist living room corner at golden hour, one empty wall, one tall window on the left with sheer linen curtains, beige linen sofa partially visible on the right, pale oak floor, soft natural light, no people in frame, soft shadows, photographic" - "an empty bedroom corner with a single tall window on the right, white walls, pale oak floor, no furniture, late afternoon sun pooling on the floor, photographic, no people" - "a small home-office alcove with a built-in shelf on the back wall, no desk yet, no chair yet, natural light from the left, photographic, ready to be styled" Hard rules: - Photographic. No cartoon, no illustration, no flat vector. - No people, no pets in frame. - No commercial branding visible. - Warm soft light, real materials, slight imperfection. - Aspect ratios: 3:2 for landing hero; 1:1 for empty-state. ``` ## 5. Use cases & content to include Build dedicated UI sections or flows for each of these — they tell you what content the app must support. - **The empty corner.** A renter has a corner in their living room. Left side: a single tall window with sheer curtains, no blinds. Right side: a beige linen sofa they're keeping. Floor: terracotta tile. Walls: warm white, recently painted, cannot be changed. They drop the photo, drop a Pinterest screenshot of a Japandi nook and a magazine page with a fiddle-leaf fig, mark the sofa and window as 'keep'. Twenty-five seconds later they see six directions. The Japandi one places a low walnut side table, a single ikebana branch in a clay vessel, a small wool rug in cream, and a paper-lantern floor lamp. The shopping list prices the rug at €329 from a known Portuguese retailer. - **The kid's-room-turned-office.** A parent's child has just moved out. The room has a single bed pushed against the wall, a poster-board on one wall, a small wardrobe. They photograph the room, mark the wardrobe and floor as 'keep' (rented apartment, no replacing flooring), don't mark the bed. The six directions treat the bed as removeable: most directions show a desk and a reading chair where the bed was; one direction keeps a daybed for guest use. The shopping list flags the wardrobe dimensions and proposes a desk that fits beside it. - **The bay-window seat.** A first-time homeowner has a bay window with awkward proportions — too shallow for a sofa, too wide to leave empty. They photograph it, mark the window as 'keep' (obviously), and ask for six directions. The renders show a fitted bench in five of six directions (Scandinavian, Japandi, minimalist-warm, country-modern, coastal) and a reading chair with a side table in one (mid-century). The shopping list flags the fitted-bench renders as "custom build, not a stock product — see fits note". - **The studio under-the-stairs alcove.** A renter in a studio apartment has a triangular dead zone under the open stairs. Photo, mark the stairs as 'keep' (structural), six directions. The renders show a low bookshelf following the slope in Scandinavian and Japandi, a small upholstered nook with a tall plant in biophilic, a moody reading corner with a single pendant light in dark-academia. Shopping list per direction with custom-fit warnings on the bookshelf options. - **The "lock the rug, reroll everything else".** The user loves the wool boucle rug in the Japandi render. They tap it and select "Keep this rug, reroll everything else." The next call uses the rendered Japandi image as a `locked_element_ reference` with the rug marked. The new six directions all show the same rug in the same position, with the surrounding furniture/plant/lamp/art varying. - **The "wrong vibe" swap.** The user dislikes the Maximalist direction. They long-press it and pick "Industrial" from the swap menu. The app re-runs only that one direction with the new style. Twenty seconds later the Maximalist tile is replaced with Industrial. - **The change-one-thing edit.** The user keeps the Scandinavian direction but wants the rug to be a flatweave instead of a pile rug. They tap the rug in the rendered image and type "swap to a flatweave wool rug in the same colour". One Nano Banana Pro single-change call. The new rug appears in the same position; everything else is unchanged. The shopping list re-resolves the rug item only. - **The "imagine a different paint colour too" opt-in.** A user who owns their home toggles "imagine paint changes too". The six directions now also propose wall colours that fit each vibe — a chalky bone-white for Scandinavian, a deep eucalyptus green for dark-academia, a warm clay for biophilic. The shopping list adds paint products with brand + colour code. - **The "I have a measurement" power user.** The user enters the room's floor area: 11 m². The planner passes this to the shopping-list call. The 230×300 rug in the Maximalist direction gets flagged as 'tight' with a specific note ("160×230 would suit this room better"). The 200×140 rug in the Scandinavian direction is 'yes — fits'. - **The export to Sheets.** The user clicks "Export shopping list to Sheet". A Google Sheet opens in a new tab with the six directions as tabs, each with its itemised list. (This is the post-I/O 2026 Workspace integration; the Sheet is created without an OAuth handshake.) The "rolling out" tag on the button is honest until the feature is fully rolled out. - **The share-this-room.** A user shares the Japandi direction with their partner. A magic-link email lands; the partner opens a read-only view of just that one direction with the shopping list. They reply with a comment ("love the rug, the side table looks small — can we see a bigger one?"). - **The before-you-buy panel.** Before the user clicks any retailer link, the app surfaces a small "Before you click" note in plain English: "These prices were retrieved at