# 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