================ 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. --- # Pattern Reader ## 1. Project **Pattern Reader** is a chart-restoration tool for cross-stitchers, embroiderers, and counted-thread makers who have inherited or rescued a pattern whose symbol key has fallen off. The user photographs the chart — a yellowed graph-paper sheet covered in tiny hand-drawn symbols, or a printed page torn from a 1970s magazine, or a hand-copied notebook page passed down from a grandmother — and the app rebuilds the chart as an interactive zoomable grid. Every cell is detected. Every distinct symbol is clustered. Every symbol is resolved to a modern DMC thread number with a colour swatch, a confidence score, and the model's reasoning shown plainly. The stitcher then confirms, overrides, or refines each resolution before the chart is downloadable as a clean PDF, an Excel-grid CSV, or a Pattern Maker (.pat) file ready to stitch. This is the kind of app an embroiderer in Mumbai builds when her mother-in-law hands her a folded sheet of squared paper covered in ink symbols — a Sindhi sūf chart someone in the family copied out of a 1980s craftwork pamphlet, the printed key long lost. It is also the kind of app a Romanian woman in her thirties builds when she finds, inside her bunica's sewing box, an iie blouse chart drawn on the back of a calendar page — every symbol carefully inked, no key attached, and the only person who knew the symbols died two springs ago. Same shape of moment, different tradition, different lost key. The single demo that proves the magic: photograph a flea-market cross-stitch chart of a folk-art rooster, slightly creased, the key torn off along the perforation → in under 30 seconds the app shows a zoomable interactive grid. Each symbol is highlighted in a distinct soft colour, with a small panel down the right side listing every distinct symbol the app detected (twelve in this chart), each one resolved to a suggested DMC number with a colour swatch beside it: ✕ → DMC 304 (Christmas Red Medium), ● → DMC 310 (Black), ◆ → DMC 729 (Old Gold Medium), and so on. Each suggestion shows a confidence chip ("0.86 — confident") and a short reason ("✕ appears in densest blocks; usually the dominant warm colour in folk-rooster charts of this style"). The stitcher taps any suggestion, sees three alternates, and either confirms or overrides. The "Download" button at the bottom is greyed until every symbol has been confirmed — because the model suggests, the embroiderer confirms. In the harder cases — a hand-drawn Otomí tenango chart from Tenango de Doria where the original was redrawn three times by three aunts using slightly different ink symbols across the same sheet, or a Palestinian tatreez sampler photocopied four times across forty years until the symbols smudged into near-identical blobs, or a Bukharan Jewish suzani draft from Tashkent where the symbol legend was in Cyrillic Uzbek and is now half-illegible — the app reads what it can read, marks confidence honestly, and asks the maker. It never silently fills in. **Tagline:** _Photograph any old cross-stitch chart with no symbol key, in any tradition, any era — the grid comes back, every symbol resolved to a modern thread number, every choice confirmed by the person who will actually stitch it._ ## 2. Target audience - Inheritors of family stitching boxes — granddaughters and daughters-in-law given a sewing kit after a death, full of charts on graph paper with no legends - Flea-market and estate-sale stitchers who buy unfinished projects with the pattern but no key - Traditional-textile keepers across living craft traditions — Otomí tenango embroiderers in Hidalgo, Palestinian tatreez stitchers, Sindhi sūf and kashida embroiderers, Hmong paj ntaub makers, Romanian iie and ie cătrințe stitchers, Bukharan suzani drafters, Hardanger and Ukrainian rushnyk stitchers, Mexican deshilado workers, Bangladeshi nakshi kantha embroiderers, Japanese sashiko and kogin practitioners - Cross-stitch group members translating club-shared photocopies whose keys faded a decade ago - Museum textile volunteers digitising hand-drawn pattern archives for community access - Embroidery teachers building modern teaching kits from out-of-print mid-century pattern books - Disability-community stitchers (low-vision, dyslexic) who need a chart re-rendered with higher symbol contrast or alternative symbol sets they can tell apart - Indie pattern designers digitising their own decades-old hand-drawn drafts so they can re-issue them - Genealogy and oral-history projects pairing chart documentation with the stories of who stitched what for whom ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Reads any hand-drawn or printed symbol chart** — graph-paper drafts in pen, ink, pencil, or marker; printed magazine charts in any decade; photocopies of photocopies; charts on the backs of envelopes, on lined notebook paper, on the inside of cardboard. Gemini 3.5 Flash does the grid detection, the symbol clustering, and the colour suggestion in one multimodal call. - **Detects the grid even when the grid is implied** — many hand-drawn charts use thicker every-10-cell rules, or skip the grid entirely on small motifs. The app reconstructs the cell lattice from symbol positions and confirms the dimensions with the user before reading symbols. - **Clusters symbols, not pixels** — ✕ and ✗ drawn by two different aunts on the same sheet are the same symbol; ● and ⬤ are the same symbol; a small slash and a small backslash are different symbols. The clustering is shown to the user as a panel they can split or merge. - **Resolves to modern DMC numbers, with the original always preserved** — the app never modernises away the source. The detected symbol stays the symbol; the suggested thread is a suggestion. The stitcher decides. Alternative palettes (Anchor, Madeira, DMC Light Effects, Sulky, Cosmo, Olympus) are one tap away. - **The maker confirms, the model suggests — always in that order** — the "Download chart" button is disabled until every symbol resolution is either confirmed or overridden. There is no silent default. The model's confidence is shown honestly; the maker's confirmation is the source of truth. - **Respects living traditions** — when the model recognises a known tradition (Otomí, tatreez, sashiko, kogin, sūf, suzani, nakshi kantha, kasuti), it surfaces a respectful note in the suggestion panel ("This appears to be a tatreez sampler; the dominant red in these traditions is most often DMC 321 or 498, but please confirm with your family palette") — never a museum-card lecture, never an aestheticising flourish. - **Re-renders for low-vision and dyslexic stitchers** — the same chart can be re-rendered with high-contrast symbols, symbols chosen from a low-confusion set, larger cell size, or per-cell coloured blocks instead of monochrome symbols. The original chart stays intact alongside the re-render. - **Exports for real stitching tools** — clean PDF for printing, CSV grid for spreadsheet editing, .pat file for Pattern Maker, DST or PES for machine-embroidery hoop systems, and a plain-text "row-by-row" reader for stitchers who follow charts by row rather than by eye. ## 4. Features to build - Camera capture for full chart + key (mobile-first), with paper-edge crop guides and a "the key is on a separate sheet" workflow - Upload from photo library, scanner, or PDF (some users have a 600 dpi scan of the chart and a separate JPG of a partial key fragment) - Automatic chart-vs-key-vs-finished-piece detection — distinguishes a symbol chart, a key/legend page, a photograph of a finished stitched piece (which the app can also reverse-engineer into a chart), and an unrelated page - Grid detection — finds the cell lattice even when grid lines are faint, broken, or absent on small motifs; reconstructs implied lattice from symbol centres; asks the user to confirm dimensions ("we count 64 wide × 88 tall; is that right?") - Symbol clustering — groups every distinct mark into the smallest plausible set of symbols, with a visible cluster panel the user can split (✕ vs ✗ are different) or merge (✕ in two ink shades is one) - Per-symbol thread resolution — for every cluster, propose a primary DMC number, two alternates, and a one-line reason; confidence shown explicitly - Symbol-key salvage — if a torn-off legend fragment is uploaded alongside the chart, the app reads whatever symbols and numbers it can extract and uses them as ground truth for the resolution call - Multi-palette support — DMC (default), Anchor, Madeira, DMC Light Effects, Sulky, Cosmo (Japanese), Olympus (Japanese), with one-tap palette switch and equivalence tables - Tradition awareness — recognise commonly stitched traditions (tatreez, Otomí tenango, Hmong paj ntaub, sashiko, kogin, sūf, suzani, nakshi kantha, Hardanger, kasuti, Romanian iie, Mexican deshilado) and seed the colour suggestions from the tradition's typical palette while still asking the maker - Interactive zoomable grid — pinch and pan; tap a cell to see its cluster, the suggested thread, the confidence, the model's reason, and the override controls - Confirmation flow — every symbol must be confirmed or overridden before download. A progress bar shows "7 of 12 symbols confirmed"; download stays disabled until 12 of 12 - Re-render for accessibility — high-contrast symbol set, low-confusion symbol set, large-cell render, monochrome-symbol vs full-colour-cell block render - Print-ready PDF export with chart + key + thread shopping list (with skein-count estimate per colour based on cell counts and DMC coverage rules) - CSV grid export for spreadsheet editors who like to plan in Numbers or Excel - .pat file export for Pattern Maker and equivalent stitch software - Row-by-row plain-text export for stitchers who follow by reading - Floss inventory check — paste your owned-DMC list (or photograph a floss-box); the app marks which suggested threads you already own and which you need to buy - Family chart sharing — share a chart-in-progress with an aunt in Lahore or a cousin in Querétaro who actually knows the family palette; she can confirm symbols from her phone - Provenance card — a one-line note per chart for who drew it, when, in which tradition; printed at the foot of the PDF export ## 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 hand-drawn symbol charts on graph paper, on lined paper, on the back of envelopes, on photocopies smudged across forty years. Identifies the grid lattice (even when implied), the distinct symbol set, and the approximate cell counts. One API call per chart face; multi-page charts (some folk patterns sprawl across three or four sheets) are submitted as a single multi-image call with explicit page numbering. - **Structured output / JSON Schema** — the response matches the `Chart` schema below. Every field is typed; the schema is included verbatim in the system instruction and as `responseSchema`. - **Symbol clustering and resolution reasoning** — the model groups visually similar marks into a cluster set (smallest plausible set of distinct symbols) and proposes a DMC number per cluster with a one-line reason. The reasoning string is short, concrete, and shown to the user. It is never marketing copy. - **Search grounding** — for the tradition-aware suggestion call. "Otomí tenango red" needs to resolve against actual published references for traditional dye/thread palettes; "Palestinian tatreez Bethlehem red" similarly. Grounded search prevents hallucinated palette claims. The call returns citation URIs from `groundingMetadata`, which surface as "where this came from" links in the suggestion panel. - **Long context (1M tokens)** — for the floss-inventory check and the cross-chart family-palette resolution. When a user has thirty-six previous charts in the same family tradition, the model reads them all at once and suggests "in this family's previous charts, ✕ has been resolved to DMC 321 sixteen times out of twenty — would you like to apply that as the default here?" **Guardrail**: each parsed Chart object averages ~2,500 tokens (the grid arrays are the heaviest field); a family palette over 60 charts ≈ ~150k tokens. For families with 200+ charts, chunk by tradition or by decade before the cross-chart call. - **Gemini 3.5 Flash image generation** (Nano Banana 2) — generates colour-cell mockups of the chart at full resolution (no symbols, just coloured cells) so the stitcher can see the chart "stitched" in any candidate palette before they commit to buying floss. Also generates the high-contrast symbol re-render for accessibility. - **Thinking levels** — `medium` for the primary grid-and-symbol-and-resolution call (the load-bearing intelligence). `low` for the per-symbol re-resolution call (when a user overrides one symbol and asks "given that ✕ is DMC 321, what would you now suggest for ●?"). `low` for tradition recognition and the floss-inventory match. ### 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 family chart sharing) requires the sender domain to be authorised in Firebase Auth. Charts are private to the owner and explicitly-invited family members. No public-by-default. - **Database — Required.** Firestore for `users`, `charts`, `symbol_clusters`, `cell_grid` (subcollection per chart, large), `confirmations`, `floss_inventories`, `traditions`, `family_palettes`, `share_invitations`. - **File storage — Required.** Firebase Storage for the original chart photographs (preserved at upload resolution, forever) and for the rendered preview PDFs. **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 chart upload. Pre-signed URLs only; chart photographs are not publicly addressable. - **Email — Required (transactional).** Family chart-sharing invitations via email link (Firebase Auth magic links). Export delivery emails (PDF / CSV / .pat attachments) to whichever address the user nominates. - **Payments — Not needed for v1.** Free for personal use. A possible future "printed colour book" tier could pipe to a print-on-demand partner and charge for the physical artefact only. - **External APIs:** Gemini API for all intelligence; optional DMC catalogue API for live-updated colour names + RGB swatches (the app ships with a static JSON of the DMC catalogue, Anchor catalogue, Madeira catalogue, and a tradition-palette catalogue covering tatreez, Otomí, sashiko, kogin, sūf, suzani, nakshi kantha, kasuti, Romanian iie, Hardanger, and Hmong paj ntaub for the offline case). **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional palette-API token) 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 chart photographs are private to them and the family members they invite · 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 model suggests, the maker confirms — every confirmation is logged with the user id and timestamp so a chart's resolution history is auditable. **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 | |------|-------|---------------|---------------| | Read chart → grid + symbol clusters + initial resolution (`Chart` schema) | `gemini-3.5-flash` | medium | (none) | | Re-resolve a single symbol after user override | `gemini-3.5-flash` | low | (none) | | Recognise tradition + suggest culturally-grounded palette | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema` on this call — see note) | | Resolve symbol-key fragment if user uploaded a partial key | `gemini-3.5-flash` | medium | (none) | | Cross-chart family-palette suggestion (long-context over user's chart history) | `gemini-3.5-flash` | low | (none) — long-context | | Match suggested DMC list to user's owned-floss inventory | `gemini-3.5-flash` | low | (none) | | Render colour-cell mockup of chart in candidate palette | `gemini-3.1-flash-image` | n/a | n/a | | Render high-contrast accessibility version of chart | `gemini-3.1-flash-image` | n/a | n/a | *Note for builders:* on the image-generation calls (`gemini-3.1-flash-image`), 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-recognition call, do not specify `responseSchema` — `responseSchema` and `google_search` grounding are mutually exclusive in a single Gemini call. Instruct the model to emit JSON in the text body and parse server-side; read citation URIs from `response.groundingMetadata.groundingChunks[].web.uri`. ### Primary structured-output schema (seed this verbatim in the prompt) ```typescript import { z } from "zod"; const Cell = z.object({ row: z.number().int().min(0), col: z.number().int().min(0), symbol_cluster_id: z.string().nullable(), // null = blank cell raw_glyph_observed: z.string(), // verbatim, e.g. "✕", "●", "small slash" read_confidence: z.number().min(0).max(1), }); const SymbolCluster = z.object({ cluster_id: z.string(), // "c1", "c2", ... representative_glyph: z.string(), // best canonical render alternate_glyphs_observed: z.array(z.string()), // ["✕", "✗", "X in pencil"] cell_count: z.number().int().min(0), // how many cells in the chart use this cluster density_rank: z.number().int().min(1), // 1 = most-used cluster clustering_confidence: z.number().min(0).max(1), could_plausibly_split_into: z.array(z.string()).nullable(), could_plausibly_merge_with: z.array(z.string()).nullable(), }); const DmcSuggestion = z.object({ palette: z.enum([ "DMC", "Anchor", "Madeira", "DMC_LightEffects", "Sulky", "Cosmo", "Olympus", "OTHER", ]), thread_number: z.string(), // "DMC 321", "Anchor 47" thread_name: z.string(), // "Christmas Red" hex_swatch: z.string(), // "#BE2A2C" suggestion_confidence: z.number().min(0).max(1), reasoning_one_line: z.string(), // shown to user tradition_grounded: z.boolean(), // true if backed by a tradition palette lookup tradition_source_note: z.string().nullable(), // "common dominant red in published tatreez samplers" }); const SymbolResolution = z.object({ cluster_id: z.string(), primary_suggestion: DmcSuggestion, alternates: z.array(DmcSuggestion).max(3), user_confirmed: z.boolean(), // server starts false, user flips user_override: DmcSuggestion.nullable(), // populated if user overrode confirmed_at: z.string().nullable(), // ISO timestamp once confirmed confirmed_by_user_id: z.string().nullable(), }); const TraditionGuess = z.object({ tradition_name: z.string().nullable(), // "Palestinian tatreez", "Otomí tenango", null guess_confidence: z.number().min(0).max(1), evidence_one_line: z.string().nullable(), user_confirmed: z.boolean(), }); const KeyFragment = z.object({ fragment_image_uri: z.string(), resolved_entries: z.array(z.object({ symbol_glyph: z.string(), thread_text_visible: z.string(), // "DMC 321" / "Rouge — 321" / partial: "DMC 32_" parsed_thread_number: z.string().nullable(), parse_confidence: z.number().min(0).max(1), })), }); const Chart = z.object({ chart_id: z.string(), source_image_uris: z.array(z.string()), // one or many; multi-sheet folk patterns can sprawl key_fragment: KeyFragment.nullable(), // populated only if a key fragment was uploaded artefact_type: z.enum([ "printed_chart_full", "printed_chart_no_key", "hand_drawn_chart_full", "hand_drawn_chart_no_key", "photocopy_degraded", "photo_of_finished_piece", "key_fragment_only", "unknown", ]), paper_type_note: z.string().nullable(), // "graph paper, 5mm cells", "lined notebook page", "calendar-back paper" era_guess: z.string().nullable(), // "1970s-80s printed pamphlet", "hand-drawn, undated" grid: z.object({ detected_width_cells: z.number().int().min(1), detected_height_cells: z.number().int().min(1), grid_visible: z.boolean(), // false if implied from symbol positions grid_confidence: z.number().min(0).max(1), every_n_cell_thicker_rule: z.number().int().nullable(), // many charts mark every 10 user_corrected_dimensions: z.boolean(), }), cells: z.array(Cell), // row × col flattened symbol_clusters: z.array(SymbolCluster), resolutions: z.array(SymbolResolution), tradition_guess: TraditionGuess, provenance_note: z.string().nullable(), // "drawn by Sundri-aunty, Karachi, 1980s; copied by mother 1995" user_supplied_palette_preference: z.enum([ "DMC", "Anchor", "Madeira", "DMC_LightEffects", "Sulky", "Cosmo", "Olympus", "UNDECIDED", ]), reading_confidence_overall: z.number().min(0).max(1), flagged_for_user_review: z.array(z.object({ field_path: z.string(), // "cells[row=12,col=44].raw_glyph_observed" reason: z.string(), })), }); type Chart = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent silently downgrades `thinkingLevel` on the the chart parse call to save quota — pin `gemini-3.5-flash` with the matrix-specified `thinkingLevel` explicitly. Flash silently merges visually-similar symbols (✕ and ✗ become one cluster), under-counts cells in dense areas, and misses faint pencil grid lines entirely. - Symbol cluster set is too large — every photocopy-fleck becomes its own cluster. The system instruction must say "the smallest plausible set of distinct symbols, biased toward merging when uncertain, and surfacing the merge in `could_plausibly_split_into` so the user can split if needed". - Symbol cluster set is too small — ✕ and ✗ get merged when the maker actually intended two colours. Surface `could_plausibly_split_into` whenever cluster confidence is below 0.85. - Chart cells output as a sparse object map instead of a row-major array — downstream rendering breaks. The schema fixes this: `cells: Cell[]` flat array. - Model fills in blank cells with a "background" cluster — wrong. Blank cells have `symbol_cluster_id: null`. The system instruction must say this explicitly. - Model invents DMC numbers — the suggestions hallucinate ("DMC 1738"). Constrain the model to the loaded DMC catalogue (~500 numbers); validate server-side; reject suggestions outside the catalogue. - Model gives breezy reasoning that flatters the user instead of explaining — "this looks like a beautiful traditional red". The system instruction must say "the reason should explain *why this DMC* over alternates: density, position in the grid, tradition palette evidence, or visual hue similarity to the symbol's ink colour. Never aesthetics." - Tradition recognised but suggestion still defaults to a generic Western palette — the tradition-call output must be fed into the resolution call so the tradition palette is the default, not a footnote. - User overrides one symbol and the others stay stale — when a user overrides a symbol, run the re-resolve call so the model can re-suggest the others given the new constraint (without re-resolving the user's already-confirmed cells). - Download enabled before all clusters confirmed — the download button must be disabled while any `resolutions[i].user_confirmed` is false. This is a hard rule. The maker confirms, the model suggests. - Family-palette suggestion call run on a single chart — wait until the user has at least three confirmed charts of their own before offering the family-palette suggestion. One chart is not a family palette. - Floss-inventory call confuses DMC 321 with Anchor 47 — both are roughly the same red, but they are not interchangeable. The match call must respect palette boundaries and only suggest cross-palette equivalences with an explicit "approximate equivalent" badge. - Image-generation call (colour-cell preview) renders a different chart shape — the prompt must pass the exact cell-grid dimensions and the colour-per-cluster mapping; do not let the image model "interpret" the chart. ### Negative constraints (hard rules) - Do NOT auto-confirm any symbol resolution. The model suggests; the maker confirms. Every download is gated on user confirmation of every cluster. - Do NOT modernise away the source. The original photographed chart is preserved at upload resolution forever. The detected symbol set is preserved verbatim. The DMC suggestion is a suggestion; the symbol stays the symbol. - Do NOT invent DMC numbers or thread names. The candidate set is the loaded DMC / Anchor / Madeira / Sulky / Cosmo / Olympus catalogues. Validate server-side; reject anything outside. - Do NOT make tradition claims without grounding. If the model thinks a chart is tatreez, it must say so with a `tradition_grounded: true` flag only after the grounded-search tradition-recognition call has run. Otherwise `tradition_grounded: false` and the suggestion is presented as palette-agnostic. - Do NOT lecture about cultural tradition. The tradition-aware note is one sentence, the maker's tradition, the maker's call. No museum-card paragraphs in the resolution UI. - Do NOT smooth or beautify the chart. The interactive grid is faithful to the cell count, the symbol set, and the blank cells of the source. If the original is irregular (some hand-drawn folk charts deliberately are), preserve that. - Do NOT auto-publish or auto-share. Charts are private by default. Sharing is explicit, per-chart, per-family-member. - Do NOT use the user's chart photographs 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 collapse confidence into a thumbs-up/thumbs-down badge. The numeric confidence is shown to the user, and the model's one-line reason is shown verbatim. Epistemic honesty is the brand. - Do NOT show stock floss-skein images. The chart photograph is the hero. Catalogue swatches are tiny inline RGB blocks. No glossy product shots. ### 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 chart → grid + symbol clusters + initial resolution Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are reading a counted-thread embroidery chart. The chart may be a printed cross-stitch pattern from any decade (1950s pamphlets, 1970s craftwork magazines, 1990s photocopies, current publications), a hand-drawn chart on graph paper, on lined notebook paper, on the back of an envelope, on a calendar page, on the inside of cardboard, or a photocopy-of-a-photocopy degraded across forty years. The chart may be from a living folk tradition: Palestinian tatreez, Mexican Otomí tenango, Hmong paj ntaub, Romanian iie, Sindhi sūf or kashida, Bangladeshi nakshi kantha, Indian kasuti, Hardanger, Ukrainian rushnyk, Bukharan suzani, Japanese sashiko or kogin, deshilado from Aguascalientes, or many others. It may also be a generic Western sampler. Treat all traditions with equal care; do not assume European default. Your job has three parts: 1. Detect the cell grid — the lattice of squares each containing either one symbol or nothing. 2. Cluster the marks into the smallest plausible set of distinct symbols. 3. Propose a DMC thread number for each cluster, with a one-line reason and a confidence score. The chart photograph may be one image or several (some folk patterns sprawl across multiple sheets). Multipage charts are submitted as a SINGLE call with multiple images, in order. Upload each page via the Gemini Files API (`files/*` resource name) or send as `inlineData` (base64). Do NOT pass Firebase Storage public URLs directly to `generateContent` — the API does not fetch them server-side. Include an explicit "page 1 of 2 / page 2 of 2" header at the start of each image's accompanying text so the model can sequence reliably. Output ONLY the Chart JSON matching the provided schema. GRID DETECTION - Find the cell grid even when grid lines are faint, broken, hand-drawn unevenly, or absent entirely on small motifs. If grid lines are absent, reconstruct the implied lattice from the positions of symbol centres. - Many charts use a thicker rule every 5 or 10 cells; record that in `grid.every_n_cell_thicker_rule`. - If you are uncertain about cell dimensions, set `grid.grid_confidence` below 0.8 and surface a flagged_for_user_review entry asking the user to confirm or correct the dimensions. - Cells are output as a single flat row-major array `cells[]` with explicit `row` and `col` indices. Blank cells have `symbol_cluster_id: null`. Do NOT invent a "background" cluster for blanks. SYMBOL CLUSTERING - Group every distinct mark into the smallest plausible set of symbols, biased toward merging when uncertain. - Two marks that look like the same symbol drawn by two different hands (✕ in pen vs ✕ in pencil) belong to the same cluster. - Two marks that look different but plausibly could be the same symbol drawn carelessly (✕ and a hasty + with one tail shorter) — use clustering_confidence around 0.75 and surface `could_plausibly_split_into` so the user can split if needed. - When clustering confidence is below 0.85, also populate `could_plausibly_split_into` and/or `could_plausibly_merge_with`. - `density_rank` orders the clusters by cell count, 1 being the most-used cluster. - The representative_glyph is your best canonical render of the cluster (Unicode if possible, plain-text description if not — e.g. "small filled circle", "diagonal slash NE-SW"). RESOLUTION (DMC suggestions) - For each cluster, propose a primary DMC suggestion and up to three alternates. - The candidate set is the standard DMC palette (~500 numbers, pre-loaded in the build). Do NOT propose a thread_number outside this set. If a tradition palette suggests an Anchor or Madeira number, propose its DMC equivalent as primary and note the Anchor/Madeira equivalent in `alternates`. - The `reasoning_one_line` explains WHY this DMC over alternates. Acceptable reasons: density rank ("most-used cluster; typically the dominant outline colour in folk-rooster charts"), position in the grid ("appears only in the border, suggesting a contrast accent"), ink hue ("the symbol's pen ink is dark blue, suggesting the cluster represents a dark blue thread"), tradition palette evidence ("dominant red in published tatreez samplers from the Bethlehem region"). Unacceptable reasons: aesthetic flourishes ("a beautiful traditional red"), affirmations ("a lovely choice"), filler ("a strong contender"). - Every suggestion has a numeric `suggestion_confidence` between 0 and 1. Be honest. A symbol you genuinely cannot guess gets a primary suggestion at confidence 0.3 with the reason "low confidence — please confirm against the original or a family reference". - `user_confirmed` ALWAYS starts false. `user_override` ALWAYS starts null. Both are populated later by the app when the user confirms or overrides. TRADITION GUESS - If you recognise visual hallmarks of a known tradition (Otomí tenango motifs, tatreez sampler structures, sashiko stitching diagrams, kogin geometric drafts, sūf darn-stitch motifs), record a `tradition_guess` with confidence and a one-line evidence reason. Do NOT mark `tradition_grounded: true` in any DMC suggestion based on this guess alone — `tradition_grounded` is only set true when the dedicated grounded-search call has run. - If you do not recognise a tradition, leave `tradition_name` null. Do not guess. A generic European sampler is fine to call null. HARD RULES - Do NOT auto-confirm any cluster. Every `user_confirmed` is false. - Do NOT invent DMC numbers. Constrain to the loaded catalogue. - Do NOT fill in blank cells. Blanks are `symbol_cluster_id: null`. - Do NOT smooth irregular hand-drawn charts. Preserve cell-count asymmetry, deliberate-or-accidental skips, and the original chart's exact dimensions. - Do NOT translate or modernise the symbols. ✕ stays ✕; ● stays ●; a small slash stays a small slash described in plain text. - Do NOT include cultural commentary in the JSON output. The tradition_guess.evidence_one_line is a single sentence about visual hallmarks, not a museum card. - flagged_for_user_review names any field where confidence is below 0.7 with a one-sentence reason. No commentary. JSON only. ``` --- ### Call: Re-resolve a single symbol after user override Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` The user has overridden one symbol cluster's DMC suggestion. Given the new constraint, re-suggest DMC numbers for the remaining clusters whose user_confirmed is still false. Do NOT touch any cluster whose user_confirmed is true; those are the user's source of truth. You receive: - The full Chart object with the user's current confirmations and overrides applied. - An explicit `changed_cluster_id` field. Your task: return an array of updated `SymbolResolution` objects ONLY for the clusters whose user_confirmed is false. Do NOT re-resolve confirmed clusters. Hard rules: - Respect the user's confirmed clusters as constraints. If the user said cluster c1 = DMC 321, your suggestions for other clusters should be consistent with c1 = DMC 321 (e.g. avoid suggesting DMC 321 again for a different cluster; consider what palettes commonly pair with DMC 321). - Update `reasoning_one_line` to reference the constraint where relevant ("paired with the confirmed DMC 321 outline; a typical shadow companion in this palette family"). - Do NOT increase confidence above what the prior call assigned just because the user confirmed another cluster. Confidence is honest. - Do NOT propose changes to user-confirmed clusters. Output: an array of SymbolResolution objects, one per unconfirmed cluster, with `user_confirmed: false` and `user_override: null`. No commentary outside the structured output. ``` --- ### Call: Recognise tradition + suggest culturally-grounded palette Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding ``` You receive the chart image(s) and the initial cluster set with representative glyphs. Your task: identify whether the chart belongs to a known living textile tradition, and if so, surface the typical palette colours that tradition is documented to use — backed by grounded search citations. Traditions to consider include but are not limited to: Palestinian tatreez (with regional variants: Bethlehem, Ramallah, Hebron, Galilee), Mexican Otomí tenango (Tenango de Doria), Mexican deshilado (Aguascalientes), Hmong paj ntaub, Romanian iie and ie cătrințe, Ukrainian rushnyk, Hardanger, Bukharan suzani, Sindhi sūf and kashida, Bangladeshi nakshi kantha, Indian kasuti, Japanese sashiko, Japanese kogin-zashi, Bhutanese kira motifs, Filipino binakol weaving (when reproduced as embroidery charts), Berber flatweave motifs reproduced as cross-stitch. There are many others. If you do not recognise a tradition, return tradition_name: null and proceed; do not guess. For a recognised tradition, return the typical palette as a list of DMC suggestions, each with a citation URL from grounded search. Use search queries like: - "tatreez Bethlehem traditional red DMC equivalent" - "Otomí tenango embroidery typical palette" - "Hmong paj ntaub traditional colours" - "sashiko traditional indigo DMC equivalent" 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. Hard rules: - Do not lecture. The evidence_one_line is one sentence about visual hallmarks. The palette note per DMC suggestion is one sentence about provenance. - Do not assert tradition with confidence above 0.7 without at least one strong visual hallmark AND one search citation supporting the palette claim. - If multiple regional variants are plausible (Bethlehem tatreez vs Galilee tatreez), surface both with their distinct palettes and let the user choose. - Do not erase. If a chart looks like a fusion of two traditions (which happens — embroiderers borrow), say so. - The palette suggestion is a suggestion. The maker confirms. JSON shape: { "tradition_name": "Palestinian tatreez (Bethlehem region)" | null, "guess_confidence": 0.0 - 1.0, "evidence_one_line": "one sentence", "palette_suggestions": [ { "dmc_number": "DMC 321", "thread_name": "Christmas Red", "role_in_tradition": "dominant outline red", "citation_note": "appears repeatedly as the dominant warm in published Bethlehem-region samplers" }, ... ] } No commentary outside the JSON. ``` --- ### Call: Resolve symbol-key fragment if user uploaded a partial key Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` The user has uploaded a key fragment alongside their chart — a torn piece of the legend, a separate sheet, a hand-written equivalence table on the back of the chart, or a photograph of an earlier attempt at decoding. Read whatever you can from it. For each visible entry in the fragment, return: - symbol_glyph (verbatim, as drawn) - thread_text_visible (verbatim, including partial reads: "DMC 32_", "Rouge - 32_", "BLUE / 9__") - parsed_thread_number (your best guess from the visible text, or null if you cannot guess with confidence ≥ 0.6) - parse_confidence Hard rules: - Do NOT invent thread numbers to fill gaps. If the visible text is "DMC 32_", parsed_thread_number stays null unless you can confirm from cell density or hue. - Do NOT translate the key. If it is in Spanish, French, Russian, Polish, Hindi, Arabic, Cantonese, leave the thread_text_visible in the original language. Parse only the numeric thread codes. - If a symbol in the fragment does not match any cluster in the chart, flag it — it may be a symbol the chart uses that you missed, or it may be from a different chart entirely. The key fragment is GROUND TRUTH where confidence is high. Treat parsed entries with parse_confidence ≥ 0.85 as resolved suggestions that the downstream resolution call should default to, with the user still asked to confirm. Output: a KeyFragment object. No commentary. ``` --- ### Call: Cross-chart family-palette suggestion (long-context) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none, long context) ``` You receive every confirmed chart in this user's library at once (long-context). Your task: identify whether a family-palette pattern has emerged — symbols that the user has consistently resolved to the same DMC numbers across multiple charts of the same tradition or the same family hand. For the current chart, surface family-palette defaults: "in this family's previous charts, the ✕ cluster has been confirmed as DMC 321 sixteen times out of twenty; would you like to default to that here?" Hard rules: - Only surface family-palette suggestions when the user has at least three confirmed charts of the same tradition OR the same identified family hand. One chart is not a family palette. - Be conservative. The suggestion threshold is "≥ 70% of prior confirmations agree" AND "at least 3 confirmed charts in the cohort". - Always show the count of prior confirmations and the count disagreeing. The maker sees the evidence. - Do NOT auto-apply. The suggestion is shown to the maker in the resolution UI; they confirm as with any other suggestion. Output: an array of family-palette suggestion objects per cluster in the current chart: [ { "cluster_id": "c1", "suggested_dmc": "DMC 321", "prior_confirmations_agreeing": 16, "prior_confirmations_disagreeing": 4, "cohort_size": 20, "cohort_description": "charts confirmed in Palestinian tatreez tradition" }, ... ] No commentary outside the structured output. ``` --- ### Call: Match suggested DMC list to user's owned-floss inventory Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive: - The list of confirmed DMC suggestions for the current chart, with estimated skein-count per number. - The user's owned-floss inventory: either a list of DMC numbers the user has entered, or a parsed list extracted from a photograph of the user's floss box. For each suggested thread, return: - already_owned: boolean - skeins_already_owned: number (0 if not owned) - skeins_needed_to_buy: number (estimated requirement minus owned) - approximate_equivalent_in_other_palette: nullable — if the user owns Anchor 47 and the chart calls for DMC 321, surface "Anchor 47 — approximate equivalent" with a clear "approximate" badge. Do NOT mark it as a substitute by default; the maker decides. Hard rules: - Do NOT auto-substitute approximate equivalents. The maker chooses. - Cross-palette equivalences are always labelled "approximate" with a short hue-difference note where the published equivalence isn't exact. - DMC Light Effects (metallic, glow-in-the-dark) numbers are NOT equivalent to standard DMC numbers of similar hue; flag separately. Output: an array of FlossMatch objects. No commentary. ``` --- ### Call: Render colour-cell mockup of chart in candidate palette Model: `gemini-3.1-flash-image` · n/a · n/a ``` Render a colour-cell preview of the chart: the same cell grid, the same cell dimensions, the same blank-cell layout, with each cluster rendered as a solid coloured square in its confirmed (or currently- suggested) DMC swatch colour. No symbols. No grid borders thicker than 1 px. No decorative chrome. The output is what the chart would look like "stitched" in this palette, so the maker can see whether the candidate palette feels right before buying floss. Pass into the prompt: - The grid dimensions (width × height in cells) - The list of clusters with their assigned hex swatches - The cell-to-cluster mapping as a compact run-length-encoded string to keep the prompt under the context budget for charts of any size (RLE encoding: "12•3✕5◆..." where • is blank, etc., with the legend included). Style direction: a flat, soft-pixel render. No 3D effects. No needlepoint texture overlays. No aging effects. Plain coloured squares on a paper-bone background. The point is to read the palette, not to simulate stitching. Output: a single PNG. The build will inline it next to the original chart photograph in the preview UI. ``` --- ### Call: Render high-contrast accessibility version of chart Model: `gemini-3.1-flash-image` · n/a · n/a ``` Render the chart with a high-contrast, low-confusion symbol set chosen from the accessibility symbol family (the loaded set includes ✕ ● ◆ ▲ ▼ ★ ⬢ ⬣ ⊕ ⊗ ⊙ ⊘ ⊠ and a dozen others, chosen specifically to be distinguishable by low-vision and dyslexic readers). The grid is the same; the symbols are the same shapes in the user's confirmed cluster mapping, but rendered at a higher contrast ratio and with a larger default cell size. Pass into the prompt: - The grid dimensions - The cluster-to-accessibility-symbol mapping (the build chooses which accessibility symbol to assign to each cluster, NOT the model) - The cell-to-cluster mapping (RLE encoded as above) Style direction: black symbols on bone-paper background; cell size large enough to be readable at 12 cells per inch when printed at A4 / Letter. No decorative chrome. Output: a single PNG. ``` ## 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 Mumbai flea-market find.** An embroiderer browsing a Sunday market in Bandra picks up a folded sheet of squared paper for fifty rupees — a Sindhi sūf chart drawn in fine pen, the printed key torn off along the perforation. She photographs the chart on her kitchen tiles when she gets home. The app detects an 84 × 96 grid, clusters eleven distinct ink symbols, recognises sūf hallmarks with confidence 0.71, and proposes a palette led by DMC 321 (the dominant warm), DMC 310 (the outline black), and DMC 729 (the gold). She confirms ten of eleven; for the eleventh — a small flower glyph she doesn't recognise — she taps "see alternates" and picks DMC 552 herself. - **The bunica's box.** A Romanian woman in her thirties opens her grandmother's sewing box and finds an iie blouse chart drawn on the back of a 1987 calendar page. The grid is faint — pencil on cardstock — and the symbols are tiny. The app reconstructs the implied lattice (the grid lines are absent on the densest section), clusters eight symbols, and surfaces a tradition guess of "Romanian iie, possibly Maramureș region" at confidence 0.66 with a one-sentence visual evidence note. The granddaughter taps to confirm the tradition, accepts six DMC suggestions, and overrides two — she remembers her grandmother stitching this in deep wine, not Christmas red. - **The Otomí tenango with three hands.** A Mexican embroiderer in Tenango de Doria has a chart her two aunts and her mother all worked on together — the symbols are slightly different across the sheet because three women drew them on three afternoons. The app detects the variation and offers a split-or-merge UI: cluster c3 could plausibly split into c3a (her mother's slightly-rounded ●) and c3b (her aunt Conchita's smaller dot). The embroiderer merges them — they all meant the same thread — and proceeds. - **The tatreez sampler photocopied four times across forty years.** A Palestinian woman in Detroit inherits a tatreez sampler her great-grandmother drew in Ramallah in the 1950s; the chart has been photocopied four times across four decades and the symbols are now smudged. The app reads what it can, flags six clusters as low-confidence, and asks her to confirm dimensions ("we count 120 wide × 64 tall — does that match the proportions you remember?"). She corrects to 120 × 68 and continues. - **The Hmong paj ntaub from a community archive.** A volunteer at a Hmong community centre in Saint Paul is digitising a folder of hand-drawn paj ntaub charts donated by an elder. She photographs each one; the app recognises paj ntaub geometric hallmarks with confidence 0.78 and seeds the palette from grounded references for Hmong colour conventions. The elder is invited to confirm via a magic-link share; she taps through twelve charts in one sitting. - **The Soviet-pamphlet rooster.** A Ukrainian woman in Kraków buys a 1973 craftwork pamphlet at a flea market — a rooster chart printed in Cyrillic, the key page detached. The app reads the Cyrillic symbol labels from a separate key-fragment upload (which the user found loose in the pamphlet's spine), parses thread codes where it can ("МУЛИНЕ № 12"), and resolves the rest of the unkeyed symbols from cell density. She confirms all twelve clusters in eight minutes. - **The sashiko draft from a deceased aunt.** A Japanese-Canadian woman in Vancouver finds her late aunt's notebook of hand-drawn sashiko drafts; some have keys, some don't. The unkeyed ones are simpler — sashiko traditionally uses one or two thread colours, often indigo on bone — so the resolution is quick. The app recognises sashiko hallmarks at high confidence and proposes DMC 824 (Blue Very Dark) as the primary, with a citation note linking to traditional indigo palette references. - **The kogin-zashi geometric draft.** A Japanese embroiderer with a kogin-zashi draft on graph paper from her grandmother's village in Aomori. The app recognises the kogin geometric structure and proposes the traditional white-on-indigo palette; she confirms and exports. - **The accessibility re-render.** A low-vision stitcher with macular degeneration wants to use a chart she has confirmed but can't see the original tiny symbols. She taps "re-render for accessibility"; the app generates a new chart with high-contrast, low-confusion symbols at a larger cell size, ready to print at A3 on her stitching room printer. - **The flossbox photograph.** A stitcher photographs her organised floss box (every skein in its little compartment) before starting a project. The app extracts every DMC number visible on the labels, builds her owned-inventory, and marks the chart's shopping list with "you already own this" beside DMC 321, 310, 729, and "buy 1 skein" beside DMC 552. - **The donation to a textile archive.** A volunteer at the Palestinian Museum Digital Archive Initiative digitises forty tatreez samplers from a single family donation and exports each one as a structured chart JSON + colour-cell PNG + provenance card. The archive accepts the structured format directly. - **The family share.** A Bukharan woman in New York shares a suzani draft with her aunt in Tashkent via magic-link email. The aunt opens the link on her phone, sees the chart, and confirms three symbol resolutions her niece was uncertain about — "yes, ✕ is the wine red, and ◆ is the gold, like Babo Sara used to thread." ## 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 two hands holding a folded, slightly yellowed cross-stitch chart on graph paper, on a wooden kitchen table, with a basket of skeins out of focus in the background. One paragraph: "Pattern Reader takes any old cross-stitch chart, even with no key, in any tradition or era, and helps you rebuild it as a chart you can actually stitch — with every symbol resolved to a modern thread number, every choice yours to confirm." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample chart" → loads the demo chart in section 8a. 2. **Empty state — "Start a chart".** Three big input methods: 📷 Photograph the chart · 🖼 Upload a scan · 🔗 Drop a PDF. A short explainer below each ("Best for what you have on your kitchen table right now", "Best if you scanned at 300 dpi or better", "Best for a previously digitised PDF"). A small fourth option: "Add a key fragment? It helps — drop it here." A fifth: "Photograph a finished piece instead" (for users reverse-engineering a stitched sampler). 3. **Capture flow** (mobile-first). Live viewfinder with paper-rectangle crop guides. Capture chart → "is there a key on a separate sheet?" → optional key capture → "any extra sheets for this chart?" → loop. The flow keeps the camera at fixed focus and warmth so paper colours stay consistent across multi-sheet patterns. 4. **Processing screen.** A vertical layout showing the original chart photograph at top, with a step-by-step honest progress bar below: "Finding the grid…" → "Counting the cells…" → "Sorting the symbols…" → "Suggesting thread colours…" → "Checking for a tradition we recognise…". Each step takes 4-10 seconds. The user can close the app and come back. 5. **Chart detail view — the interactive grid.** A two-column layout on desktop, stacked on mobile. Left: the photograph of the original chart (zoomable; the detected grid overlay is toggleable; the user can correct grid dimensions inline). Right: the interactive zoomable grid, every cell coloured by its cluster (faint pastel tints during the suggestion phase, full colour after confirmation). Sticky panel on the right edge: the symbol cluster list, each row showing the representative glyph + cluster confidence + suggested DMC + colour swatch + confidence chip + "(why?)" link → opens the one-line reason. Each row has a "Confirm" button and a "See 3 alternates" button. Progress: "7 of 12 symbols confirmed" with a horizontal progress bar. The download button at the bottom is greyed until all 12 are confirmed; tapping it while greyed shows a small tooltip: "Confirm every symbol before downloading — you decide the threads, not us." 6. **Tradition panel.** Below the symbol list, a soft-bordered card: "We think this might be Palestinian tatreez (Bethlehem region). Confidence 0.78. Visible hallmarks: the dense red border structure, the central tree-of-life motif, the diagonal corner blocks." Two buttons: "Yes, this is tatreez" / "No, choose a different tradition" / "Not from a recognised tradition". If confirmed, the palette suggestions reload tradition-grounded. 7. **Alternates modal.** Tap "See 3 alternates" → modal shows three alternate DMC suggestions with swatches, names, confidences, reasons. Tap to choose. The chosen alternate slots in as the primary; the user still confirms. 8. **Family palette panel.** Once a user has 3+ confirmed charts in the same tradition, a soft-bordered card appears: "In your previous tatreez charts, the ✕ cluster has been confirmed as DMC 321 sixteen times out of twenty. Use this as the suggested default here?" Yes / No. 9. **Floss inventory drawer.** A small drawer on the right edge: "Your floss box." Inside: a sortable list of DMC numbers the user owns. Add by typing, photographing the box, or pasting a list. The chart's shopping list (when ready) is filtered into "already owned" and "need to buy". 10. **Accessibility re-render screen.** A toggle: "Show me this chart in a high-contrast, low-confusion symbol set, larger cells, printable at A3." The Nano Banana 2 re-render appears side-by-side with the original. User can tweak symbol set, cell size, and colour-block-vs-monochrome render. 11. **Export screen.** A horizontal list of export formats: PDF (with chart + key + shopping list), CSV grid, .pat (Pattern Maker), DST / PES (machine-embroidery), plain-text row-by-row reader. A preview thumbnail for each. A small switch: "include the provenance note on the PDF" (default on). 12. **Library view.** Magazine-grid of the user's charts. Filter by tradition, by completion status, by language, by date added. Default sort: most recently confirmed. 13. **Footer.** "Made for the charts whose keys fell off — and the people who still want to stitch them." Privacy: "Your charts 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 Pattern Reader." - Subhead: "Photograph any old cross-stitch chart with no symbol key, in any tradition, any era — the grid comes back, every symbol resolved to a modern thread number, every choice confirmed by the person who will actually stitch it." - One paragraph (≤ 60 words) explaining who this is for and what makes it different from a generic OCR app: it detects the cell grid even when it's implied, it clusters symbols across hands and inks, it resolves to your palette of choice — and it never finishes the chart without your confirmation. - Visual: a small annotated illustration of a hand-drawn chart on graph paper with the relevant elements labelled (grid lattice, symbol cluster, blank cell, every-10-cell thicker rule, partial key) — not a generic embroidery icon. **Slide 2 — Try it now.** - One short prompt: "Try with the sample chart". - A live demo input pre-loaded with the sample chart in section 8a (the Sindhi sūf rooster). - 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the grid detected even though the pencil is faint, the cluster split for the two ✕ variations, the tradition recognition note for sūf). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample chart in `/data/seed-charts/` for your own scans." - "Adjust the prompts in `/server/prompts/` to your tradition or your family's palette." - "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 hand-drawn and printed cross-stitch charts in any tradition, in pen, pencil, marker, or photocopy. Detects the grid even when it is implied. Clusters every distinct symbol into the smallest plausible set. - **Gemini 3.5 Flash (structured output)** — returns the Chart schema with grid dimensions, cell-by-cell symbol mapping, and per-cluster DMC suggestions with reasoning and confidence. - **Gemini 3.5 Flash (long context)** — once your library grows, the family-palette suggestion call sees every confirmed chart at once to surface "you have resolved this symbol as DMC 321 sixteen times before". - **Gemini 3.5 Flash + grounded search** — recognises living textile traditions and proposes culturally-grounded palettes with citations, never lectures. - **Gemini 3.5 Flash Image (Nano Banana 2)** — renders the chart in your candidate palette as solid coloured squares, so you can see it "stitched" before buying floss. Also generates the high-contrast accessibility re-render. - **Firebase Auth** — Google and Apple sign-in, family chart-sharing via magic links. - **Firestore** — stores your charts and confirmations, syncs across devices in real time. - **Firebase Storage** — keeps the original chart photographs at upload resolution, forever. - **Cost note** — see the detailed breakdown in 6d. A typical chart (40 × 60 cells, twelve clusters, first ingest + confirmation flow) costs about $0.05 of Gemini API spend, total. - **Privacy note** — your chart photographs are private to you and the family members 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. The model suggests; you confirm. **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) - `DMC_CATALOGUE_VERSION` — pinned version of the static DMC catalogue JSON shipped with the build **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: the chart-parse call (Gemini 3.5 Flash, medium thinking) is the most expensive single call; family-palette runs only weekly and only when the library is ≥ 3 confirmed charts. - One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this chart forever" — gone in 60 seconds, including the photograph from Storage), what is never sent for training. **Documentation links:** - AI Studio Build docs - Gemini API multimodal, structured output, long context, grounded search docs - Nano Banana 2 image-generation docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on .pat and DST/PES export formats for the curious **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) - **Read chart → grid + clusters + initial resolution (Gemini 3.5 Flash, medium thinking)** — typical chart ≈ 1-2 images, ~3,000 output tokens (the cells array is the heaviest field for a 40 × 60 grid). ~$0.022/chart. - **Re-resolve a single symbol after user override (Gemini 3.5 Flash, low thinking)** — small payload, runs each time the user overrides. Typical chart sees 2-4 overrides during confirmation. ~$0.003 per override. - **Recognise tradition + grounded palette (Gemini 3.5 Flash + grounded search)** — runs once per chart at parse time. ~$0.002/chart. - **Resolve key fragment (Gemini 3.5 Flash, medium thinking)** — runs only when user uploads a key fragment, which is the minority case. ~$0.008/chart when invoked. - **Cross-chart family-palette suggestion (Gemini 3.5 Flash, low thinking, long-context)** — runs weekly per user, only when library ≥ 3 confirmed charts. Long-context input. ~$0.05 per run for a 30-chart library. - **Floss inventory match (Gemini 3.5 Flash, low thinking)** — runs once per chart at shopping-list time. ~$0.0005/chart. - **Nano Banana 2 colour-cell preview render** — runs once per palette preview, often 1-3 times per chart during the user's exploration. ~$0.03/render. - **Nano Banana 2 high-contrast accessibility render** — runs once per chart when the user opts in. ~$0.03/render. - **Expected per-chart cost on first ingest with one palette preview and one accessibility render:** ~$0.09. **Without accessibility render:** ~$0.06. **Without preview render and accessibility render:** ~$0.03. - **Image storage:** Firebase Storage standard tier, ~$0.026/GB/month. A high-resolution chart scan is ~2-4 MB; a library of 200 charts uses ~600 MB ≈ ~$0.016/month. ## 7. Design language - **Mood:** The kitchen-table look of a stitcher unfolding a chart late at night, the lamp warm, the floss box half-open beside her, the chart laid flat with one hand holding down a creased corner. Not a craft-blog. Not a craft-supply-store. The grandmother's box of charts, dusted off and made readable. - **Typography:** A soft humanist serif for chart titles and provenance notes (Source Serif Pro or EB Garamond — settable to "Lora" if the build prefers a more modern serif). A clean grotesque for app chrome (Inter or Geist). A monospace for the cluster glyph list (JetBrains Mono or IBM Plex Mono) so the symbols are crisp. NEVER a "handwriting font" for app content — the chart photograph carries the hand-feel; the chrome should be quiet. - **Palette:** Bone-paper background `#F4EFE6` for the chart view, deep ink `#1B1714` for body text, a soft moss `#5A6B4F` for the confirm-success state, a muted brick `#A33A2C` only for the "still to confirm" pill and override actions, and a small palette of cluster-tint pastels (rotated through soft blue `#A8C0D6`, soft peach `#E6C0A6`, soft sage `#B6C5A5`, soft lavender `#C8B6D6`, etc.) — these are the faint background tints for cell-cluster highlighting during the suggestion phase, NEVER the suggested DMC colour itself. The DMC swatch is always the literal hex from the catalogue. - **Imagery:** The chart photograph is the hero. Never replaced by a vector rendering until the user has confirmed every cluster. Even then, the original is one tap away. The colour-cell preview Nano Banana 2 render and the original sit side-by-side so the maker can compare. Embroidered samples (when shown in tradition-recognition cards) are credited, small, and pixel-honest — not glossy product photography. - **Hand-feel touches:** A barely-visible paper grain on the chart-detail background. The grid overlay on the original photograph fades in with a thin 200ms transition (respects `prefers-reduced-motion`: instant). When a user confirms a symbol, the tint on the matching cells deepens from pastel to the literal DMC hex with a 180ms ease, like the colour soaking into the cell. When a user overrides, the override modal slides up with a single small slide gesture, not a bouncy spring. - **Spacing:** consistent 4-px base. Generous whitespace — the grid needs room to breathe; symbol legibility depends on it. - **Radius:** consistent token set (e.g. 6 / 12 / 20 px). Cluster-list rows use 6; the colour-cell preview frame uses 12; the welcome card uses 20. - **Shadows:** subtle, layered, warm-toned. Avoid heavy drop-shadows. The cluster-list panel uses a single 1px hairline border, no shadow. - **Motion:** purposeful — entrance fades, hover lifts, confirmation tint-deepening. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The cell-tint-deepening on confirm 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. The download button has a very visible disabled state (greyed, with a small lock icon, and a tooltip explaining why). Loading uses skeletons not spinners where possible. Empty states have helpful next-action guidance ("Photograph a chart to start — even a hand-drawn one on graph paper works"). ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible chart titles, symbol sets, DMC suggestions, tradition notes, family provenance lines that fit the domain (use the seed content in section 8a as a starting point). When inventing, lean on real cross-stitch and embroidery vocabulary — DMC numbers from the actual catalogue, real tradition names spelled correctly, real palette conventions — but never claim that a fictional chart is a real archival document or that a fictional confirmation is a real cultural authority. - Tone: warm, direct, free of corporate language. This template is for a person at a kitchen table with a chart in front of her, not for a craft-supply marketing team. - Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. No 'AI-powered'. - Body copy: short paragraphs (2-4 sentences). Use lists where appropriate. - Plain language. Avoid jargon — except where the stitcher already speaks the jargon ("DMC 321", "the every-10 thicker rule", "tradition palette", "skein count", ".pat file", "every-other-cross sampler"). - Where the app outputs AI-generated content, never label it as "AI says" — let the suggestion speak naturally with the model's one-line reason shown plainly. Confidence numbers are surfaced as small chips, not hidden. ## 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 library (sidebar):** - "Asha's Sindhi sūf rooster" (1 chart, 84 × 96 cells, 11 confirmed clusters) — hand-drawn pen on graph paper, found at Bandra flea market, tradition: Sindhi sūf, palette led by DMC 321 / 310 / 729. - "Bunica Ileana's iie blouse" (1 chart, 60 × 120 cells, 8 confirmed clusters) — pencil on the back of a 1987 calendar page, inherited from grandmother in Maramureș, tradition: Romanian iie, palette led by DMC 902 / 310 / 3756. - "Tía Conchita's Otomí tenango" (3 charts, family album) — three hand-drawn pages by three women on three afternoons, Tenango de Doria, Mexico; cluster-merge applied; palette led by DMC 666 / 552 / 700 / 718. - "Babo Sara's suzani draft" (1 chart, 100 × 100 cells, 14 clusters, awaiting Aunt Malka's confirmation via magic-link) — Bukharan, Tashkent c. 1970, fragmentary Cyrillic Uzbek key recovered. **Sample chart in detail view (this is what the demo should show):** - **Chart title:** "Asha's Sindhi sūf rooster (Bandra flea market, March 2026)" - **Artefact type:** "hand_drawn_chart_no_key" - **Paper type note:** "graph paper, 5mm cells, faint ruled, slightly yellowed" - **Era guess:** "1980s-90s; pen lettering style of mid-period Karachi pattern pamphlets" - **Grid:** 84 × 96, grid_visible true, every-10-cell thicker rule yes, grid_confidence 0.93 - **Symbol clusters (11):** - c1: ✕ (representative), alternates ["✕ in heavy ink", "✕ in light pen"], density rank 1, cell count 612, clustering_confidence 0.91 - c2: ● (representative), density rank 2, cell count 504, clustering_confidence 0.95 - c3: ◆ (representative), density rank 3, cell count 388, clustering_confidence 0.89 - c4: ▲ (representative), density rank 4, cell count 240, clustering_confidence 0.92 - c5: ○ (representative), density rank 5, cell count 184, clustering_confidence 0.84 - c6: ■ (representative), density rank 6, cell count 160, clustering_confidence 0.88 - c7: small slash NE-SW (representative, plain-text), density rank 7, cell count 120, clustering_confidence 0.78 - c8: small slash NW-SE (representative, plain-text), density rank 8, cell count 96, clustering_confidence 0.78 - c9: ⊙ (representative), density rank 9, cell count 72, clustering_confidence 0.81 - c10: ★ (representative), density rank 10, cell count 48, clustering_confidence 0.75 - c11: small flower glyph, four-petal (representative, plain-text), density rank 11, cell count 24, clustering_confidence 0.69 (flagged for user review — could plausibly merge with c9) - **Tradition guess:** Sindhi sūf, confidence 0.71, evidence "geometric symmetry typical of sūf darn-stitch motifs; dense warm-outline structure characteristic of mid-period Karachi pamphlet style" - **Resolutions (initial suggestions, all user_confirmed: false):** - c1 → DMC 321 (Christmas Red), confidence 0.88, reason "most-used cluster; typically the dominant outline warm in sūf charts of this style" - c2 → DMC 310 (Black), confidence 0.92, reason "second-densest cluster; outline black is standard in this tradition" - c3 → DMC 729 (Old Gold Medium), confidence 0.82, reason "tertiary cluster in dense bands across central motif; gold accent typical for sūf" - c4 → DMC 826 (Blue Medium), confidence 0.74, reason "appears in border-band positions; blue is a common secondary in mid-period Sindhi charts" - c5 → DMC 3712 (Salmon Medium), confidence 0.69, reason "softer presence in upper-third; warm pink is plausible but please confirm against the family palette" - c6 → DMC 700 (Green Bright), confidence 0.77, reason "appears in vegetal-pattern blocks; bright green is the conventional sūf foliage" - c7 → DMC 798 (Delft Blue Dark), confidence 0.70, reason "small accent diagonal; cool secondary" - c8 → DMC 814 (Garnet Dark), confidence 0.68, reason "small accent diagonal opposite c7; dark warm secondary" - c9 → DMC 3825 (Pumpkin Pale), confidence 0.65, reason "small dotted accent; pale orange is plausible but low confidence — please confirm" - c10 → DMC 742 (Tangerine Light), confidence 0.62, reason "star highlights; bright accent — please confirm" - c11 → DMC 552 (Violet Medium), confidence 0.58, reason "small flower glyph; purple-violet is plausible for the floral motif but please confirm" - **Provenance note:** "Found at Bandra flea market, March 2026. Pen on graph paper. Possibly copied from a Karachi craftwork pamphlet of the late 1980s." - **User palette preference:** DMC - **Reading confidence overall:** 0.81 **Sample input artefacts (for the build to demonstrate):** - A hand-drawn cross-stitch chart of a rooster on faintly-ruled 5mm graph paper, pen ink, key torn off, Sindhi sūf tradition. - A pencil chart on the back of a 1987 wall-calendar page, Romanian iie blouse pattern. - Three hand-drawn pages of an Otomí tenango chart on bond paper, each by a different hand, ink and brush. - A 1973 Cyrillic-Uzbek embroidery pamphlet partial page with a torn key fragment listing four symbols and four МУЛИНЕ thread codes. - A photocopy-of-a-photocopy of a tatreez sampler in red and black, Bethlehem region, with smudged symbols. **Sample voice copy:** - Onboarding: "Photograph one of your old charts. We'll read it — even if the key fell off years ago." - Processing: "Finding the grid…" / "Counting the cells…" / "Sorting the symbols…" / "Suggesting thread colours…" / "Checking for a tradition we recognise…" - Empty library: "Your library is waiting for its first chart. Photograph one to start — even a hand-drawn one on graph paper works." - Error (couldn't read grid): "We couldn't find the cell grid clearly here. Try a flatter photo, or set the dimensions yourself and we'll read the symbols against your grid." - Save confirmation: "Added to your library — Asha's Sindhi sūf rooster, 84 × 96 cells, 11 clusters waiting for your confirmation." - Confirmation progress: "7 of 12 symbols confirmed. 5 to go." - Download disabled tooltip: "Confirm every symbol before downloading — you decide the threads, not us." - Confirmation success: "All 12 symbols confirmed. Your chart is ready to download in any of these formats." - Tradition guess: "We think this might be Palestinian tatreez (Bethlehem region) — confidence 0.78. Does that match?" - Family-palette suggestion: "In your previous tatreez charts, the ✕ cluster has been confirmed as DMC 321 sixteen times out of twenty. Use that as the default here?" **Sample family-share invitation email subject + body:** - Subject: "Aunt Malka — I found Babo Sara's suzani draft. Can you confirm the colours?" - Body: "Hi Tante Malka — I scanned Babo Sara's suzani draft, the one with the wine-and-gold border. The app's made guesses for fourteen symbols but I need you to confirm — you stitched alongside her. Tap to open the chart and tap each symbol to confirm or change." [Open Chart] ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of two hands holding a folded, slightly yellowed cross-stitch chart on graph paper, on a wooden kitchen table at evening, a basket of skeins blurred at the edge of frame. Generate via Nano Banana 2 with a prompt emphasising "wooden table, warm desk-lamp light, hands of a woman in her forties, late evening, gentle out-of-focus skein basket, real worn graph paper with faint pencil grid and ink symbols, soft shadow under the basket". Skin tone left undecided — the prompt is "any hands" so the build can vary the hero image per remix. - **App icon / wordmark:** Set in the display serif. Slightly worn paper texture behind it. A small ✕ ● ◆ trio replaces the dot of the i in "Pattern", as a quiet symbol cluster. - **Empty-state illustration:** A simple line drawing of a folded chart with one corner curled, a torn perforation where the key used to be. Hand-drawn aesthetic, not a flat icon. - **Demo chart photographs:** Generated per the prompts in section 8a — Nano Banana 2 prompts that specifically request "pen on faint-ruled graph paper, slight yellowing at the edges, soft afternoon window light, no key attached, top-down photograph, no people in frame". Each demo chart should look photographed, not rendered. - **Tradition reference imagery:** Tiny credited thumbnails (≤ 80px) from public-domain or open-licence museum sources, shown only inside the tradition-recognition card, never decoratively elsewhere. - **Stock fallbacks:** If image generation fails, fall back to the photographed sample chart from `/public/samples/sample-suf-rooster.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"). - 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 chart", "Drop a previously-scanned PDF", "Add a key fragment", "Invite a family member to confirm" — never a generic "Add your first item". - The interactive grid supports pinch-and-pan, double-tap to zoom-to-cell, and arrow-key navigation across cells. - Tapping a cell highlights every other cell in the same cluster across the grid (faint outline), so the stitcher can see at-a-glance where this symbol appears. - Cluster confirmation tint-deepening: when a user taps "Confirm" on a cluster, every cell in that cluster animates its background tint from pastel suggestion-colour to the literal DMC swatch hex over 180ms. `prefers-reduced-motion` jumps instantly. - 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. The cluster-list panel populates cluster-by-cluster as the parse call returns. - If an AI call fails, show a calm, specific error ("We couldn't find the cell grid clearly here — try a flatter photo, or set the grid dimensions yourself in the side panel") and offer retry. - Low-confidence cells in the grid are faintly underlined; tapping reveals the alternate cluster IDs the model considered. - The download button is disabled until every cluster is confirmed. Disabled-state tooltip: "Confirm every symbol before downloading — you decide the threads, not us." - The colour-cell preview render (Nano Banana 2) loads behind a loading skeleton matching the grid dimensions; it never replaces the original chart, it sits beside it. ## 11. Tech & responsive requirements - **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 chart parse, the re-resolve, the key-fragment parse, and the family-palette suggestion; `gemini-3.5-flash` for tradition recognition (with grounded search) and floss-inventory matching; `gemini-3.1-flash-image` for the colour-cell preview and the high-contrast accessibility re-render. Set `thinkingLevel` explicitly per call (omit on image-generation calls). - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed library on first launch. Cell-grid data lives in a subcollection per chart because the array can grow large for big patterns; keep the parent chart document under 1 MB. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email as fallback for family sharing. - **Storage:** Firebase Storage for original chart photographs and rendered PNGs. Pre-signed URLs only. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The interactive grid view is the load-bearing layout; verify pinch-and-pan on iOS Safari and Android Chrome. - 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 chart-detail view so family-share confirmations from other users appear live. - Optimistic UI on writes; reconcile on response. - 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 chart capture; 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 chart photo still uploads when WebRTC is denied; rotation drops the camera track on iOS — re-bind on `orientationchange`. - Large charts: cell-grid rendering uses canvas (not 7,000 React divs) for charts above ~2,000 cells. Threshold is configurable. ## 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 original chart photographs have `alt` describing the artefact ("photograph of a hand-drawn cross-stitch chart, 84 by 96 cells, pen on graph paper, eleven distinct symbols, no key attached"). - Form fields have associated `