================ 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. --- # Garden Almanac ## 1. Project **Garden Almanac** is a personal sowing-and-harvest planner for people who grow food in a real plot of ground, in a real city, at a real latitude — not for a generic temperate-North-American garden. The user photographs each of her beds (raised, in-ground, container, vertical), tells the app where she is, and the app produces a year-long calendar of sowings, transplants, thinnings, side-dressings, harvests, and end-of-season clearings, calibrated to her last-frost date, her first- frost date, her hardiness zone, her elevation, and the specific crops she actually grows. Every Saturday morning she opens it and reads that week's task list: this week, thin the carrots in bed 2 to one finger's width; this week, top the broad beans in bed 1 before the black-fly find them; this week, the squash transplants in the south container are ready for the first compost top-up. This is the kind of app a Vietnamese-American gardener in Sacramento builds at her kitchen table after she gives up trying to translate the back of her seed packets from "Zone 5b" to her actual February heat. Mustard greens, water spinach, bitter melon, and Thai basil do not appear on the seed-packet calendars she finds online — and the ones that do appear are written for somebody in Vermont. It is also the kind of app a Caribbean-British gardener in Birmingham builds when she takes on a new allotment plot and realises that the chart in the back of the seed catalogue assumes she lives in Kent, three hardiness zones away from her actual cold, wet Midlands clay. Same shape of moment, different climate, different crops, same gap between the generic calendar and the real one. The single demo that proves the magic: photograph each of her three allotment beds once → in under 30 seconds the app generates a year-long calendar pinned to her city's actual frost dates, with a weekly task list that arrives in her inbox every Saturday morning. The first one reads: "this week — thin the carrots in bed 2 to one per inch, side-dress the tomatoes in bed 1 with compost, harvest the outer leaves of the chard before it bolts in next week's heat, and sow the next succession of lettuce in the south container before Tuesday's rain". On the calendar a row of small icons appears for each task; tapping any one opens the relevant bed photograph with a ghost-outline of where in the bed to thin, side-dress, or sow. And in the harder cases — a mountain garden at 2,400 metres in central Mexico where frost can land any night October to April, an allotment in northern Scotland where the growing season is twelve weeks and the day length swings from six to nineteen hours, a Mediterranean roof garden where the limiting factor is the summer drought rather than the winter cold — the app reads the climate honestly and refuses to pretend the temperate-North-American defaults apply. It uses the city's actual climate normals (period 1991-2020, public WMO data) and the user's elevation, not a national average. **Tagline:** _Turn three beds and four crops into a year-long sowing-and-harvest calendar — in any climate, any zone, any latitude, with weekly tasks that know what's actually planted where._ ## 2. Target audience - Allotment holders in the UK, Ireland, Germany, the Netherlands, Czechia, Poland — multiple beds across one plot, one growing season under maritime or continental conditions - Community-garden plot holders in North American cities, often growing crops from their family's home cuisine: collards in Atlanta, callaloo in Toronto, perilla in Vancouver, hoja santa in Houston, ñame in Miami - Home gardeners in tropical and subtropical climates where the relevant calendar is wet-season vs dry-season, not last-frost vs first-frost — Mexico City, Lagos, Bangalore, Kuala Lumpur, Cairns, São Paulo - Mountain gardeners at altitude where the frost calendar bends sharply — Quito, Cusco, Addis Ababa, Kathmandu, Denver, Yerevan - Container gardeners on balconies and rooftops who treat the container as the bed, the building as the microclimate, and the slab of concrete underneath as a heat sink - Heritage-crop gardeners preserving family seed: a Yoruba-British gardener in Manchester saving ewedu seed each year, a Hmong-American family in Saint Paul saving long-bean seed across three generations - Beginner gardeners in their first year, who do not yet know what to do in February and want the calendar to tell them — and tell them in plain language, without zone numbers they do not yet understand - Older gardeners who have grown food for forty years but want a calendar that finally accounts for their actual June bolting and their actual September first-frost rather than a national average - Teachers running a school garden with eight children, four beds, and a need for a printable weekly task list pinned to the shed door - Permaculture and polyculture gardeners who plant multiple crops in one bed and need the calendar to schedule tasks per crop, not per bed ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Knows your actual climate, not a national default** — the app uses the city's WMO 1991-2020 climate normals (or the closest grounded source for the location) to derive the user's last-spring-frost date, first-autumn-frost date, hardiness zone, growing-season length, monthly mean temperatures, monthly rainfall, and day-length curve. It NEVER assumes USDA Zone 7 or RHS H4 or any other temperate-North-American or English default. A garden in Sacramento gets a Sacramento calendar; a garden in Birmingham gets a Birmingham calendar; a garden in Mexico City gets a Mexico City calendar where the bigger constraint is the May–October rainy season, not frost. - **Reads each bed visually** — Gemini 3.5 Flash multimodal reads the photograph of each bed and infers its construction (raised, in-ground, container, vertical), its rough dimensions, its aspect (sun pattern across the day based on visible shadows), what is currently planted in it, and at roughly what growth stage. The user confirms or corrects; the app does not silently pretend. - **Schedules the actual crop, not "vegetables"** — the calendar names every task at the crop level. "Side-dress the kabocha squash in bed 3 with compost" is the task, not "fertilise". The calendar handles bitter melon, ewedu, ngo gai, hoja santa, callaloo, perilla, kabocha, tomatillo, chayote, ackee, fiddleheads, chickpea — alongside lettuce, tomato, courgette, and bean. - **The weekly task list arrives when it is useful** — Saturday morning, in the user's local timezone, before she heads to the allotment. Not a push notification at random. The list is short, specific, ordered by what is time-sensitive, and grouped by which bed she'll be standing in. - **The bed photograph is the source of truth** — the calendar and task list always link back to a recent photograph of the bed with the work area marked on it. The user can tap "thin the carrots in bed 2" and see the photograph of bed 2 with a translucent overlay of which row to thin, and an arrow showing the spacing. - **Honest about uncertainty** — when the app cannot tell whether a seedling is a beetroot or a chard from the photograph, it asks. When the local frost-date data has a long tail (frosts have happened as late as 14 May once in 23 years), the calendar shows the safe date and surfaces the tail. When a new variety has no published days-to-maturity for your zone, the app says "estimating from family typical" rather than inventing a precise number. - **Heritage-crop friendly, no Vermont default** — the seed library accepts crops the user names herself, with their family-language names preserved alongside any standardised English / Latin binomial. ewedu (Corchorus olitorius), water spinach / rau muống / kangkong / 空心菜 (Ipomoea aquatica), bitter melon / karela / 苦瓜 (Momordica charantia), perilla / 깻잎 (Perilla frutescens var. crispa), tomatillo / miltomate (Physalis philadelphica). - **Printable for the shed door** — every weekly task list is one A4 / Letter page with the bed-photographs alongside, a tick column down the right margin, and the date and weather forecast across the top. Tuck it into the shed door, work through it on Saturday morning, recycle on Sunday. ## 4. Features to build - Camera capture for each bed (mobile-first), with a wide-frame mode and a guided "stand back and frame the whole bed in landscape" prompt - Upload from photo library — accepts overhead drone shots of larger plots, accepts pasted screenshots of plot plans, accepts hand-drawn plot sketches photographed - Manual bed entry — a user without good photographs can describe a bed in words (dimensions, aspect, soil type, what's planted) and the app accepts that as ground truth - Automatic bed-type detection — raised wooden, raised metal galvanised, in-ground bordered, in-ground unbordered, large container, small container, vertical / trellised, polytunnel, cold-frame, balcony rail planter — distinguished and used in scheduling (raised beds warm earlier in spring; containers dry out faster; polytunnels extend each end of the season by 3-6 weeks) - Multimodal parse of a bed photograph — bed dimensions estimate, aspect inference, current crops detected with rough counts, growth stages estimated, weeds and pests visible flagged, mulch or bare-soil noted - Location-based climate grounding — the user provides her city (and optionally her postcode or coordinates); Gemini 3.5 Flash with grounded search retrieves the climate normals, last and first frost mean dates, frost-date tails, hardiness zone (USDA / RHS / Australian / South African — picked by country), and a day-length curve - Climate-honest scheduling — the calendar engine takes (crops × bed types × climate normals × user's recorded events) and generates the year's calendar of sow, transplant, thin, stake, side-dress, top, prune, harvest, save-seed, clear, and cover-crop tasks - Heritage-crop seed library — the user can name a crop in her own language (ewedu, rau muống, 깻잎, miltomate, kabocha, ackee, callaloo, hoja santa) with optional Latin binomial; the app then schedules it correctly even when the seed packet is silent - Variety-level scheduling — within "tomato" the calendar distinguishes determinate ("bush") from indeterminate ("vine") and from cherry varieties; staking, side-shooting, and topping tasks differ per variety - Polyculture support — a single bed can hold three crops, each with its own task list; the calendar groups tasks by bed for the field walk - Weekly task list — generated every Saturday morning at 06:00 in the user's local time; delivered to email and accessible in the app; one page A4 / Letter, ordered by bed, ordered within bed by urgency - Per-task bed overlay — the bed photograph is annotated with a translucent ghost-outline showing where to thin, where to side-dress, where to harvest, where to sow next - Weather-aware adjustments — the app pulls a 7-day local forecast; if heavy rain is forecast Sunday, sowing tasks are pushed earlier in the week; if a late frost is forecast Tuesday, tender transplants are flagged with a cover-cloth reminder - Phenology learning — the user records when each crop actually flowered, bolted, set fruit, and was harvested; the app refines next year's calendar to her actual microclimate rather than the city's bulk average - Voice capture in the garden — the user dictates a 20-second note from the bed ("the lettuce in bed 1 is bolting earlier than the chart said, the squash flowered three days ago, there's an aphid colony on the broad beans"); the app structures it per crop and per bed - Pest and disease identification from photograph — leaf-curl, blight, powdery mildew, aphid, cabbage white caterpillar, slug damage, vine borer — identified conservatively, with a confidence score and the user asked to confirm - Companion-planting and rotation advice — when the user adds next year's seed list, the app proposes a bed rotation that respects family rotation rules (brassicas / alliums / legumes / solanaceae / cucurbits / roots) and flags monoculture risk - Saturday morning push — opt-in email digest with the week's task list, the bed photographs, and the weather forecast inline. Default time is 06:00 local; user can change. - Printable PDF — the weekly list as a one-page printable PDF designed to be Blu-Tacked to the shed door - Annual summary at year-end — the app produces a year-in-review with what was planted, what was harvested, what failed, and three concrete suggestions for next year based on the year's phenology data - Voice narration of the weekly list — Gemini TTS reads the week's tasks aloud in the user's language while she walks to the plot (Spanish, Portuguese, Vietnamese, Hindi, Mandarin, Cantonese, Tagalog, Yoruba, Swahili, Amharic, alongside the European set) - Family / co-tenant sharing — two people on one allotment plot, or a parent and adult child sharing a garden, can both see the same beds and tick off the same task list ## 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) - **Multimodal image input** (Gemini 3.5 Flash) — reads bed photographs to identify bed construction, infer dimensions and aspect, detect current plantings with rough counts, estimate growth stages, and flag visible pests, disease, weeds, mulch state. One call per bed; complex polyculture beds may require a follow-up close-up. - **Spatial understanding** (Gemini 3.5 Flash) — used by the bed-reading call to give a rough geometry of the bed (length × width estimate), to mark planting rows on the photograph for the task overlays, and to localise individual crops within a polyculture for per-crop task targeting. - **Structured output / JSON Schema** — every parse call returns a typed object matching the schemas below. The schemas are included verbatim in the system instructions and as `responseSchema`. - **Search grounding** — for climate-normals retrieval. "Birmingham, UK last-frost date" must resolve to a real WMO-grounded value with the period and source visible. Grounded search keeps the calendar pinned to real local climate, not a guess. NOTE: search grounding and `responseSchema` cannot be combined in one Gemini call — the climate-grounding call emits its JSON in the text body and we parse server-side. - **Long context (1M tokens)** — used by the annual review and the multi-year phenology learning call: the app reads every voice note, every photograph caption, every task completion record from the season and produces the year-in-review and the refined next-year calendar from the whole season at once. **Guardrail:** a season of phenology data for a 4-bed garden averages ~80k tokens; a 20-bed community garden across 3 years approaches 600k tokens — chunk by year if total exceeds 500k. - **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — narrates the weekly task list in the user's language, at a walking pace ("for the walk to the allotment, not for a podcast"). Pauses encoded as `…` between tasks and a blank-line `—` between beds. - **Image generation** (`gemini-3.1-flash-image`, Nano Banana 2) — generates the per-task overlay: an annotated illustration showing where in the bed to thin, where to side-dress, where to harvest. Optional and used sparingly; the primary visual is always the user's own photograph with a translucent geometry overlay rendered client-side from the spatial-understanding output. - **Thinking levels** — `medium` for the primary bed-reading call (multimodal + spatial + crop ID + stage estimation) and for the annual review call. `low` for the climate-grounding call, the pest-ID call, and the calendar-generation call (which is mostly deterministic once climate + bed + crops are known). Surface `thoughtSummary` only on the small "(i) how the app read this bed" affordance next to a low-confidence bed parse. ### 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 co-tenant invitations and the Saturday digest) requires the sender domain to be authorised in Firebase Auth. Gardens are private to the owner and explicitly-invited co-tenants. No public-by-default. - **Database — Required.** Firestore for `users`, `gardens`, `beds`, `crops`, `plantings`, `tasks`, `weekly_lists`, `phenology_events`, `garden_members`. Real-time listeners on the current week's task list so a co-tenant ticking off a task updates the other tenant's view. - **File storage — Required.** Firebase Storage for bed photographs (preserved at upload resolution, forever) and the generated per-task overlay images. **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 bed upload. Pre-signed URLs only; bed photographs are never publicly addressable. - **Email — Required (transactional).** Saturday morning digest with the week's task list as inline HTML and a PDF attached. Co-tenant invitations via magic link (Firebase Auth). Sent via Firebase Extensions "Trigger Email" with a SendGrid or Postmark connector; the user wires the SMTP credentials. - **Scheduling — Required.** Saturday morning digest is fired by a Cloud Scheduler job (auto-provisioned alongside Cloud Run by AIS Build) that fans out per garden in the user's local timezone. Per-task reminders for time-critical tasks (frost forecast, first-fruit) are also driven from Cloud Scheduler. - **Payments — Not needed for v1.** Free for personal use. A future "printed annual almanac" tier could pipe to a print-on-demand partner (Lulu, Blurb) and charge for the physical artefact only. - **External APIs:** Gemini API for all intelligence. Climate normals are retrieved via Gemini 3.5 Flash with grounded search to public sources (WMO, NOAA, Met Office, INM-Mexico, BoM-Australia). For weekly weather forecasts, optionally Open-Meteo (no key required) or the user's chosen weather provider with an env-var key. NEVER hard-code a USDA-zone fallback. **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional SendGrid / Postmark key, optional weather-provider key) 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 garden' inside the UI (Settings → Delete this garden forever — gone in 60 seconds) · explicit opt-in for any analytics · the user's garden data 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) · the bed photographs may incidentally include the user's house or family members and are kept strictly private to the garden's invited members. **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 schemas are below. **Convert the Zod schemas 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 | |------|-------|---------------|---------------| | Read bed photograph → `BedReading` schema | `gemini-3.5-flash` | medium | (none) | | Retrieve local climate normals + frost dates | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Generate the year's calendar from (climate × beds × crops) | `gemini-3.5-flash` | low | (none) | | Generate this week's task list from (calendar × forecast × last week's events) | `gemini-3.5-flash` | low | (none) | | Identify a pest or disease from a close-up photograph | `gemini-3.5-flash` | medium | (none) | | Parse a voice memo from the garden into structured events | `gemini-3.5-flash` | low | (none) | | Generate TTS narration of the weekly task list | `gemini-3.1-flash-tts-preview` | n/a | n/a | | Generate a per-task overlay illustration (rare; user's photograph preferred) | `gemini-3.1-flash-image` | n/a | n/a | | Annual review + phenology learning, archive-wide | `gemini-3.5-flash` | medium | (none) — long-context over the whole season | *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. On the climate-grounding call, `responseSchema` is intentionally OMITTED — search grounding and structured output cannot be combined in one Gemini call. Instruct the model to emit its JSON in the text body and parse server-side; read citation URLs from `response.groundingMetadata.groundingChunks[].web.uri`. ### Primary structured-output schemas (seed these verbatim in the prompt) ```typescript import { z } from "zod"; const CropOccupant = z.object({ crop_name_user: z.string(), // "kabocha", "ewedu", "rau muống" crop_name_common_english: z.string().nullable(), // "winter squash" latin_binomial: z.string().nullable(), // "Cucurbita maxima" family: z.enum([ "brassica", "allium", "legume", "solanaceae", "cucurbita", "umbellifer", "asteraceae", "chenopod", "poaceae", "amaranth", "malvaceae", "lamiaceae", "polygonaceae", "convolvulaceae", "rutaceae", "vitis", "other", ]), variety_or_cultivar: z.string().nullable(), // "San Marzano", "Red Kuri" variety_habit: z.enum([ "determinate", "indeterminate", "bush", "vine", "trellised", "ground-running", "tree", "perennial-clump", "biennial", "annual-unknown", ]).nullable(), approximate_count: z.number().nullable(), // 6 plants in the row growth_stage: z.enum([ "not-yet-sown", "seed-tray", "true-leaves", "transplant-ready", "established", "rosette", "vegetative", "bud", "flowering", "fruit-set", "fruit-developing", "harvest-ready", "post-harvest", "going-to-seed", "bolted", "spent", ]), position_in_bed: z.string().nullable(), // "north row", "central, between the leeks" spacing_appears_appropriate: z.boolean().nullable(), visible_issues: z.array(z.enum([ "aphid", "caterpillar", "slug-damage", "mildew", "blight", "leaf-curl", "yellowing", "wilting", "flea-beetle", "vine-borer", "rust", "bolting-early", "drought-stress", "waterlogged", "nutrient-deficient", "other", ])), }); const BedReading = z.object({ bed_id: z.string(), photograph_image_uris: z.array(z.string()), bed_construction: z.enum([ "raised-wooden", "raised-metal-galvanised", "raised-stone-brick", "in-ground-bordered", "in-ground-unbordered", "large-container", "small-container", "vertical-trellis", "polytunnel-bed", "cold-frame", "balcony-rail-planter", "rooftop-bed", "hugelkultur", "other", ]), approximate_length_metres: z.number().min(0).max(50).nullable(), approximate_width_metres: z.number().min(0).max(50).nullable(), approximate_depth_centimetres: z.number().min(0).max(200).nullable(), inferred_aspect: z.enum([ "full-sun-all-day", "morning-sun-afternoon-shade", "afternoon-sun-morning-shade", "dappled-shade", "full-shade", "indeterminate", ]), visible_shadows_inform_aspect: z.boolean(), // true if the photo had useful shadows mulch_state: z.enum([ "bare-soil", "compost-mulch", "straw-mulch", "wood-chip-mulch", "leaf-mould", "plastic-mulch", "living-mulch", "weeds-as-mulch", "other", ]), soil_appears: z.enum([ "moist-friable", "dry-cracked", "waterlogged-puddled", "freshly-tilled", "compacted", "indeterminate", ]), weed_pressure: z.enum(["none", "low", "moderate", "high"]), current_occupants: z.array(CropOccupant), reading_confidence: z.number().min(0).max(1), flagged_for_user_review: z.array(z.object({ field_path: z.string(), reason: z.string(), })), }); const ClimateNormals = z.object({ location_label_user_facing: z.string(), // "Birmingham, UK" latitude: z.number().min(-90).max(90), longitude: z.number().min(-180).max(180), elevation_metres: z.number().nullable(), source: z.string(), // "Met Office 1991-2020" period_start_year: z.number(), period_end_year: z.number(), last_spring_frost_mean_iso_md: z.string(), // "04-19" — month-day, no year last_spring_frost_tail_iso_md: z.string().nullable(), // latest observed in record first_autumn_frost_mean_iso_md: z.string(), first_autumn_frost_tail_iso_md: z.string().nullable(), growing_season_days_mean: z.number().min(0).max(366), hardiness_system: z.enum([ "USDA", "RHS", "Australian-AHS", "South-African-SAHS", "European-Sunset", "Tropical-no-frost", "other", ]), hardiness_zone: z.string(), // "USDA 9b", "RHS H4", "AHS 3" monthly_mean_temp_celsius: z.array(z.number()).length(12), monthly_mean_rainfall_mm: z.array(z.number()).length(12), monthly_mean_daylight_hours: z.array(z.number()).length(12), wet_season_months: z.array(z.number().min(1).max(12)).nullable(), dry_season_months: z.array(z.number().min(1).max(12)).nullable(), monsoon_pattern: z.string().nullable(), // "South-west monsoon, June–September" notes: z.string().nullable(), }); const Task = z.object({ task_id: z.string(), bed_id: z.string(), crop_occupant_ref: z.string().nullable(), // points at a CropOccupant type: z.enum([ "sow-direct", "sow-indoor", "prick-out", "pot-on", "harden-off", "transplant", "thin", "weed", "side-dress", "water-deeply", "mulch", "top-up-compost", "stake", "tie-in", "side-shoot", "top", "prune", "net", "cloche", "fleece-cover", "shade-cloth", "pest-check", "spray-organic", "remove-affected-leaves", "first-harvest", "main-harvest", "succession-harvest", "save-seed", "clear", "cover-crop", "rest", ]), description_user_facing: z.string(), // "Thin the carrots in bed 2 to one finger's width" urgency: z.enum(["today", "this-week", "next-week", "flexible"]), weather_sensitive: z.boolean(), weather_constraint: z.string().nullable(), // "before Tuesday's rain", "after the frost risk passes" estimated_minutes: z.number().min(1).max(240).nullable(), per_task_overlay_uri: z.string().nullable(), // photo of bed with where-to-work marked }); const WeeklyList = z.object({ garden_id: z.string(), week_starting_iso: z.string(), // ISO date of the Saturday local_timezone: z.string(), // "Europe/London", "America/Sacramento" forecast_summary: z.string(), // "Cool, showers Sun-Mon, dry from Tue" forecast_low_celsius: z.number().nullable(), forecast_high_celsius: z.number().nullable(), frost_risk: z.boolean(), tasks: z.array(Task), }); const PhenologyEvent = z.object({ event_id: z.string(), garden_id: z.string(), bed_id: z.string(), crop_occupant_ref: z.string(), type: z.enum([ "sown", "germinated", "transplanted", "first-true-leaves", "first-flower", "first-fruit-set", "first-harvest", "bolted", "blighted", "lost-to-pest", "lost-to-frost", "cleared", "saved-seed", ]), date_observed_iso: z.string(), user_note: z.string().nullable(), source: z.enum(["voice-memo", "photo", "manual-tick", "task-completion"]), }); type BedReading = z.infer; type ClimateNormals = z.infer; type WeeklyList = z.infer; type PhenologyEvent = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent silently downgrades `thinkingLevel` on the the bed-reading call call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash misidentifies seedlings (chard read as beetroot, perilla read as basil), confuses bed construction (raised-metal called raised-wooden), and skips the spatial geometry that the per-task overlay depends on. - Crop assumed temperate-North-American — model defaults to lettuce / tomato / squash and fails to recognise ewedu, hoja santa, rau muống, perilla, karela. Pin in the system instruction: "the user may grow crops from any cuisine and any continent; if you do not recognise a crop, ask rather than guess". - Climate normals hallucinated — model emits a plausible-sounding last-frost date without grounding. Hard rule: the climate-normals call must run with `google_search` grounding and must include the citation URL in the parsed response (read from `groundingMetadata.groundingChunks[].web.uri`, not from the JSON body — the model hallucinates URLs). - Tropical / equatorial garden squeezed into a frost-date schema — for any location with no real frost (Lagos, Singapore, Mexico City lowlands, Cairns) the `last_spring_frost_mean_iso_md` and `first_autumn_frost_mean_iso_md` fields are NOT meaningful. The schema allows nulls there, BUT the climate-grounding model frequently invents fake "Jan 1" / "Dec 31" placeholders rather than nulls. Hard rule: if a location has no historical sub-zero days, set both fields null and the scheduling engine pivots to wet-season vs dry-season planning. - USDA hardiness zone forced onto a non-US garden — Birmingham UK gardener gets "USDA Zone 8" instead of "RHS H4"; Sydney gardener gets a USDA zone instead of Australian-AHS. Pin the hardiness system to the user's country in the system instruction. - Variety habit silently assumed — every tomato called "indeterminate". Every squash called "vine". Hard rule: if the variety habit is not visible from the photograph or stated by the user, mark `variety_habit: null` and ask, rather than assume. - Day-length curve ignored at high latitudes — Reykjavik, Edinburgh, Anchorage, Punta Arenas: the May day length is over sixteen hours and the December day length is under six hours; this matters for sowing timing of light-sensitive crops. The scheduling call must read `monthly_mean_daylight_hours` and not just temperature. - Polyculture bed parsed as one crop — the model picks the dominant crop and discards the others. Hard rule: every visible crop is recorded as a separate `CropOccupant`, with `position_in_bed` describing its location ("north row, between the leeks"). - Frost-date tail forgotten — model returns only the mean. Hard rule: also surface the latest observed date in the climate record, so the user can choose whether to plant on the mean date or wait two more weeks for safety. - Pest-ID call over-confident — model says "this is late blight" from a single mid-resolution photograph. Hard rule: pest identification returns a confidence score, asks for a closer follow-up photograph if confidence is below 0.7, and links to a one-paragraph plain-English description of the pest before recommending any treatment. - Per-task overlay generated by Nano Banana 2 when the user's own photograph would do — the user wants to see HER bed, not a generic illustration. Hard rule: the per-task overlay is the user's most recent photograph of the bed with a translucent geometry layer rendered client-side from the spatial-understanding output; Nano Banana 2 is used only when the user has no recent photograph of the bed (offered as a fallback, never as the default). - Saturday digest fires at 06:00 UTC for everybody — the Vietnamese-American gardener in Sacramento gets her digest at 22:00 Friday her time. Hard rule: Cloud Scheduler fans out per garden in the garden's `local_timezone`. - TTS reads "Cucurbita maxima" word-for-word in Latin in the middle of a Vietnamese list — wrong. The narration call substitutes the user's own crop name (rau bí, in this case) into the spoken list and reserves the binomial for the visible text only. - Annual review extrapolates to next year's weather — model writes "next summer will be hotter and drier, so plant more drought-tolerant crops". Hard rule: the annual review reports the past year's observations and the past decade's climate normals; it does NOT forecast next year's weather. ### Negative constraints (hard rules) - Do NOT assume USDA hardiness zones, last-frost dates around mid-April, or first-frost dates around mid-October. Those are temperate-North-American defaults. The app must NEVER default to them. If climate data cannot be retrieved with grounded search, ask the user for her last-frost-mean and first-frost-mean dates manually and proceed; do not invent. - Do NOT translate crop names into "the right English word" when the user has given a name in her own language. "ewedu" stays "ewedu" in the calendar and in the weekly list; "Corchorus olitorius" appears parenthetically once, the first time the crop is mentioned. Same for rau muống, hoja santa, karela, perilla, miltomate, kabocha. First-occurrence parenthetical Latin or English gloss, never thereafter. - Do NOT hand back a "fertiliser" task; hand back a side-dress-with-compost or apply-comfrey-tea or apply-blood-and-bone task, specific to the user's stated approach (organic, conventional, or unstated). Default to organic where unstated. - Do NOT invent a days-to-maturity for a heritage variety with no published number. Set the field null and note "estimated from family typical" in the user-facing description. - Do NOT diagnose a plant disease at high confidence from a single mid-resolution photograph. Ask for a close-up if confidence is below 0.7. Recommend a treatment only after the diagnosis is confirmed. - Do NOT recommend any chemical pesticide without naming the specific active ingredient, the legal status in the user's country (where known via grounded search), and a non-chemical alternative. - Do NOT use the user's garden 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 auto-publish or auto-share. Gardens are private by default. Sharing is explicit, per-garden, per-co-tenant. - Do NOT silently overwrite the user's own phenology observations with model-inferred dates. The user's record is the source of truth; the model's inference is a suggestion the user accepts or rejects. - Do NOT forecast next year's weather in the annual review. Report observed data only. - Do NOT generate a per-task overlay illustration with Nano Banana 2 when a recent photograph of the bed exists. The user's own photograph is the default; generated illustration is the fallback. ### 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: Read bed photograph → `BedReading` schema Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are reading a photograph of a garden bed taken by the gardener herself. She may be standing at the head of the bed, at the side, or above it. The bed may be raised wooden, raised galvanised metal, in-ground bordered or unbordered, a large or small container, a vertical trellised structure, a polytunnel bed, a cold-frame, a balcony rail planter, a rooftop bed, or a hugelkultur mound. Decide which, and record it. The gardener may be growing crops from any cuisine and any continent. Expected crops include — but are not limited to — lettuce, tomato, courgette, squash, bean, pea, carrot, beetroot, chard, kale, cabbage, broccoli, leek, onion, garlic, parsley, mint, basil, sage, thyme, sunflower, calendula, AND ewedu (Corchorus olitorius, jute mallow), rau muống / kangkong / 空心菜 (Ipomoea aquatica, water spinach), karela / bitter melon / 苦瓜 (Momordica charantia), perilla / 깻잎 (Perilla frutescens var. crispa), hoja santa (Piper auritum), tomatillo / miltomate (Physalis philadelphica), kabocha (Cucurbita maxima), callaloo (Amaranthus dubius), bittermelon long bean / đậu đũa (Vigna unguiculata subsp. sesquipedalis), Thai basil / húng quế (Ocimum basilicum var. thyrsiflora), ngo gai / culantro (Eryngium foetidum), molokhia (Corchorus olitorius), ackee (Blighia sapida), chayote (Sechium edule), amaranth grain (Amaranthus cruentus), and many others. If you do not recognise a crop, mark it crop_name_common_english null, latin_binomial null, family "other", and flag for user review — do NOT guess into a familiar Western crop name. Infer bed geometry from the photograph: approximate length, width, depth (for containers and raised beds), aspect (which way it faces and the sun pattern across the day, inferred from visible shadows and the angle of the light), and the construction. If the photograph has no useful shadows or no reference object (a hand, a foot, a watering can), set the geometry fields null and say so in visible_shadows_inform_aspect = false. For every crop visibly growing in the bed, emit a CropOccupant. In a polyculture bed (three crops planted together) emit three CropOccupant entries with each crop's position_in_bed described in words ("north row, between the leeks"). Estimate growth stage conservatively from the closed enum. If you see visible pest damage (slug holes, aphid colony, caterpillar chewing), disease (powdery mildew, blight, leaf-curl), or stress (wilting, yellowing, bolting early), record it under visible_issues on the relevant CropOccupant. If the photograph is too distant or too dark to be sure, do NOT record an issue speculatively. Hard rules: - Preserve the user's crop names where the photograph caption or bed metadata provided one. Do not "correct" "rau muống" to "water spinach" or "ewedu" to "jute mallow" in crop_name_user. - If a bed is currently bare (just-cleared, freshly-tilled, cover-cropped), emit zero CropOccupants and set mulch_state and soil_appears accordingly. - If you cannot tell the bed's construction (a close-up of the plants with no bed edge visible), set bed_construction "other" and flag for user review. - reading_confidence: a single per-bed score, NOT per crop. If even one crop is uncertainly identified, drop the bed's score below 0.8 and list the uncertain crop in flagged_for_user_review. - For polyculture beds, mark each crop's position_in_bed clearly so the per-task overlay renderer can target the right area of the photograph. - spacing_appears_appropriate is your judgement based on visible density; if you cannot tell, set null. Do NOT default to true. Output ONLY the BedReading JSON matching the provided schema. No commentary. ``` --- ### Call: Retrieve local climate normals + frost dates Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You are retrieving the published climate normals and frost dates for a gardener's location. Given a city, region, country (and optionally a postcode or coordinates), retrieve and return: - location_label_user_facing - latitude, longitude (decimal degrees) - elevation_metres (where available) - source (the agency the normals come from: WMO, NOAA / NWS for the US, Met Office for the UK, INM / CONAGUA / SMN for Mexico, BoM for Australia, JMA for Japan, KMA for Korea, CMA for China, NIMH for India, SAWS for South Africa, etc.) - period_start_year, period_end_year (e.g. 1991, 2020 — most modern WMO normals) - last_spring_frost_mean_iso_md and first_autumn_frost_mean_iso_md (month-day; null if location has no historical frost) - last_spring_frost_tail_iso_md and first_autumn_frost_tail_iso_md (latest / earliest observed in the period; null if no frost) - growing_season_days_mean (between mean last-frost and mean first-frost; 366 if no frost) - hardiness_system (choose: USDA for the United States, Mexico north of ~Mexico City latitude where USDA is published; RHS for the UK and Ireland; AHS for Australia; SAHS for South Africa; European-Sunset where appropriate; Tropical-no-frost where there is no historical sub-zero record) - hardiness_zone in that system's notation - monthly_mean_temp_celsius array of 12 - monthly_mean_rainfall_mm array of 12 - monthly_mean_daylight_hours array of 12 (compute from latitude if not directly published — civil daylight at the 15th of each month) - wet_season_months / dry_season_months / monsoon_pattern where applicable (especially for tropical and monsoon-pattern locations — South Asia, West Africa, Central America, South-east Asia, northern Australia) - notes (anything important the gardener should know: altitude effect, urban-heat-island effect for inner-city locations, rain shadow, late-frost tail) Hard rules: - Use `google_search` grounding for any location. Never invent normals. - If the location has no historical sub-zero record (tropical lowlands, subtropics near the coast, equatorial), set BOTH frost fields null. Do NOT invent "Jan 1" or "Dec 31" placeholders. - Pick the hardiness system for the country, NOT USDA by default. Birmingham UK is RHS H4, not USDA Zone 9. Sydney is AHS 3, not USDA. Mexico City is high enough that USDA publishes a zone (10a at the city centre), but a mountain garden in Tepoztlán at 2,000 metres is colder and needs the elevation note. - For high latitudes (above ~55°), include the daylight hours monthly array — the May day length is over sixteen hours and the December day length is under six hours, and this matters for scheduling. - If multiple sources disagree (a city straddles a microclimate boundary), pick the more conservative source for frost dates (the later mean last-frost, the earlier mean first-frost) and explain in notes. Output the response as JSON in the text body (NOT via `responseSchema` — `responseSchema` and `google_search` cannot be combined in the same Gemini call today). Server-side: parse the JSON, then read citation URLs from the response's `groundingMetadata.groundingChunks[].web.uri` — do NOT ask the model to include URLs in the JSON body; it will hallucinate them. No commentary outside the JSON. ``` --- ### Call: Generate the year's calendar from (climate × beds × crops) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive a ClimateNormals object for the gardener's location, an array of BedReading objects for her current beds, and a list of crops she intends to grow this year (some already in the beds, some not yet sown). Generate the year's calendar of tasks per bed per crop, pinned to her actual climate. The calendar is the set of all Task objects for the next 12 months, ordered by date. For each crop in each bed, schedule: - sow tasks (direct or indoor, depending on the crop's habit and the user's climate) - prick-out and pot-on tasks where indoor sowing is involved - harden-off and transplant tasks - thin tasks (carrots, beetroot, parsnip, lettuce, radish, anything direct-sown densely) - weed, side-dress, water-deeply, mulch, top-up-compost tasks at the appropriate intervals - stake / tie-in / side-shoot / top tasks for indeterminate tomatoes, vining cucumbers, climbing beans, sprawling squash - pest-check tasks at the crop's known vulnerable window (broad bean + black-fly mid-spring; brassicas + cabbage white mid-summer) - first-harvest and main-harvest tasks at the crop's known maturity-from-sow / maturity-from-transplant window - save-seed tasks for crops the user has flagged for seed-saving - clear and cover-crop tasks at end-of-season Hard rules: - Pin sow / transplant dates to the gardener's last-spring-frost date and first-autumn-frost date, NOT to a generic temperate-NA default. Tender crops (tomato, basil, courgette, bean, pepper, bittermelon, kabocha, ewedu, hoja santa) transplant out AFTER the mean last-frost; hardy crops (broad bean, garlic, onion-set, spinach, kale, brassica seedling) can go in earlier or in autumn for overwintering. - For tropical / no-frost locations, schedule by wet-season vs dry-season instead of frost dates. In a monsoon climate, schedule most direct sowings two weeks before the wet season begins and most harvests before the wet season peaks. - For high-latitude locations, weight by `monthly_mean_daylight_hours` for light-sensitive crops (lettuce will bolt in 19-hour-daylight northern summer; spinach will bolt; bok choi will bolt — sow through summer in succession with shade and bolt-tolerant cultivars if available, or shift these crops to spring and autumn). - Respect the bed's `inferred_aspect`. A morning-sun-afternoon-shade bed cannot grow Mediterranean tomatoes successfully; suggest the user move the tomato to a sunnier bed and propose a shade-tolerant crop (chard, lettuce, parsley) for the original bed. - For each variety where habit is known (determinate / indeterminate / vine / bush), schedule the appropriate staking / topping / side-shoot tasks. If habit is null, surface a question in the flagged_for_user_review list of the calendar response. - For a polyculture bed, schedule the tasks per crop, but group them by bed for the field walk. The Task.bed_id and Task.crop_occupant_ref together identify the work. - Default to organic methods unless the user has chosen otherwise. "Side-dress with compost", "apply comfrey tea", "apply blood-and- bone" are the default fertility tasks; chemical fertiliser is NOT a default. - For pest tasks, default to non-chemical: hand-pick caterpillars, net brassicas, rinse aphids with water, encourage ladybirds. - For heritage crops with no published days-to-maturity, estimate from family-typical and surface "estimated from family typical" in the task's description. - Do NOT schedule a task whose constraint cannot be met (planting a tropical perennial in a Reykjavik garden). Surface that as a flagged item the user can read, with the climate reason. Output ONLY the calendar as a JSON object containing tasks[] and flagged_for_user_review[]. No commentary. ``` --- ### Call: Generate this week's task list from (calendar × forecast × last week's events) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive the year's calendar (all Task objects), a 7-day weather forecast for the gardener's location, and the phenology events she recorded during the previous week (what germinated, what flowered, what was harvested, what was lost). Generate this week's WeeklyList. Hard rules: - Pull from the calendar every task whose due-window includes this week (Saturday-to-Friday in the gardener's local timezone). - Bring forward any task from next week that is weather-sensitive and would otherwise fall during the forecast bad-weather window. Push back any sowing task that falls during forecast heavy rain; push forward any transplant task that needs to be done before a forecast frost. - If a phenology event from last week makes a task redundant (the crop was harvested early, the bed was cleared, a plant was lost to slug), drop the task. - If a phenology event reveals a new task (a crop has set first fruit → add a feed task; a crop has bolted → add a clear task and surface a save-seed question), add the task. - Order tasks by urgency (today, this-week, next-week, flexible) and within urgency by bed (so the gardener can walk one bed at a time). - Each task's description_user_facing is concrete and short, naming the bed, the crop, and the specific action. "Thin the carrots in bed 2 to one finger's width" — not "thin row". - Forecast summary is one sentence in plain language. Forecast_low and forecast_high are the week's extremes. Frost_risk true if any forecast night drops below 1°C. - If the forecast contradicts a calendar task (calendar says "sow carrots", forecast says "5 days of heavy rain"), bring forward a cover-the-soil task and defer the sowing. Output ONLY the WeeklyList JSON. No commentary. ``` --- ### Call: Identify a pest or disease from a close-up photograph Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You receive a close-up photograph of a plant suspected of pest damage or disease. Identify the pest or disease conservatively. Hard rules: - Return a confidence score 0-1. If confidence is below 0.7, do NOT name a single pest or disease; instead, list the top three candidates with their distinguishing features and ask the user for a closer photograph showing the underside of a leaf, the stem, or the affected fruit. - For any pest or disease, return: - common_name (in English and, where the user has indicated, in her language) - latin_binomial where applicable - distinguishing_features (what you saw that told you this) - lifecycle_stage_visible (egg, larva, nymph, adult, mycelium, spore-bearing — for pests and fungi where stage matters) - severity_observed (light, moderate, severe) - one paragraph plain-English description - For treatment recommendations, default to non-chemical: physical removal, biological control (ladybirds for aphids, encarsia for whitefly), cultural change (mulch, drip-irrigate instead of overhead, rotate the bed next year). - If a chemical treatment is the only viable option, name the specific active ingredient, state the legal status in the user's country if known via grounded search (separate call, not here), and require the user to opt in explicitly before scheduling a spray task. - Do NOT diagnose late blight, fire blight, or any quarantine-notifiable disease without surfacing the local notification requirement (UK Defra, USDA APHIS, etc.) and recommending a closer professional inspection. Output ONLY the structured JSON. No commentary. ``` --- ### Call: Parse a voice memo from the garden into structured events Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive a transcript of a voice memo the gardener recorded while walking her plot. The memo may be in any language and may switch languages mid-sentence ("the rau muống ở bed 1 đang ra hoa"). Parse it into structured PhenologyEvent objects. Hard rules: - Preserve the gardener's crop names verbatim. "rau muống" stays "rau muống"; "đậu đũa" stays "đậu đũa"; "ewedu" stays "ewedu"; "perilla" or "깻잎" stays as recorded. - Map every event in the memo to one PhenologyEvent. A memo that says "the carrots in bed 2 germinated yesterday, the tomato in bed 1 has its first flower, and the broad beans have black-fly" produces three events. - If a date is given in the memo ("yesterday", "Tuesday"), resolve it to ISO using the memo timestamp. If no date is given, use the memo timestamp as the event date. - Distinguish observation events (germinated, flowered, fruited) from loss events (bolted, blighted, lost-to-pest, lost-to-frost) from action events (sown, transplanted, cleared, saved-seed). - If the gardener mentions a pest but does not act, emit an event of type "lost-to-pest" only when she explicitly says the crop was lost. Otherwise, emit a flag to surface a pest-check task in next week's list. Output ONLY the array of PhenologyEvent objects. No commentary. ``` --- ### Call: Generate TTS narration of the weekly task list Model: `gemini-3.1-flash-tts-preview` · n/a · n/a ``` Voice: warm, walking-pace. Pick the Gemini 2.5 Flash TTS voice whose `languageCode` matches the gardener's preferred language — pronunciation will follow that locale automatically. Prefer the gender she has indicated for the digest voice; fall back to whichever is available rather than blocking. Pre-process the text before sending it to TTS: - Read from the WeeklyList in this order: opening greeting line, forecast summary, then bed-by-bed task list. - At each line break, insert a single ellipsis (`…`) so the TTS model produces a natural pause. Between beds, insert a blank line plus an em-dash (`—`) so the model produces a longer pause for the change of context. Gemini 2.5 TTS does not support SSML `` — these textual cues are how you signal pace. - Skip per-task overlay URIs, frost risk booleans, and bed IDs. These are visual metadata, not for speech. - For crop names in the gardener's heritage language ("rau muống", "ewedu", "깻잎", "miltomate"), keep them verbatim — do NOT substitute the English binomial in speech. - Mid-call voice switching is not supported. If the list mixes languages (a Vietnamese list with one Latin binomial in it), keep the whole narration in the gardener's chosen voice; the binomial will simply be read by the voice's native locale rules. Optional: stitch a separate TTS call client-side for a long Latin binomial if pronunciation accuracy matters more than the pause it introduces. - Target rate: ~120 words per minute — walking pace, suitable for listening while walking to the allotment. Style direction: prepend ONE short directive sentence to the text input, exactly like: "Read warmly and at a walking pace, as a gardening friend reading you Saturday's list on the way to the plot. …". There is no separate `style` API field on Gemini 2.5 TTS; the directive sentence inside the input is how style is conveyed. Phoneme overrides for Vietnamese tones, Yoruba tones, Mandarin tones, Korean batchim, Arabic emphatic consonants are NOT exposed by Gemini 2.5 TTS — no SSML `` tag. Pronunciation comes from the chosen voice's native locale. ``` --- ### Call: Generate a per-task overlay illustration (rare; user's photograph preferred) Model: `gemini-3.1-flash-image` · n/a · n/a ``` You generate a per-task overlay illustration ONLY when no recent photograph of the bed is available. The user's own bed photograph is the default visual; this call is the fallback. You receive: the BedReading for the bed, the Task to illustrate, and a brief description of where in the bed the work happens. Generate a single illustration showing the bed from a 3/4 overhead view, with the relevant work area marked by a translucent overlay (thinning rows highlighted, harvest area circled, side-dress strip marked). Style: clean, restrained, illustrative — not photorealistic, not cartoon. Avoid generic AI-stock-art tells: no glossy gradients, no bright unnecessary colour, no soft-focus bokeh. The illustration must communicate WHERE in the bed to work, not what the crop looks like. The user already knows what her own carrots look like. The overlay is the point. Do NOT generate an illustration that pretends to be the user's actual bed. Style it visibly as an illustration (line work + flat fill), clearly distinct from a photograph, so the gardener never confuses it for a real photograph of her plot. ``` --- ### Call: Annual review + phenology learning, archive-wide Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long context) ``` You receive the gardener's entire season at once: every bed reading, every climate normal, every weekly list, every phenology event, every voice memo transcript. Produce a year-in-review and refine next year's calendar. The year-in-review is a single document that contains: - What was planted, by bed, with first-sow and last-harvest dates - What was harvested, by crop, with rough total weight or count where the gardener recorded it; "not recorded" where she did not - What failed, by crop, with the gardener's stated cause where given, or your inferred cause where not (frost, pest, disease, drought, bolting, abandoned) - Three concrete suggestions for next year based on this year's phenology — for example: "your tomatoes were ready to harvest 12 days later than the city's mean — your bed runs cooler; start indoor sowing two weeks earlier next year"; "your lettuce bolted by mid-May every succession — switch to bolt-tolerant cultivars and shade-cloth from May onwards"; "your courgette in bed 3 lost every leaf to powdery mildew by August — drip-irrigate and rotate to bed 1 next year" - One paragraph honest reflection on what the data tells about HER microclimate vs the city's published normals — does her plot run warmer (urban heat island, south-facing wall), cooler (frost pocket, north-facing slope), wetter, drier? Hard rules: - Use ONLY the gardener's recorded data and the city's published climate normals. Do NOT forecast next year's weather. Do NOT predict which crops will succeed next year except where this year's data clearly indicates a microclimate effect. - Suggestions are concrete, named, and bounded ("start tomato seed indoors on 1 March instead of 15 March"); not generic ("plant earlier"). - Where this year contradicts last year, name the contradiction and ask whether the user wants to weight the two years equally or trust the more recent year. The user is the final arbiter. Output ONLY the structured annual-review object. No commentary outside it. ``` ## 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 first allotment.** A user takes on a new allotment plot in March, photographs the three empty beds, names them ("South bed", "Middle bed", "North bed by the shed"), gives the app her city (Birmingham, UK), and lists the crops she means to grow this year (potatoes, broad beans, salad, courgette, kale, chard, garlic). The app generates a year-long calendar pinned to Birmingham's actual climate (RHS H4, last-frost mean 19 April, first-frost mean 26 October) and the Saturday-morning lists begin. - **The Sacramento garden.** A Vietnamese-American gardener photographs her four beds: rau muống, kabocha, mustard greens, perilla, Thai basil, bittermelon. The app respects the crop names verbatim, pulls Sacramento's actual climate (USDA Zone 9b, last-frost mean 15 February, first-frost mean 30 November), and produces a calendar with sowings beginning in late February for the heat-loving crops and a second wave in September for the cool-weather greens. Bittermelon is given a trellis-staking task in May. - **The Mexico City rooftop.** A gardener at 2,250 metres elevation in Mexico City has six containers on her rooftop. She grows hoja santa, miltomate, chayote, cilantro, and acelga (chard). The app reports the unusual frost pattern (rare but possible January frost, mostly frost-free, but May days can hit 28°C and October nights can dip to 4°C) and produces a calendar that runs the cool-weather crops over the dry winter and the heat-loving crops through the May-October wet season. Miltomate is staked in June and the calendar warns of monsoon-arriving heavy rain and recommends a top-up of well-drained compost. - **The Birmingham allotment.** A Caribbean-British gardener in Birmingham, UK takes on a new plot and wants to grow callaloo and bonnet peppers alongside the typical UK allotment crops. The app pulls Birmingham climate (RHS H4, cool summers, late springs) and warns honestly: callaloo and bonnet peppers will need a polytunnel or cold-frame to produce reliably here, OR a south-facing wall with shelter. The calendar offers both options and produces task lists accordingly. - **The succession lettuce.** A user direct-sows lettuce every three weeks from March through August. The app schedules the next sowing every three weeks, brings it forward if rain is forecast, and pushes it back if a heat wave is forecast (lettuce germinates poorly above 27°C). After the user records that her June sowing bolted in ten days, the app suggests switching to a bolt-tolerant cultivar for the next succession and shading the seedbed. - **The polyculture bed.** A bed with kale in the north row, leeks down the middle, and beetroot along the south edge. The app reads the photograph and emits three CropOccupant objects. The Saturday list groups the three crops' tasks under "Bed 2" so the gardener can walk one bed at a time, but the per-task overlays target the correct row of the bed photograph. - **The voice memo from the plot.** The gardener records a 25-second memo at the plot on a Sunday afternoon: "the broad beans in the south bed have black-fly already, the carrots in the middle bed germinated yesterday, the courgette is throwing male flowers but no female yet, and I think a slug took the lettuce seedling on the north end." The app parses four phenology events and adjusts next Saturday's list accordingly (pest-check on the broad beans, thin the carrots once true leaves appear, no female-flower task yet, slug-patrol the lettuce row). - **The Hmong-American long-bean saver.** A Hmong-American gardener in Saint Paul, Minnesota saves her own long-bean (đậu đũa) seed every year. The app schedules a save-seed task at the right window (late-season pods left to dry on the vine), a pod-collection task, and a seed-cleaning task. The annual review at year-end records the variety as "family seed" and carries it forward to next year's planning. - **The mountain garden in Cusco.** A gardener at 3,400 metres in Cusco, Peru grows native potato varieties, quinoa, kiwicha (Amaranthus caudatus), and oca. The app pulls Cusco's actual climate (highland subtropical, year-round risk of frost at night above 3,000 m, sharp wet-season May–September), reports honestly that the European hardiness systems don't apply cleanly here, and schedules native-Andean planting windows around the local rain pattern rather than around frost dates. - **The container-only balcony.** A gardener on a 4 m² balcony in Lisbon has eight containers: cherry tomato, Thai basil, sage, rocket, strawberry, lemon verbena, parsley, pak choi. The app reads each container as a separate "bed", flags that container plants need more frequent watering and feeding than in-ground beds, and adjusts the calendar (daily water-deeply tasks in July; fortnightly liquid feed; top-up compost in March and September). - **The school garden.** A teacher with eight children, four raised beds, and a desire for a printable Friday-afternoon task list pinned to the shed door. The app's printable PDF mode is the primary surface; the digest is sent to her work email on Friday lunchtime so she can print and laminate before the bell. - **The co-tenant share.** Two friends share an allotment plot in Bristol. Both have their own login; both see the same beds; both can tick off tasks. When one of them photographs the courgette flowering on Tuesday, the other sees the phenology event on Wednesday morning. The Saturday list goes to both inboxes. - **The retirement garden.** A 71-year-old gardener in Adelaide has gardened for fifty years and wants the calendar pinned to her actual microclimate (south-facing slope, mild Mediterranean, AHS Zone 3). She records her own dates ("first tomato 28 December again this year — that's three years running 10 days earlier than the BoM mean for Adelaide"). The annual review concludes her garden runs warmer than the city's published mean and refines next year's calendar accordingly. ## 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 three small allotment beds in early-spring evening light: bare soil ready, a watering can leaning on a wooden bed edge, a robin on the shed roof. One paragraph: "Garden Almanac turns three beds and four crops into a year-long sowing-and-harvest calendar — in any climate, any zone, with weekly tasks that know what's planted where." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample garden" → loads the demo garden in section 8a. 2. **Empty state — "Start a garden".** Three big input methods: 📷 Photograph your beds · ✍️ Describe your beds · 📤 Upload a plot plan. A short explainer below each ("Best for a quick walk around the plot today", "Best if your phone camera is broken or the beds are too wet to photograph", "Best if you already have a plot plan from your allotment association"). 3. **Location & climate setup.** Ask for the city, country, optionally postcode, optionally elevation. Show what the app retrieved: "Birmingham, UK — Met Office 1991–2020 — last frost mean 19 April, first frost mean 26 October, RHS H4. Source: metoffice.gov.uk/…" with the citation URL visible. Let the user correct any field (a frost-pocket gardener may want to set her last-frost date two weeks later than the city's mean). NEVER assume USDA Zone 7 or any temperate-NA default. 4. **Bed capture flow** (mobile-first). Live viewfinder with a wide-frame prompt ("stand back, frame the whole bed in landscape, include a hand or foot at one corner for scale if you can"). After each bed, prompt: "Name this bed" and "What crops are you growing here?" with autocomplete that includes heritage-crop names from a curated multilingual library (ewedu, rau muống, hoja santa, perilla, miltomate, kabocha, callaloo, bittermelon, chayote, Thai basil, ngo gai, molokhia, ackee). The library is editable — the user can add her own. 5. **Bed detail view.** A two-column layout on desktop, stacked on mobile. Left column: the photograph of the bed (zoomable, with the spatial-understanding output rendered as a translucent overlay of detected rows / crops, toggle-able). Right column: bed metadata (construction, dimensions, aspect, mulch, weed pressure) and the list of crops currently planted with their growth stages and visible-issues. Sticky header: bed name → aspect chip → "(i) how the app read this bed". Edit any field inline. 6. **Calendar view.** Year at a glance — twelve columns of months, each bed as a row, each crop as a colour-coded band stretched across its sow-to-harvest window. Tasks appear as small dots along each band, clustered on the actual due-week. Hover a dot → preview the task. Click → opens the task detail with the bed overlay. Two zoom levels: year and month. The current week is a vertical highlighted band that scrolls into view on open. 7. **This week's task list.** The hero screen. Forecast summary across the top with the week's low / high and a frost-risk chip if relevant. Below: tasks grouped by bed, ordered within bed by urgency. Each task shows the description, the crop, the bed, the urgency chip, an estimate of how long it'll take, and a "show me where" button that opens the bed photograph with the work area highlighted. A tick column down the right side for completed tasks; ticking emits a phenology event automatically. 8. **Voice memo capture.** A single big mic button on a "log a walk" screen. Recording up to 90 seconds. After recording, a transcript appears with the parsed phenology events highlighted ("germinated", "first flower", "lost to slug"). The user confirms or edits; on save, events are written to the garden's phenology log. 9. **Phenology log.** A timeline of every event the gardener has recorded, per crop per bed. Filterable by event type ("show me every first-harvest"), by bed, by crop. Used by the annual review and by the calendar engine's microclimate refinement. 10. **Saturday digest.** The settings panel for the weekly digest. Time of delivery (default 06:00 local), language, voice (for the optional audio narration), email address. Preview of last Saturday's digest as it landed in the user's inbox. 11. **Annual review.** Available from October-November in the user's hemisphere. A year-in-review document (rendered in the same Apple-style typography as the rest of the app, NOT as a marketing dashboard) with what was planted, what was harvested, what failed, and three concrete suggestions for next year. The microclimate paragraph (does this plot run warmer / cooler / wetter / drier than the city's mean?) is the most important part. 12. **Sharing & co-tenants.** Modal: "Invite a co-tenant to this garden". Magic-link email; arrival drops the co-tenant straight into the same garden with their own avatar. Editable role: co-tenant (can edit), viewer (read-only). 13. **Settings.** Climate-source override, default fertility approach (organic / mixed / conventional — defaults to organic), notification language, digest time, delete-this-garden-forever. 14. **Footer.** "Made for the gardeners the seed-packet calendar doesn't reach." Privacy: "Your garden is yours. We never train on it." 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 Garden Almanac." - Subhead: "Turn your beds and your crops into a year-long sowing-and-harvest calendar — pinned to your actual climate, not somebody else's." - One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic gardening app: it uses your city's real climate normals (not a North American default), it knows the crops you actually grow (including heritage crops the seed packets ignore), and it sends one short task list every Saturday morning instead of a constant feed of notifications. - Visual: a small annotated illustration of three garden beds in early spring with the relevant chips labelled (your climate, your beds, your crops, your Saturday) — not a generic plant icon. **Slide 2 — Try it now.** - One short prompt: "Try with the sample garden". - A live demo pre-loaded with the four sample gardens in section 8a (Birmingham allotment, Sacramento home garden, Mexico City rooftop, Adelaide retirement garden). - 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the bed parse picking up the rau muống in a Sacramento polyculture, the climate-grounding call returning Birmingham's RHS H4 with the Met Office citation, the calendar's Saturday-morning task list arriving with the bed-photograph overlays). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample gardens in `/data/seed-gardens/` for your own beds." - "Adjust the prompts in `/server/prompts/` to fit the crops you actually grow." - "Wire up your Gemini API key and Firebase project via the env-var list in the capabilities panel." - 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 + spatial)** — reads each bed photograph: bed construction, rough geometry, aspect from visible shadows, current crops, growth stages, visible pests and stress. One call per bed. - **Gemini 3.5 Flash + grounded search** — retrieves your city's actual climate normals from a real published source (Met Office, NOAA, BoM, INM, etc.), and pins the calendar to YOUR last-frost date and YOUR hardiness system, not USDA-by-default. - **Gemini 3.5 Flash (calendar engine)** — generates the year's calendar from your climate × your beds × your crops, scheduling every sow, transplant, thin, side-dress, harvest, and clear task at the right week. - **Gemini 3.5 Flash (weekly list)** — every Saturday at your local 06:00 (changeable), pulls this week's tasks, adjusts for the forecast, and folds in last week's phenology events. - **Gemini 3.5 Flash (long context, annual review)** — at year-end, reads every voice memo, every photograph caption, every task completion across the season and produces the year-in-review and the refined next-year calendar. - **Gemini TTS** — narrates the weekly list in your language at a walking pace, for the walk to the plot. Vietnamese, Spanish, Portuguese, Hindi, Mandarin, Yoruba, Swahili, English, and many more. - **Nano Banana 2** — used sparingly, as a fallback when no recent photograph of the bed is available; the default per-task overlay is your own bed photograph with a translucent geometry layer drawn over it. - **Firebase Auth** — Google and Apple sign-in, co-tenant invitations via magic links. - **Firestore** — stores your garden, syncs across devices in real time so a co-tenant tick updates everyone's view. - **Firebase Storage** — keeps your bed photographs at upload resolution, forever. - **Cloud Scheduler** — fires the Saturday digest in your local timezone, fans out per garden. - **Cost note** — see the detailed breakdown in 6d. A typical 4-bed garden costs about $0.18 to set up (climate normals + bed reads + year-calendar) and about $0.04 per week of digest. The annual review costs about $0.30. - **Privacy note** — your garden is private to you and the co-tenants you invite. 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. Your bed photographs may incidentally include your house or family; we never make them public. **Backend services this app depends on:** - Auth: see section 4b - Database: see section 4b - Storage: see section 4b - Email: see section 4b - Scheduling: 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) - `SENDGRID_API_KEY` or `POSTMARK_TOKEN` — optional, for the Saturday digest emails. The Firebase Extensions "Trigger Email" extension wires either. - `WEATHER_PROVIDER_KEY` — optional. Default uses Open-Meteo with no key. **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: the year-calendar generation is a one-time-per-year cost (~$0.05 per garden); the Saturday weekly-list call is the recurring cost (~$0.04 per garden per week); the climate-grounding call runs once per garden when location is set, then cached. - One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this garden forever" — gone in 60 seconds), what is never sent for training, why your photographs are kept private even though garden topics seem benign (your house, your kids, your address can be inferred). **Documentation links:** - AI Studio Build docs - Gemini API multimodal, spatial, long-context, TTS, image-generation docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on Cloud Scheduler + per-timezone fan-out - A short note on Open-Meteo's no-key weather forecast endpoint **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) - **Bed reading (Gemini 3.5 Flash, medium thinking)** — typical bed photograph ≈ 1 image, ~600 output tokens. ~$0.009/bed. A 4-bed garden setup: ~$0.036 in bed reads. - **Climate-normals grounding (Gemini 3.5 Flash, low thinking, grounded search)** — runs once per garden when location is set, cached forever (re-runs only if user changes city). ~$0.02 per garden, including the grounded search calls. - **Year-calendar generation (Gemini 3.5 Flash, low thinking)** — input is climate + beds + crops list, output is a year of tasks. ~$0.05 per garden per year. - **Weekly task list (Gemini 3.5 Flash, low thinking)** — runs every Saturday morning per garden. Input is calendar + forecast + last week's events; output is one weekly list. ~$0.04 per garden per week. **About $2.08 per garden per year** if the user keeps the digest running for the whole season. - **Pest / disease ID (Gemini 3.5 Flash, medium thinking)** — runs only when the user submits a close-up. ~$0.012 per ID. - **Voice memo parse (Gemini 3.5 Flash, low thinking)** — runs per memo. A 30-second memo ≈ ~$0.002. - **TTS narration of the weekly list (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A 350-word weekly list ≈ $0.005 per narration. Cached per week; charged once. - **Per-task overlay illustration (Nano Banana 2)** — fallback only. ~$0.03 per illustration. Most users never trigger this because their own bed photograph is used. - **Annual review (Gemini 3.5 Flash, medium thinking, long-context)** — runs once per year at year-end. Input is the whole season's data. ~$0.30 per garden per year. - **Expected per-garden first-year cost:** ~$2.50 — almost all of it the Saturday digests. **Setup cost only:** ~$0.18. - **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A high-res bed photograph is ~3 MB; a 4-bed garden with weekly photographs over a year is ~0.6 GB ≈ ~$0.02/month. ## 7. Design language - **Mood:** A real gardener's notebook that happens to live on your phone. Not a slick tech product. Not a Pinterest aesthetic. The kitchen table at 6:00 on a Saturday morning with the kettle just on, the printed list on the fridge, the boots by the back door, the seed packets fanned out from last weekend's seed-swap. - **Typography:** Display serif for headings and the calendar's month names (Source Serif Pro or Charter). A workhorse humanist sans for body and chips (Inter or IBM Plex Sans). The crop names in the calendar use the serif; the metadata chips use the sans. A handwriting-styled accent (sparingly) only for the user's own annotations on bed photographs — never for app-generated content. - **Palette:** Bone-paper background `#F4EFE6` for the calendar and weekly-list view, deep earth `#2E2A26` for body text, sage accent `#5F7A53` for the primary chip / current-week highlight, tomato accent `#A33A2C` only for frost-risk warnings, slate blue `#3A5773` for the user's own annotations and saved memos. A muted ochre `#B58A3B` for the citation source in the climate panel. Borrowed from a cardboard seed-packet rack, not from SaaS design systems. - **Imagery:** The photographs of the beds are the hero. Never replace them with stock garden photographs; never crop them tighter than the user did. The per-task overlay is a thin translucent geometry drawn over the user's photograph in client-side SVG — never a Nano-Banana-generated picture that pretends to be the user's plot. Period maps and climate visualisations use restrained pastel fills and thin black borders — not full-saturation political colours. - **Hand-feel touches:** A barely-visible paper grain on the weekly-list background. The "show original photograph" expand on a bed thumbnail slides in with a thin shadow — like lifting a polaroid off the page. Hover on a calendar-band reveals the crop name and the variety in a soft tooltip; never aggressively glow. - **Spacing:** consistent 4-px base. Generous whitespace — the calendar needs room for the months to breathe. - **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Task chips use 6; the bed cards use 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 calendar's year-to-month zoom 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 ("Photograph the first bed to begin", not "Add your first item"). ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible names, dates, locations, crops, sample beds that fit the domain (use the seed content in section 8a as a starting point). When inventing, span continents and climates — Sacramento polyculture, Birmingham allotment, Mexico City rooftop, Adelaide retirement garden — but never claim a fictional garden is a real archival document. - Tone: warm, direct, plain-spoken, free of corporate language. This template is for a person walking the plot on Saturday morning, not for a SaaS dashboard user. - 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 experienced allotment holder wants to see "RHS H4" and "days-to-maturity"; the beginner sees "your last frost" and "weeks to first harvest"). - 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 (a low-confidence crop ID shows as a faintly underlined name; tapping it reveals the alternates the model considered). ## 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 gardens (sidebar):** - "Plot 47, Allotments off Bordesley Road" — Birmingham, UK, RHS H4. Three beds, four crops at start of season (potatoes, broad beans, salad mix, garlic). Climate source: Met Office 1991–2020. Contributors: Linnet (owner), Marcus (co-tenant). - "Backyard, 24th & H" — Sacramento, USA, USDA 9b. Four raised beds, polyculture: rau muống, kabocha, mustard greens, perilla, Thai basil, bittermelon, ngo gai. Climate source: NOAA 1991–2020. Contributors: My (owner). Family seed-saver since 2018. - "Azotea, Colonia Roma Norte" — Mexico City, MX, 2,250 m elevation. Six containers and one half-barrel: hoja santa, miltomate, chayote (trellised), cilantro, acelga, manzanilla, and one tepín pepper. Climate source: SMN/CONAGUA 1991–2020. Contributor: Lupe. - "Stirling Street back garden" — Adelaide, AU, AHS Zone 3 (mild Mediterranean). Five in-ground beds, established for thirty years. Tomatoes (Black Krim, Tigerella), broad beans, garlic, rocket, parsnips, runner beans, oregano. Climate source: BoM 1991–2020. Contributor: Mary, 71, gardener for fifty years. **Sample bed in detail view (this is what the demo should show):** - **Bed name:** "Middle bed, Plot 47" - **Bed construction:** raised-wooden, approximately 3.0 m × 1.2 m × 25 cm depth - **Inferred aspect:** "morning-sun-afternoon-shade" (large beech tree to the west casts shadow from ~3 pm in May; visible shadows in photograph confirm) - **Visible shadows inform aspect:** true - **Mulch state:** compost-mulch - **Soil appears:** moist-friable - **Weed pressure:** low - **Current occupants (3):** - "kale (cavolo nero / 'Nero di Toscana')", brassica, biennial-unknown habit, ~8 plants in the north row, vegetative stage, no visible issues - "leek ('Musselburgh')", allium, biennial habit, ~12 plants in the central row, vegetative stage, no visible issues - "beetroot ('Boltardy')", chenopod, annual, ~14 plants along the south edge, true-leaves stage, possible flea-beetle marks on two leaves (flagged for user review at confidence 0.55) - **Reading confidence:** 0.88 - **Flagged for user review:** "current_occupants[2].visible_issues — possible flea-beetle on two leaves; please confirm with a closer photograph" **Sample climate-normals retrieval (this is what the demo should show):** - **Location label:** "Birmingham, UK" - **Latitude:** 52.480 - **Longitude:** -1.903 - **Elevation:** 140 m - **Source:** "Met Office 1991–2020 (Coleshill weather station, 8 km east of city centre)" - **Last spring frost mean:** 04-19 (April 19) - **Last spring frost tail:** 05-14 (latest observed in record) - **First autumn frost mean:** 10-26 (October 26) - **First autumn frost tail:** 10-04 (earliest observed in record) - **Growing season days mean:** 190 - **Hardiness system:** RHS - **Hardiness zone:** "RHS H4" - **Notes:** "Coleshill is the nearest WMO-grade station; central Birmingham runs ~1°C warmer due to urban heat island, especially overnight in winter. If your allotment is on the city edge near Bordesley, treat the frost dates as published rather than warmer." **Sample this-week's task list (this is what the Saturday digest delivers, 4 May, Plot 47):** - **Week starting ISO:** 2026-05-02 - **Local timezone:** Europe/London - **Forecast summary:** "Cool start, showers Sunday and Monday, drying out Tuesday, sunny Wednesday-Friday with a chilly night Wednesday (low ~3°C — possible last frost — fleece the tender seedlings)." - **Forecast low / high:** 3°C / 16°C - **Frost risk:** true - **Tasks:** - South bed — "Earth up the early potatoes (Charlotte) before Sunday's rain — pull soil over the haulm to cover the shoots. ~25 minutes." - Middle bed — "Thin the beetroot to one finger's width once true leaves appear (check after Tuesday's dry-out). ~10 minutes." - Middle bed — "Check the leeks for any visible leek-moth damage; pinch off and bin affected leaves. ~10 minutes." - Middle bed — "Pest-check the kale for cabbage white eggs on the undersides of leaves (the brassica nets aren't on yet). ~5 minutes." - North bed — "Fleece the courgette seedlings Wednesday afternoon — frost forecast that night. ~5 minutes." - North bed — "Sow the next succession of salad mix in the south-edge row, only after Tuesday's rain clears. ~10 minutes." - Polytunnel — "Pot on the tomato seedlings (Black Krim, Sungold) — they have their second true leaves. ~30 minutes." - General — "Top up the slug-patrol around the bean rows; consider an organic ferric-phosphate pellet if pressure rises. ~10 minutes." **Sample voice memo (Lupe in Mexico City, parsed):** - Audio length: 24 seconds - Memo language: Spanish with some Nahuatl-origin crop names - Transcript: "Subí a la azotea. La hoja santa en el contenedor grande tiene flores, primera vez este año, hoy. El miltomate ya está poniendo frutos pero pequeñitos. La acelga necesita riego, está medio caída. Y vi una hormiguita en el chayote, hay que estar pendiente." - Parsed events (4): - hoja santa, bed "contenedor-grande" — `first-flower`, 2026-05-22, "primera vez este año" - miltomate, bed "contenedor-grande" — `first-fruit-set`, 2026-05-22, "frutos pequeñitos" - acelga, bed "contenedor-mediano" — observation/flag, "necesita riego, medio caída" → next list adds a deep-water task - chayote, bed "barril" — observation/flag, "hormiguitas, estar pendiente" → next list adds a pest-check task **Sample input artefacts (for the build to demonstrate):** - A wide-frame photograph of a raised wooden bed in early-May Birmingham — beetroot true-leaves, kale 30 cm, leeks pencil-thick, compost mulch, with the beech tree shadow visible at the east end. - An overhead photograph of a Sacramento polyculture bed — rau muống in shallow water, perilla flowering, Thai basil, ngo gai, and a single bittermelon vine reaching for a trellis. - A close-up of a hoja santa flower in a Mexico City rooftop container, with the heart-shaped leaves and the white inflorescence visible. - A handheld photograph of three Adelaide tomato varieties (Black Krim, Tigerella, Sungold) tied to bamboo stakes in mid-summer, with side-shoots visibly forming. - A close-up of suspected powdery mildew on a courgette leaf, sent to the pest-ID call — the model returns confidence 0.78 with two suggested treatments. **Sample voice copy:** - Onboarding: "Photograph one of your beds. We'll tell you what to do in it this week." - Processing: "Reading the bed…" / "Looking up your local frost dates…" / "Drawing the year's calendar…" / "Picking this week's tasks…" - Empty garden: "This garden is waiting for its first bed. Photograph the bed you'll work in first." - Error (couldn't read): "We couldn't tell what's planted here from this angle — want to step back a metre and try again, or just type the crops in?" - Save confirmation: "Saved the middle bed to Plot 47 — kale, leeks, beetroot, mulched in compost." - Frost-risk warning: "Frost forecast Wednesday night (low ~3°C). Your courgette seedlings will need fleece — we've added it to this week's list." - Low-confidence note: "Some crops were hard to identify from this photograph. Tap any underlined crop name to see what the model considered." - Climate-grounding citation: "Climate source: Met Office 1991–2020 — Coleshill weather station. View source." **Sample family / co-tenant invitation email subject + body:** - Subject: "Marcus — I'm setting up Plot 47 in Garden Almanac. Will you join as co-tenant?" - Body: "Hi Marcus — I've added the three beds and our crops for this year. Could you join so you see the Saturday list too and can tick off whatever you do midweek? Tap to join." [Open Garden] **Sample Saturday digest subject:** - "Saturday — Plot 47 — 4 May — 7 tasks, frost forecast Wednesday" ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of three small allotment beds in early-spring evening light, a watering can leaning on a wooden bed edge, a robin on the shed roof, kettle steam visible from the kitchen window in the background. Generate via Nano Banana 2 with a prompt emphasising "real wooden bed edges, dewy compost mulch, gentle gold-hour light, a robin perched on a shed roof in soft focus, no people in frame, no glossy AI sheen". - **App icon / wordmark:** Set in the display serif. Slightly weathered paper texture behind it. No icon — just type. - **Empty-state illustration:** A simple line drawing of a single garden bed seen from a 3/4 angle with three rows marked. Hand-drawn aesthetic, not a flat icon. - **Demo bed photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "real soil texture, real garden weather (dappled light, slight wind on leaves), a hand or trowel in frame for scale where possible, no people, no glossy AI sheen". Each demo bed should look photographed, not rendered. - **Climate visualisations:** Lightweight SVG charts in the climate-setup view (monthly mean temperature, monthly rainfall, monthly daylight hours). Restrained pastels, thin black borders, no full-saturation infographic look. - **Stock fallbacks:** If image generation fails, fall back to the photographed sample bed from `/public/samples/sample-bed.jpg`. Never to a "🌱" emoji. - **Generated imagery:** prefer Nano Banana 2 over stock photography for the hero and the demo beds. 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: "Photograph the first bed", "Set your city", "Add a crop you grow" — 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 reach the climate-normals source — want to enter your last-frost date manually?") and offer retry. - Low-confidence crop identifications in the bed view are faintly underlined; tapping reveals the alternates the model considered. - The calendar's year-to-month zoom takes 450 ms with `prefers-reduced-motion` falling back to instant. - Ticking a task off the weekly list emits a phenology event in the background; the tick animates only with `prefers-reduced-motion` not set, and is instant otherwise. ## 11. Tech & responsive requirements - **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 bed-reading / calendar-generation / pest-ID / annual review and `gemini-3.5-flash` for climate-grounding (with `google_search`) / weekly-list / voice-memo parse. Set `thinkingLevel` explicitly per call. - **Image generation:** `gemini-3.1-flash-image` (Nano Banana 2) only for the per-task overlay fallback (no recent bed photograph) and the hero image at first launch. - **TTS:** `gemini-3.1-flash-tts-preview` for the audio narration of the weekly list. Omit `thinkingConfig` on TTS and image-generation calls — the field is unsupported. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed gardens on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it (user-configured); magic-link email for co-tenant invites. - **Storage:** Firebase Storage for bed photographs and per-task overlay assets. Pre-signed URLs only. Storage is NOT auto-provisioned by AI Studio Build — enable it in the Firebase console. - **Scheduling:** Cloud Scheduler jobs (auto-provisioned alongside Cloud Run) for the Saturday digest, per garden, per local timezone. - **Weather forecast:** Open-Meteo with no key by default; user can swap in another provider via env var. - **Long context guardrail:** the annual review and the multi-year phenology-learning call must chunk by year if total token count exceeds 500k. A single-year, single-garden case is comfortably under that. - **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 current week's task list so a co-tenant tick updates the other's view. - Optimistic UI on writes; reconcile on response. - Bed-capture flow uses the Web Camera API with fixed focus/exposure where supported; falls back to native camera otherwise. - **iOS Safari gotchas (graceful degradation):** camera permission does NOT persist across page reloads on iOS — re-request on every bed scan; backgrounded Safari tabs pause `getUserMedia` — re-acquire the stream on `visibilitychange`; on Low Power Mode iOS may degrade resolution — always offer `` as a fallback so a bed photo still uploads when WebRTC is denied; rotation drops the camera track on iOS — re-bind on `orientationchange`. ## 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. - All images have meaningful `alt` text. The bed photographs have `alt` describing the bed ("photograph of a raised wooden bed in early May, three rows of vegetables visible: kale at the back, leeks in the middle, beetroot at the front, compost mulch, partial shade from a beech tree at the east end"). - Form fields have associated `