================ ONE-SHOT BUILD CONTRACT (read first) ================
Build this in Google AI Studio "Build" in ONE shot — a complete, working app,
no follow-up turns. These are hard rules, not suggestions:
1. TARGET = Full-Stack Web (Node server runtime, secrets, Firebase allowed).
If you target Native Android instead, you MUST drop all server/DB/Workspace/
secrets and persist locally (Room / SharedPreferences) only.
2. PIN EVERY MODEL CALL — never let the agent auto-select (it downgrades on quota):
- Reasoning / text -> gemini-3.5-flash (thinkingLevel: minimal|low|medium|high)
- 4K image + legible text -> gemini-3-pro-image (image_size "4K", up to 14 refs)
- High-volume image -> gemini-3.1-flash-image
- Expressive TTS -> gemini-3.1-flash-tts-preview (inline tags e.g. [whispers])
- Realtime audio/video (WebSocket) -> gemini-3.1-flash-live-preview
- Sandboxed agent -> antigravity-preview-05-2026
3. DIVISION OF LABOR — the model ONLY parses/extracts to a strict responseSchema.
ALL math, money (store currency as integer minor units / cents), sorting,
balancing and graph logic run in deterministic TypeScript/Python. The model
must never compute totals, splits or balances itself.
4. responseSchema sanitation — no regex patterns, no fixed-length tuples, no
format validators in the schema (they crash the OpenAPI engine). Enforce those
in server-side code AFTER parsing the JSON.
5. responseSchema and google_search grounding are MUTUALLY EXCLUSIVE in one call.
6. CODEGEN — split large output into modular, single-responsibility files so no
file is truncated by the output-token cap.
7. Every external call gets a graceful fallback (e.g. manual paste if a Workspace
read fails). Never a silent dead end.
8. ROBUST STORAGE & CANVAS — Wrap all `localStorage`/`sessionStorage` operations (especially JSON parsing and writes) in `try-catch` blocks to prevent crashes in private windows or quota overflows. Canvas drawing elements must dynamically handle window resize and scale pixel density (`window.devicePixelRatio`) to avoid blurry graphics on retina displays.
=====================================================================
# 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.
---
# Tasting-Menu Brief
## 1. Project
**Tasting-Menu Brief** is a briefing app for a private chef. The user — a
working chef who cooks for other people's dinner parties, in their
homes, on a weeknight or a Saturday — talks to a host about the
upcoming dinner, captures the guest list with all its allergies,
dislikes, and observances in their own voice, and the app produces a
proposed five-course menu, a printable allergen and swap matrix, a
costed shopping list, and a brief for the chef's own prep day. The
menu is structured. The allergens are flagged conservatively. Every
ingredient that touches a known guest hazard is shown in red, every
swap is named with the exact substitution that protects every guest
at the table.
This is the kind of app a Mexican-American private chef in Austin
builds for herself on a quiet Tuesday morning because she is tired of
re-typing the same allergen spreadsheet for every booking, then
re-typing it again when the host emails at midnight to say their
cousin from Houston is now coming and is allergic to peanuts. It is
also the kind of app a Filipino chef in Makati, Manila uses to brief
a corporate engagement dinner for twelve where two guests are halal
and one keeps Lent strictly; a Lebanese chef in Mar Mikhael, Beirut
uses to brief an Easter Sunday lunch for sixteen where one guest is
coeliac, one is on a low-FODMAP protocol, and one cannot have sesame
because of a near-miss in childhood; and a Punjabi-Australian chef
near Westfield Parramatta uses to brief a Diwali dinner for twenty
that needs to be 100% vegetarian, with three Jain guests who avoid
root vegetables and one toddler who will not eat anything green.
Same shape of moment, different cuisine, different city, same
question: how do I feed all these people, beautifully, without
hurting anyone?
The single demo that proves the magic: the chef sits at her kitchen
table on a Tuesday afternoon with the host on speakerphone, taps the
big record button, and lets the host talk for three minutes — "okay
so it's eight of us, it's my husband's fortieth, his sister is coming
down from Dallas, she had a really bad anaphylactic reaction to a
satay sauce when she was a kid so peanuts are an absolute no, tree
nuts she's never been tested for so just to be safe let's avoid them
too, my mother is coming, she does not eat dairy, never has, my
cousin is vegan but eats honey, my friend Mara is gluten-free for
real not for lifestyle she has coeliac diagnosed, my husband loves
duck, hates fennel, please nothing with anchovies in the dressings
because he can taste them a mile off, I'd love something Mexican-
ish since it's his fortieth, maybe a mole, oh and there's a chocolate
cake situation because it's his birthday" — and three minutes later
the app shows a proposed five-course menu. The mole-rubbed duck is
there with a vegan parallel of charred king oyster mushrooms in the
same mole. The cake is a flourless chocolate torte with a coconut-
cream chantilly so the dairy-free mother and the coeliac friend both
eat it. Every line item is allergen-tagged. The peanut row is red
across the whole menu — none used, none in the kitchen — and a small
sentence at the bottom of the menu confirms the no-cross-contact
protocol the chef will follow. The shopping list is costed in the
chef's currency at her usual supplier prices. The prep brief lists
what she does Friday night, what she does Saturday morning, and what
she plates on the night, including the order she fires each course
in a domestic kitchen with one oven.
And in the harder cases — the religious observance the host did not
think to mention, the allergy the guest themselves underplayed, the
dietary protocol the chef has not personally cooked for before — the
app stays cautious. A guest described as "doing keto" still gets the
sweet course offered as an opt-out, not assumed-skipped. A guest
described as "pescatarian-ish" gets a follow-up question pushed back
to the host before the menu is sent. A halal request without an
explicit alcohol stance triggers an explicit confirmation about wine
in the reduction. The hard rule, baked into every prompt: when in
doubt about an allergen, the cell is RED. Better a swap the chef
later removes than a hospital visit she did not see coming.
**Tagline:** _Voice the guest list — get a five-course menu that feeds everyone, hurts nobody, and lists every gram in your basket — in any cuisine, any city, any allergen profile, priced from your own supplier sheet._
## 2. Target audience
- Self-employed private chefs cooking in clients' homes one to three nights a week, in any cuisine — Mexican-American, Filipino, Lebanese, Punjabi-Australian, Cantonese-British, Senegalese-Parisian, Brazilian-Lisbon, Korean-Berlin, Japanese-Bay-Area
- Small in-home catering operations (a chef + one helper) running occasion dinners — birthdays, anniversaries, engagements, corporate retreats, condolence dinners after a funeral
- Personal chefs employed full-time for one or two families who need a structured way to brief weekly menus that respect the family's evolving dietary needs and the kids' developing tastes
- Pop-up dinner hosts running ticketed five-course dinners in a rotating series of borrowed kitchens — restaurant residencies, private clubs, supper-club series
- Caterers handling religious and observance-led dinners — Iftar, Seder, Easter Sunday lunch, Diwali, Lunar New Year, Thanksgiving, Ramadan suhoor breakfasts, post-fast meals at the end of Yom Kippur, the lunch after a Hindu shraadh
- Chefs in countries where standardised allergen labelling is patchy and ingredient sourcing is local and seasonal — Lagos, Bogotá, Hanoi, Tbilisi — who need conservative defaults baked in
- Culinary-school instructors teaching the practice of menu briefing as a discipline alongside knife skills and brigade structure
- Front-of-house managers and event coordinators inside small restaurants who run private-dining rooms and need a repeatable briefing format that the kitchen can read at a glance
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app.
- **Brief by voice, the way you already do it** — the chef puts the host on speaker, taps record, and lets the conversation happen. Gemini 3.5 Flash transcribes, identifies each guest by name, captures every allergy, dislike, observance, and occasion-detail in one pass, and structures it. No spreadsheet, no clipboard, no separate "intake form".
- **Allergen flagging is conservative — when in doubt, RED** — the model is pinned to a hard rule: an ingredient that *might* be present in a dish, or that *might* have touched the dish in a shared kitchen, is marked red for any guest with that allergen. The chef can later confirm and downgrade to amber; she cannot accidentally upgrade an unflagged risk. Better an over-flag than a guest in A&E.
- **Parallel plates, not "you get a salad"** — a vegan guest at a six-course tasting menu does not get a single sad bowl while everyone else eats. The app generates a parallel plate for each course that respects the diet but matches the cuisine, the technique, and the visual weight of the original. The mole-rubbed duck has a charred king oyster in the same mole; the seared scallop has a roasted celeriac steak in the same brown-butter; the panna cotta has a coconut-milk panna cotta of the same shape.
- **Birthday plate is named** — the host mentioned a fortieth; the menu carries one course flagged as the birthday plate, with a candle line in the prep brief and a one-line note in the menu PDF for the guests. The app tracks who the occasion is for and threads them through the menu sensibly (the duck is fired to the husband first; the vegan parallel of the dessert is plated for his mother).
- **Costed shopping list in your supplier prices** — the shopping list is priced against the chef's own saved supplier sheet, not invented retail numbers. Anything not in the supplier sheet is flagged with the app's best estimate and a "your price?" prompt — the chef confirms before the costed brief is final. The host can be quoted an honest cost-of-food number that the chef did not make up.
- **Prep brief that fits a domestic kitchen** — the prep timeline accounts for ONE domestic oven, ONE hob with four rings, and the realistic constraints of cooking in someone else's house. The fire order is sequenced so the chef is not blocking herself; the make-ahead components are flagged for Friday night; the things that must be plated to order are flagged for the night.
- **Multilingual and multi-script** — guest names, dietary observances, ingredient names, and the chef's own supplier names all come through verbatim in their original script. The Filipino chef writes "kalamansi" not "calamondin"; the Lebanese chef writes "كبسة" alongside "kibbeh" when she wants; the Mexican-American chef writes "huitlacoche" with its accents intact. The translation, where requested, never flattens these.
- **Auditable and printable** — every menu carries a one-page allergen matrix (guests as rows, dishes as columns, traffic-light cells), a one-page shopping list, and a one-page prep brief. Printable on the chef's home printer, emailable to the host as a single PDF, archivable so next year's anniversary dinner picks up where this year's left off.
## 4. Features to build
- Voice intake — long-form recording (up to 30 minutes) of the host briefing the dinner, transcribed and structured in one Gemini 3.5 Flash pass
- Manual intake — for chefs who prefer to type the guest list directly into a form, with the same downstream pipeline
- Per-guest profile — name (as written by the host), pronouns if mentioned, allergies (severity scale), dislikes, observances (halal, kosher, Hindu vegetarian, Jain, vegan, lacto-vegetarian, pescatarian, Lent, Ramadan, post-bariatric, low-FODMAP, low-sodium, diabetic-managed, coeliac, gluten-avoiding, dairy-free, nut-free, shellfish-free, sesame-free, soy-free, egg-free), birthday/occasion if any
- Allergen library — conservative defaults for every cuisine in the user's profile. Mole sauce: contains nuts unless explicitly verified; pesto: contains pine nuts and Parmesan unless explicitly verified; Caesar dressing: contains anchovies and egg yolk unless explicitly verified; Worcestershire sauce: contains anchovies and gluten unless explicitly verified
- Cuisine preferences — the host's prompt ("Mexican-ish, because it's his fortieth"; "something for an Iftar"; "an autumn menu for a Tuscan house") fed forward into the menu generation as a soft constraint
- Five-course menu generation — a cohesive arc with a defensible reason for each course (an opener that wakes the palate; a vegetable course; a protein course; a cheese-or-second-protein optional; a dessert that ties to the occasion)
- Parallel plates — for each course, a vegan / vegetarian / pescatarian / halal / gluten-free parallel that respects the cuisine, the technique, and the visual weight
- Birthday/occasion plate — explicit handling of the named occasion, with the prep brief carrying a "candles on at fire time" line where appropriate
- Allergen matrix — a guests-by-dishes grid with traffic-light cells: green (safe), amber (verify), red (do not serve). The default for an unverified shared-kitchen scenario is amber, not green
- Swap suggestions — for every red cell, the named swap. "Lola: red on the mole rojo (peanut + tree nut). Swap: serve the verde mole, made with pumpkin seeds only, prepared on a separate board"
- Cross-contact protocol — a short paragraph on what the chef will do in the kitchen to protect each high-severity allergen (separate boards, sanitised pan, no shared oil)
- Costed shopping list — every ingredient, with quantity in grams (or millilitres, or pieces), priced from the chef's saved supplier sheet, summed in the chef's currency, with a per-head food cost
- Supplier sheet management — the chef saves her supplier prices once; she updates when prices move; the app honours her numbers and flags anything missing
- Prep brief — Friday-night tasks, Saturday-morning tasks, fire-order on the night, with explicit notes for a domestic kitchen (one oven, four rings, one fridge, no walk-in)
- Wine and beverage notes (optional) — a short pairing note per course, with an explicit alcohol-free pairing for guests who do not drink and for the children
- Dietary explainers — a one-line explanation of any observance the chef may not personally cook for often ("Jain vegetarian: avoid root vegetables — no onions, garlic, potatoes, carrots, beets; no eggs; no honey")
- Menu print and email — a single PDF the chef sends the host the night before, with the host-facing menu on page 1, the allergen matrix on page 2, the shopping list on page 3, and the prep brief on page 4
- Archive — every brief saved per client, per date, retrievable; next year's birthday picks up the previous year's notes ("last year the brother could not have shellfish; confirm still true?")
- Multilingual menu output — the chef can output the menu in English, Spanish, Tagalog, Arabic, Mandarin, French, German, or any language the host prefers, while keeping ingredient names in their cuisine of origin
## 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)
- **Audio input + long context** (Gemini 3.5 Flash) — accepts a 5-30-minute audio briefing from the host and the chef in conversation. Transcribes, separates speakers when possible, and extracts structured guest profiles in a single multimodal call. Long-form (30 minutes ≈ ~30k input tokens of audio plus the system prompt) is well within the 1M-token window.
- **Structured output / JSON Schema** — the response matches the `Brief` schema below. Every field is typed; the schema is included verbatim in the system instruction and as `responseSchema`.
- **Multilingual** (built into Gemini 3.5 Flash) — handles English, Spanish, Tagalog, Mandarin/Cantonese, Arabic, French, Tamil, Hindi, Urdu, Bengali, Punjabi (Gurmukhi and Shahmukhi), Farsi, Amharic, Swahili, Khmer, Vietnamese, Korean, Japanese in the host's voice; preserves ingredient names in their cuisine of origin in any output language.
- **Long context (1M tokens)** — the menu-generation call sees the full brief (every guest, every allergen, every dislike, every observance, every dish across previous dinners with this host) plus the chef's full supplier sheet plus her cuisine library at once. **Guardrail**: a typical brief is ~3k tokens; a supplier sheet is ~5k tokens; the cuisine library is ~10-30k tokens; a chef's full client history is ~10-50k tokens. Total stays comfortably under 100k for almost all chefs. For a long-tenure chef with five-plus years of archives, chunk by recency: the last 18 months in full, older years summarised.
- **Search grounding** — for the dietary observance call. "Jain vegetarian" must resolve to "no root vegetables, no eggs, no honey, no fungi (mushrooms), no fermented foods consumed by some practitioners" with cited sources, not invented from training data. Grounded search prevents hallucinated halal/kosher/observance rules.
- **Gemini 3.5 Flash** — for the cheaper, faster swap-suggestion calls per allergen cell, and for the per-ingredient cost lookup when the chef does not yet have a supplier price.
- **Gemini 3.5 Flash Image (Nano Banana 2)** — generates a soft, photographed-looking thumbnail per course for the menu PDF, in the chef's plating style. Optional; skippable by chefs who prefer a typeset-only menu.
- **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — reads the menu aloud to the chef on her prep day so she can listen while her hands are in the pasta dough. Native-locale voice per output language.
- **Thinking levels** — `high` for the menu-generation call (cohesion across five courses with parallel plates and allergen constraints is genuinely hard). `medium` for the brief parse and the allergen-matrix generation. `low` for the supplier-cost lookup and the doodle-thumbnail prompt assembly. Surface `thoughtSummary` only when the chef clicks "(i) why this course?" on a course card.
### 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** (used for host invitations and menu sharing) requires the sender domain to be authorised in Firebase Auth. Briefs are private to the chef; hosts see only the menu PDF the chef chooses to share.
- **Database — Required.** Firestore for `users`, `chefs`, `clients`, `briefs`, `menus`, `guests`, `dishes`, `ingredients`, `supplier_prices`, `cuisine_library`, `archive`.
- **File storage — Required.** Firebase Storage for the briefing audio recordings (encrypted at rest, retained until the chef deletes them) + generated menu PDFs + Nano Banana 2 thumbnails. **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console and wire the bucket name into the AIS Build project before first audio upload.
- **Email — Required (transactional).** Menu delivery to the host (Firebase Auth magic links for any host-facing review surface; the menu PDF itself is sent as an email attachment from the chef's own gmail/outlook via a small outbound SMTP relay configured by the chef in settings).
- **Payments — Not needed for v1.** Free for personal use by a working chef. A future "team" tier could charge for a small in-house catering operation with multiple chefs and a shared client roster.
- **External APIs:** Gemini API for all intelligence; optional integration with the chef's existing Square / Stripe / Toast POS for client and invoice data (out of scope for v1 — manual client entry is fine).
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON, SMTP relay credentials) 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 account' inside the UI · explicit opt-in for any analytics · the host's briefing audio is treated as confidential and is never sent to Gemini for model training (use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms) · guest dietary information is personal health-adjacent data and is encrypted at rest · guest names are minimised in any non-essential UI surface.
**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. See the matrix.
3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schema is 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. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow.
7. **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;
}
```
8. **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 length-2 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing.
### Per-call model + tools matrix
| Call | Model | thinkingLevel | Tools enabled |
|------|-------|---------------|---------------|
| Parse briefing audio → `Brief` schema | `gemini-3.5-flash` | medium | (none) |
| Resolve dietary observance details | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) |
| Generate five-course menu with parallel plates | `gemini-3.5-flash` | high | (none) — long-context over brief + supplier sheet + cuisine library |
| Generate allergen matrix from menu + brief | `gemini-3.5-flash` | medium | (none) |
| Suggest swap for a red allergen cell | `gemini-3.5-flash` | low | (none) |
| Cost lookup for missing supplier price | `gemini-3.5-flash` | low | `google_search` grounding |
| Generate course thumbnail | `gemini-3.1-flash-image` | n/a | n/a |
| Generate TTS narration of menu (chef listening on prep day) | `gemini-3.1-flash-tts-preview` | n/a | n/a |
*Note for builders:* on TTS and image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body.
*Note for builders:* `responseSchema` and `google_search` grounding are mutually exclusive in a single Gemini call. On the two grounded calls in this matrix (observance resolution, cost lookup), instruct the model to emit JSON in the text body and parse server-side. Read citation URLs from `response.groundingMetadata.groundingChunks[].web.uri` — do not ask the model to put URLs in the JSON.
### Primary structured-output schema (seed this verbatim in the prompt)
```typescript
import { z } from "zod";
const AllergenSeverity = z.enum([
"anaphylactic", // EpiPen carrier, hospital-history
"severe", // diagnosed, no exposure tolerated
"moderate", // diagnosed, small cross-contact may be okay
"intolerance", // discomfort, not life-threatening
"preference", // dislike, not medical
"unverified", // mentioned without detail — treat as severe by default
]);
const ObservanceKind = z.enum([
"halal",
"kosher",
"hindu_vegetarian",
"jain_vegetarian",
"vegan",
"lacto_vegetarian",
"ovo_vegetarian",
"pescatarian",
"lent_observance",
"ramadan_observance",
"ramadan_suhoor",
"post_bariatric",
"low_fodmap",
"low_sodium",
"diabetic_managed",
"coeliac",
"gluten_avoiding_lifestyle",
"dairy_free_medical",
"dairy_free_lifestyle",
"ketogenic",
"whole30",
"other_specified",
]);
const Allergen = z.object({
ingredient: z.string(), // "peanut", "tree nut", "sesame", "shellfish"
severity: AllergenSeverity,
cross_contact_concern: z.boolean(), // true → avoid even shared utensils/oil
carries_epipen: z.boolean(),
history_note: z.string().nullable(), // verbatim from host: "near-miss as a child"
});
const Guest = z.object({
guest_id: z.string(),
name_verbatim: z.string(), // as the host said it
pronouns_if_mentioned: z.string().nullable(),
relationship_to_host: z.string().nullable(), // "husband's sister"
allergies: z.array(Allergen),
dislikes: z.array(z.string()), // "fennel", "anchovies", "raisins"
observances: z.array(ObservanceKind),
occasion_if_any: z.string().nullable(), // "fortieth birthday"
approximate_age_band: z.enum([
"infant", "toddler", "child", "teen",
"adult", "elder", "unspecified",
]),
notes_for_chef: z.string().nullable(), // anything else the host said about them
});
const Dish = z.object({
dish_id: z.string(),
course_number: z.number().int().min(1).max(5),
course_role: z.enum([
"opener", // a small bite to wake the palate
"first_course",
"vegetable_course",
"main_course",
"second_protein_optional",
"cheese_course_optional",
"dessert",
"occasion_plate",
]),
primary_dish_name: z.string(),
primary_dish_cuisine_note: z.string().nullable(),
primary_dish_short_description: z.string(),
primary_dish_technique_summary: z.string(),
primary_dish_visual_note: z.string(), // "deep brick-red sauce over duck breast, charred herbs"
parallel_plates: z.array(z.object({
diet_label: z.string(), // "vegan parallel", "gluten-free parallel", "halal parallel"
plate_name: z.string(),
short_description: z.string(),
technique_summary: z.string(),
visual_match_note: z.string(), // how it matches the primary visually
served_to_guest_ids: z.array(z.string()),
})),
is_occasion_plate: z.boolean(),
occasion_plate_note: z.string().nullable(), // "carries the candles for the fortieth"
fire_order_note: z.string(), // when this course is fired, relative to others
});
const IngredientLine = z.object({
ingredient_name: z.string(), // "huitlacoche" — preserve diacritics + cuisine of origin
ingredient_name_locale: z.string().nullable(), // "kalamansi (Filipino lime)"
category: z.enum([
"produce", "protein", "dairy", "dry_goods",
"spices", "beverages", "other",
]),
quantity_value: z.number(),
quantity_unit: z.enum([
"g", "kg", "ml", "l", "piece", "bunch", "head", "leaf", "tsp", "tbsp", "cup",
]),
used_in_dish_ids: z.array(z.string()),
supplier_price_per_unit: z.number().nullable(),
supplier_price_currency: z.string(), // "USD", "PHP", "LBP", "AUD"
supplier_price_is_guess: z.boolean(), // true → flag for "your price?"
line_cost: z.number().nullable(),
});
const AllergenMatrixCell = z.object({
guest_id: z.string(),
dish_id: z.string(),
status: z.enum(["safe", "verify", "do_not_serve"]),
reason: z.string(), // "contains peanut — do not serve"
swap_offered: z.string().nullable(), // which parallel plate is served instead
});
const PrepStep = z.object({
when: z.enum([
"supplier_order", // days before
"day_minus_1_morning",
"day_minus_1_evening",
"day_0_morning",
"day_0_afternoon",
"service_arrival",
"service_first_fire",
"service_per_course",
"service_plating",
"service_dessert_fire",
"post_service",
]),
description: z.string(), // "rest the duck breast, dry the skin, score"
duration_minutes_estimate: z.number().int().nullable(),
for_dish_ids: z.array(z.string()),
domestic_kitchen_note: z.string().nullable(), // "uses the oven — coordinate with course 2"
});
const Brief = z.object({
brief_id: z.string(),
client_id: z.string(),
client_name: z.string(), // chef's label for the household
dinner_date_iso: z.string(),
party_size: z.number().int().min(1).max(40),
occasion: z.string().nullable(),
cuisine_direction: z.string().nullable(),// "Mexican-ish"
kitchen_constraints: z.array(z.string()),// "one oven", "no walk-in fridge"
guests: z.array(Guest),
hard_no_ingredients: z.array(z.string()),// derived from the whole guest list
cross_contact_protocols: z.array(z.string()),
proposed_menu: z.array(Dish), // exactly 5 entries except where occasion warrants 4 or 6
allergen_matrix: z.array(AllergenMatrixCell),
shopping_list: z.array(IngredientLine),
prep_brief: z.array(PrepStep),
per_head_food_cost: z.number().nullable(),
total_food_cost: z.number().nullable(),
food_cost_currency: z.string(),
generation_confidence: z.number().min(0).max(1),
flagged_for_chef_review: z.array(z.object({
field_path: z.string(), // "guests[2].allergies[0].severity"
reason: z.string(),
})),
});
type Brief = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent silently downgrades `thinkingLevel` on the the briefing parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash drops nuance in the host's voice (the difference between "she's vegan" and "she's vegan-ish, eats eggs at her mum's house") and silently flattens allergy severity.
- Allergen severity inflated downward when the host is breezy — host says "oh and Mara is gluten-free" without specifying coeliac. Hard rule pinned in the system instruction: `unverified` and `gluten_avoiding_lifestyle` are not the same. If the host did not specify, parse as `unverified` and surface a "confirm with host: is Mara coeliac or lifestyle?" follow-up question. Default behaviour for `unverified` is to treat as severe in the matrix until confirmed.
- The vegan parallel is "a salad" — the model produces a token vegan plate that is visually and gastronomically lesser than the primary. Hard rule: every parallel plate must match the cuisine, the technique, the visual weight, and the position on the plate. If the primary is a seared duck breast in a mole, the vegan parallel is a roasted vegetable in the same mole, not a green salad.
- Allergen matrix shows `safe` by default — wrong default. The default for any unverified ingredient-versus-allergen pair is `verify` (amber), and any ingredient that is explicitly in the dish and matches an allergen is `do_not_serve` (red). Green is earned, not assumed.
- "Mexican-ish" interpreted as Tex-Mex chain cuisine — flatten the prompt. Hard rule: respect the chef's saved cuisine library; treat the host's directional words as a soft input that is filtered through the chef's actual repertoire. The mole the chef cooks is the chef's mole.
- Costs invented at retail-grocery prices — the model fills in supplier prices from training data. Hard rule: never fill in a supplier price the chef did not save; flag missing prices and surface them as "your price?" prompts; the cost lookup call is the only path to a non-chef-provided number, and it carries a citation.
- Dietary observance over-confident — model asserts "Jain observance allows fish" or "kosher allows shellfish if blessed" — both wrong. Hard rule: any observance the chef has not personally cooked for in this archive triggers the grounded search call before menu generation; the result is included in the system context for the menu call as a fenced rules block.
- Halal request without an alcohol stance auto-allowed — model assumes wine in reduction is fine. Hard rule: if a halal guest is on the list and any dish contains alcohol in any form (cooking wine, beer batter, mirin, vanilla extract above trace), flag it as `do_not_serve` and offer a swap. Cooked-off alcohol is not a get-out.
- Cross-contact ignored — a peanut anaphylactic guest at the table, the menu contains a satay sauce on one parallel plate. Hard rule: if any guest has anaphylactic severity for any ingredient, that ingredient is excluded from the entire menu, not just from that guest's plate. The kitchen does not work with it that night.
- Audio briefing parsed without speaker separation — the host and the chef both talk; the model attributes the chef's clarification ("I usually do that with cashews") to the host as a guest preference. Hard rule: identify each utterance's speaker where possible; if unclear, treat any factual claim about a guest as host-asserted only when supported by host phrasing ("she", "my sister", "the kids"); chef phrasing ("I usually", "I tend to") is parsed into chef_notes, not guest fields.
- Birthday plate is generic — model produces "a chocolate cake" without acknowledging the host's hint ("there's a chocolate cake situation"). Hard rule: when the host names a specific dish or hints at one, the occasion plate carries the named dish (or its closest cousin from the chef's library) and is flagged `is_occasion_plate: true`.
- Long-context call ignores supplier sheet — model invents prices even though the sheet is in context. Hard rule: the system instruction pins "use only the supplier_prices supplied in context; flag any ingredient not on the sheet".
### Negative constraints (hard rules)
- Do NOT under-flag any allergen. When in doubt, RED. Better an over-flag the chef confirms down to amber than an under-flag that hospitalises a guest.
- Do NOT translate ingredient names away from their cuisine of origin. "huitlacoche", "kalamansi", "za'atar", "fenugreek", "katsuobushi" stay verbatim in the menu and the shopping list, with a parenthetical English gloss on first occurrence only ("kalamansi (Filipino lime)") and never thereafter.
- Do NOT invent supplier prices. If a price is not in the chef's saved sheet, the line carries `supplier_price_is_guess: true`, surfaces a "your price?" prompt to the chef, and the per-head food cost is not finalised until the chef confirms.
- Do NOT skip the dietary-observance grounded lookup for observances the chef has not previously briefed for. The grounded result is part of the menu-generation context; menu generation is blocked until the lookup completes.
- Do NOT propose a parallel plate that is visually or gastronomically lesser than the primary. The vegan plate eats as well, looks as good, and lands on the table at the same time as the omnivore plate.
- Do NOT auto-publish or auto-send the menu. The chef reviews the brief, the matrix, the costs, and the prep brief in app, makes any swaps, and only then sends the PDF.
- Do NOT use the chef's briefing audio, guest names, or guest allergy data 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 ignore alcohol when a halal or pregnant or recovering guest is on the list. Cooking wine, beer batter, mirin, sake, brandy in the chocolate cake, vanilla extract above trace all count. If alcohol is present, the dish is flagged and a swap is offered.
- Do NOT modernise or smooth observance language. "Jain vegetarian" stays "Jain vegetarian", not "strict vegetarian"; "halal" stays "halal", not "Muslim-friendly"; "kosher" stays "kosher", not "Jewish-friendly".
### 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: Parse briefing audio → `Brief` schema
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You are listening to a private chef brief a dinner-party host about an
upcoming dinner. The audio is typically 3-30 minutes long. Two voices
appear in most recordings: the host (the person planning the dinner)
and the chef (the user of this app). Either may speak first; either may
talk over the other; either may interrupt to clarify. Where the audio
makes speaker identification reliable, attribute each utterance. Where
it does not, fall back on linguistic cues — the host uses guest names
and possessive phrases ("my sister", "his mum"); the chef uses
ingredient names, technique words ("I usually braise that"), and
clarifying questions ("does she eat shellfish?").
The audio is uploaded via the Gemini Files API (`files/*` resource name) or sent as
`inlineData` (base64). Do NOT pass Firebase Storage public URLs
directly to `generateContent` — the API does not fetch them server-side.
Your task: produce the Brief JSON matching the provided schema. Capture
every guest the host names. Capture every allergy, dislike, observance,
and occasion-detail the host mentions. Capture every constraint the
chef mentions about the kitchen ("they have one oven", "no induction
hob"). Capture the cuisine direction the host steers toward, even if
loose ("something Mexican-ish", "an autumn menu", "nothing too heavy
because they're driving home after").
Languages encountered include English, Spanish (Mexican, Castilian,
Caribbean, Argentinian registers), Tagalog with English code-switching,
Mandarin and Cantonese, Arabic (Lebanese, Egyptian, Gulf, Maghrebi
registers), French, Tamil, Hindi in Devanagari script when chef names
ingredients in text follow-up, Urdu in Nastaliq, Bengali, Punjabi
(Gurmukhi and Shahmukhi), Farsi, Amharic in Ge'ez, Swahili, Khmer,
Vietnamese, Korean, Japanese, Portuguese (Brazilian and European),
Italian, German. The host and chef may code-switch mid-sentence;
preserve their language choices.
Hard rules:
- ALLERGEN FLAGGING IS CONSERVATIVE. When in doubt about severity,
default to `unverified`. Treat `unverified` as severe in downstream
matrix generation. The model's job is to over-flag here, not
under-flag. A hospital visit is worse than a swap the chef later
declines.
- Identify allergies precisely. "She's allergic to peanuts and is
anaphylactic about it" → severity: anaphylactic, carries_epipen:
inferred-true, history_note: verbatim quote from host. "She doesn't
do peanuts" → severity: unverified, history_note: verbatim quote.
- Identify observances precisely. "She's vegan" → vegan. "She's
vegan but eats honey" → vegan with notes_for_chef explaining the
honey exception. "She does Jain" → jain_vegetarian AND surface a
`flagged_for_chef_review` entry asking the chef to confirm Jain
rules with the host (no root vegetables, no eggs, no honey).
- Identify occasions precisely. "It's his fortieth" → guest with
occasion_if_any: "fortieth birthday" AND a Brief-level `occasion`
field "fortieth birthday dinner".
- Preserve verbatim what the host said. The `name_verbatim` field is
exactly what the host called the guest, even if the spelling is
ambiguous ("Mara"? "Marra"? "Marrah"? — pick the most likely and
flag for chef review).
- Distinguish dislikes from allergies. "Hates fennel" is a dislike,
not an allergy. "Hates anchovies" is a dislike. "Can't have
anchovies because of an allergy" is an allergy.
- Capture cross-contact concern. If the host uses words like
"can't even be in the kitchen with peanuts", "needs separate
utensils", "epipen", flag cross_contact_concern: true.
- Preserve language and code-switching. If the host says "Lola
has the allergy" in English mixed with Tagalog, preserve the
Tagalog word verbatim.
- Do NOT invent guest names. If the host says "my husband's sister"
without a name, use name_verbatim: "[husband's sister]" and flag.
- Do NOT invent allergies. If the host did not mention an allergy
for a guest, the allergies array is empty.
- Do NOT decide the menu in this call. Menu generation is a separate
call.
- Distinguish chef utterances from host utterances. The chef's
ingredient mentions ("I usually braise that with allspice") are
context for the menu-generation call, not guest preferences.
Output ONLY the Brief JSON matching the provided schema.
No commentary. JSON only.
```
---
### Call: Resolve dietary observance details
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: search grounding
```
You receive the name of a dietary observance ("Jain vegetarian",
"halal", "kosher", "Hindu shraadh meal", "Ramadan suhoor", "Eastern
Orthodox Lent in week three of fast", "Mormon Word of Wisdom",
"Coptic Lent on a fasting day", "post-bariatric stage four"). Your
task: produce a short rules document the menu-generation call will
treat as authoritative.
Use `google_search` grounding for every observance. Do not rely on
training memory — practices vary by community, by sect, by region.
Return a JSON object in the text body:
- observance_name (verbatim)
- short_description (one sentence)
- excluded_ingredients (array of strings, conservative)
- conditionally_excluded (array of {ingredient, condition})
- required_practices (array, e.g. "separate utensils for dairy and
meat" for kosher)
- contested_or_varies (array of strings explaining where practice
varies, e.g. "some Jains permit dairy; many do not")
- source_citation_urls (array — you will not populate this; the
server reads citations from groundingMetadata)
Hard rules:
- Conservative defaults. If a practice varies, exclude the contested
ingredient. The chef can re-include later with the host's blessing.
- Preserve the observance's own naming. "Jain vegetarian" stays "Jain
vegetarian", not "strict vegetarian".
- Note regional variation explicitly in contested_or_varies. The
Iftar of a Cairo family differs from the Iftar of a Dhaka family.
- Do NOT assume. If a host did not specify their sect of Lent (Roman
Catholic? Eastern Orthodox? Coptic?), flag this and exclude the
intersection of all common Lent practices.
- Do NOT ask the model to put URLs in the JSON — it will hallucinate
them. Citation URLs are read server-side from
groundingMetadata.groundingChunks[].web.uri.
Output the rules JSON in the text body. No commentary.
```
---
### Call: Generate five-course menu with parallel plates
Model: `gemini-3.5-flash` · thinkingLevel: high · Tools: (none — long context)
```
You are a private-chef sous-chef. You receive the parsed Brief
(every guest, every allergen, every observance, every dislike, the
occasion, the cuisine direction), the chef's saved cuisine library
(her usual repertoire and her sauces, by cuisine), the chef's saved
supplier sheet (every ingredient she has bought before, with her
price), and a fenced rules block per dietary observance present at
the table (produced by the grounded observance call).
Your task: propose a cohesive five-course menu (occasionally four or
six if the occasion warrants) with a parallel plate per course for
each diet present at the table. The menu honours the cuisine
direction the host steered toward; respects every allergen at the
strictest interpretation; protects against cross-contact; threads the
occasion plate through; and fits within the kitchen constraints the
chef recorded.
Hard rules:
- ALLERGEN PROTECTION IS NON-NEGOTIABLE. If a guest has an
anaphylactic-severity allergen, the menu does NOT contain that
ingredient in any course, in any sauce, in any garnish, including
on parallel plates other guests eat. The kitchen does not work
with it that night.
- PARALLEL PLATES ARE FIRST-CLASS. Every parallel plate matches the
primary in technique, weight, and visual register. A vegan
parallel for a seared duck is a roasted celeriac or king oyster
in the same sauce, on the same plate shape. Not a salad. Not
"the vegetable from course two, with more of it".
- OCCASION PLATE IS NAMED. If the brief carries an occasion (a
fortieth, an engagement, an anniversary), one course is the
occasion plate. It carries the host's hinted dish if specified,
or the closest cousin from the chef's library. Its
`is_occasion_plate` flag is true; its occasion_plate_note explains
the gesture for the menu PDF.
- COHESION ACROSS COURSES. The five courses tell a story. Heavy
follows light, sharp follows soft, sweet closes. Repeating
ingredients across courses is permitted only when the technique
meaningfully differs. Do not put the same braised shallot in
courses two and four.
- USE THE SUPPLIER SHEET. Every ingredient is named exactly as the
chef has it in her sheet. If you propose an ingredient she does
not have, you have introduced a "your price?" prompt and the
chef must confirm. Prefer her existing ingredients.
- HONOUR THE CHEF'S CUISINE LIBRARY. The host's directional words
("Mexican-ish") are filtered through the chef's library. The mole
she cooks is the mole that goes on the menu. If she has three
moles in her library (rojo, verde, negro), propose the one that
best fits the constraints (rojo and negro often have peanuts
and tree nuts; verde with pumpkin seeds is often the safer
choice for a peanut-anaphylactic table).
- DOMESTIC KITCHEN CONSTRAINTS. The fire order respects ONE oven,
one hob with four rings, one fridge, no walk-in. Do not propose
three courses that all need the oven at 200°C at the same time.
- KIDS AND ELDERS. If the guest list contains an `infant`,
`toddler`, `child`, or `elder`, name a softer variant of the
course they will eat. For an elder with a sensitive stomach,
the parallel plate is gentler in chilli; for a toddler, the
parallel plate is recognisable food (a small portion of the
protein, a known starch).
- PRESERVE INGREDIENT NAMES IN THEIR CUISINE OF ORIGIN. huitlacoche,
kalamansi, za'atar, sambal oelek, gochujang, dashi, kasundi,
panch phoron — never anglicise. First occurrence in the menu may
carry a brief parenthetical English gloss.
- DO NOT INVENT PRICES. Supplier prices come from the supplier sheet
only. Anything else is flagged `supplier_price_is_guess: true`.
Output ONLY the Brief JSON, with proposed_menu populated. The
allergen_matrix and shopping_list will be populated by the
subsequent calls.
No commentary. JSON only.
```
---
### Call: Generate allergen matrix from menu + brief
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You receive the parsed Brief (guests + allergens) and the proposed
menu (dishes + parallel plates with their full ingredient lists).
Your task: produce the allergen matrix — one cell per (guest, dish)
pair, with status, reason, and the swap offered for any red cell.
Hard rules:
- DEFAULT IS NOT GREEN. The default status for any (guest, dish) cell
is `verify` (amber). Green is earned by the dish containing none of
the guest's allergens AND no cross-contact concern.
- ANAPHYLACTIC SEVERITY IS CATEGORICAL. If a guest has an
anaphylactic-severity allergen and the dish (or its parallel plate
served to that guest) contains that ingredient in any form, status
is `do_not_serve` and a swap is named.
- CROSS-CONTACT IS PART OF THE FLAG. If a guest has
cross_contact_concern: true for peanuts, and the menu uses peanut
oil in a separate course that is fired in the same pan, the cell
is amber with reason "shared pan; clean and re-season before
course X".
- UNVERIFIED SEVERITY IS TREATED AS SEVERE. If severity is
`unverified`, the cell is `do_not_serve` for any dish containing
the ingredient; the chef can confirm with the host and downgrade.
- ALCOHOL IS AN ALLERGEN FOR HALAL, PREGNANT, RECOVERING. If any
guest has any of these and the dish contains alcohol in any form
(cooking wine, beer, mirin, sake, brandy, vanilla extract above
trace), status is `do_not_serve` and a swap is named.
- SWAP IS NAMED, NOT VAGUE. "Swap: serve the verde mole parallel"
not "Swap: vegetarian option". The cell points at the specific
parallel plate the guest receives.
- REASON IS HUMAN-READABLE. The cell's reason goes on the chef's
printed matrix; she reads it at a glance the day of service.
Output ONLY the updated Brief JSON with allergen_matrix populated.
No commentary. JSON only.
```
---
### Call: Suggest swap for a red allergen cell
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive one red allergen cell — a guest, a dish, an allergen
match — and the rest of the menu. Your task: propose a swap for that
guest.
Hard rules:
- The swap is a parallel plate that already exists in the menu where
possible. If the menu already carries a vegan parallel and the
red cell is for a vegan guest who reacted to a dairy ingredient
in the omnivore plate, the swap is "serve the vegan parallel".
- If no existing parallel matches, propose a new parallel plate
that fits the cuisine, the technique, and the visual register of
the primary, AND that avoids ALL the guest's allergens.
- Name the swap concretely. "Serve the verde mole parallel with
pumpkin seeds (no peanuts, no tree nuts)" — not "Serve a vegan
option".
- The swap respects every other guest at the table. If the swap
introduces a new ingredient that triggers another guest's
allergen, surface that as a follow-up.
Output: a single string description of the swap. No commentary.
```
---
### Call: Cost lookup for missing supplier price
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: search grounding
```
You receive an ingredient the chef does not have in her supplier
sheet, her city, her currency, and the date of the dinner. Your task:
return an estimated supplier price per the relevant unit (per kg,
per piece, per litre) for a wholesale-grade supplier in her city,
with a citation.
Use `google_search` grounding. Do not rely on training memory; prices
move.
Return a JSON object in the text body:
- ingredient (verbatim)
- estimated_price_per_unit (number)
- unit (string)
- currency (string)
- supplier_type_assumed (e.g. "restaurant wholesale supplier")
- city
- confidence ("low", "medium", "high")
- note_for_chef (one sentence)
Hard rules:
- This is an estimate, not a quote. The chef confirms before the
costed brief is final.
- Cite the search result(s) the estimate came from — server reads
citations from groundingMetadata. Do not put URLs in the JSON.
- Mark confidence honestly. If the search returned only retail
prices (Whole Foods, Carrefour, Lulu), confidence is low — the
chef's wholesale price is meaningfully different.
- Currency matches the chef's saved supplier currency. Do not
convert.
Output the estimate JSON in the text body. No commentary.
```
---
### Call: Generate course thumbnail
Model: `gemini-3.1-flash-image` · n/a · n/a
```
You generate one small, soft, photographed-looking thumbnail of a
plated course for inclusion in the menu PDF. Style: warm, natural
window light, the plate filling 60-70% of the frame, the rest of the
frame a softly out-of-focus napkin or wood table. NEVER a glossy
food-photography render. The thumbnail evokes the chef's actual
plating style — restrained, ingredient-led, one focal element.
Prompt template (the server fills the {} placeholders from the dish
record):
"A small, soft, photographed thumbnail of {dish_name}, plated on a
{plate_shape} ceramic plate, with {visual_note}. Natural warm window
light, 1pm afternoon, a {napkin_color} linen napkin softly out of
focus at the edge, a worn {table_material} table. No people, no
hands, no logos, no text on the plate. Realistic, gentle, asymmetric
plating. Avoid glossy food-magazine style. Avoid over-styling. Avoid
herbs scattered as garnish unless the dish specifies them."
Do NOT generate text rendered on the plate or napkin. Do NOT include
recognisable real chefs, restaurant logos, or branded plateware.
```
---
### Call: Generate TTS narration of menu (chef listening on prep day)
Model: `gemini-3.1-flash-tts-preview` · n/a · n/a
```
Voice: calm, professional, like a sous-chef reading the day's menu
to the brigade at handover. Pick the Gemini 2.5 Flash TTS voice
whose `languageCode` matches the chef's chosen output language —
pronunciation will follow that locale automatically.
Pre-process the text before sending it to TTS:
- Read the menu in order: course number, course name, the primary
dish, then any parallel plates with a short "served to {guest
names}" cue.
- At each course boundary, insert a blank line plus an em-dash (—)
for a beat of silence. Gemini 2.5 TTS does not support SSML
``; these textual cues are how you signal pace.
- At each line break inside a course description, insert an
ellipsis (…) for a natural pause.
- Skip the allergen matrix, the shopping list, and the prep brief.
Those are read on the printed page, not aloud.
- Target rate: ~130 words per minute — handover pace, not a podcast.
Style direction: prepend ONE short directive sentence to the text
input: "Read calmly and clearly, as a sous-chef reading the day's
menu to the brigade. …". There is no separate `style` API field on
Gemini 2.5 TTS; the directive sentence inside the input is how
style is conveyed.
Ingredient names from other cuisines (huitlacoche, kalamansi,
za'atar) are pronounced according to the chosen voice's native
locale. No phoneme overrides are supported.
```
## 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 fortieth in Austin.** A Mexican-American chef in Austin briefs a party of eight for a husband's fortieth birthday. One guest is anaphylactic to peanuts and probably tree nuts. One is dairy-free for medical reasons. One is coeliac with formal diagnosis. One is vegan but eats honey. The host wants "Mexican-ish, because it's his fortieth". The chef's library carries three moles; the app picks verde because rojo and negro both routinely carry peanuts and tree nuts. The birthday plate is a flourless chocolate torte with coconut-cream chantilly so the dairy-free mother and the coeliac friend both eat it.
- **The corporate Iftar in Makati.** A Filipino chef briefs an in-home Ramadan Iftar for twelve. Eight guests are halal, two are vegetarian, one is gluten-avoiding, one is diabetic-managed. The chef has not personally cooked an Iftar before; the grounded observance call returns the rules block (no pork, no alcohol in any form including in vanilla extract above trace; suhoor protein-led; Iftar opened with dates and water). The app threads the cuisine through Filipino-Lebanese fusion the chef has built her career on.
- **The Easter Sunday lunch in Beirut.** A Lebanese chef briefs an Easter Sunday lunch for sixteen. One guest is coeliac; one is on a low-FODMAP protocol; one cannot have sesame because of a childhood near-miss. The chef's pantry runs on tahini and za'atar — both sesame-bearing. The app proposes a no-sesame parallel for every course, with a cross-contact protocol (separate boards, sanitised pan, no shared oil); the chef confirms and the menu carries a small note at the bottom: "no sesame in any dish, no cross-contact with sesame in the kitchen".
- **The Diwali dinner in Parramatta.** A Punjabi-Australian chef briefs a Diwali dinner for twenty. The menu is 100% vegetarian; three guests are Jain (no root vegetables, no eggs, no honey, no fungi); one toddler will not eat anything green. The chef has cooked Jain meals once before; the grounded observance call returns the Jain rules block; the menu uses raw bananas instead of potatoes, asafoetida instead of garlic and onions, and a saffron rice pudding without honey for the dessert. The toddler's parallel is a small bowl of plain dal and rice.
- **The pen-pal anniversary in Manila.** A Filipino chef briefs a thirtieth-anniversary dinner for ten. The host's husband loves duck and hates fennel. The host's mother does not eat dairy. The chef's library carries adobo'd duck — the menu's main course. The vegan parallel is jackfruit adobo with the same lacquer. The cake is a flourless chocolate-coconut torte. The host had asked for "no anchovies in the dressings because he tastes them a mile off" — the app surfaces this as a hard no in the brief and propagates it through every line of the shopping list.
- **The shiva dinner in Brooklyn.** A Brooklyn chef briefs the post-funeral dinner of a Jewish family the week of mourning. The host wants kosher-style though the family is not strict; one guest from out of town is genuinely kosher and will not eat anything that has not been prepared on separate utensils. The app produces a kosher-strict parallel for that one guest, with a printed protocol page the chef hands to the host so the kashering-kit is ready.
- **The bariatric anniversary in Buenos Aires.** A chef briefs an anniversary dinner for six where the host is six months post-bariatric surgery and is on a stage-four diet — small portions, protein-led, no sugar, no carbonation. The app builds a five-course menu of small protein-led portions; the parallel for every other guest is the same dish at a larger portion. The chef does not have to cook two separate menus.
- **The pop-up dinner in Beirut.** A Lebanese chef runs a six-night pop-up residency at a borrowed kitchen in Mar Mikhael. Each night brings a different ten-person party with a different brief. The app handles six briefs per week as separate menus on the same supplier-sheet base. Friday's three-allergen brief overlaps with Saturday's no-pork brief; the chef sees on Wednesday morning what ingredients she can buy once and use across both nights.
- **The retreat in Bali.** An Australian chef caters a yoga retreat for fourteen in a Balinese villa over four nights. The brief comes in once on Tuesday and yields four menus, with the prep brief sequenced across the week and the shopping list aggregated for one Tuesday-afternoon market trip. The cross-contact protocol carries forward across all four nights.
## 6. Page structure
Build the following screens / sections in this order. Adjust copy to fit the voice, but keep the structural intent.
1. **Welcome / sign-in.** A photographed-looking image of a chef at a kitchen table with an open notebook and a phone on speaker, an apron over a chair. One paragraph: "Tasting-Menu Brief turns a three-minute call with your host into a five-course menu with allergens, swaps, and a costed shopping list." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample brief" → loads the demo brief in section 8a.
2. **Empty state — "Start a brief".** Two big input methods: 🎙 Record a call · ✍ Type a brief. Short explainer below each ("Best for an actual phone call with the host", "Best if you already have an email from the host with the guest list"). A small button under: "Use the sample brief → Austin fortieth, party of 8".
3. **Voice intake screen.** Big red record button. Live waveform; live elapsed time; pause/resume. Subtitle area shows the running transcript with speaker labels (Host · Chef) once the first 30 seconds are processed. The chef can stop at any time; the app uploads the audio file, kicks off the parse, and routes to the processing queue.
4. **Manual intake form.** A clean form: client name, dinner date, party size, occasion (optional), cuisine direction (optional), kitchen constraints (optional), and a repeatable Guest block (name, allergies, observances, dislikes, occasion-for-this-guest, age band). Save and continue → processing queue.
5. **Processing queue / brief in progress.** A vertical list of the brief's stages with honest, step-by-step labels: "Listening to the host…" → "Resolving Jain rules with grounded search…" → "Drafting the menu…" → "Checking every allergen…" → "Costing the shopping list…". Each step takes 4-20 seconds. The chef can close the app and come back.
6. **Brief review — the menu canvas.** A large central canvas: five course cards (or four / six) stacked vertically on mobile, side-by-side on desktop. Each card carries the primary dish on the left, parallel plates on the right, a fire-order note at the bottom, and a small "(i) why this course?" icon revealing the model's reasoning. The birthday plate is highlighted with a thin sepia border and a small candle icon. The chef can swap any course in place — tap the primary, choose another dish from her library, the parallel plates and allergen matrix re-resolve automatically.
7. **Allergen matrix.** A grid: guest rows, dish columns. Cells are coloured green / amber / red with a single-character status. Tap any cell → see the reason, the swap, and a "confirm with host" affordance if the cell carries an unverified severity. A footer row aggregates "hard nos across the table" — the ingredients excluded entirely.
8. **Shopping list.** A printable list in supplier-sheet order, with quantities in grams or millilitres or pieces, prices from the saved sheet, totals at the bottom, and a per-head food cost. Anything not in the sheet is flagged with an amber dot and a "your price?" prompt. The chef can edit any number; the per-head cost re-totals.
9. **Prep brief.** A timeline of prep steps from "supplier order — Wednesday" through "post-service" — with the chef's domestic-kitchen constraints honoured (one oven, four rings, one fridge, no walk-in). Each step carries a duration estimate and links to the dishes it serves. A "fire order on the night" sub-section sequences the courses with the precise minute each one goes on.
10. **Menu PDF preview.** A four-page PDF: page 1 — the host-facing menu (typeset, course-by-course, with optional Nano Banana 2 thumbnails); page 2 — the allergen matrix; page 3 — the shopping list; page 4 — the prep brief. "Send to host" emails the menu pages only; "Print all four" gives the chef her working set.
11. **Client archive.** Every brief saved per client. Tapping a past brief reveals last year's menu, the swaps, the prices paid. Next year's anniversary picks up the previous year's notes ("last year the brother could not have shellfish; confirm still true?").
12. **Supplier sheet.** A simple editable table: ingredient · category · supplier · unit · price · last updated. Anything the chef has bought before lives here. The app updates `last_updated` every time a price flows through a shopping list.
13. **Cuisine library.** The chef's repertoire: dishes she actually cooks, organised by cuisine and by course role (opener, vegetable course, main, dessert). Each dish carries its ingredient list, its technique summary, its visual note, and the parallel plates it usually carries. The menu-generation call draws from this library.
14. **Footer.** "Made for the chef cooking in someone else's kitchen." Privacy: "Your briefs are yours. We never train on them." Capabilities `(i)` icon in header.
## 6b. First-visit onboarding
Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it.
**Slide 1 — What this is.**
- Headline: "Welcome to Tasting-Menu Brief."
- Subhead: "Voice the guest list — get a five-course menu that feeds everyone, hurts nobody, and lists every gram in your basket."
- One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic recipe app: it parses an actual conversation with the host, flags every allergen conservatively (when in doubt, RED), produces parallel plates that match the primary in cuisine and weight, and costs the shopping list from the chef's own supplier sheet.
- Visual: a small annotated illustration of a five-card menu strip with the allergen matrix below — not a generic chef's-hat icon.
**Slide 2 — Try it now.**
- One short prompt: "Try with the sample brief".
- A live demo input pre-loaded with the Austin fortieth brief from the seed content in section 8a.
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the peanut row of the allergen matrix in red across all courses, the vegan parallel for the duck course, the flourless chocolate torte as the birthday plate).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the sample brief in `/data/seed-briefs/` for your own first client."
- "Adjust the prompts in `/server/prompts/` to fit your cuisine."
- "Save your supplier sheet in `/data/supplier-sheet.csv` and your cuisine library in `/data/cuisine-library.json`."
- Primary CTA: "Use this template" → links to AI Studio Build remix entry point.
- Secondary: "Just exploring — close" (sets localStorage flag, never auto-shows again).
**Accessibility:** focus trap, `Esc` closes, `role="dialog"`, `aria-modal="true"`, `aria-labelledby`, focus restored to trigger on close. Respect `prefers-reduced-motion`.
**Don't:**
- Don't gate content behind the modal. The page beneath must be fully usable.
- Don't auto-reshow on return visits. Use `localStorage['onboarding-seen-v1']`.
- Don't include unrelated CTAs (newsletter signup, social follow). Keep it about the template only.
## 6c. Capabilities info button (persistent in header)
Add a persistent `(i)` icon in the top-right of the header (next to the primary nav). Click → opens a modal/panel titled **"What powers this app"**.
**Panel contents (in this order):**
**Gemini capabilities used (the hero list):**
- **Gemini 3.5 Flash (multimodal audio + long context)** — listens to the host briefing call, separates speakers, captures every allergy, observance, dislike, and occasion-detail in one pass. The same call can take a 30-minute conversation.
- **Gemini 3.5 Flash (long context, high thinking)** — the menu-generation call sees the brief, your cuisine library, your supplier sheet, and the dietary observance rules block at once. The five courses are designed cohesively, with parallel plates that match in cuisine and weight.
- **Gemini 3.5 Flash + grounded search** — resolves dietary observances (Jain, halal, kosher, Lent, post-bariatric) with citations, not from model memory. Observance practices vary; the grounded result protects you against assumption.
- **Gemini 3.5 Flash** — produces the per-allergen swap suggestions and the cost-lookup for ingredients you have not yet saved in your supplier sheet.
- **Gemini 3.5 Flash Image (Nano Banana 2)** — generates small, soft, photographed-looking thumbnails per course for the menu PDF. Optional; skippable.
- **Gemini TTS** — reads your menu aloud on prep day so you can listen while your hands are in the dough.
- **Firebase Auth** — Google and Apple sign-in for you (the chef). Hosts receive the menu PDF by email; they do not need an account.
- **Firestore** — stores your briefs, clients, supplier sheet, and cuisine library, synced across your phone and laptop.
- **Firebase Storage** — encrypted audio recordings, generated PDFs, and Nano Banana 2 thumbnails. You delete them at any time.
- **Cost note** — see the detailed breakdown in 6d. A typical brief (one dinner, eight guests) costs about $0.30 of Gemini API spend, total, processed once.
- **Privacy note** — your client briefs are private to you. This app uses the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. Guest dietary information is health-adjacent data and is encrypted at rest.
**Backend services this app depends on:**
- Auth: see section 4b
- Database: see section 4b
- Storage: see section 4b
- Email: see section 4b
- Payments: see section 4b (not used in v1)
- External APIs: see section 4b
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only)
- `SMTP_HOST` / `SMTP_USER` / `SMTP_PASS` — outbound email relay for sending the menu PDF to your host (use your existing Gmail / Outlook credentials)
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: the menu-generation call uses `high` thinking and runs once per brief, costing about $0.10. The audio-parsing call ranges $0.05-$0.20 depending on how long your host talked.
- One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this brief forever" — gone in 60 seconds), what is never sent for training.
**Documentation links:**
- AI Studio Build docs
- Gemini API multimodal-audio, long-context, grounded-search, TTS docs
- Firebase Auth, Firestore, Firebase Storage docs
- A short note on the conservative-allergen-flagging rule
**Accessibility:** same standards as the onboarding modal — focus trap, `Esc`, ARIA, restored focus.
**Behaviour:**
- Always available — single click from anywhere in the app.
- Tooltip on the `(i)` icon: "How this app is built".
- Mobile: opens as a full-screen sheet that slides up.
- Should be the most honest part of the app — never hand-wave service requirements; never say "AI" without naming the specific Gemini model and capability.
## 6d. Detailed cost breakdown (deployer reads this BEFORE shipping)
- **Parse briefing audio (Gemini 3.5 Flash, medium thinking)** — a typical 5-minute briefing call is ~5k input tokens of audio (Gemini bills audio at ~32 tokens/second) plus ~3k output tokens of structured Brief JSON. ~$0.02-$0.08 per brief depending on call length.
- **Resolve dietary observance (Gemini 3.5 Flash + grounded search)** — runs once per distinct observance per brief; ~$0.002/observance. Most briefs trigger 0-2 lookups.
- **Generate five-course menu (Gemini 3.5 Flash, high thinking, long-context)** — long-context input (brief + supplier sheet + cuisine library + observance rules) is ~20-40k tokens; output is ~5k tokens of structured Menu. ~$0.07-$0.15 per brief.
- **Generate allergen matrix (Gemini 3.5 Flash, medium thinking)** — ~$0.02 per brief.
- **Swap suggestion (Gemini 3.5 Flash)** — ~$0.0005/swap. Most briefs trigger 0-5 swaps.
- **Cost lookup (Gemini 3.5 Flash + grounded search)** — ~$0.002/lookup. Most briefs trigger 0-3 lookups (only for ingredients not in the supplier sheet).
- **Course thumbnail (Gemini 3.5 Flash Image / Nano Banana 2)** — ~$0.03/image. Five courses ≈ $0.15 per brief. Optional; skippable.
- **TTS narration (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A typical menu narration is ~2,000 characters ≈ $0.006 per narration. Cached per menu; charged once.
- **Expected per-brief cost:** ~$0.20-$0.40 depending on observances triggered, swaps needed, and whether thumbnails are generated. **Typical chef's monthly cost** at four briefs a week: ~$5-$8/month of Gemini API spend.
- **Storage:** Firebase Storage standard tier, ~$0.026/GB/month. A typical audio recording is ~5 MB; a typical menu PDF is ~1 MB; a year's worth of one chef's briefs ≈ ~5 GB ≈ $0.13/month.
## 7. Design language
- **Mood:** A working chef's kitchen at 3pm, between lunch service and the evening's dinner. Not a glossy food magazine. Not a restaurant POS. The notebook open on the bench, the apron over the chair, the kettle warm, the next dinner sketched in pencil. Warm, considered, functional.
- **Typography:** Display serif for menu content and the dish names on the printed PDF (Source Serif Pro or Adobe Caslon). A handwriting-styled accent (sparingly) only for the chef's own pencil annotations and for the "(i) why this course?" callout — never for the structured allergen matrix, which sits in clean grotesque. Clean grotesque for app chrome (Inter or Geist).
- **Palette:** Bone-paper background `#F4EFE6` for the menu canvas, deep ink `#1B1714` for body text, a herb green `#5A6E4A` for the safe cells of the allergen matrix, a warm amber `#C28A2A` for the verify cells, a confident red `#A33A2C` for the do-not-serve cells (the same red as the redaction palette in series A — kept consistent across the gallery). A muted blue `#3A5773` for the chef's own annotations. Borrowed from a working-kitchen notebook, not from a SaaS dashboard.
- **Imagery:** The course thumbnails (Nano Banana 2) are soft and photographed-looking — natural window light, asymmetric plating, no scattered-herb garnishes. The hero image is a chef's hands on a wooden bench, an open notebook, a kettle in the background — photographed-looking, not rendered. Period or chain-restaurant references explicitly avoided.
- **Hand-feel touches:** A barely-visible paper grain on the menu-canvas background. The allergen matrix cells subtly inset (1px) so the red cells read as "punched out" rather than "stuck on". The fire-order timeline uses a thin vertical line with small tick marks, like a chef's prep-list at the pass.
- **Spacing:** consistent 4-px base. Generous whitespace — the courses need air.
- **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Course cards use 6; the menu-PDF preview uses 12; the welcome card uses 20.
- **Shadows:** subtle, layered, warm-tinted. Avoid heavy drop-shadows.
- **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The matrix-cell colour transition (when a swap is applied and an amber turns green) is the one place where motion carries meaning; respect reduced-motion by jumping rather than animating.
- **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Record your briefing call to start", "Add your first ingredient to your supplier sheet").
## 8. Content generation rules
- Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'.
- Invent plausible names, dates, occasions, dishes, ingredient names, quantities, and prices that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real-world chef vocabulary across cuisines — Mexican-American moles by name (rojo, verde, negro, amarillo), Filipino dishes by name (adobo, sinigang, kare-kare, kinilaw), Lebanese dishes by name (kibbeh, fattoush, muhammara, sayyadiyeh), Punjabi vegetarian dishes by name (chana masala, baingan bharta, dal makhani, sarson da saag) — but never claim that a fictional client or dinner is real.
- Tone: warm, direct, free of corporate language. This template is for a working chef, not a food-tech company.
- Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'.
- Body copy: short paragraphs (2-4 sentences). Use lists where appropriate.
- Plain language. Avoid jargon — except where the user already speaks the jargon (the chef wants to see "fire order", "parallel plate", "supplier sheet", "mise en place"; the host wants to see "menu", "allergens", "shopping list").
- Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. Use small uncertainty cues only where epistemic honesty requires them (an amber matrix cell shows a tiny dot; tapping it reveals the verification step).
## 8a. Seed content (use these specific examples)
Anchor every generated copy + sample data point in the concrete content below. Use these names, numbers, dates, and snippets verbatim where helpful, or generate close variants that sit in the same world.
**Sample chefs (in the demo gallery):**
- **Carmen Ortega** — Mexican-American private chef in Austin, Texas. Three years independent. Cuisine library carries 60 dishes across Mexican and Mexican-American registers, with three moles (rojo, verde, negro). Supplier sheet keyed in USD. Eight to twelve in-home dinners a month.
- **Reyna Lopez-Cruz** — Filipino chef in Makati, Manila. Two years independent. Cuisine library carries Filipino classics plus a Filipino-Lebanese fusion thread she developed during a year in Dubai. Supplier sheet keyed in PHP. Four to six dinners a month.
- **Nadia Khoury** — Lebanese chef in Mar Mikhael, Beirut. Six years independent. Cuisine library spans Lebanese classics, Levantine breakfast, Armenian-Lebanese plates from her grandmother. Supplier sheet keyed in LBP and USD (dual). Six to eight dinners a month plus a six-night pop-up residency twice a year.
- **Harpreet Singh** — Punjabi-Australian chef in Parramatta, Sydney. Five years independent. Cuisine library is 80% Punjabi vegetarian with explicit Jain-compatible variants flagged. Supplier sheet keyed in AUD. Ten to fourteen dinners a month, including Diwali, Vaisakhi, and weekly Sunday family dinners.
**Sample brief in detail view (this is what the demo should show):**
- **Client:** Sarah Mendoza & David Bauer
- **Dinner date:** Saturday 12 September 2026, 7pm
- **Party size:** 8
- **Occasion:** David's fortieth birthday
- **Cuisine direction:** "Mexican-ish, because it's his fortieth"
- **Kitchen constraints:** one oven, four induction rings, one fridge, no walk-in, one small chest freezer in the garage
- **Guests (8):**
- Sarah Mendoza — host, no allergies, dislikes raw onion, no observances
- David Bauer — host, no allergies, hates fennel and anchovies, no observances, occasion: fortieth birthday
- Lola Bauer — David's sister from Dallas, ANAPHYLACTIC to peanut (carries EpiPen, near-miss as a child with satay sauce), avoid tree nuts as a precaution (never formally tested), no observances
- Margaret Bauer — David's mother, dairy-free for medical reasons (diagnosed lactose intolerance plus a separate dairy-protein sensitivity), no other restrictions
- Theo Mendoza — Sarah's cousin, vegan but eats honey, prefers Mexican over Tex-Mex
- Mara Rodriguez — coeliac (formally diagnosed), strict
- Liam Mendoza — Sarah's son, age 9, will not eat anything green
- Inez Bauer — David's grandmother, age 78, soft food preferred, no chilli
- **Hard nos across the table:** peanut, tree nut (precaution), gluten/wheat, dairy
- **Proposed menu (5 courses):**
- Course 1 — **Opener:** chilled cucumber and tomatillo agua de jamaica with charred lime and a pinch of Tajín on the rim. Served to all. Vegan, gluten-free, dairy-free, nut-free. Liam's parallel: cucumber spears with the agua as a dipping cup, no Tajín.
- Course 2 — **Vegetable course:** roasted heirloom beets with verde mole, pumpkin-seed crumble, queso fresco. Verde mole is made with pumpkin seeds only — no peanuts, no tree nuts. Vegan parallel: same plate, queso fresco swapped for a roasted poblano crema thickened with cashew-free coconut milk. Liam's parallel: roasted beet wedges with a small bowl of the verde mole on the side, queso fresco served separately.
- Course 3 — **Main course (omnivore):** mole-rubbed duck breast, served pink, with charred spring onions and a small mound of black beans cooked with epazote. Vegan parallel for Theo: charred king oyster mushrooms cooked in the same verde mole, with the same charred spring onions and black beans (cooked with vegetable stock). Gluten-free parallel for Mara: same dish, the duck and mole are gluten-free already; the only adjustment is to confirm the epazote is not a shared-jar contaminated. Dairy-free for Margaret: same dish, mole is dairy-free already; confirm the duck rendering uses olive oil not butter. Liam's parallel: a small portion of the duck, sliced thin, with plain black beans, no mole.
- Course 4 — **Cheese course (optional, skipped tonight given the dairy-free needs):** the chef proposes skipping the cheese course; the brief carries this as a flagged decision for the chef to confirm with Sarah.
- Course 5 — **Dessert / occasion plate:** flourless dark-chocolate torte with coconut-cream chantilly, candied orange zest, and a single birthday candle for David. Naturally gluten-free, dairy-free, peanut-free, tree-nut-free. Vegan parallel for Theo: same torte recipe with a substitute egg-replacer (linseed gel) and the same coconut-cream chantilly; the torte serves vegan-friendly already. Liam's parallel: a small slice without the candied orange (he won't eat citrus peel).
- **Allergen matrix (sample cells):**
- Lola × all courses: GREEN (no peanut or tree nut in any course; verde mole verified pumpkin-seed-only; cross-contact protocol in effect)
- Margaret × course 3 primary: GREEN (duck rendered in olive oil, mole dairy-free; cross-contact protocol in effect)
- Mara × course 3 primary: AMBER → confirm gluten-free black beans (no shared scoop with wheat flour), confirm mole pot has not held wheat-thickened sauce
- Theo × course 3 primary: RED → do not serve, swap: serve king oyster mushroom parallel
- Theo × course 5 primary: GREEN if egg-replacer parallel served
- Liam × course 2 primary: AMBER → child will not eat queso fresco served on the dish; serve parallel
- **Cross-contact protocol:** no peanuts or tree nuts in the kitchen for the night; separate cutting board for Mara's gluten-free dishes (white board, kept apart); olive oil only for duck (not butter); confirm Sarah's pantry has no peanut residue (separate shopping run if uncertain).
- **Shopping list (excerpt):** duck breast 4 × 250g (USD 18/breast = $72), heirloom beets 8 × 200g ($1.20/200g = $9.60), pumpkin seeds 200g ($0.80/100g = $1.60), tomatillo 800g ($3.50/lb ≈ $6.16), king oyster mushroom 4 × 150g ($4/150g = $16), 70% dark chocolate 400g ($14/400g = $14), coconut cream 400ml ($3.50/400ml = $3.50). Per-head food cost: $19.40. Total food cost: $155.20.
- **Prep brief (excerpt):**
- Wednesday — supplier order placed (Salt & Time for duck; Boggy Creek for beets and tomatillo; H-E-B for pantry and coconut cream).
- Friday morning — verde mole base made (toast pumpkin seeds, char tomatillo and serrano, blend with cilantro and spinach); chocolate torte baked, cooled, wrapped.
- Friday evening — duck breasts dried in the fridge, skin scored.
- Saturday afternoon — beets roasted, agua de jamaica chilled, coconut chantilly whipped.
- Saturday 6:30pm — chef arrives at the Mendoza-Bauer house, sets up the parallel-plate board (Lola's plate is plated on the herb-green serving plate to make the no-nut plate visually distinct in the kitchen), confirms cross-contact protocol with Sarah.
- Saturday 7:05pm — first course fired (cold; just plating).
- Saturday 7:30pm — second course fired (warm beets out of the oven).
- Saturday 8:00pm — third course fired (duck on, 6 minutes per side; mushrooms parallel on a separate pan with the same mole; black beans warming).
- Saturday 9:00pm — dessert fired (torte plated, candle lit for David).
- **Generation confidence:** 0.93
**Sample input artefacts (for the build to demonstrate):**
- A 4-minute audio recording of Sarah Mendoza on speakerphone with Carmen Ortega, briefing the fortieth-birthday dinner above.
- A typed brief for Reyna Lopez-Cruz's corporate Iftar (12 guests, 8 halal, 2 vegetarian, 1 gluten-avoiding, 1 diabetic-managed).
- A typed brief for Nadia Khoury's Easter Sunday lunch (16 guests, 1 coeliac, 1 low-FODMAP, 1 sesame-anaphylactic).
- A typed brief for Harpreet Singh's Diwali dinner (20 guests, 100% vegetarian, 3 Jain, 1 toddler-who-won't-eat-green).
- A sample supplier sheet for Carmen Ortega's Austin business (45 ingredients in USD).
- A sample cuisine library JSON for Carmen Ortega (12 openers, 18 vegetable courses, 22 mains, 10 desserts; 3 moles).
**Sample voice copy:**
- Onboarding: "Talk through your next dinner with your host. We'll write the menu, flag the allergens, and total the basket."
- Processing: "Listening to your host…" / "Checking the Jain rules with sources…" / "Drafting the menu…" / "Flagging every peanut…"
- Empty brief: "This brief is waiting for its host. Tap the record button to start the call, or type the guest list directly."
- Error (couldn't transcribe): "We couldn't pick up the host's voice clearly here. Want to upload the audio again, or type the brief in directly?"
- Save confirmation: "Brief saved — Sarah & David's fortieth, 12 September, party of 8."
- Allergen detected: "We flagged peanut as RED across the whole menu — Lola's anaphylactic history. The kitchen is peanut-free tonight."
- Low confidence note: "We're not sure if Mara is coeliac or lifestyle gluten-free. Want to confirm with the host before we finalise?"
**Sample host invitation email subject + body:**
- Subject: "Sarah — your menu for the 12th, with the allergens"
- Body: "Hi Sarah — your menu for David's birthday dinner is attached. Five courses, with Lola's peanut allergy covered top to bottom (no nuts in the kitchen that night), Margaret's dairy needs handled with a coconut-cream chantilly, and the chocolate torte is naturally gluten-free for Mara. Have a look and reply with any adjustments. — Carmen" [Menu attached]
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a chef's hands at a kitchen bench, a notebook open with pencil notes, a phone face-up on speaker mode beside it, a kettle softly out of focus, late-afternoon warm window light. Generate via Nano Banana 2 with a prompt emphasising "wooden bench, warm afternoon light, a chef's hands of unspecified ethnicity, soft out-of-focus kettle, real worn notebook, no logos, no faces, no text on the notebook visible".
- **App icon / wordmark:** Set in the display serif. Slightly worn paper texture behind it. No icon — just type.
- **Empty-state illustration:** A simple line drawing of an open notebook with five small course-sketches and the words "5 courses" pencilled across. Hand-drawn aesthetic, not a flat icon.
- **Course thumbnails (in menu PDF):** Generated per the prompts in section 4b — Nano Banana 2 prompts that specifically request "soft natural window light, asymmetric plating, no scattered-herb garnishes, restrained, the plate fills 60-70% of the frame". Each thumbnail should look photographed, not rendered.
- **Stock fallbacks:** If image generation fails, fall back to a small typeset card with the dish name in the display serif. Never to a "🍽" emoji.
- **Generated imagery:** prefer Nano Banana 2 over stock photography. Prompt for warmth, asymmetry, and slight imperfection — avoid the glossy 'AI render' look.
- **Optimisation:** WebP/AVIF, `loading="lazy"`, explicit `width`/`height` to prevent layout shift.
- **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only.
### Build-time asset manifest (explicit specs)
Every image, illustration, and visual reference mentioned above must resolve to ONE of the three buckets below — runtime-generated, seed-shipped, or user-supplied. Do NOT ship `` tags whose `src` is not listed here. Do NOT depend on bare "section 8a prompts" without binding them to explicit paths and model IDs.
**Bucket 1 — Runtime-generated (Nano Banana Pro `gemini-3-pro-image` for hero/demo photographs; Nano Banana 2 `gemini-3.1-flash-image` for in-app illustrations and reference-conditioned variants).** Cached to Firebase Storage; served via signed URL. Every reference above to "Nano Banana 2" or "Nano Banana Pro" MUST be wired to one of these specific calls with an explicit model id:
- `/public/generated/hero.webp` (2400×1500, WebP) — model `gemini-3-pro-image` — uses the literal prompt described as "Hero image (landing screen)" above. Run once at build; commit a `/public/samples/hero-fallback.webp` (1600×1000) generated from the same prompt with `gemini-3.1-flash-image` so the page renders if quota is exhausted.
- `/public/generated/demo/{demo-slug}-{NN}.webp` (1600×1200, WebP) — model `gemini-3.1-flash-image` (reference-conditioned where the prior frame is passed as input) — one path per "Demo X" image referenced above. The slug derives from the seed example in section 8a; the NN index covers each frame in the demo sequence.
- `/public/generated/illustrations/{name}.webp` (1024×1024, WebP) — model `gemini-3.1-flash-image` — one path per named illustration above ("Empty-state illustration", "Recipe-card hero illustrations", "Curriculum picker imagery", "Period-style frames", etc.). Each illustration's prompt is the literal description above; ship a deterministic seed in the request so re-runs are reproducible.
**Bucket 2 — Seed assets shipped with the deliverable.** Every "Stock fallback" path referenced above (e.g. `/public/samples/sample-X.jpg`) is generated once via Nano Banana 2 (`gemini-3.1-flash-image`) at 1024×1024 WebP using the same prompt as its Bucket-1 counterpart, then committed to the repo so the page renders identically if Gemini quota is exhausted or the user is offline. Replace any `.jpg` extension above with `.webp` to match the optimisation rule. Also commit these empty-state seeds (1024×1024 WebP, single-stroke hand-drawn line, no colour fill):
- `/public/samples/empty-state-primary.webp` — line drawing of the app's primary empty surface (the named "Empty-state illustration" above), generated from that exact prompt.
- `/public/samples/empty-state-archive.webp` — line drawing of an empty saved/archive view, single-stroke outline.
- `/public/samples/empty-state-error.webp` — line drawing of a hand placing a single object aside with care, used when an AI call fails.
**Bucket 3 — User-supplied.** Uploads from the user's camera / file picker land at the Firebase Storage path conventional for this template (named in section 4b). The build ships with Bucket-1 + Bucket-2 only; no user-supplied images at first paint.
**Hard rules**
- Every `` tag MUST have a `src` that resolves to a path listed in Bucket 1, Bucket 2, or a Bucket 3 upload path. Anything else is a build error.
- No bare `image.jpg` / `hero.jpg` / `placeholder.png` references anywhere in the code.
- Model IDs: `gemini-3-pro-image` for hero-quality photographic generation; `gemini-3.1-flash-image` for in-app illustrations, reference-conditioned variants, empty-state seeds, and stock fallbacks. Never use a legacy model id (no `imagen-*`, no `gemini-1.5-*-image`).
- File format: WebP everywhere (AVIF acceptable where the target browsers support it). No `.jpg` / `.jpeg` / `.png` in `/public/samples/`.
## 10. Interactivity & states
- Every interactive element has hover, focus, active, and disabled states.
- Forms validate inline and show specific error messages (not "Invalid input").
- Loading states use skeletons that match the eventual layout, not spinners.
- Empty states explain the next action with a button whose label fits THIS app's domain: "Record your briefing call", "Type your first guest", "Add an ingredient to your supplier sheet", "Save your first cuisine library dish" — never a generic "Add your first item".
- Smooth scroll for in-page anchors.
- All AI-generated content streams in token-by-token where supported, with a clear "thinking…" indicator before content starts arriving.
- If an AI call fails, show a calm, specific error ("We couldn't hear the host clearly — try a quieter room, or type the brief in directly") and offer retry.
- Low-confidence guest fields in the brief are faintly underlined; tapping reveals what the model heard and a "correct this" affordance.
- The allergen-matrix cell transition (when a swap is applied and an amber cell turns green) takes 240 ms with `prefers-reduced-motion` falling back to instant.
- The chef can swap any course in place by tapping the primary dish card; the swap menu surfaces three alternatives from her cuisine library that respect every allergen on the table.
## 11. Tech & responsive requirements
- **Deterministic arithmetic — server-side TypeScript only.** Any money-split / settlement / total / VAT / unit-conversion math runs in TypeScript on the server using integer-cents arithmetic. The Gemini call only extracts entities (line items, payer, currency code, raw quantities) into structured JSON. Never let the model do the sum — Gemini will return plausible-looking arithmetic that does not actually balance.
- **TTS markdown-stripping preprocessor:** before sending any user-authored markdown to `gemini-3.1-flash-tts-preview`, strip non-spoken markdown: `#`/`##`/`###` headings (keep the title text), `**bold**` (keep the inner text), `[label](url)` (keep `label`, drop URL), `` ``` `` fenced code blocks (skip entirely), `>` block-quote markers (keep the text), and `|` table pipes (read row-by-row as sentences). Insert `…` between sentences for a short pause and a blank line plus `—` between paragraphs for a long pause. The model does not understand markdown; raw markdown will be read aloud as literal characters ("asterisk asterisk").
- **File downloads on Safari / Firefox:** when offering local-disk save of any export (PDF, CSV, MP3, ZIP, JSON, image), fall back to `` with a blob URL — the File System Access API (`showSaveFilePicker()`) is Chromium-only. Detect with `'showSaveFilePicker' in window`; otherwise use the anchor-download path.
- **Stack:** React + TypeScript + Tailwind CSS. Functional components + hooks. Use Shadcn UI primitives where appropriate.
- **Build runtime:** AI Studio Build — full-stack with Cloud Run server-side functions. All Gemini API calls happen server-side; API key lives in Secrets Manager, never in client bundle.
- **Model selection:** explicitly pin `gemini-3.5-flash` for the briefing parse, the menu generation, and the allergen matrix; `gemini-3.5-flash` for the observance lookup, the swap suggestions, and the cost lookup; `gemini-3.1-flash-image` for course thumbnails; `gemini-3.1-flash-tts-preview` for the chef-listens-while-prepping narration. Set `thinkingLevel` explicitly per call.
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed brief on first launch.
- **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email as fallback for host-facing surfaces.
- **Storage:** Firebase Storage for audio recordings, PDFs, and Nano Banana 2 thumbnails. Pre-signed URLs only.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+.
- Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness.
- Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS.
- Zero horizontal overflow at any width. Zero layout shift on load.
- Persist user data in Firestore. Use real-time listeners on the brief view so multiple devices stay in sync.
- Optimistic UI on writes; reconcile on response.
- Voice intake uses the Web MediaRecorder API; on iOS Safari, falls back to native record-to-file and upload.
- **iOS Safari gotchas (graceful degradation):** Safari `MediaRecorder` only supports `audio/mp4` (AAC) — feature-detect and persist as AAC mono; mic permission does NOT persist across page reloads on iOS — re-request on every intake; an incoming call interrupts the audio session (`MediaStreamTrack.onmute` fires) — auto-pause, persist the partial chunk to IndexedDB, and prompt the chef to resume; backgrounded Safari tabs pause `getUserMedia` — pair `visibilitychange` with a screen Wake Lock during a 30-minute briefing so the call isn't silently dropped; iOS may also drop long `MediaRecorder` sessions — flush every 5s and stream chunks to IndexedDB.
## 12. Accessibility (WCAG 2.2 AA)
- Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`.
- All interactive controls reachable by keyboard with a visible focus ring.
- Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The red/amber/green of the allergen matrix is supplemented with a single-character status (R, A, G) and a screen-reader label so colour is never the only signal.
- All images have meaningful `alt` text. Course thumbnails have `alt` describing the dish ("mole-rubbed duck breast, charred spring onions, black beans, served on a cream ceramic plate").
- Form fields have associated `