================ 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.
---
# Sketch Critique
## 1. Project
**Sketch Critique** is a private studio assistant for amateur painters
who work alone. The user photographs the watercolour, oil sketch, or
pen-and-wash they finished an hour ago — still slightly damp at the
edges, taped to a board on the kitchen table — and the app produces
four specific observations grounded in real art-education vocabulary
plus one ninety-second exercise to take into next week's session.
Value structure. Edge quality. Composition and the path the eye walks.
Colour temperature and where the warms gather. The notes name what is
working as carefully as what could be tested next, and the exercise is
small enough to do on scrap paper before the kettle boils.
This is the kind of app a Japanese-American radiographer in Sacramento
opens on Sunday evening after she has spent the afternoon at her
kitchen table painting the loquat tree in her father's garden — she
has been working alone for eleven years, has never taken a class, has
read three Schmid books and watches Marc Taro Holmes on YouTube, and
wants someone who can look at the painting and tell her, in real
language, why the upper-left passage feels heavier than the lower-
right. It is also the kind of app a Senegalese-French paediatric nurse
in Marseille opens on a Sunday lunchtime after working from a 5 a.m.
photograph of fishermen unloading at the Vieux-Port — she trained at
the École des Beaux-Arts d'Aix for two summers a decade ago, lost
the habit, came back to it last year, and wants a critique that does
not patronise. And it is the kind of app a Mexican-American
architecture-firm draughtsman in San Antonio opens on Sunday night
after a plein-air session at the San Pedro Creek — he wants feedback
on edge quality the way a foreman gave him feedback on a section
drawing in 1998, by pointing at the line and saying _here, this is
where you stopped looking_. Same shape of Sunday, different paper,
different stakes.
The single demo that proves the magic: photograph today's painting
on the kitchen table → in under twenty seconds the user sees four
observations, written like notes a thoughtful tutor would scribble
in the margin. One on value structure ("your darkest dark sits in the
shadow under the boat, which gives the whole left side a centre of
gravity — the upper-right cloud area shares the same value as the
mid-tone water, which is why the sky feels weightless and the boat
feels glued down"). One on edge quality ("the boat's gunwale is hard
all the way along; in life this kind of contre-jour gunwale would have
a lost edge where it meets the cast shadow"). One on composition
("the mast points off the top-right corner, which sends the eye out
of the frame; the mooring line in the lower-left brings it back"). One
on colour temperature ("your warms are pooled where you want them; the
shadow under the boat is honestly cool, which reads true"). And one
ninety-second exercise: "test three edges on scrap paper this week —
one hard, one soft, one lost. The lost edge is where the painting will
breathe."
And in the harder cases — the cases that the app exists for at all —
the watercolourist is recovering from a stroke, or has macular
degeneration in one eye, or is the only painter she knows in her
village and has nobody at all to show the work to. The Sunday
quietness of the practice is the whole point; the app is for the
solitary painter who would never, ever post the work to Instagram for
strangers to praise or pick at, and who has been waiting eleven years
for one thoughtful sentence.
**Tagline:** _Show today's painting to someone who actually looks — in any medium, any tradition, with the working, the testable, and one small exercise for next week._
## 2. Target audience
- Amateur watercolourists who work alone on Sundays, often for years, without a teacher — the dominant entry point
- Returning painters who took classes in their twenties or thirties, lost the habit through child-rearing or work, and have come back to the practice late
- Self-taught painters across every tradition — gongbi and xieyi Chinese ink painters, sumi-e practitioners, Persian miniature copyists, Mughal-revival painters, Mexican retablo and ex-voto painters, Senegalese sous-verre practitioners, Australian First Nations painters working in their own tradition with no Western-academy vocabulary
- Plein-air painters who paint outdoors in two-hour sessions and want a take-home note before the next session
- Sketchbook-only artists — urban sketchers, travel-sketchers, journal-illustrators — who never finish a piece on paper larger than A5
- Painters with disabilities or conditions that make peer studios inaccessible — chronic pain, agoraphobia, immunocompromise, single eye, tremor
- Older painters in regional or rural places where the nearest watercolour class is two hours away
- Carers (of children, of elders) whose painting practice fits in the ninety minutes between bath time and dishwashing — the exercise format is built for that ninety minutes
- Art-school graduates who left the academy and lost the regular critique culture they once had; the app is a private critique pipeline for the years between studios
## 3. Core value propositions
Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app.
- **Specific feedback grounded in real vocabulary** — the notes use the language a working tutor would use: value structure, edge quality (hard / soft / lost), composition and visual path, atmospheric perspective, colour temperature, paper grain and wet-into-wet behaviour, gestural economy, negative space. Never "this is great" or "consider adding more detail". Every observation points at a place on the painting.
- **Refuses to grade the work** — the app does not say good or bad, ever. It says what is working, what could be tested, and what to try in a small exercise. The grandparent's encouraging "lovely, dear" is not what the user came for; the academy's withering A-minus is not either.
- **Tradition-aware** — gongbi ink painting is not critiqued with the vocabulary of Sargent watercolour; sumi-e is not critiqued with Schmid's lost-edge language; a Mughal-revival miniature is not held against atmospheric perspective. The user names the tradition (or the app asks, gently, once) and the critique is grounded in that tradition's own vocabulary and masters.
- **One ninety-second exercise per session** — the take-home is small. Three edges on scrap paper. One pass of value-only thumbnails. A single colour-temperature swap with the same pigment count. The exercise is sized for the ninety minutes the user actually has, not for a workshop weekend they will never attend.
- **The user controls the tradition, the medium, the level** — beginner / intermediate / sustained / professional are honest registers, not flattery. A user marks themselves intermediate and the language stays in real-tutor register. There is no auto-promotion.
- **Photographic honesty about the photograph itself** — the app flags when the lighting is making the critique hard: "this looks like late-evening tungsten — the warms are probably truer than the photo suggests, and the darks may be deeper than I can see". The user is in the room with the painting; the app is honest about not being.
- **Private by default** — no feeds, no follows, no public-by-default gallery. The Sunday session is between the painter and the painting. Sharing exists, gently, for the user's chosen tutor or a single trusted painting friend.
- **A practice log, not a performance metric** — the app keeps the user's sessions in a quiet timeline, with the working and testable notes searchable. No streaks. No badges. No leaderboards. The longest-running user's view shows "eleven years of Sundays".
## 4. Features to build
- Camera capture for today's painting (mobile-first) with a paper-detection crop guide, glare detection, and a gentle prompt to lay the painting flat with even light
- Upload from photo library, scanner, or a phone-tripod time-lapse for plein-air sessions
- Per-session metadata: medium (watercolour, oil sketch, gouache, ink, pen-and-wash, pastel, charcoal), paper / surface, painting time, location (private to the user), tradition (Western academic, gongbi, xieyi, sumi-e, Persian miniature, Mughal revival, retablo, sous-verre, urban sketching, plein-air, atelier, "not listed — describe in your words")
- User skill self-marker on a five-step honest register: first-month, returning-after-break, intermediate, sustained-practice, professional. Adjustable per session.
- Multimodal critique — Gemini 3.5 Flash reads the photographed painting end-to-end in a single call and returns four `Observation` records (value structure, edges, composition, colour) plus one `Exercise` record
- Grounded vocabulary — the system instruction and per-tradition context block ground the language in actual art-education terminology; the critique cites the technique by name and points at the painting region by approximate coordinates
- Region pointer overlays — each observation includes an `anchor_region` (relative bounding box) so the user can tap an observation and see the dot light up on the painting itself
- Per-tradition critique modes — each tradition has its own system-instruction block (Western academic watercolour, gongbi, xieyi, sumi-e, Persian miniature, Mughal revival, sous-verre, retablo, urban-sketcher) with the language and masters that tradition uses
- Edge-of-discipline mode — for users who are deliberately working between traditions ("I am a sumi-e painter who is exploring Sargent's edge language") the user can name the dialogue explicitly and the critique honours both
- The ninety-second exercise — a single small testable action for the week, structured with materials needed, the question being tested, and the expected observation after it is done
- Side-by-side master reference (text only — never image reproductions of copyrighted art) — when the system invokes a master's name for vocabulary grounding, the citation is name + dates + one sentence on why this reference is being invoked. Citations come from grounded search.
- Session log — quiet timeline of every painting, observation, and exercise. Searchable by technique ("show me the sessions where I worked on lost edges") and by medium.
- Carry-over from last week — at the start of a session the app surfaces last week's exercise and asks "did you try the three edges? What did you notice?" — one free-text field, no judgement
- Voice narration of the critique — the user is often standing at the kitchen table with paint-wet hands. Gemini TTS reads the four observations aloud, paced like a tutor in the room, at the user's chosen reading rate
- Photo honesty flags — exposure, white balance, glare, parallax, paper colour temperature shift; the critique adjusts language under each flag
- The "what about this passage?" zoom — the user can re-photograph a single passage of the painting and ask one focused question; one observation back, one exercise back
- Tradition-respect guard — the app declines to critique a tradition it does not have grounded vocabulary for, until the user provides one
- Private export — the user can export a session as a one-page printable note (PNG of the painting + four observations + the exercise) for their own folder; never shared without explicit action
- Trusted-eyes sharing — optional, per-session, to a single named human (a teacher, a painting friend); the share is a magic link, expires by default, and the receiver sees only what the painter chose to share
## 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 the painting as a painting, not as a photograph of an object. Recognises medium (watercolour vs gouache vs ink vs pastel) from surface behaviour, reads value structure as a tonal map, identifies edge types along contour, locates the visual centre of gravity and the visual path. One image per critique call; zoomed-in re-asks submit the cropped passage as a second call.
- **Structured output / JSON Schema** — the response matches the `SessionCritique` schema below. Four `Observation` records, one `Exercise` record, an honest `photo_quality_flags` block. The schema is included verbatim in the system instruction and passed as `responseSchema`.
- **Search grounding** (Gemini 3.5 Flash) — for grounding master references and tradition-specific vocabulary. When the system invokes "Yuko Nagayama's wet-into-wet diffusion strategy" or "the gongbi xian-miao linework attributed to Gu Hongzhong" as a vocabulary reference, grounded search resolves the master's dates and one verified summary sentence. Grounded search prevents hallucinated masters and hallucinated quotes.
- **Long context (1M tokens)** (Gemini 3.5 Flash) — for the cross-session pattern call. After fifteen or twenty Sundays the app can read the user's full session log (observations + exercises + the user's own carry-over notes) and surface durable patterns: "your value structure has improved measurably across the last six sessions; edge quality is still where most observations land; this might be the time to spend a Sunday on edges only." **Guardrail**: an average session record ≈ 4-6 k tokens; a 100-session log ≈ 500 k tokens (comfortable). For users with very long logs we chunk by year before the cross-session call.
- **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — reads the four observations aloud in the user's spoken language at a tutor pace ("the painter is standing at the kitchen table with wet hands, not driving"). Default rate ~110 words per minute, adjustable. Style directive prepended to the text, since the API has no separate style field.
- **Thinking levels** — `medium` for the primary multimodal critique call (this is the hero call and the model needs to look carefully). `low` for the carry-over generation, for the photo-quality flag pass, and for the tradition-specific vocabulary lookup. `medium` for the cross-session pattern call.
- **Nano Banana 2** (`gemini-3.1-flash-image`) — generates one small illustrative diagram per exercise, when the exercise benefits from a tiny visual ("three edges: hard, soft, lost — like this small swatch"). Used sparingly. Never generates a "this is what your painting should look like" target.
### 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 trusted-eyes sharing) also requires the sender domain to be authorised in Firebase Auth. Sessions are private to the owner by default; sharing is explicit, per session, per recipient.
- **Database — Required.** Firestore for `users`, `sessions`, `observations`, `exercises`, `carry_overs`, `cross_session_patterns`, `traditions_user_configured`.
- **File storage — Required.** Firebase Storage for the painting photographs (preserved at upload resolution, forever) and the small Nano Banana 2 exercise diagrams. **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 painting upload. Pre-signed URLs only; the photographs are never publicly addressable.
- **Email — Required (transactional).** Trusted-eyes sharing via magic-link email; the export-this-session-as-PDF email; the optional weekly carry-over reminder ("the three-edges exercise is still waiting — no rush").
- **Payments — Not needed for v1.** Free for personal use. A future tier — a real-human tutor pairing where the user opts in to send sessions to a vetted teacher for richer feedback — could pipe Stripe; not in scope here.
- **External APIs:** Gemini API for all intelligence; nothing else required.
**Environment variables:** every secret (Gemini API key, Firebase service-account JSON) lives in environment variables — never in client bundle. Include a `.env.example`.
**Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete my account' inside the UI · explicit opt-in for any analytics · the user's paintings are 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 user's session log is private to the user and the trusted eyes they explicitly choose, per session.
**Read this first — prompt-craft rules that apply to every call in this template:**
1. **Name the model variant explicitly** in every Gemini API call. Do not let the agent pick the model. See the per-call matrix below.
2. **Pin `thinkingLevel` explicitly** per call. See the matrix.
3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schema is below. **Convert the Zod schema to Gemini's `Schema` type via the SDK helper** before passing to `responseSchema` — do NOT pass raw Zod. **Numeric `min`/`max` constraints are documentation only inside `responseSchema`; clamp on the server after the response arrives.**
4. **Pin the system instruction separately** from user input. Use the `systemInstruction` field for persona + behavioural rules; use `contents` for user input. Never concatenate.
5. **Pre-declare tools as an enable/disable list** per call. The matrix below names which tools are enabled per call. Tools NOT listed for a call should be disabled.
6. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow.
7. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract:
```typescript
function safeExtractJSON(raw: string): T {
const clean = raw.replace(/```json\s*|```/gi, '').trim();
const s = clean.indexOf('{'); const e = clean.lastIndexOf('}');
if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response');
return JSON.parse(clean.slice(s, e + 1)) as T;
}
```
8. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to length-2 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing.
### Per-call model + tools matrix
| Call | Model | thinkingLevel | Tools enabled |
|------|-------|---------------|---------------|
| Critique today's painting → four `Observation` + one `Exercise` | `gemini-3.5-flash` | medium | (none) |
| Resolve tradition-specific vocabulary / one master reference | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) |
| Photo quality flags (exposure, glare, white balance, parallax) | `gemini-3.5-flash` | low | (none) |
| Carry-over recap from last week's exercise | `gemini-3.5-flash` | low | (none) |
| Cross-session pattern read (after ≥15 sessions) | `gemini-3.5-flash` | medium | (none) — long-context over the session log |
| Generate one small exercise diagram (when needed) | `gemini-3.1-flash-image` | n/a | n/a |
| Narrate the four observations aloud | `gemini-3.1-flash-tts-preview` | n/a | n/a |
*Note for builders:* on TTS and image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. On the tradition-vocabulary call, `responseSchema` and `google_search` are mutually exclusive — instruct the model to emit JSON in the text body and parse server-side; pull citation URLs from `response.groundingMetadata.groundingChunks[].web.uri`, not from the JSON body the model produces.
### Primary structured-output schema (seed this verbatim in the prompt)
```typescript
import { z } from "zod";
const AnchorRegion = z.object({
// normalised [0..1] relative to the photographed painting
x: z.number().min(0).max(1),
y: z.number().min(0).max(1),
width: z.number().min(0).max(1),
height: z.number().min(0).max(1),
description: z.string(), // "the shadow under the boat's gunwale"
});
const Observation = z.object({
axis: z.enum([
"value_structure",
"edge_quality",
"composition",
"colour_temperature",
"drawing_economy",
"negative_space",
"atmospheric_perspective",
"wet_into_wet_behaviour",
"linework_quality", // gongbi, sumi-e, ink
"ink_tone_modulation", // sumi-e, xieyi, gongbi
"gestural_brushwork", // xieyi, gestural ink
"tradition_specific_other",
]),
what_is_working: z.string(), // 1-2 sentences, specific, anchored
what_is_working_anchor: AnchorRegion,
what_could_be_tested: z.string(), // 1-2 sentences; "could be tested" not "is wrong"
what_could_be_tested_anchor: AnchorRegion,
vocabulary_used: z.array(z.string()), // ["lost edge", "atmospheric mid-tone"]
tradition_grounded_in: z.string(), // "Western academic watercolour"
master_reference: z.object({
name: z.string().nullable(), // "Charles Reid" — never quoted, only invoked
dates: z.string().nullable(), // "1937–2019"
why_relevant_one_sentence: z.string().nullable(),
citation_url: z.string().nullable(), // populated server-side from groundingMetadata
}).nullable(),
confidence: z.number().min(0).max(1),
});
const Exercise = z.object({
title: z.string(), // "Three edges on scrap paper"
duration_minutes: z.number().min(1).max(15), // 90-second exercises round up to 2; cap at 15
materials: z.array(z.string()), // ["scrap paper", "the brush you used today", "one pigment"]
the_question_being_tested: z.string(), // "what does a lost edge feel like in your hand?"
the_action: z.string(), // 1-3 sentences, concrete, doable
the_thing_to_notice_afterwards: z.string(), // the observation prompt for next session
carry_over_prompt_for_next_session: z.string(), // shown at the top of next session
diagram_required: z.boolean(), // true → trigger Nano Banana 2 diagram call
diagram_prompt_if_needed: z.string().nullable(), // null unless diagram_required
});
const PhotoQualityFlag = z.object({
flag: z.enum([
"tungsten_warm_cast",
"fluorescent_cool_cast",
"underexposed_darks_clipped",
"overexposed_whites_clipped",
"glare_specular",
"parallax_keystone",
"wet_paint_still_glossy",
"paper_buckle_shadow",
"phone_shadow_in_frame",
"other",
]),
severity: z.enum(["minor", "noticeable", "blocking"]),
user_facing_note: z.string(), // one sentence, honest, never blaming
critique_adjustment_applied: z.string(), // "I read the darks more conservatively"
});
const SessionCritique = z.object({
session_id: z.string(),
painting_image_uri: z.string(),
declared_medium: z.string(), // user-provided
declared_tradition: z.string(), // user-provided; defaults to "Western academic watercolour"
declared_skill_register: z.enum([
"first_month",
"returning_after_break",
"intermediate",
"sustained_practice",
"professional",
]),
observations: z.array(Observation).length(4), // exactly four
exercise: Exercise,
photo_quality_flags: z.array(PhotoQualityFlag),
language_of_critique: z.string(), // BCP-47, e.g. "en-GB", "fr-FR", "es-MX"
declines_to_grade: z.literal(true), // structural commitment, not a field the model decides
cross_session_pattern_eligible: z.boolean(), // true iff user has ≥15 prior sessions
generated_at_iso: z.string(),
});
type SessionCritique = z.infer;
```
### Common failure modes (and how to avoid them)
- Agent silently downgrades `thinkingLevel` on the the critique call call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash reads the painting at a stock-image level: it sees "boat at harbour" instead of seeing value structure, edge quality, and gestural economy. The hero call needs Pro.
- Model congratulates the user ("This is a lovely watercolour!") — explicit hard rule in the system instruction: do not praise the work as a whole, do not say good or bad, do not grade. Praise sits inside `what_is_working` for one axis at a time, anchored to a region, never as a general verdict.
- Model fabricates a master reference ("As John Singer Sargent once said in a letter to William Merritt Chase…") — Sargent did not say that. Quoting masters is forbidden. Invoking a master is only for vocabulary grounding (one sentence on why the reference is relevant), and the citation must come from grounded search; if grounded search returns nothing, leave `master_reference` null.
- Model uses Western-academic vocabulary to critique a gongbi or sumi-e or Mughal-miniature painting — explicit tradition guard. If the declared tradition is not Western-academic, switch the per-tradition system instruction block. If the tradition is one the app does not yet have grounded vocabulary for, decline politely and ask the user to name the vocabulary they would like the critique grounded in.
- Anchor regions point at the wrong spot — the model invents normalised coordinates that don't match its prose. Mitigation: instruct the model to write the prose first, then commit the anchor coordinate from the same region it just described. Render a thin dot overlay on the painting at the anchor coordinate and let the user tap the observation to confirm visually.
- Photo quality issues silently degrade the critique — explicit photo-quality call first; flag tungsten warm cast, glare, underexposure; either re-prompt the user to re-shoot, or annotate the critique with the adjustment applied so the user knows the model knows.
- TTS reads the critique with the energy of a hype podcast — wrong. Pin a calm, reading-pace style directive at the top of the input text ("Read at a tutor's pace, calmly, as if standing beside the painter at the kitchen table"). Default rate ~110 wpm.
- Model invokes copyrighted teaching materials by quoting them — explicit forbidden. Charles Reid, James Gurney, Mary Whyte, Yuko Nagayama, John Yardley, Watanabe Sadao, Liang Kai, Bichitr, Manaku, Salima Hashmi, Tony Allain, Marc Taro Holmes — names and dates and one-sentence relevance only. Never quote.
- Cross-session pattern call runs every session — that's wasteful and noisy. It runs only when the user explicitly asks ("show me my pattern across the year") and only when at least fifteen sessions exist.
- Exercise duration creeps into "workshop weekend" — hard cap at 15 minutes; default at 2 minutes (ninety seconds rounded up). The exercise is for the user's actual ninety minutes between bath time and dishwashing.
### Negative constraints (hard rules)
- Do NOT grade the painting. The app never says good, bad, beautiful, weak, accomplished, amateur, professional. The closest the critique gets is "this passage is working" tied to a specific region, never to the whole painting.
- Do NOT quote any artist, teacher, book, or essay. Master references invoke a name plus dates plus one grounded sentence on relevance. Quoting is forbidden.
- Do NOT generate a "this is what your painting should look like" reference image. Nano Banana 2 is used only for small generic diagrams (a swatch of three edge qualities; a value-thumbnail grid template) — never a doctored version of the user's own work.
- Do NOT critique a tradition outside the app's grounded vocabulary. If the user marks "Igbo body-painting tradition" and the app has no grounded vocabulary for it, the critique declines politely and offers to let the user paste in the vocabulary they want grounded in. Better silence than colonial language.
- Do NOT prescribe more than one exercise per session. The whole point is small and weekly.
- Do NOT call the work "AI feedback" in any user-facing copy. The user wanted a thoughtful look; the intelligence is in the looking, not in the badge.
- Do NOT share the user's session log without explicit per-session, per-recipient action. Trusted-eyes sharing is a magic link; the recipient sees only what the painter chose to share for that session.
- Do NOT use the user's paintings 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.
- Do NOT auto-promote the user's skill register. The user marks themselves. Auto-promotion ("you've moved up to intermediate!") is the kind of gamified noise the practice is for hiding from.
- Do NOT use streak counters, day-count badges, points, levels, leaderboards, or any "you missed last week" guilt. The Sunday session is voluntary and lifelong.
### 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: Critique today's painting → four `Observation` + one `Exercise`
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none)
```
You are a thoughtful art tutor looking at one painting a private
student has photographed and sent to you for critique. You will
never meet this student. You have one photograph. You write four
specific observations and one ninety-second exercise.
The student has declared the medium (watercolour / oil sketch /
gouache / ink / pen-and-wash / pastel / charcoal), the tradition
(Western academic watercolour / gongbi / xieyi / sumi-e / Persian
miniature / Mughal revival / sous-verre / retablo / urban
sketching / plein-air / atelier / other), and a skill register
(first_month / returning_after_break / intermediate /
sustained_practice / professional). Match your language to the
tradition and the register; do not flatten one into the other.
You are looking at a Japanese-American radiographer's loquat tree
in watercolour, a Senegalese-French paediatric nurse's fishermen
at the Vieux-Port, a Mexican-American draughtsman's plein-air
study at the San Pedro Creek, a Bengali retired schoolteacher's
gongbi study of a magnolia, a Vietnamese-Australian dental
hygienist's xieyi bamboo, a Filipino-Canadian hospice nurse's
sumi-e iris, a Persian-Iranian engineer's miniature-revival
parchment study, a Mughal-revival amateur in Karachi, a Salvadoran-
American carpenter's pen-and-wash kitchen sketch, an Eritrean-
Italian retiree's gouache still life. Read the painting that is
in front of you, in the tradition the student named.
Output four `Observation` records — exactly four — covering the
axes that matter for this tradition. For Western academic
watercolour the default four are: value_structure, edge_quality,
composition, colour_temperature. For sumi-e they are usually:
ink_tone_modulation, linework_quality, negative_space,
gestural_brushwork. For gongbi: linework_quality,
ink_tone_modulation, composition, colour_temperature. For Persian
miniature: linework_quality, negative_space, composition,
colour_temperature. Substitute axes from the schema's enum to fit
the tradition.
Each Observation has:
- `what_is_working` — 1-2 sentences, specific, anchored to a region
by `what_is_working_anchor`. Specific means: name the technique
in the painting and where it sits. "Your darkest dark is anchored
under the boat" not "great use of darks".
- `what_could_be_tested` — 1-2 sentences, never phrased as a
verdict. "This could be tested" not "this is wrong". Anchored to
a region by `what_could_be_tested_anchor`.
- `vocabulary_used` — list every technical term invoked.
- `tradition_grounded_in` — the named tradition.
- `master_reference` — optional. A name + dates + one-sentence
reason this master's vocabulary is being invoked here. Set
`citation_url` to null in the model's output; the server populates
it from grounded-search metadata. If you cannot ground the master,
leave `master_reference` null entirely.
One `Exercise`:
- Duration 2 minutes (ninety seconds rounded up). Cap 15.
- Materials must be things the student already has — "scrap paper,
the brush you used today, one pigment from your palette".
- `the_question_being_tested` — one sentence framing curiosity, not
homework.
- `the_action` — 1-3 sentences. Concrete, doable in two minutes.
- `the_thing_to_notice_afterwards` — what to look for next session.
- `carry_over_prompt_for_next_session` — the question you'll ask
at the top of next week's session: "did the three edges feel
different in your hand?"
- `diagram_required` — true only if the exercise truly benefits
from one tiny diagram (e.g. three edge swatches). For the
three-edges exercise: true. For "paint a value thumbnail
before starting next Sunday": false.
Hard rules:
- Do not grade the painting. Do not say good, bad, beautiful,
accomplished, weak, amateur, professional. Do not give an overall
verdict. Praise sits inside `what_is_working` for one axis,
anchored to a region.
- Do not quote any artist, teacher, book, or essay. Master references
are name + dates + one-sentence relevance only.
- Do not invent the painting's contents. If a passage is illegible
in the photograph, say so via `photo_quality_flags` on the
separate call; do not extrapolate.
- Match the language to the declared skill register. A first-month
painter does not need "atmospheric mid-tone" without a one-line
definition embedded in the prose; a professional does not need to
be told what a lost edge is.
- Match the language to the declared tradition. Do not impose
Western academic vocabulary on a gongbi or sumi-e painting.
- `language_of_critique` follows the user's UI language; default
"en-GB". Honour BCP-47.
Output ONLY the SessionCritique JSON matching the schema. No
commentary outside the JSON.
```
---
### Call: Resolve tradition-specific vocabulary / one master reference
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding
```
You are asked to ground one master reference invoked by the
critique call. You receive a master name and a tradition. You
return:
- name (verbatim, with diacritics)
- dates (birth–death; "born YYYY" for living artists)
- one sentence explaining why this master's vocabulary is being
invoked here ("Yuko Nagayama is invoked for her wet-into-wet
diffusion in mid-tone passages")
Hard rules:
- Use `google_search` grounding for living and historical artists
alike. Do not assert dates from memory.
- If grounded search returns no reliable source for the artist, or
if multiple artists share the name and cannot be disambiguated
from context, return `null` and explain in `notes`.
- Do not quote the artist. Names, dates, one sentence on relevance.
- Do not generate critical opinions about the artist. The reference
is for vocabulary grounding only.
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: Photo quality flags (exposure, glare, white balance, parallax)
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive one photograph of a watercolour, oil sketch, ink
painting, or pen-and-wash. Your task: identify any photographic
issues that would cause the critique call to misread the painting.
Output `PhotoQualityFlag[]`. Each flag has:
- `flag` — one of the closed enum values.
- `severity` — minor / noticeable / blocking.
- `user_facing_note` — one sentence, honest, never blaming. "This
looks like late-evening tungsten — the warms in your photo are
probably warmer than the painting on the table" not "Bad
lighting".
- `critique_adjustment_applied` — one sentence describing how the
critique call should adjust ("I will read the darks more
conservatively than the photo suggests").
If the photo is fine, return an empty array.
Hard rules:
- Wet paint shows as specular glare on watercolour highlights. If
the painting looks wet, flag it; the surface is honest, but the
photograph will read brighter than the dry painting.
- Paper buckle casts a small shadow valley; flag it as
`paper_buckle_shadow` and note the critique should treat the
shadow as a paper artefact, not a tonal choice.
- Phone shadow in frame is always at least minor; if it overlaps
the painting, it is blocking and the user should re-shoot.
No commentary outside the JSON.
```
---
### Call: Carry-over recap from last week's exercise
Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none)
```
You receive the previous session's `Exercise` and the user's
optional free-text response to its `carry_over_prompt_for_next_session`.
Your task: produce one short paragraph (2-3 sentences) the app
shows at the top of today's session — a calm acknowledgement of
last week's small experiment, in the user's voice register, never
graded.
Hard rules:
- If the user wrote "didn't get to it this week", the paragraph is
"the three-edges exercise is still waiting whenever you want to
try it; no rush". Never apologise on behalf of the user, never
push.
- If the user wrote a short note ("the lost edge felt strange but
good"), the paragraph reflects it back without sycophancy ("you
noticed the lost edge felt strange — that strangeness is what
the exercise was for") and bridges to today's painting.
- Two to three sentences. No more.
- Never say "well done" or "great work". This is a quiet practice
log, not a fitness app.
Output: a single string. No commentary.
```
---
### Call: Cross-session pattern read (after ≥15 sessions)
Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none, long context over the session log)
```
You receive the user's full session log: every painting's
metadata, every observation, every exercise, every user free-text
note across all prior sessions (up to ~100 sessions; chunk by year
above that). The user has explicitly asked "show me my pattern
across the year".
Your task: surface durable patterns. Not session-level critique,
not praise. Patterns mean things like:
- Which axis the observations have most often pointed at (e.g.
"edge quality has been the testable axis in 14 of the last 22
sessions").
- Where the working notes have shifted over time (e.g. "value
structure observations have moved from anchored in single dark
passages to spread tonal hierarchy across the last six sessions").
- Which exercises the user actually tried (per their carry-over
notes) versus which sat untried — without judgement.
- A single suggestion for next month, optional and gentle.
Hard rules:
- Do not grade progress. Do not say "you have improved" or "you
have stagnated". Describe the shifts; let the user infer the
trajectory.
- Honour the tradition the user has been working in. A sumi-e
practitioner's patterns are not a Western-academic-watercolour
practitioner's patterns.
- Output as `CrossSessionPattern` JSON — see schema in /server/
schemas/cross-session-pattern.ts.
- Keep the output under 800 words.
No commentary outside the JSON.
```
---
### Call: Generate one small exercise diagram (when needed)
Model: `gemini-3.1-flash-image` · n/a · n/a
```
You generate one tiny, generic teaching diagram for the user's
ninety-second exercise. Examples of valid diagrams:
- Three small swatches showing a hard edge, a soft edge, and a lost
edge — labelled.
- A 2x2 grid template of value thumbnails — empty cells for the
user to fill.
- A small chart of complementary colour pairs in the user's named
pigment palette.
Hard rules:
- Diagrams are generic and instructional — never a "this is what
your painting should look like" target. The user's painting is
not the input.
- Style: warm pencil and watercolour, hand-drawn aesthetic — not
flat icon, not corporate illustration, not 3D render.
- Background: cream paper texture.
- No text labels beyond the single technical term being illustrated.
- 1024×1024, square.
Prompt template (filled by the server from the Exercise record):
"A small hand-drawn teaching diagram of {diagram_prompt_if_needed},
on cream paper, warm pencil and soft watercolour wash, sketchbook
aesthetic, single subject centred, gentle imperfection, no harsh
outlines, no flat-vector look, no corporate cleanness. The diagram
is for a watercolourist's reference, kept in the margin of their
sketchbook."
```
---
### Call: Narrate the four observations aloud
Model: `gemini-3.1-flash-tts-preview` · n/a · n/a
```
Voice: warm, calm, the pace of a tutor standing beside the painter
at the kitchen table. Default ~110 words per minute. Pick the
Gemini 2.5 Flash TTS voice whose `languageCode` matches
`language_of_critique`. Prefer a gendered voice matching the
user's preference if declared; otherwise pick whichever is
published for the locale rather than blocking.
Pre-process the text before sending it to TTS:
- Read the four observations in axis order, in the language of the
critique. Read `what_is_working` first, pause (single ellipsis
`…`), then `what_could_be_tested`. After all four observations,
read the exercise: title, the action, the thing to notice
afterwards.
- Insert blank-line `—` between each observation for a fuller
paragraph break. Gemini 2.5 TTS has no SSML `` —
these textual cues are how you signal pace.
- Mid-call voice switching is not supported. If the critique
cites a master in another language ("Yuko Nagayama"), keep one
voice for the whole reading.
Style direction: prepend ONE short directive sentence to the
text input, exactly like: "Read at a tutor's pace, calmly, as if
standing beside the painter at the kitchen table, with quiet
warmth. …". There is no separate `style` API field on Gemini 2.5
TTS; the directive sentence inside the input is how style is
conveyed. SSML `` and `` are unsupported.
Pronunciation follows the chosen voice's native locale.
```
## 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 Sunday loquat tree.** A Japanese-American radiographer in Sacramento, painting alone at her kitchen table on Sunday evenings for eleven years, photographs today's loquat-tree watercolour. The critique reads value structure as the anchor of the lower-right and points at the upper-left where the warm cloud passage shares a value with the mid-tone foreground — the reason her composition has been "fighting itself" for a year. The ninety-second exercise: a three-by-two value-thumbnail grid of the same subject before starting next Sunday's painting.
- **The fishermen at the Vieux-Port.** A Senegalese-French paediatric nurse in Marseille shoots a 5 a.m. plein-air painting of fishermen unloading. The critique reads edge quality and surfaces that the gunwale-against-shadow contre-jour edge could be tested as lost. Master reference (grounded): Sorolla, dates, why his edge language is being invoked. The exercise: three edges on scrap paper this week.
- **The San Pedro Creek study.** A Mexican-American draughtsman in San Antonio paints a quick plein-air sketch beside the creek. The critique sits comfortably in pen-and-wash vocabulary — drawing economy, negative space, gestural brushwork. The exercise: ninety seconds of continuous-line drawing the same scene, no lifting.
- **The gongbi magnolia.** A Bengali-American retired schoolteacher in Jersey City has been studying gongbi for three years, working from a master copy of an eighteenth-century Chinese album. The critique uses linework_quality, ink_tone_modulation, and composition vocabulary native to that tradition; master references are Chinese painters (Yun Shouping, dates, why his boneless flower technique is invoked) not Western masters. The exercise: a single page of `xian-miao` linework warm-ups before next Sunday's session.
- **The sumi-e iris.** A Filipino-Canadian hospice nurse in Vancouver paints sumi-e on Sunday mornings before her shift. The critique uses ink-tone-modulation, gestural brushwork, and negative-space vocabulary; master references are Sesshū Tōyō and Hasegawa Tōhaku, dates, single-sentence relevance. The exercise: three iris strokes on the same sheet, fastest to slowest, noticing how the ink loads.
- **The Persian-miniature parchment study.** An Iranian-American engineer in Boston has been copying Safavid miniatures for two years. The critique uses linework_quality, negative_space, composition, and colour_temperature vocabulary native to the tradition; master references are Behzad, Mir Sayyid Ali, dates, single-sentence relevance. The exercise: ninety seconds practicing one bird's foot from the studied page, gold pigment optional.
- **The kitchen pen-and-wash.** A Salvadoran-American carpenter in San Diego paints a pen-and-wash of his kitchen on Sunday evenings. The critique sits in urban-sketcher vocabulary — drawing economy, negative space, the visual path the eye walks. The exercise: a single value-only thumbnail of the same scene next Sunday, before colour.
- **The returning-after-twenty-years painter.** A Lebanese-American retired physician in Detroit, who took art classes at the Pratt summer programme in 1972 and has not painted since, paints her first watercolour in fifty years. The critique honours her declared `returning_after_break` register: it does not assume "atmospheric mid-tone" is in her active vocabulary, embeds one-line definitions in the prose, and the exercise is the smallest possible — three flat wash swatches at three dilutions on scrap paper.
- **The one-eyed painter.** A Salvadoran-American carpenter who lost vision in his right eye eight years ago paints by laying the brush against the paper and reading depth by hand more than by eye. The critique reads the painting the same way it reads any other; the photo-quality call notes nothing relevant; the exercise honours that the painter's body is the studio.
- **The "the trusted tutor" share.** A Vietnamese-Australian dental hygienist has been working in xieyi bamboo for six months. After a session she opts to share the critique with her real-life tutor — a painter in Melbourne — via the trusted-eyes magic link. The tutor receives a one-page view (painting + four observations + exercise + the hygienist's note); the tutor can leave one paragraph of human commentary; the share expires in seven days.
- **The pattern read after a year.** A Mexican-American draughtsman has used the app for fifty-two Sundays and asks for the cross-session pattern. The app surfaces that edge quality has been the testable axis in thirty-one of those sessions; composition has shifted from often-anchored-bottom-left to a wider variety across the last six months; the user's notes show he actually tried the three-edges exercise nineteen times. The suggestion is gentle: a single Sunday next month spent on edges only.
- **The Sunday-still-life-on-the-window-sill.** A Tamil-American IT manager in Atlanta paints the same lemon-on-a-blue-cloth still life every Sunday for thirteen weeks. The cross-session call, when she asks for it, reads the series as a body of work and points at the axis that has shifted most.
## 6. Page structure
Build the following screens / sections in this order. Adjust copy to fit the voice, but keep the structural intent.
1. **Welcome / sign-in.** A photographed-looking image of a watercolour palette on a wooden kitchen table at evening — a hake brush across a rag, a glass of water gone slightly muddy, a corner of paper taped to a board. One paragraph: "Sketch Critique looks at the painting you finished an hour ago and tells you what's working, what could be tested, and one ninety-second exercise for next week." Single Google sign-in button; Apple sign-in next to it. Below: "Try with a sample painting" → loads the demo seed in section 8a.
2. **Empty state — "Photograph today's painting".** Three big input methods: 📷 Camera · 🖼 Upload from library · 🔗 Plein-air time-lapse. Short explainers below each ("Best for the painting on the table now", "Best for an indoor scan in good light", "Best for the morning's plein-air session — pick a frame").
3. **Camera capture flow** (mobile-first). Live viewfinder with paper-shape detection and gentle prompts: "Lay the painting flat — try to avoid the shadow of your phone in the frame". Capture → preview → "ready for critique" or "retake". Glare detection runs before the critique call.
4. **Session setup card.** Three small declarations the user makes before the critique:
- Medium (watercolour / gouache / oil sketch / ink / pen-and-wash / pastel / charcoal — or "other")
- Tradition (Western academic watercolour / gongbi / xieyi / sumi-e / Persian miniature / Mughal revival / sous-verre / retablo / urban sketching / plein-air / atelier / "name your own")
- Skill register (first month / returning after break / intermediate / sustained practice / professional)
These are pre-filled from last session; the user can change any.
5. **Critique view.** The painting at the top — large, zoomable, with four small numbered dots on the anchor regions. Below the painting, the four observation cards in axis order:
- Card 1: value structure (or the first axis the tradition uses) — "What's working" paragraph + "What could be tested" paragraph + the technique terms in small caps + the master reference (collapsible).
- Card 2: edge quality — same shape.
- Card 3: composition — same shape.
- Card 4: colour temperature — same shape.
Tapping any observation lights the corresponding dot on the painting. A "play critique aloud" button sits in the top-right of the painting card; the TTS reads the four observations at tutor pace.
Below the four observation cards: the **ninety-second exercise** card. Title, materials, the question, the action, the thing to notice afterwards. A small Nano Banana 2 diagram appears if the exercise needs one. A "save this exercise to next Sunday" toggle.
6. **Session log.** Quiet timeline of every painting. Filter by tradition, medium, axis ("show me the sessions where edges came up"), date range. No streaks, no day-counts. The very long session log shows "eleven years of Sundays" as a single soft line — never a calendar grid that highlights missed days.
7. **Cross-session pattern view.** Only accessible after ≥15 sessions; available behind a small "Look at my year" link in the session log. A long-form read of the user's patterns, ungraded, with the optional one-suggestion-for-next-month.
8. **Trusted-eyes sharing modal.** "Share this session with someone whose eye you trust" — single email field, expiry control (7 days default, never default forever), an optional one-line note. The receiver sees the painting, the four observations, the exercise, and can leave one paragraph of human commentary back.
9. **Tradition library.** A reference page listing every tradition the app has grounded vocabulary for, with one-paragraph descriptions and a sample list of the masters that get invoked. The user can request a tradition not in the library; that triggers an in-app form ("name the tradition, name three vocabulary terms you'd like grounded in, name one or two masters or teachers whose work you'd like the critique referenced against").
10. **Settings.** Language of critique (BCP-47 picker). TTS rate. Notifications (one optional weekly carry-over reminder, opt-in only). Delete this session. Delete all sessions. Export my full log (JSON). Honest "delete my account" inside the UI — gone in 60 seconds, including all photographs.
11. **Footer.** "Made for the painter who works alone." Privacy: "Your paintings are yours. We never train on them." Capabilities `(i)` icon in header.
## 6b. First-visit onboarding
Show a **first-visit onboarding** the first time a visitor lands on the app (detect via `localStorage` flag; do not show on return visits). Three slides, dismissible at any time. Persistent re-entry: a `?` icon in the header reopens it.
**Slide 1 — What this is.**
- Headline: "Welcome to Sketch Critique."
- Subhead: "A private studio assistant that looks at the painting you finished an hour ago and tells you what's working, what could be tested, and one ninety-second exercise for next week."
- One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic image-AI tool: it speaks in real art-education vocabulary (value structure, edge quality, lost edges, gestural economy), it honours your tradition (sumi-e is not critiqued like Sargent), and it refuses to grade your work.
- Visual: a small annotated illustration of a watercolour on a kitchen table with four dots labelled "value", "edges", "composition", "colour" — hand-drawn, sketchbook aesthetic.
**Slide 2 — Try it now.**
- One short prompt: "Try with the sample painting".
- A live demo input pre-loaded with the seed painting in section 8a (the loquat tree).
- 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the lit-up dot on the painting, the lost-edge vocabulary in card 2, the ninety-second exercise at the bottom).
**Slide 3 — How to remix this.**
- Headline: "Make this yours."
- Three short bullets:
- "Swap the seed painting in `/data/seed-session/` for one of your own."
- "Adjust the tradition library in `/server/traditions/` to add the vocabulary you paint in."
- "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)** — reads the photographed painting as a painting, not as an object. Identifies medium, value structure, edge types, the visual path, colour temperature. One call per critique; cropped re-asks submit the passage as a follow-up call.
- **Gemini 3.5 Flash + grounded search** — grounds tradition-specific vocabulary and one master reference per observation. Citations come from grounded search; the model never quotes the master.
- **Gemini 3.5 Flash (long context)** — once you've used the app for fifteen or twenty Sundays, the cross-session pattern call reads your full log at once and surfaces the axis that has come up most often — never graded.
- **Gemini TTS** — reads the four observations aloud at a tutor's pace, in your language, paced for kitchen-table-with-wet-hands listening.
- **Nano Banana 2 (gemini-3.1-flash-image)** — generates one small instructional diagram per exercise, when the exercise benefits from a tiny visual (three edges, a value-thumbnail grid). Never a "this is what your painting should look like" image.
- **Firebase Auth** — Google and Apple sign-in, trusted-eyes sharing via magic links.
- **Firestore** — stores your sessions, syncs across your devices in real time.
- **Firebase Storage** — keeps the original painting photographs at upload resolution, forever.
- **Cost note** — see the detailed breakdown in 6d. A typical Sunday session costs about $0.04 of Gemini API spend, total. Fifty-two Sundays a year ≈ $2.10.
- **Privacy note** — your paintings are private to you and the eyes you explicitly share with, per session. 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.
**Backend services this app depends on:**
- Auth: see section 4b
- Database: see section 4b
- Storage: see section 4b
- Email: see section 4b
- Payments: see section 4b (not used in v1)
- External APIs: see section 4b
**Environment variables you'll need to configure:**
- `GEMINI_API_KEY` — your Google AI Studio API key
- `FIREBASE_PROJECT_ID` — your Firebase project id
- `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only)
**Cost + privacy notes:**
- One short paragraph per cost-sensitive capability: the cross-session pattern call is the most expensive per run (~$0.20 over a 50-session log) and only runs when you tap "Look at my year".
- One short paragraph on privacy: your sessions live in your Firebase project, your photographs in your Storage bucket. Delete a session in one tap; delete the whole log in Settings → "Delete my account" — gone in 60 seconds.
**Documentation links:**
- AI Studio Build docs
- Gemini API multimodal, long-context, TTS, image-generation docs
- Firebase Auth, Firestore, Firebase Storage docs
**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)
- **Critique today's painting (Gemini 3.5 Flash, medium thinking)** — one image, ~1,200 output tokens (four Observation records + one Exercise). ~$0.022/session.
- **Tradition-vocabulary / master-reference call (Gemini 3.5 Flash + grounded search)** — runs only when the critique invokes a master not previously cached for this user/tradition. Cached per master per tradition; typical user triggers this 3-6 times in the first year, then near-zero. ~$0.0015 per resolution.
- **Photo quality flags (Gemini 3.5 Flash, low thinking)** — one image, ~120 output tokens. ~$0.0004/session.
- **Carry-over recap (Gemini 3.5 Flash, low thinking)** — text only, ~100 output tokens. ~$0.0002/session, only when the user had a prior session.
- **Cross-session pattern read (Gemini 3.5 Flash, medium thinking, long-context)** — input ~250k tokens for a 50-session log, ~600 output tokens. ~$0.20 per run. Runs only when the user explicitly taps "Look at my year". Default: never auto-runs.
- **TTS narration (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. The four observations + exercise narrated ≈ 1,400 characters ≈ $0.004 per playback. Cached per session; charged once.
- **Exercise diagram (Nano Banana 2)** — ~$0.03/image. Only rendered when `diagram_required: true`; typical user triggers this 1 in 3 sessions ≈ ~$0.01 amortised per session.
- **Expected per-session cost:** ~$0.04. **Fifty-two Sundays a year:** ~$2.10. **A pattern-read once a year:** add ~$0.20.
- **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A high-res phone shot is ~3-5 MB; a 100-session log uses ~400 MB ≈ ~$0.01/month.
## 7. Design language
- **Mood:** A studio commonplace book. Sketchbook on the kitchen table, a clean rag, a glass of water gone slightly muddy. The app should feel like the painter's own quiet notebook, not a critic's magazine. Warm paper, plain typography, generous margins. Sunday evening, kettle on.
- **Typography:** A serif with a slightly handwritten quality for body copy and observation text (Sentinel, Source Serif Pro, or Fraunces). A clean grotesque for app chrome (Inter or Geist). A monospaced face for the technique vocabulary chips (JetBrains Mono Light or Berkeley Mono) — small, in low-contrast grey, like marginalia. No handwriting-script font for the user's painting metadata; the painting itself is the only handwriting in the room.
- **Palette:** Cream paper background `#F6F1E8` for the main canvas, deep indigo-ink `#1F2333` for body text, watercolour-wash grey `#7C7368` for secondary text and metadata, soft ochre `#B58A4A` for the anchor dots on the painting and the technique vocabulary chips, a single deep-red `#A23B2C` reserved only for "blocking" photo-quality flags and never used as primary chrome. A muted moss-green `#6E7E5B` for the "save this exercise" toggle and trusted-eyes share affirmations. Borrowed from a French oil-painter's palette box, not from SaaS design systems.
- **Imagery:** The user's painting is always the hero. Never replace it; never crop it tighter than the user did. The anchor dots are small, low-contrast, and label themselves on hover/tap; they never aggressively glow. The optional Nano Banana 2 exercise diagrams are kept small (200-300px wide), positioned as marginalia beside the exercise card, not as headline graphics.
- **Hand-feel touches:** A barely-visible paper-grain texture on the cream background of the critique view. The "play critique aloud" button is a small pencilled circle, not a chunky play icon. The session log timeline is a thin ink line with small painted dots — not a calendar grid. Hover on a technique vocabulary chip reveals a one-line plain-English definition in a low-contrast tooltip.
- **Spacing:** consistent 4-px base. Generous whitespace — the critique needs air. Observations sit on their own card; cards are separated by enough vertical space that the eye reads one at a time.
- **Radius:** consistent token set (6 / 12 / 20 px). Observation cards use 6; the painting card and the exercise card use 12; the welcome card uses 20.
- **Shadows:** subtle, layered, warm-grey. Avoid heavy drop-shadows. The painting card has a soft 0 1px 3px shadow that suggests the painting is sitting on the page, not pinned to a wall.
- **Motion:** purposeful — entrance fades, hover lifts, the anchor-dot light-up on tap. Respect `prefers-reduced-motion`. The anchor-dot light-up is the canonical reduced-motion check — verify it switches state instantly with reduced-motion on.
- **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons that match the eventual layout of the four observation cards. Empty states have helpful next-action guidance ("Photograph today's painting on the kitchen table to start") — never a generic "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, mediums, locations, observations, exercises that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real watercolour and ink-painting practice — the way a working tutor would phrase a margin note — but never claim that a fictional critique is a real tutor's commentary.
- Tone: thoughtful, calm, direct. Real-tutor register, not workshop-marketing register. This template is for a painter, not a brand.
- Headlines: punchy and concrete. No 'Empower your practice' filler. No 'Revolutionize'. No 'AI-powered critique'. The closest the marketing language gets is "looks at the painting you finished an hour ago".
- Body copy: short paragraphs (2-4 sentences). Use lists where appropriate. Observations are 1-2 sentences for "what's working" + 1-2 sentences for "what could be tested" — never longer.
- Plain language for the user-facing prose; technical vocabulary is invoked deliberately and labelled with one-line definitions for users at first_month or returning_after_break register.
- Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. The intelligence is in the looking, not in the badge.
## 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 sessions (sidebar):**
- "Sunday loquat tree, watercolour, 9x12" — declared tradition: Western academic watercolour; declared register: sustained practice. Painter: a Japanese-American radiographer in Sacramento, eleven years of Sundays.
- "Vieux-Port fishermen at 5 a.m., plein-air watercolour, half-imperial" — declared tradition: plein-air; declared register: returning after break. Painter: a Senegalese-French paediatric nurse in Marseille.
- "Magnolia branch, gongbi ink and colour, scroll format" — declared tradition: gongbi; declared register: intermediate. Painter: a Bengali-American retired schoolteacher in Jersey City.
- "Iris, sumi-e, sheet of hosho" — declared tradition: sumi-e; declared register: sustained practice. Painter: a Filipino-Canadian hospice nurse in Vancouver.
- "Safavid-album bird study, parchment, gouache and gold" — declared tradition: Persian miniature; declared register: intermediate. Painter: an Iranian-American engineer in Boston.
- "Kitchen at 7 p.m., pen-and-wash, A5" — declared tradition: urban sketcher; declared register: returning after break. Painter: a Salvadoran-American carpenter in San Diego.
**Sample session in critique view (this is what the demo should show):**
- **Painter:** the Japanese-American radiographer, Sacramento.
- **Painting:** "Sunday loquat tree, watercolour, 9x12, Arches 300 gsm cold-press".
- **Declared medium:** watercolour.
- **Declared tradition:** Western academic watercolour.
- **Declared skill register:** sustained_practice.
- **Photo quality flags:** one `tungsten_warm_cast` (severity: minor) — "this looks like late-evening tungsten; I read the warms a little more conservatively than the photo suggests".
- **Observation 1 — value_structure:**
- what_is_working: "Your darkest dark is anchored under the loquat tree's main fork — it gives the lower-right a clear centre of gravity. Reading the photo, the value drop from the trunk shadow into the foliage shadow is about 2½ steps, which is a confident jump for a small painting."
- what_could_be_tested: "The upper-left cloud passage shares a value with the mid-tone foreground grass — they read at roughly the same step on the value scale. Testing one of them lighter (the sky) or one of them darker (the grass) would let the eye separate the two planes."
- vocabulary_used: ["value scale", "centre of gravity", "value drop", "atmospheric mid-tone"]
- tradition_grounded_in: "Western academic watercolour"
- master_reference: name "Edward Wesson" · dates "1910–1983" · why_relevant: "Wesson's value structure was anchored by one decisive darkest dark per painting, often in the lower third — a habit worth holding in mind for this kind of garden subject."
- confidence: 0.86
- **Observation 2 — edge_quality:**
- what_is_working: "The wet-into-wet halo where the foliage meets the sky on the right side breathes — that's a soft edge functioning as soft."
- what_could_be_tested: "The bottom edge of the loquat fruit cluster is hard the entire way around. A lost edge somewhere on the shadow side of the cluster — where the warm orange runs into the deep foliage — would let the cluster sit inside the tree rather than on top of it."
- vocabulary_used: ["wet-into-wet halo", "hard edge", "soft edge", "lost edge"]
- tradition_grounded_in: "Western academic watercolour"
- master_reference: name "Charles Reid" · dates "1937–2019" · why_relevant: "Reid's edge teaching framed the three-edge vocabulary (hard, soft, lost) as a daily practice; the three-edge exercise below is in that lineage."
- confidence: 0.91
- **Observation 3 — composition:**
- what_is_working: "The tree's main trunk reads as the vertical axis of the painting, with the diagonal of the lower branches carrying the eye from upper-left to lower-right."
- what_could_be_tested: "The mast of distant background (the wooden fence post in the upper-right corner) points off the picture plane — the eye exits there and the lower-left corner has nothing to bring it back. A single low note in the lower-left (a clump of grass, a small fallen fruit, a shadow) could close the loop."
- vocabulary_used: ["vertical axis", "diagonal", "picture plane", "visual loop"]
- tradition_grounded_in: "Western academic watercolour"
- master_reference: null
- confidence: 0.79
- **Observation 4 — colour_temperature:**
- what_is_working: "The warms are pooled where the eye wants them — in the loquat fruit cluster and across the foreground grass. The shadow side of the trunk reads honestly cool, which is the relationship the subject asks for in late afternoon."
- what_could_be_tested: "The cloud passage in the upper-left is currently the same warmth as the foreground grass; pushing the cloud passage cooler (a quiet ultramarine wash) would separate sky-warmth from earth-warmth and give the foreground its own warmth back."
- vocabulary_used: ["warm/cool", "sky-warmth", "earth-warmth"]
- tradition_grounded_in: "Western academic watercolour"
- master_reference: null
- confidence: 0.83
- **Exercise:**
- title: "Three edges on scrap paper"
- duration_minutes: 2
- materials: ["a scrap of the same paper", "the brush you used today", "one pigment from the loquat passage"]
- the_question_being_tested: "what does a lost edge feel like in your hand?"
- the_action: "Paint three small shapes side by side — about a thumbnail wide each. The first shape: paint into dry paper, finish the edge crisply. The second: pre-wet a small halo around the shape, paint into the damp paper, watch the edge soften. The third: pre-wet a larger area; paint into it; watch the edge dissolve completely. Label them 1, 2, 3."
- the_thing_to_notice_afterwards: "Which edge took the longest to lay down. Which edge surprised you. Which one you reached for next week without thinking."
- carry_over_prompt_for_next_session: "did the three edges feel different in your hand?"
- diagram_required: true
- diagram_prompt_if_needed: "three small watercolour swatches side by side, the first with a crisp hard edge, the second with a soft halo, the third dissolving into a wet field, labelled 1, 2, 3 in pencil"
**Sample input artefacts (for the build to demonstrate):**
- A photographed-looking watercolour of a loquat tree taped to a board on a wooden kitchen table at evening.
- A plein-air half-imperial watercolour of fishermen at the Vieux-Port, shot at 5 a.m. light.
- A gongbi magnolia branch on a long scroll, photographed from above.
- A sumi-e iris on a sheet of hosho, photographed in north-window light.
- A Persian-miniature bird study on parchment, painted with gouache and gold pigment.
- A pen-and-wash sketch of a Salvadoran-American kitchen at 7 p.m., A5 sketchbook.
**Sample voice copy:**
- Onboarding: "Photograph the painting you finished an hour ago. We'll look at it the way a thoughtful tutor would."
- Processing: "Looking at the value structure…" / "Reading the edges…" / "Walking the composition…" / "Listening for the warms…"
- Empty session log: "Your session log is waiting for its first painting. Photograph today's work to start. There's no rush."
- Error (couldn't read): "We couldn't make the painting out clearly in this light. Want to try one shot in even daylight, or upload a scan?"
- Save confirmation: "Saved Sunday loquat tree, watercolour, 9x12."
- Photo quality flag detected: "This looks like late-evening tungsten — your warms in the photo are probably warmer than the painting on the table. I read them a little conservatively."
- Carry-over prompt: "Last Sunday's exercise was three edges on scrap paper. Did you get to it? It's fine either way."
- Low-confidence observation footnote: "I'm less sure about this one — the cluster passage was hard to read in this light. Tap the dot to check."
- Tradition not yet supported: "I don't have grounded vocabulary for that tradition yet. Want to name three words you'd like the critique grounded in, and one or two painters to ground them against?"
- Trusted-eyes share: "Shared with Marisol — the link expires in seven days. You can revoke it earlier in Settings."
**Sample trusted-eyes share email subject + body:**
- Subject: "I'd value your eye on today's loquat tree — three quick observations from the studio app"
- Body: "Hi Marisol — I painted the loquat tree today and the studio app gave me four observations and an exercise. I'd love your eye on it too, when you have a minute. Link expires in seven days." [Open Session]
## 9. Media & assets
- **Hero image (landing screen):** A photographed-looking shot of a watercolour palette on a wooden kitchen table at evening, a hake brush across a folded rag, a glass of water gone slightly muddy, a corner of paper taped to a board. Generate via Nano Banana 2 with a prompt emphasising "wooden kitchen table, warm desk-lamp light, watercolour palette with mixed pigments, hake brush, muddy water glass, no people in frame, late evening, soft shadow on the table, gentle imperfection".
- **App icon / wordmark:** Set in the body serif. Slightly worn paper texture behind it. No icon — just type.
- **Empty-state illustration:** A simple line drawing of a sketchbook open on a kitchen table, a brush across it. Hand-drawn aesthetic, not a flat icon.
- **Demo painting photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "creased watercolour paper, soft afternoon window light, taped to a board with masking tape at the corners, no people in frame, gentle imperfection in the painting itself". Each demo painting should look photographed in a real studio.
- **Exercise diagrams:** Generated per the Nano Banana 2 prompt in 4b — generic teaching diagrams, never doctored versions of the user's painting.
- **Stock fallbacks:** If image generation fails, fall back to the photographed sample painting at `/public/samples/sample-loquat-tree.jpg`. Never to a "🎨" emoji.
- **Generated imagery:** prefer Nano Banana 2 over stock photography. Prompt for warmth, asymmetry, and slight imperfection — avoid the glossy 'AI render' look.
- **Optimisation:** WebP/AVIF, `loading="lazy"`, explicit `width`/`height` to prevent layout shift.
- **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only.
### Build-time asset manifest (explicit specs)
Every image, illustration, and visual reference mentioned above must resolve to ONE of the three buckets below — runtime-generated, seed-shipped, or user-supplied. Do NOT ship `` tags whose `src` is not listed here. Do NOT depend on bare "section 8a prompts" without binding them to explicit paths and model IDs.
**Bucket 1 — Runtime-generated (Nano Banana Pro `gemini-3-pro-image` for hero/demo photographs; Nano Banana 2 `gemini-3.1-flash-image` for in-app illustrations and reference-conditioned variants).** Cached to Firebase Storage; served via signed URL. Every reference above to "Nano Banana 2" or "Nano Banana Pro" MUST be wired to one of these specific calls with an explicit model id:
- `/public/generated/hero.webp` (2400×1500, WebP) — model `gemini-3-pro-image` — uses the literal prompt described as "Hero image (landing screen)" above. Run once at build; commit a `/public/samples/hero-fallback.webp` (1600×1000) generated from the same prompt with `gemini-3.1-flash-image` so the page renders if quota is exhausted.
- `/public/generated/demo/{demo-slug}-{NN}.webp` (1600×1200, WebP) — model `gemini-3.1-flash-image` (reference-conditioned where the prior frame is passed as input) — one path per "Demo X" image referenced above. The slug derives from the seed example in section 8a; the NN index covers each frame in the demo sequence.
- `/public/generated/illustrations/{name}.webp` (1024×1024, WebP) — model `gemini-3.1-flash-image` — one path per named illustration above ("Empty-state illustration", "Recipe-card hero illustrations", "Curriculum picker imagery", "Period-style frames", etc.). Each illustration's prompt is the literal description above; ship a deterministic seed in the request so re-runs are reproducible.
**Bucket 2 — Seed assets shipped with the deliverable.** Every "Stock fallback" path referenced above (e.g. `/public/samples/sample-X.jpg`) is generated once via Nano Banana 2 (`gemini-3.1-flash-image`) at 1024×1024 WebP using the same prompt as its Bucket-1 counterpart, then committed to the repo so the page renders identically if Gemini quota is exhausted or the user is offline. Replace any `.jpg` extension above with `.webp` to match the optimisation rule. Also commit these empty-state seeds (1024×1024 WebP, single-stroke hand-drawn line, no colour fill):
- `/public/samples/empty-state-primary.webp` — line drawing of the app's primary empty surface (the named "Empty-state illustration" above), generated from that exact prompt.
- `/public/samples/empty-state-archive.webp` — line drawing of an empty saved/archive view, single-stroke outline.
- `/public/samples/empty-state-error.webp` — line drawing of a hand placing a single object aside with care, used when an AI call fails.
**Bucket 3 — User-supplied.** Uploads from the user's camera / file picker land at the Firebase Storage path conventional for this template (named in section 4b). The build ships with Bucket-1 + Bucket-2 only; no user-supplied images at first paint.
**Hard rules**
- Every `` tag MUST have a `src` that resolves to a path listed in Bucket 1, Bucket 2, or a Bucket 3 upload path. Anything else is a build error.
- No bare `image.jpg` / `hero.jpg` / `placeholder.png` references anywhere in the code.
- Model IDs: `gemini-3-pro-image` for hero-quality photographic generation; `gemini-3.1-flash-image` for in-app illustrations, reference-conditioned variants, empty-state seeds, and stock fallbacks. Never use a legacy model id (no `imagen-*`, no `gemini-1.5-*-image`).
- File format: WebP everywhere (AVIF acceptable where the target browsers support it). No `.jpg` / `.jpeg` / `.png` in `/public/samples/`.
## 10. Interactivity & states
- Every interactive element has hover, focus, active, and disabled states.
- Forms validate inline and show specific error messages (not "Invalid input"). Skill-register changes do not require confirmation; tradition changes prompt a one-line "this changes the vocabulary the critique uses — proceed?" confirmation.
- Loading states use skeletons that match the eventual layout of the four observation cards, not spinners.
- Empty states explain the next action with a button whose label fits THIS app's domain: "Photograph today's painting on the kitchen table", "Upload last Sunday's scan", "Pick a frame from your plein-air time-lapse" — never a generic "Add your first item".
- Smooth scroll for in-page anchors. The "play critique aloud" button scrolls the critique into view if invoked from outside.
- All AI-generated content streams in token-by-token where supported, with a meaningful "looking…" indicator (not a spinner) showing which axis the model is currently considering.
- If an AI call fails, show a calm, specific error ("We couldn't read this painting clearly — try one shot in even daylight, or upload a scan?") and offer retry.
- Anchor dots on the painting light up when their corresponding observation card is tapped, focused, or read aloud. The light-up state respects `prefers-reduced-motion` (instant switch with reduced-motion on).
- Low-confidence observations show a small "(i) less certain" badge next to the axis title, plain-English explanation on tap.
- The exercise card's "save this exercise to next Sunday" toggle persists optimistically and reconciles on response.
## 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 the critique and cross-session pattern calls, `gemini-3.5-flash` for the vocabulary, photo-quality, and carry-over calls, `gemini-3.1-flash-image` for exercise diagrams, and `gemini-3.1-flash-tts-preview` for narration. Set `thinkingLevel` explicitly per call (omit on TTS and image calls).
- **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed session on first launch.
- **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email as fallback for trusted-eyes sharing.
- **Storage:** Firebase Storage for original painting photographs. Pre-signed URLs only.
- **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+.
- Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness.
- Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS.
- Zero horizontal overflow at any width. Zero layout shift on load.
- Persist user data in Firestore. Use real-time listeners on the session log view.
- Optimistic UI on writes (save exercise, share trusted-eyes); reconcile on response.
- Camera 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 session; 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 sketch photo still uploads when WebRTC is denied; rotation drops the camera track on iOS — re-bind on `orientationchange`.
- Long-context cross-session call guarded by an explicit user tap (never auto-runs) and by a token-budget check before invocation. Chunk by year for users with >100 sessions.
## 12. Accessibility (WCAG 2.2 AA)
- Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`.
- All interactive controls reachable by keyboard with a visible focus ring.
- Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The ochre anchor-dot colour against the cream background is checked specifically; if the contrast drops below 3:1, the dot is given a thin indigo outline.
- All images have meaningful `alt` text. The user's painting photographs have `alt` describing the artefact ("watercolour of a loquat tree on cold-press paper, taped to a board, kitchen table") — generated by the critique call as a side output, editable by the user.
- Form fields have associated `