================ 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. --- # Tax-Return Decoder ## 1. Project **Tax-Return Decoder** is a first-time-filer's companion for anyone who has just arrived in a country's tax system and has to file a return they can't read. The user tells the app a few plain-English facts about their life ("I work as a sole trader", "I rent", "my partner doesn't earn this year") and the app produces the right ordered guide: which forms, in what order, and for every box on every form an explanation in their first language with a literal reading of what the tax authority is asking. Every explanation is grounded in the actual tax authority's own published guidance — HMRC, IRS, Revenue, ATO, IRD, CRA, SAT, BIR, Skatteverket — and the URL of the page that produced the wording is shown beside it. This is the kind of app a Filipino freelance illustrator in London opens at her kitchen table the first January she has to file a UK Self-Assessment return — she's been on payroll all her life, the SA100 form has nineteen pages, and "Box 17. Total balancing charges where Business Premises Renovation Allowance has been claimed" makes her close the laptop and stare at the ceiling. It is also the kind of app a Mexican-American sole trader in Austin opens the first April after going self-employed — Schedule SE, Schedule C, Form 8995, quarterly estimateds, the QBI deduction — and what he wants is not advice, but a translation of what the IRS is actually asking, so he can answer it himself or hand it to a CPA with the answers already drafted. Same shape of moment: a person sitting in front of a form designed for a citizen who grew up inside that tax system, written in language the system assumes you already understand. The single demo that proves the magic: the user picks "United Kingdom · Self-Assessment · sole trader · first year" and answers five plain-English questions ("Do you have any employment income this year? Do you have rental income? Did you make any pension contributions? Did your partner earn under £12,570? Did you work from home?"). Within 20 seconds the app produces an ordered guide: "You will file SA100 (the main return) plus SA103S (self-employment short pages). Skip SA105 (you have no rental income). Marriage Allowance is worth checking but is claimed on the main return, not a separate form." For each page of each form, every box gets a plain-English explanation with the original wording quoted verbatim, an example answer for a fictional version of the user's situation, and the URL from gov.uk that produced the wording. **Hard rule.** The app never tells the user what to enter. It translates the form's intent into a language and reading level the user can follow, and explicitly hands the answer back to the person. Anything the model cannot ground in the tax authority's published guidance is surfaced as "talk to an accountant" — visibly, unmissably, with a one-line reason. The app is a decoder, not an adviser. **Tagline:** _Read the form in any tax system, in any language — every box explained, every word grounded in the source, the answer left to you._ ## 2. Target audience - First-time filers in a country they recently moved to — the freelance illustrator in London, the postdoc in Boston, the construction-site supervisor in Dublin, the software engineer in Berlin, the nurse in Toronto - New sole traders, freelancers, gig workers in any country whose tax authority assumes they already know the lexicon - Returning expatriates filing in two countries at once — the dual UK/US filer trying to map the IRS and HMRC forms onto the same income, the Mexican-American sole trader filing 1040 + Schedule SE while still owing in Mexico - Adult children translating a tax form for an elderly parent who never had to file before — the daughter in Manila helping her widowed father file for the first time after retirement, the granddaughter in Lyon helping her North-African grandfather file a French return for a small rental - Non-native readers of the language the tax authority publishes in — the Tagalog-first filer reading HMRC English, the Vietnamese-first filer reading Australian Tax Office English, the Tamil-first filer reading Singapore IRAS English, the Amharic-first filer reading IRS English - International students with a small amount of taxable income for the first time — the Bangladeshi PhD candidate at MIT, the Korean undergrad in Melbourne, the Nigerian master's student in Lisbon - Asylum seekers and recently-naturalised citizens with their first full-year tax obligation in their new country - Small landlords with one or two rental properties — the Polish-Irish landlord in Dublin renting one flat in Drumcondra, the Cuban-American grandmother in Miami renting a second home - Anyone working a single tax problem with a CPA / accountant present in the conversation, who wants to come to the appointment with the boxes already understood - Tax-clinic volunteers (VITA in the US, TaxAid in the UK, Citizens Advice) using the app as a translation aid while sitting next to a client ## 3. Core value propositions Surface these clearly through copy, visual emphasis, and section ordering — they are the reasons users pick this app. - **Translates the form, never the answer.** The app reads HMRC's wording, the IRS's wording, Revenue's wording — verbatim — and produces a plain-language gloss in the user's first language. The literal box label is quoted exactly. The answer to put in the box belongs to the user. This boundary is visible on every screen. - **Grounded in the tax authority's own published guidance.** Every box explanation comes with a citation URL to the official source: gov.uk for HMRC, irs.gov for the IRS, revenue.ie for Revenue, ato.gov.au for the ATO, ird.govt.nz for IRD, canada.ca for the CRA, sat.gob.mx for SAT, bir.gov.ph for BIR. The Gemini 3.5 Flash grounded-search call retrieves the exact URL; the URL is displayed; the user can open it in one tap. - **First-language explanations in any of fifty-plus languages.** Tagalog, Vietnamese, Mandarin and Cantonese, Korean, Tamil, Hindi, Urdu, Bengali, Punjabi, Amharic, Swahili, Farsi, Khmer, Arabic, Polish, Spanish, Portuguese, French, German, Italian, Greek, Turkish, Russian, Ukrainian — and the user can change language mid-session without losing state. - **The right forms in the right order, for THIS person's situation.** The user answers five to ten plain-English questions about their year ("I work for myself", "I rent", "my partner doesn't earn"); the app produces an ordered list of forms with a one-line "why this form is for you" and a one-line "what this form is asking for". No long-form questionnaires, no decision trees the user has to navigate by themselves. - **Every box explained, in three voices.** A literal reading (what the form is asking for in the simplest possible English), a first-language gloss (the same explanation in Tagalog, Cantonese, etc.), and a working example answered for a fictional person whose life maps onto the user's. The example never claims to be advice; it's a worked example beside the box. - **"Talk to an accountant" is a visible, named result, not a footnote.** When the model encounters a question it cannot ground — a complex Schedule D capital-gains treatment, a Substantial Presence Test edge case, an HMRC double-taxation-treaty election, a Self-Employed Health Insurance Deduction interaction with a Marketplace subsidy — the app surfaces a coloured callout naming the topic, the page of the form it touches, and a sentence the user can copy into an email to a CPA. The accountant referral is part of the product, not the failure case. - **One-tap "open the actual form" and "open the official guidance".** The user can read the model's explanation alongside the actual page of HMRC's notes or the IRS's instructions, side by side. Trust is built by transparency, not assertion. - **Save-as-you-go, return next session.** A first return takes most people three or four sittings spread across a week. The app remembers where the user stopped, in which language, with which answers drafted, and resumes there on the next visit. Nothing is sent to a tax authority by the app — the app is read-only with respect to the actual filing system. ## 4. Features to build - Country + tax-system picker on first visit (UK Self-Assessment, US Federal 1040, Republic of Ireland Form 11, Australia individual return, Canada T1 General, New Zealand IR3, Mexico Declaración Anual, Philippines BIR Form 1701/1701A, Spain Modelo 100, France Formulaire 2042, Germany ESt 1A, Sweden Inkomstdeklaration 1) — language picker independent of country - Five-to-ten-question plain-English profiler ("Do you work for yourself? Do you have a regular job? Do you rent? Do you own a home you live in? Did your partner earn under [country threshold]? Did you make pension contributions? Did you work from home? Did you have any side income?") - Ordered "your forms" list, generated from the profiler answers — each form has a one-line "why this is for you" and a "what this form is asking for" - Form-by-form decoder — for each form, every numbered box gets: the literal label verbatim from the form, a plain-English gloss, a first-language gloss, a worked example for a fictional version of the user's situation, and a "see the official guidance" URL - Glossary of tax terms — "allowable expenses", "imputed income", "QBI deduction", "personal allowance", "tax-free threshold", "marginal rate", "withholding", "tax credit vs deduction", "non-resident alien", "ordinarily resident" — defined in plain English, with the country-specific definition flagged - Multi-language switcher — user can change the explanation language at any point without losing the profile or the form-by-form state - Talk-to-an-accountant flagger — any box the model cannot ground in published guidance triggers a coloured callout with a draft message the user can copy ("Hi — my UK Self-Assessment Box 41 'Adjustments to profit' is asking about a 2024 change of accounting basis from cash to accruals; can you help me decide whether to elect under section 25A?") - Tax-clinic mode — a separate UI flavour where a volunteer is sitting next to a client; the explanations stay on-screen as the volunteer types, no chat-bot affordances, larger type, one form per screen - Side-by-side "official guidance" panel — the cited gov.uk / irs.gov / etc. page renders alongside the model's explanation; user verifies the model's gloss against the source - Voice mode — read each box aloud in the user's first language at a calm pace, for users whose first language is more easily spoken than read (Khmer first-generation, older Tagalog speakers, oral cultures) - Drafting pad per box — a private note field beside each box where the user can write their working answer; never transmitted, never claimed to be the filed answer - Print-friendly summary — when the user has stepped through every box, the app produces a printable per-form sheet with their drafted answers in one column and the box labels in the other, intended as a handoff to a tax-clinic volunteer or CPA, NOT a submission - Resume-where-you-left-off — last session's country, language, profile answers, drafted notes are restored on the next visit - Glossary search — type any tax-form phrase ("schedule self-employment short", "personal services income", "imputed rental") and get the definition in the user's first language - Year-aware — the user picks the tax year; the form versions, threshold figures, and links are scoped to that year. Older years are read-only; current year is editable. - "What changed this year" briefing — for the current year, a short note summarising the public changes the tax authority published (new thresholds, new schedules, eliminated boxes), grounded and cited - Privacy by construction — drafted notes live in Firestore under the user's account; nothing is exported anywhere except a print-to-PDF the user explicitly triggers; the app never connects to a tax authority's actual filing system - Export-to-CPA bundle — the user can produce one PDF with all the form names, every box's official label, the model's plain-English gloss, and the user's drafted answer — to email to an accountant ## 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) - **Long context (1M tokens)** — the model reads the actual tax authority's published guidance for the chosen form (often 40-120 pages of HMRC notes, or the IRS's "Instructions for Form 1040" PDF, or Revenue's Form 11 Guide), keeps the user's profile, and the previous boxes' decisions, all in the same call. The form-by-form decoder pass is the canonical long-context call: a single SA100 + SA103S notes pack is ~180k tokens; a 1040 + Schedule C + SE + 8995 notes pack is ~260k tokens; both fit comfortably under the 1M ceiling. **Guardrail**: chunk by form (one form at a time) and abort if the assembled context for any single call exceeds 800k tokens — for that case, fall back to per-page decoding with the page's notes as the only attached document. - **Search grounding** (Gemini 3.5 Flash + `google_search`) — every box explanation is grounded in the official guidance. The grounding call returns citation URLs (gov.uk, irs.gov, revenue.ie, ato.gov.au, ird.govt.nz, canada.ca, sat.gob.mx, bir.gov.ph) which are surfaced in the UI. The model emits JSON in the text body of the response, NOT via `responseSchema` — grounding and `responseSchema` cannot be combined in the same call. Server-side, parse the JSON and read the citation URIs from `response.groundingMetadata.groundingChunks[].web.uri`. - **Multilingual output** (Gemini 3.5 Flash) — every box's plain-English gloss is mirrored into the user's first language. Languages span Tagalog, Vietnamese, Mandarin (Simplified + Traditional), Cantonese (yue-Hant + yue-Latn romanisation for older speakers), Korean, Japanese, Tamil, Hindi (Devanagari), Urdu (Nastaliq), Bengali, Punjabi (Gurmukhi + Shahmukhi), Amharic (Ge'ez), Swahili, Farsi (Nastaliq), Khmer, Arabic (MSA + Levantine register option), Spanish (peninsular + Latin American), Portuguese (European + Brazilian), French, German, Italian, Greek, Turkish, Russian, Ukrainian, Polish, Romanian. Dialect and register awareness: Cantonese for older speakers uses traditional script and avoids modern internet slang; Brazilian Portuguese matches the immigrant-Brazilian register, not Lusitanian academic. - **Structured output / JSON Schema** — the profile-to-forms mapping and per-box decoder outputs both produce typed JSON. The schemas are documented below and seeded verbatim in the system instruction. Structured output is used on the calls that don't need grounding (the profile-to-forms decision and the talk-to-an-accountant classification); the grounded box-decoding call emits JSON in the text body and parses server-side. - **Multimodal image input** (Gemini 3.5 Flash) — optional, for the user who has a paper or PDF form already and wants the app to read it. The user uploads a page; the model reads which form it is, which boxes are on it, and routes to the right decoder. This is offered behind a small "I have the form already" toggle on the country picker; the primary flow is "pick the form" → render the decoder. - **Gemini TTS** (`gemini-3.1-flash-tts-preview`) — reads each box's gloss aloud in the user's first language at a calm pace. Pick the voice whose `languageCode` matches the gloss language. Pre-process pauses with `…` (line breaks) and `—` (paragraph breaks). Prepend a one-sentence style directive: "Read at a calm, unhurried pace, as if explaining a form to someone at the kitchen table. …" - **Thinking levels** — `medium` for the form-by-form decoder pass (the box-by-box plain-English + first-language gloss + worked example). `low` for the profile-to-forms mapping (a tight decision with a clear schema). `medium` for the talk-to-an-accountant classifier (the model must recognise its own grounding gaps). `low` for the glossary lookup. Omit `thinkingConfig` entirely on the TTS call. ### 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 resume-across-devices) requires the sender domain to be authorised in Firebase Auth. Sessions are private to the owner; nothing is shared by default. - **Database — Required.** Firestore for `users`, `sessions`, `profiles`, `forms_decoded`, `box_notes`, `accountant_flags`. Each session is scoped to one tax year, one country, one filer. - **File storage — Optional.** Firebase Storage for the user-uploaded form scans (multimodal path). **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 the first photo upload. Photos are private; pre-signed URLs only; the user can delete every uploaded photo from a Settings page that reports "0 photos stored" once it completes. The text-only flow (no uploads) is the default and does not need Storage at all. - **Email — Optional.** Magic-link sign-in via Firebase Auth for cross-device resume; "email this CPA bundle to myself" — a single button that emails the print-to-PDF to the user's own address. No email to third parties without the user pasting the recipient and confirming. - **Payments — Not needed for v1.** Free for personal use. A possible future tier: pay-per-CPA-handoff routing to a vetted accountant marketplace; not in scope for v1. - **External APIs:** Gemini API for all intelligence. Grounded search uses Gemini's `google_search` tool — no separate Google Search API key needed. No external tax-software vendor APIs (the app is a decoder, not a filer); the app never wires to TurboTax, HMRC Government Gateway, IRS Free File, or any equivalent. This is a hard product boundary. **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, optional SendGrid / Postmark token if magic-link email goes through a third-party SMTP) 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 this session' inside the UI · explicit opt-in for any analytics · the user's drafted answers are never sent to Gemini for model training (use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms) · the user's drafted answers are never transmitted to any tax authority by this app; submission is the user's act, made elsewhere · the talk-to-an-accountant flag is generated client-visibly with no automatic referral to any service. **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 for non-grounded calls. 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. **`responseSchema` and `google_search` are mutually exclusive** in any one Gemini call. The grounded box-decoding call instructs the model to emit JSON in the text body and parses server-side. Citation URLs come from `response.groundingMetadata.groundingChunks[].web.uri`, NOT from the JSON body — the model will hallucinate URLs if asked to put them in the JSON. 7. **State negative constraints explicitly** — they are listed below. They are hard rules the model must follow. 8. **Grounded responses can wrap JSON in ```json fences or add prose preamble.** Server-side, strip fences and brace-extract: ```typescript function safeExtractJSON(raw: string): T { const clean = raw.replace(/```json\s*|```/gi, '').trim(); const s = clean.indexOf('{'); const e = clean.lastIndexOf('}'); if (s === -1 || e === -1) throw new Error('No JSON boundaries in grounded response'); return JSON.parse(clean.slice(s, e + 1)) as T; } ``` 9. **Strip unsupported Zod modifiers before passing to `responseSchema`** — Gemini's OpenAPI subset rejects `.regex()` / `pattern`, fixed-length `z.tuple()`, and other custom validators. Use a sanitizer that flattens tuples to 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 | |------|-------|---------------|---------------| | Profile → ordered list of forms for THIS filer | `gemini-3.5-flash` | low | (none) | | Decode form box-by-box, grounded in published guidance | `gemini-3.5-flash` | medium | `google_search` grounding (no `responseSchema` — emit JSON in text body) | | Translate one box gloss to the user's first language | `gemini-3.5-flash` | low | (none) | | Generate a worked example for a fictional version of the filer's situation | `gemini-3.5-flash` | low | (none) | | Classify whether a box needs "talk to an accountant" | `gemini-3.5-flash` | medium | (none) | | Glossary lookup ("what does 'allowable expense' mean here?") | `gemini-3.5-flash` | low | `google_search` grounding (no `responseSchema`) | | TTS narration of one box's gloss in source language | `gemini-3.1-flash-tts-preview` | n/a | n/a | | Read a user-uploaded form photo → identify form + page + boxes | `gemini-3.5-flash` | medium | (none) | *Note for builders:* on TTS calls, omit `thinkingConfig` entirely — the field is not supported on that model. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. The grounded calls (box decode, glossary lookup) MUST NOT include `responseSchema`; instruct the model to emit JSON in the text body and parse server-side. ### Primary structured-output schemas (seed these verbatim in the prompt) ```typescript import { z } from "zod"; const TaxAuthority = z.enum([ "HMRC_UK", "IRS_US", "REVENUE_IE", "ATO_AU", "IRD_NZ", "CRA_CA", "SAT_MX", "BIR_PH", "AEAT_ES", "DGFIP_FR", "BZSt_DE", "SKV_SE", ]); const FormReference = z.object({ form_code: z.string(), // "SA100", "SA103S", "1040", "Schedule C", "BIR 1701A" form_title_official: z.string(), // "Self-Assessment tax return (SA100)" authority: TaxAuthority, tax_year: z.string(), // "2025-26" (UK) or "2025" (US) why_this_form_is_for_you: z.string(), // one short sentence, plain English what_this_form_is_asking_for: z.string(), // one short sentence, plain English is_optional: z.boolean(), // some forms are situational estimated_pages: z.number().int().min(1), }); const ProfileToForms = z.object({ filer_summary_one_line: z.string(), // "self-employed illustrator, renting, partner under £12,570" forms_in_order: z.array(FormReference), forms_explicitly_not_needed: z.array(z.object({ form_code: z.string(), reason_one_line: z.string(), // "no rental income this year" })), followups_to_ask_user: z.array(z.string()), // ambiguities the profiler could not resolve }); const BoxGloss = z.object({ box_number_verbatim: z.string(), // "Box 17" box_label_verbatim: z.string(), // exact wording from the form page_number_in_form: z.number().int().min(1), literal_reading_plain_english: z.string(), // one short sentence in simplest English first_language_gloss: z.string(), // same explanation in user's first language first_language_bcp47: z.string(), // "tl-PH", "yue-Hant", "es-MX" worked_example_label: z.string(), // "Working example for a sole-trader illustrator with £18,400 turnover this year" worked_example_value: z.string(), // the value the fictional person would enter worked_example_explanation: z.string(), // why that fictional value, in 1-2 sentences needs_accountant_flag: z.boolean(), needs_accountant_reason_one_line: z.string().nullable(), draft_message_for_accountant: z.string().nullable(), }); const BoxDecoded = z.object({ form_code: z.string(), tax_year: z.string(), boxes: z.array(BoxGloss), }); const AccountantFlag = z.object({ form_code: z.string(), box_number_verbatim: z.string(), topic_short: z.string(), // "Substantial Presence Test", "Section 24 mortgage-interest restriction" why_the_model_cannot_decide: z.string(), draft_message_for_accountant: z.string(), estimated_accountant_minutes_to_answer: z.number().int().min(5).max(120).nullable(), }); const GlossaryEntry = z.object({ term: z.string(), authority: TaxAuthority, tax_year: z.string(), definition_plain_english: z.string(), definition_in_user_language: z.string(), user_language_bcp47: z.string(), example_one_line: z.string(), }); type ProfileToForms = z.infer; type BoxDecoded = z.infer; type AccountantFlag = z.infer; type GlossaryEntry = z.infer; ``` ### Common failure modes (and how to avoid them) - Agent answers "Yes, you should enter £4,500 in Box 17" — the model has volunteered an answer the app must never give. The system instruction must repeat the boundary in plain terms: the worked example is for a fictional person; the user's own answer is never offered. If a user asks "what should I put?" the assistant's literal answer is "I can't tell you what to put — but here is the box label, here is the literal reading, here is what a fictional person in your situation would enter, and here is the URL of the official guidance." - Agent generalises across countries — "Box 17 is for capital-allowances" applied to a US filer reading the IRS form. Pin the `TaxAuthority` enum into every call. Reject any output where `form_code` does not match the requested authority. - Agent hallucinates form box labels — generates plausible-sounding wording rather than reading the actual form. The grounded box-decoding call MUST be backed by `google_search` against the authority's own published notes; verify post-hoc that the `box_label_verbatim` appears (case-insensitive) in the cited URL's page content during a daily smoke test. - Agent cites unofficial sources — "according to TurboTax's blog" or "according to AccountingWeb". The system instruction enumerates the allowed citation domains by `TaxAuthority` and rejects any citation outside that allowlist. Server-side: filter `groundingMetadata.groundingChunks[]` to retain only allowlisted domains; if none remain, set `needs_accountant_flag = true` for that box. - Agent silently translates "personal allowance" into a US user's first language as "personal exemption" — overlapping but distinct concepts. The translator call gets the literal English term as input and is told never to introduce a target-country tax concept that wasn't in the source. Glossary entries are country-scoped (`TaxAuthority` is part of the schema) so the same English term can have different definitions per country. - Agent loses the user's language mid-session — after a country switch the explanations come back in English. Persist `first_language_bcp47` in the session document and inject it into every system instruction. - Agent reads the wrong tax year's form — the model picks up the most recent guidance page Google indexes, which is sometimes a year newer than the user's filing year. The grounded query MUST include the explicit year ("Self-Assessment 2024 to 2025 SA100 notes"); the response includes `tax_year` and the server discards results whose `tax_year` does not match the user's session. - Agent reads the IRS notes for "Form 1040 (2024)" when the user is filing "Form 1040 (2025)" because the user typed the wrong year — surface a year mismatch inline ("You selected 2025; the official notes I found are for 2024. Use 2025 notes?") instead of silently substituting. - Agent invents URLs for citations — putting URLs inside the JSON body of a grounded call. Read URLs ONLY from `response.groundingMetadata.groundingChunks[].web.uri` server-side. The JSON body's "citation_url" field, if the model emits one, is to be discarded. - TTS reads "HMRC" as "Hmm-rick" instead of "H-M-R-C" — wrap acronyms in the pre-process step with the spoken-out form ("H M R C") before sending to TTS, since Gemini 2.5 TTS does not support SSML `` or letter-by-letter spelling tags. - Agent merges Cantonese and Mandarin — uses "yue" voices to deliver Mandarin or vice versa. The TTS voice is selected from a hardcoded language → voice table keyed on the user's `first_language_bcp47`, including `zh-CN`, `zh-TW`, `yue-HK` as distinct entries. ### Negative constraints (hard rules) - Do NOT tell the user what to enter in any box. The app provides the literal label, a plain-English reading, a first-language gloss, and a worked example for a fictional person. The actual answer belongs to the user. The assistant's response to "what should I put here?" is always "I can't tell you what to put — here's the box label, the reading, an example for a fictional filer in your situation, and the official URL." - Do NOT cite anything other than the country's own tax authority. Allowed: gov.uk/hmrc, irs.gov, revenue.ie, ato.gov.au, ird.govt.nz, canada.ca/cra, sat.gob.mx, bir.gov.ph, agenciatributaria.es, impots.gouv.fr, bzst.de, skatteverket.se. Disallowed: TurboTax, H&R Block, AccountingWeb, Reddit, any non-authoritative blog. Server-side allowlist filter. - Do NOT generalise across tax systems. UK Self-Assessment Box 17 is not the same as US 1040 Line 17. The `TaxAuthority` value is mandatory in every schema. - Do NOT translate proper nouns: form codes ("SA100", "1040"), authority names ("HMRC", "IRS", "Revenue Commissioners"), section names ("Schedule C"). The first-language gloss explains them in context. - Do NOT invent thresholds, percentages, or figures. If the model does not have a grounded citation for "the Personal Allowance is £12,570 for 2025-26", set the field to null and flag for accountant. Especially: tax brackets, tax-free thresholds, mileage rates, depreciation rates, capital-gains exemption amounts. - Do NOT extrapolate to advice. The app translates the question, not the answer. Do not say "you should take the Marriage Allowance — it will save you about £252". Say: "The Marriage Allowance is a transfer of personal allowance to a spouse who earns less than the threshold. The threshold for 2025-26 is [cited]. The maximum transfer this year is [cited]. Whether to elect is the filer's decision." - Do NOT submit anything anywhere. The app is read-only with respect to the actual tax-filing system. No integrations with Government Gateway, IRS Free File, ROS, ATO myTax, or any equivalent. This boundary is part of the privacy story. - Do NOT auto-share or auto-export. Drafted answers stay in the user's account. CPA bundles are produced only on explicit user action. - Do NOT log the user's drafted answers, income figures, or personal details server-side beyond what is needed for the active Gemini call. Drafted answers live in Firestore under the user's auth; Cloud Run logs strip request bodies; analytics is opt-in and does not capture form-level content. - Do NOT silently fall back to English when the user's language is one the model handles less well — surface "this explanation is also available in English if the [language] version feels stiff" as a toggle, never as a silent substitution. - Do NOT use period-of-the-year urgency copy ("file before midnight!"). The app is a decoder, not a deadline tracker. Deadlines are surfaced once, with a citation, in the header; the rest is calm. ### 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: Profile → ordered list of forms for THIS filer Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You are mapping a person's plain-English life situation to the ordered list of tax forms they need to file, in one specific country's tax system, for one specific tax year. Inputs you receive: - TaxAuthority enum value (HMRC_UK, IRS_US, REVENUE_IE, ATO_AU, IRD_NZ, CRA_CA, SAT_MX, BIR_PH, AEAT_ES, DGFIP_FR, BZSt_DE, SKV_SE). - tax_year (e.g. "2025-26" for UK, "2025" for US). - The user's profiler answers as a JSON object with fields like has_employment_income, has_self_employment_income, has_rental_income, partner_earns_under_threshold, made_pension_contributions, worked_from_home, has_savings_income, has_capital_gains, has_foreign_income, resident_full_year, etc. Your job: produce one ProfileToForms object matching the schema. Hard rules: - forms_in_order is the order in which the filer should approach the forms, not necessarily the order printed in the form numbering. The main return goes first; supplementary pages come after, in the order they will be answered. - For each form: why_this_form_is_for_you is a single short sentence in plain English that names the user's specific situation. Example for a UK self-employed illustrator: "You earned more than £1,000 from your own work this year, so HMRC needs the self-employment short pages." - For each form: what_this_form_is_asking_for is a single short sentence describing the form's purpose, not its contents. - forms_explicitly_not_needed: name every supplementary form the filer might worry about and explicitly say why it does not apply (e.g. "SA105 — not needed, you have no rental income"). - followups_to_ask_user: if any profiler answer was ambiguous, list it here as a short plain-English question. Example: "Are you registered for VAT? (Affects whether you may also need to file a VAT return.)" - Do NOT recommend amounts, deductions, or elections. - Do NOT cite unofficial sources. - Use country-specific terminology. UK uses "Self-Assessment", not "tax return". US uses "Form 1040", not "annual return". Ireland uses "Form 11" or "Form 12" depending on PAYE/self-employed status. Output the ProfileToForms object as JSON matching the provided schema. No commentary. ``` --- ### Call: Decode form box-by-box, grounded in published guidance Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: `google_search` grounding (no `responseSchema`) ``` You are decoding one page of one tax form for a first-time filer. The filer has limited fluency in the form's language; your output will be rendered in plain English plus the filer's first language, beside a worked example for a fictional version of the filer's situation. Inputs you receive in `contents`: - TaxAuthority value, e.g. HMRC_UK. - form_code, e.g. "SA103S". - tax_year, e.g. "2025-26". - page_number_in_form, e.g. 2. - filer_summary_one_line, e.g. "self-employed illustrator, renting, partner under £12,570". - first_language_bcp47, e.g. "tl-PH". Use `google_search` grounding to find the tax authority's own published notes for this exact form, this exact page, this exact tax year. Read the boxes on that page. For every numbered box on the page produce one BoxGloss entry. Hard rules: - box_label_verbatim is the exact wording from the form, no rewording, no abbreviation, no smoothing of casing or punctuation. If you cannot find the verbatim wording from a grounded source, set needs_accountant_flag = true and leave box_label_verbatim blank rather than guessing. - literal_reading_plain_english is one short sentence in the simplest English a non-native reader can follow. No tax jargon unless the jargon IS the box (in which case include it in quotes and define it in the same sentence). - first_language_gloss is the same explanation in the filer's first language. Use the first_language_bcp47 locale and dialect. For Cantonese for elderly speakers, use traditional script and avoid modern internet slang. For Brazilian Portuguese, use the immigrant-Brazilian register, not Lusitanian academic. - worked_example_label names the fictional filer's situation in one short phrase, e.g. "Working example for a sole-trader illustrator with £18,400 turnover and £6,200 allowable expenses". - worked_example_value is the value the fictional filer would enter, expressed exactly as the form expects (£ to the nearest pound, US dollars to the nearest dollar, peso amounts with the centavos as the form expects, etc.). - worked_example_explanation is one or two sentences explaining why the fictional filer's value is what it is. - needs_accountant_flag = true whenever: - the box involves a treaty-based election; - the box involves a multi-year carryforward (capital losses, unused personal allowances, foreign tax credit carryovers); - the box involves residency / domicile / Substantial Presence determinations; - the box involves trust or estate income; - the box involves cross-border self-employment; - the box involves a regime change (cash → accruals, sole trader → company, employee → contractor mid-year); - the model's grounded citations do not include the verbatim box label. - When needs_accountant_flag = true, populate needs_accountant_reason_one_line and draft_message_for_accountant with the message the user can copy and paste into an email. - DO NOT tell the filer what to enter in their box. The worked example is for a fictional person. The filer's actual answer is not part of your output. - DO NOT cite unofficial sources. Only the country's tax authority (gov.uk, irs.gov, revenue.ie, ato.gov.au, ird.govt.nz, canada.ca, sat.gob.mx, bir.gov.ph, agenciatributaria.es, impots.gouv.fr, bzst.de, skatteverket.se) is allowed. - DO NOT invent thresholds, rates, or figures. Where a threshold appears in a box label, cite it from the authority's notes for that tax year. Output your response as a BoxDecoded JSON object 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 groundingMetadata.groundingChunks[].web.uri — do NOT include URLs in the JSON body; you will hallucinate them. No commentary outside the JSON. ``` --- ### Call: Translate one box gloss to the user's first language Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You translate one box's plain-English gloss into the filer's first language, with country-specific tax terminology preserved. Inputs: - The plain-English gloss for the box (one or two sentences). - The literal box label (verbatim, English). - The TaxAuthority and tax_year. - The target language as BCP-47, e.g. tl-PH, yue-Hant, es-MX, pt-BR, fa-IR, ar-LY. Hard rules: - Preserve form codes verbatim: SA100, 1040, BIR 1701A, Modelo 100. Do not translate them. - Preserve authority acronyms: HMRC, IRS, ATO, SAT, BIR, BZSt. On first occurrence add a parenthetical expansion in the target language (e.g. "HMRC (kawanihan ng buwis ng Reino Unido)"); thereafter just HMRC. - Preserve threshold figures in the original currency. £12,570 stays £12,570; do not convert to the target country's currency. - Use the dialect/register appropriate to the BCP-47 code: - yue-Hant for older Cantonese speakers — traditional script, no internet slang. - zh-CN for mainland Mandarin, simplified script. - zh-TW for Taiwanese Mandarin, traditional script. - pt-BR for Brazilian Portuguese, immigrant register; pt-PT for European Portuguese. - es-MX for Mexican Spanish; es-ES for peninsular Spanish. - ar-MSA for Modern Standard Arabic; ar-LY/ar-EG for the spoken Levantine/Egyptian registers if specified. - fa-IR for Persian in Nastaliq; ps-AF for Pashto if specified. - ta-IN for Tamil, hi-IN for Hindi in Devanagari, ur-PK for Urdu in Nastaliq, bn-IN for Bengali, pa-IN for Punjabi Gurmukhi, pa-PK for Shahmukhi, am-ET for Amharic in Ge'ez, sw-KE for Swahili, km-KH for Khmer, vi-VN for Vietnamese, tl-PH for Tagalog. - Do NOT introduce a tax concept from the target country. A Tagalog gloss of a UK Box 17 must not call it a BIR concept. Box 17 is whatever HMRC says Box 17 is; the Tagalog translation explains the HMRC concept in Tagalog, not a Tagalog tax concept. - Preserve uncertainty and hedges. "Usually" stays "usually". Do not over-confident-translate. Output: a single string in the target language. No commentary. ``` --- ### Call: Generate a worked example for a fictional version of the filer's situation Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You produce one worked example for one box of one tax form. The example is for a fictional filer whose situation is plausibly similar to the actual filer's, but is NOT the actual filer's. Inputs: - The box's literal label (verbatim). - The plain-English gloss. - The TaxAuthority and tax_year. - A one-line summary of the actual filer's situation (e.g. "self-employed illustrator, renting, partner under £12,570"). Hard rules: - The fictional person is named and given a one-line situation: "Anya, a sole-trader illustrator in Leeds, £18,400 turnover and £6,200 allowable expenses". The name is INVENTED and culturally plausible for the country. - The fictional value goes in the box in the exact format the form expects (whole pounds, whole dollars, pesos with centavos). - The explanation is one or two sentences and explains WHY the fictional value is what it is, referring back to the gloss. - The fictional figures are realistic and self-consistent across boxes — if the same fictional filer appears on Box 14 (Turnover) and Box 19 (Total expenses), the same turnover and expenses numbers carry through. - Do NOT tell the filer what to enter. The example is a worked case for a fictional person; the boundary is visible. - Do NOT exceed two sentences in the explanation. Output: a JSON object with worked_example_label, worked_example_value, worked_example_explanation. No commentary. ``` --- ### Call: Classify whether a box needs "talk to an accountant" Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You classify whether one box of one tax form should be flagged for "talk to an accountant" — i.e. the model's grounded reading is insufficient and a qualified accountant should answer. Inputs: - The box's literal label (verbatim). - The plain-English gloss. - The grounded citation URLs the box-decoder retrieved. - The TaxAuthority and tax_year. - The filer's one-line summary. Flag = true whenever: - The box involves a treaty-based election (double-taxation treaties, FATCA, tax-residency tie-breakers). - The box involves a multi-year carryforward where prior-year records the model cannot see are required. - The box involves residency/domicile determinations (Substantial Presence Test, UK statutory residence test, Italian fiscally resident determination). - The box involves trust or estate income. - The box involves cross-border self-employment, foreign earned income exclusion, foreign tax credits. - The box involves a regime change mid-year (sole trader → company, employee → contractor, accruals → cash). - The grounded citations do not include verbatim text matching the box label. - The box involves a discretionary election with significant long-term consequence (e.g. UK Marriage Allowance can interact with future personal-allowance use). - The box is a "do not know" — the model's reading does not resolve the question. Output: an AccountantFlag JSON object if flag = true, otherwise {"form_code": ..., "box_number_verbatim": ..., "needs_accountant": false}. When flagged, populate topic_short with a short topic phrase (max 6 words), why_the_model_cannot_decide in one sentence, draft_message_for_accountant as a short email the user can copy verbatim, and estimated_accountant_minutes_to_answer (a rough guess: 15, 30, 60, or null). No commentary outside the JSON. ``` --- ### Call: Glossary lookup Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: `google_search` grounding (no `responseSchema`) ``` You define one tax term as it is used by ONE specific tax authority in ONE specific tax year. Inputs: - The English term (e.g. "allowable expense", "QBI deduction", "personal allowance", "tax-free threshold"). - The TaxAuthority. - The tax_year. - The user's first_language_bcp47. Use `google_search` grounding against the authority's own published notes. Allowed domains only: gov.uk, irs.gov, revenue.ie, ato.gov.au, ird.govt.nz, canada.ca, sat.gob.mx, bir.gov.ph, agenciatributaria.es, impots.gouv.fr, bzst.de, skatteverket.se. Output a GlossaryEntry JSON object in the text body. Hard rules: - definition_plain_english is one or two short sentences. - definition_in_user_language is the same definition in the filer's first language, respecting the BCP-47 dialect. - example_one_line is a single illustrative example in plain English. "An allowable expense is a cost you incurred wholly and exclusively for the business — for example, the cost of printer ink you used only for invoices." - If the term is country-specific and the user is reading it in the wrong country's context, surface that with a brief flag in the definition. Citations: read from groundingMetadata.groundingChunks[].web.uri server-side. Do NOT include URLs in the JSON body. No commentary outside the JSON. ``` --- ### Call: TTS narration of one box's gloss in source language Model: `gemini-3.1-flash-tts-preview` · n/a · n/a ``` Voice: calm, unhurried, like explaining a form at the kitchen table to someone who has not done this before. Pick the Gemini 2.5 Flash TTS voice whose `languageCode` matches the gloss language. Prefer a voice that sounds adult and warm rather than news-reader. Pronunciation follows the voice's native locale. Pre-process the text before sending to TTS: - Read from the first_language_gloss field. - Replace authority acronyms with spelled-out spoken forms: "HMRC" → "H M R C", "IRS" → "I R S", "ATO" → "A T O", "SAT" → "S A T", "BIR" → "B I R", "BZSt" → "B Z S t", "CRA" → "C R A", "DGFIP" → "D G F I P". - At each line break in the gloss, insert a single ellipsis ("…") so the TTS produces a natural pause. At paragraph breaks, insert a blank line plus an em-dash ("—"). Gemini 2.5 TTS does not support SSML ; these textual cues are how you signal pace. - Skip the verbatim box label and the worked-example value; both are visual. The narration is the plain-language gloss only. - Mid-call voice switching is not supported. If the gloss contains a quoted English phrase (e.g. a form code in the middle of a Tagalog sentence), keep the whole narration in the gloss language; the form code will be pronounced in the gloss-language accent. That is the right outcome. - Target rate: ~110 words per minute — explanation pace, not podcast pace. Style direction: prepend ONE short directive sentence to the text input, exactly like: "Read at a calm, unhurried pace, as if explaining a form to someone at the kitchen table for the first time. …". There is no separate `style` API field on Gemini 2.5 TTS; the directive sentence inside the input is how style is conveyed. There is no SSML ; pronunciation comes from the chosen voice's native locale. ``` --- ### Call: Read a user-uploaded form photo → identify form + page + boxes Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You receive one or more photographs of a tax form, taken by the filer. Your job: identify which form (form_code), which page, which TaxAuthority and which tax_year, and the list of boxes visible on this page. Submit multipage uploads as a SINGLE call with multiple images, in order. Upload each image via the Gemini Files API (`files/*` resource name URI) 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 3 / page 2 of 3" header at the start of each image's accompanying text. Hard rules: - Identify the form by its printed code (e.g. "SA100", "1040", "Schedule C", "BIR 1701A"). Read the printed year in the header. - If the photographed form is for a different tax_year than the user's session, return the photographed year verbatim in tax_year and set needs_clarification = true with a short note. - For each visible numbered box, return box_number_verbatim and box_label_verbatim, exactly as printed. - Do NOT translate or rewrite the printed text. Verbatim only. - Do NOT decode the boxes — that is a separate call. Your output is the identification of the form and the box-label inventory. Output a JSON object with form_code, page_number_in_form, authority, tax_year, boxes_visible[] (each with box_number_verbatim and box_label_verbatim), and needs_clarification (boolean) with an optional one-line reason. No commentary outside the JSON. ``` ## 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 Filipino freelance illustrator in London.** First UK Self-Assessment year. She left payroll in March, started invoicing in May, and never looked at a tax return before. Her first language is Tagalog. She picks UK Self-Assessment → sole trader → "I work for myself, I rent, my partner doesn't earn this year, I worked from home some days". The app produces SA100 + SA103S in the right order, Box 1-21 of SA103S decoded in Tagalog with worked examples for a fictional London-based illustrator at £18,400 turnover. The "use of home as office" box (Box 19) gets a small accountant flag — the simplified flat-rate method and the apportioned-bills method both apply, and the choice is the filer's. - **The Mexican-American sole trader in Texas.** First year self-employed. Schedule C and Schedule SE for the first time. First language Spanish (es-MX register). His worked-example fictional filer is "Carlos, food-truck operator in Austin, $42,000 gross receipts, $11,800 in deductible expenses". Schedule SE's self-employment tax is decoded in Spanish at the level a first-time filer can read; the QBI deduction (Form 8995) gets an accountant flag because his wife's W-2 income changes the threshold interaction and he is over the simplified-method ceiling. - **The Polish-Irish landlord in Dublin.** Rents one flat in Drumcondra. First time filing a Form 11. First language Polish. The app produces Form 11 with the rental income section decoded in Polish, including a careful note that "expenses wholly and exclusively incurred in the production of rental income" has a specific Revenue meaning. The Section 23 / Section 50 reliefs are flagged for accountant because they involve historical capital allowances the model cannot see in the user's situation. - **The Vietnamese postdoc in Boston.** First year on a 1040 instead of a 1040-NR — the Substantial Presence Test crossed in September. First language Vietnamese (vi-VN). The app produces 1040 + Schedule 1, but the entire residency-determination section is wrapped in an accountant flag with a draft message ("Hi — I crossed the Substantial Presence Test in September 2025 — do I file 1040 as a dual-status alien or as a full-year resident under the First-Year Choice in section 7701(b)(4)?"). The plain-English explanation in Vietnamese lays out the question without resolving it. - **The Cantonese-only grandmother in Vancouver.** Her granddaughter Sarah sits next to her and runs the app. Tax-clinic mode is on: larger type, one form per screen, the Cantonese narration playing per box. Her grandmother needs only a simple T1 return for OAS + a small CPP and a TFSA. Every box is read out loud in yue-HK (traditional script). No accountant flags arise; the print-to-PDF goes home with the family. - **The Bangladeshi PhD candidate in London.** First UK self-assessment because she earns supervisory pay above the personal-allowance threshold. First language Bengali. Most boxes resolve cleanly; the "do you have any foreign income" trigger flags an accountant referral because she has interest on a savings account in Dhaka that may need to be declared under remittance basis depending on her residency status. - **The Tamil-first international student in Sydney.** First Australian individual return. First language Tamil. The app decodes the ATO myTax workflow's box equivalents in Tamil; the visa-condition-related work limitations are flagged for accountant because they touch immigration law as much as tax law. - **The Khmer-first construction worker in Sacramento.** First 1040 ever. First language Khmer. The app produces 1040 + Schedule 1 in Khmer; the worked example shows a fictional construction worker with $58,400 in W-2 wages and $1,200 in 1099-NEC side income. The line-by-line gloss is read out loud in Khmer for him to follow at his own pace. - **The Spanish-Argentine consultant in Madrid.** Modelo 100 first time. First language Spanish, peninsular register. The worked example is for "Lucía, freelance consultant in Madrid, €36,400 turnover, €4,800 deductible". The complex "rendimientos del trabajo en especie" boxes are flagged for accountant because they touch the IRPF treatment of company-paid health insurance. - **The Polish carpenter in Berlin.** First German ESt 1A. First language Polish. The "Anlage S" for self-employed income is decoded into Polish with the German verbatim labels preserved. The "Gewerbesteuer" question is flagged for accountant because the carpenter's threshold is unclear without the prior year's records. - **The Korean-American filing in two countries.** US 1040 plus the Korean year-end settlement. Two separate sessions in the app; the Korean session's form is the Korean year-end-settlement form decoded in English (the user's working language); the US session's 1040 decoded in Korean (her elderly mother's language) so she can show her mother what she's filing. The Form 2555 foreign-earned-income-exclusion line on the 1040 is flagged for accountant — the limit-versus-deduction choice is consequential. - **The Tagalog-speaking caregiver returning home.** She was working in Hong Kong, has come back to Manila, files a BIR Form 1701A for the first time. First language Tagalog (region-flavoured Cebuano in her case). The app decodes the BIR form's optional-standard-deduction boxes in Tagalog with worked examples for "Lualhati, returning caregiver, ₱180,000 gross professional income, electing the 40% Optional Standard Deduction". - **The Tax-Clinic volunteer at TaxAid.** A volunteer in Manchester, sitting next to a client who walked in worried. Volunteer turns on tax-clinic mode; the client reads the plain-English gloss in his first language while the volunteer types the answers into HMRC's actual Government Gateway in a separate browser tab. The app provides translation, not submission. ## 6. Page structure Build the following screens / sections in this order. Adjust copy to fit the voice, but keep the structural intent. 1. **Welcome / sign-in.** A photographed-looking image of a kitchen table at evening with a tax form, a pen, a half-drunk cup of tea, and a phone propped against a salt shaker. One paragraph: "Tax-Return Decoder reads the form for you — every box, in your language, grounded in your tax authority's own guidance. The answer stays yours." Single Google sign-in button; Apple sign-in next to it. Below: "Try with the sample — UK Self-Assessment for a first-year freelancer" → loads the demo session from section 8a. 2. **Country + tax year picker.** A grid of country cards (UK Self-Assessment 2025-26, US Federal 1040 2025, Republic of Ireland Form 11 2025, Australia individual 2024-25, Canada T1 General 2025, New Zealand IR3 2025-26, Mexico SAT Declaración Anual 2025, Philippines BIR 1701A 2025, Spain Modelo 100 2025, France Formulaire 2042 2025, Germany ESt 1A 2025, Sweden Inkomstdeklaration 1 2025). A small note: "Don't see your country yet? We're adding tax systems as we ground each one. Tap here to vote." Below the grid: a language selector independent of country — the user's first language for explanations. 3. **Profiler.** Five to ten plain-English questions, one per screen, no jargon. Tappable answers with optional voice-input. Examples: "Do you work for yourself?" / "Do you have a regular job (PAYE / W-2 / employed contract)?" / "Did you rent your home most of the year?" / "Did you own a home you lived in?" / "Did your partner earn under [country threshold]?" / "Did you make pension contributions this year?" / "Did you have any income from outside this country?". Each question has a "skip — I'll come back" option. 4. **Your forms.** The output of the profile-to-forms call. An ordered list of forms with a "why this is for you" line and a "what this form is asking for" line per form. Below: "Forms you don't need this year" as a collapsible list, each with a one-line reason. At the bottom: "Questions I'd like to ask you first" — the model's surfaced ambiguities, if any. 5. **Form-by-form decoder.** For each form, every page rendered as a tall scrollable view. For every box on the page: a card with the verbatim box label at the top, the plain-English gloss in the middle, the first-language gloss below in the user's language with a small "listen" button, the worked example for a fictional filer on the right (or below on mobile), the citation URL as a small footer link ("Source: gov.uk"). User's own draft note field on the right of each box, never transmitted. 6. **Accountant-flag panel.** A sticky right-rail (on desktop; bottom sheet on mobile) listing every box on the current form that the model flagged for "talk to an accountant", with the topic, the reason, and a "copy email" button. The user can dismiss flags they don't want to consult an accountant on, but the flag is logged. 7. **Side-by-side guidance.** A tab on every box card: "Open the official guidance" pops a panel rendering the cited URL (rendered as a preview iframe with a "open in new tab" link for the user to verify against the source). The panel never auto-opens; it's a confidence check the user can do whenever they want. 8. **Glossary.** A dictionary view; the user searches any term, the result is the country-and-year-scoped definition in plain English and in their first language. Recently-viewed terms appear at the top. 9. **Resume / sessions.** A list of the user's sessions (one per country-and-year). Each shows the country, year, language, percentage of boxes the user has draft-noted, and a "resume" button. A "start another session" button below. 10. **CPA bundle / Print summary.** When the user clicks "Prepare a bundle for an accountant", the app produces a printable PDF: each form's name, every box's official label, the model's plain-English gloss, and the user's draft note for that box (where written). Cover page: filer's profile summary one-liner, country, tax year. Final page: every accountant-flag's topic + draft message. 11. **Settings.** Language, country, tax year, "delete this session forever", "delete my account forever". A row showing "0 photos stored" if the user has only used the text path; if there are photos, a row to delete them all. 12. **Footer.** "Made for the form you can read in your first language." Privacy: "We translate the form. The answer stays yours. We never file anything for you." 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 Tax-Return Decoder." - Subhead: "Read the form in any tax system, in any language — every box explained, every word grounded in the source, the answer left to you." - One paragraph (≤ 60 words) explaining the boundary: this is a decoder, not an adviser. It translates the form's words into a language and reading level you can follow; the answer to put in each box belongs to you. Where the model can't read clearly, it tells you, and gives you a sentence to email a real accountant. - Visual: a small annotated illustration of a tax-form box, with arrows labelling "the verbatim label", "the plain-English reading", "your first language", "the official URL", "the worked example for a fictional person" — not a stock illustration of a calculator. **Slide 2 — Try it now.** - One short prompt: "Try with the sample session — UK Self-Assessment, sole trader, first year". - A pre-loaded session from section 8a with the profile already answered and the first three pages of SA100 + SA103S decoded. - 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the grounded citation link beside Box 14, the Tagalog gloss button under Box 19's "use of home as office", the accountant-flag callout next to Box 41's "adjustments to profit"). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Add a country in `/server/tax-systems/` — each is a small JSON manifest naming the forms and the grounding domains." - "Adjust the language list in `/server/languages/` to add or refine BCP-47 dialects." - "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 (long context, 1M tokens)** — reads the tax authority's own published notes for the chosen form (often 80-260k tokens of HMRC notes, IRS instructions, Revenue guides) while keeping the filer's profile and the previously-decoded boxes in the same conversation. - **Gemini 3.5 Flash + `google_search` grounding** — every box explanation cites the authority's own page. We display the URL beside the gloss. Citations are read from `groundingMetadata.groundingChunks[].web.uri` server-side; we do not let the model invent URLs. - **Gemini 3.5 Flash (multilingual)** — produces the first-language gloss in fifty-plus languages with dialect awareness. Cantonese for older speakers uses traditional script. Brazilian Portuguese uses the immigrant register. Tamil and Tagalog speakers see their own variants. - **Gemini 3.5 Flash (structured output)** — the profile-to-forms mapping, the accountant-flag classifier, and the form-photo identifier all return typed JSON the app renders without further parsing. - **Gemini 3.5 Flash (multimodal image input)** — optional. If the user uploads a photo of the form, the model identifies form code, page, year, and box labels verbatim before the decoder pass. - **Gemini TTS (2.5 Flash Preview)** — reads each box's first-language gloss aloud at a calm, kitchen-table pace. - **Firebase Auth** — Google and Apple sign-in, magic-link email for cross-device resume. - **Firestore** — stores your sessions, profile answers, and your draft notes per box. - **Firebase Storage** — only if you use the optional photo path; off by default. Photos are private and pre-signed. - **Cost note** — see the detailed breakdown in 6d. A full first-pass of a UK Self-Assessment with SA100 + SA103S costs about $0.45 in Gemini API spend. Glossary lookups are about $0.001 each. TTS narration of every box is about $0.04 per full form. - **Privacy note** — your drafted answers are private to your account. 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 app NEVER submits anything to your tax authority. Filing is your act, made elsewhere. - **Boundary note** — this app translates the form. It does not give tax advice. Where the model cannot ground a box, we flag "talk to an accountant" with a draft email you can copy. **Backend services this app depends on:** - Auth: see section 4b - Database: see section 4b - Storage: see section 4b (optional, only for the photo path) - Email: see section 4b (magic-link sign-in) - Payments: see section 4b (not used in v1) - External APIs: see section 4b **Environment variables you'll need to configure:** - `GEMINI_API_KEY` — your Google AI Studio API key - `FIREBASE_PROJECT_ID` — your Firebase project id - `FIREBASE_SERVICE_ACCOUNT` — service-account JSON (server-side only) - `SENDGRID_API_KEY` — optional, only if you use SendGrid for magic-link email instead of the Firebase Auth default **Cost + privacy notes:** - The grounded box-decoder call is the largest single cost item (Gemini 3.5 Flash, medium thinking, with `google_search` grounding) — ~$0.018 per page of an HMRC form (Box 1 through Box 20 of SA103S ≈ one call). - One short paragraph on privacy: where the data lives (your Firebase project), how to delete it (Settings → "Delete this session forever" — gone in 60 seconds), what is never sent for training, what is never sent to the tax authority. **Documentation links:** - AI Studio Build docs - Gemini API long-context, grounded-search, multilingual, structured-output, TTS docs - Firebase Auth, Firestore, Firebase Storage docs - A short note on the citation-allowlist domains (gov.uk, irs.gov, revenue.ie, ato.gov.au, ird.govt.nz, canada.ca, sat.gob.mx, bir.gov.ph, agenciatributaria.es, impots.gouv.fr, bzst.de, skatteverket.se) **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) - **Profile → ordered list of forms (Gemini 3.5 Flash, low thinking)** — ~500 input tokens (profiler answers + system instruction) + ~700 output tokens. ~$0.004/call. Runs once per session, plus rerun whenever the user edits a profile answer. - **Decode form page-by-page (Gemini 3.5 Flash, medium thinking, grounded search)** — typical input ~120k tokens (one form's notes from the authority's published guide) + ~3k output tokens. ~$0.165 per page when the grounding hits the full notes; ~$0.018 per page on subsequent pages of the same form (long-context caching where available). A UK SA103S (4 pages, ~25 boxes total) costs ~$0.21 first pass. The 1040 + Schedule C + SE (~9 pages, ~60 boxes) costs ~$0.60 first pass. - **Translate one box gloss to user's first language (Gemini 3.5 Flash, low thinking)** — ~$0.0008 per box. A 25-box SA103S translated to Tagalog: ~$0.02. Folded into the page-decode call where possible to save on round-trips. - **Generate worked example (Gemini 3.5 Flash, low thinking)** — ~$0.0006 per box. Folded into the page-decode call. - **Accountant-flag classifier (Gemini 3.5 Flash, medium thinking)** — ~$0.005 per box flagged; folded into the page-decode call's "boxes" array, so the marginal cost is the medium-thinking surcharge on the few boxes that are ambiguous. - **Glossary lookup (Gemini 3.5 Flash, low thinking, grounded search)** — ~$0.001 per term. A heavy user might run 20 glossary lookups in a session: ~$0.02 total. - **TTS narration (Gemini 2.5 Flash TTS)** — billed per output token (~$10/M output tokens), effectively ~$0.000003/character. A 90-character gloss read aloud: ~$0.0003. A 25-box form fully narrated: ~$0.008. Cached per box; charged once. - **Form-photo identifier (Gemini 3.5 Flash, medium thinking, multimodal)** — typical 4-image upload + identification call ~$0.025. Optional; most users do not use the photo path. - **Expected per-session cost on first ingest of one form:** a UK Self-Assessment (SA100 + SA103S, 25 boxes) full first pass with Tagalog gloss and full TTS ≈ ~$0.45. - **Expected per-session cost on first ingest of one US 1040 + Schedule C + SE:** ~$0.60. - **Resume cost (returning to a half-finished session):** ~$0.01 in re-grounding the changed page. Long-context cache hits dominate the cost story. - **Image storage (optional photo path):** Firebase Storage standard tier, ~$0.026/GB/month. A 5-page form photographed at iPhone resolution ≈ ~25 MB; per-user storage ≈ ~$0.001/month. ## 7. Design language - **Mood:** A kitchen table at 9 pm, a tax form spread out, a half-cup of tea, a phone propped against a salt shaker. Not a SaaS app. Not a tax-software product. The opposite of the IRS website. Calm, lit-from-one-side, paper-coloured, with the form's verbatim labels rendered in their original typewriter / form-printer font alongside the explanation in a warm reading serif. The form is the artefact; the app is the translator sitting next to you. - **Typography:** Reading serif for the plain-English gloss and first-language gloss (Source Serif Pro, Charter). Mono / typewriter accent for verbatim box labels (IBM Plex Mono, Courier Prime) — so the original form's wording reads as quoted, not paraphrased. Clean grotesque for app chrome and primary action buttons (Inter or Geist). First-language gloss in the script of the language: Devanagari for Hindi, Nastaliq for Urdu, Ge'ez for Amharic, Khmer for Khmer, Hangul for Korean, Hanzi for Chinese (traditional or simplified per locale). - **Palette:** Bone-paper background `#F4EFE6` for the box-card body, deep ink `#1B1714` for body text, muted slate `#3D4A5A` for the verbatim form label (the quoted wording reads as ink-on-form), sepia accent `#7B4F2A` for the small "source: gov.uk" citation footer, soft amber `#C28A1F` for the "talk to an accountant" callout block — never alarming red, this is not a warning, it's a referral. A muted teal `#2F5D60` for the user's own draft-note field so it cannot be confused with the model's output. - **Imagery:** The verbatim box label is the hero of every card. The form's wording is rendered in the typewriter face above everything else, in slate-grey, with a thin underline that hints at "this is a quote from the form, not my words". The plain-English gloss sits beneath in the warm reading serif. The first-language gloss appears below in the script of the language, rendered at the same reading size as the English gloss — never smaller, never as a footnote. - **Hand-feel touches:** The "Source: gov.uk" footer link has a tiny external-link arrow that animates a single pixel on hover, like lifting the corner of a sheet to peek at the paper underneath. The accountant-flag callout has a torn-edge top border, like a sticky note left on top of the form, never an alert icon. - **Spacing:** consistent 4-px base. Each box card has at least 32 px of whitespace around it. The page does not feel crowded; the form is supposed to feel readable. - **Radius:** consistent token set (6 / 12 / 20 px). Box cards use 12; the welcome card uses 20; the small "listen" and "open guidance" pills use 6. - **Shadows:** subtle, layered, sepia-tinted. Avoid heavy drop-shadows. - **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. No bouncing splash animations. No theatrical hero animations. The "open the official guidance" panel slides in from the right at 300 ms; reduced-motion = instant. - **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners where possible. The decoder's "thinking" state shows the names of the actual sub-steps ("reading HMRC's notes for SA103S page 2…", "translating to Tagalog…", "drafting an example for a fictional illustrator…") rather than a generic spinner. ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible filer names, country-specific situations, threshold figures (but never claim a current tax-year threshold is a real figure unless it is grounded in this session). Use the seed content in 8a as a starting point. When inventing fictional filers, give them a first name + a one-line situation + numerically plausible figures. Cultural plausibility: a UK SA103S fictional filer can be named "Anya" or "Sami" or "Lualhati" — match the worked example's cultural register to the country, but reach for diaspora first names (Filipino, Vietnamese, Bangladeshi, Polish, Mexican) where the worked example is meant to feel like the actual filer. - Tone: warm, direct, free of corporate language. This template is for a person sitting at a kitchen table on a Sunday evening, not a company. - Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. No 'AI-powered tax assistant'. - Body copy: short paragraphs (2-4 sentences). Use lists where appropriate. - Plain language. Avoid jargon — except where the jargon IS the form (in which case quote it verbatim and explain in plain English in the next sentence). - Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. The accountant-flag callout names the topic plainly without alarm. Citation URLs are visible without being shouted. ## 8a. Seed content (use these specific examples) Anchor every generated copy + sample data point in the concrete content below. Use these names, numbers, dates, and snippets verbatim where helpful, or generate close variants that sit in the same world. **Sample sessions (sidebar):** - "UK Self-Assessment 2025-26 — first-year freelance illustrator" (in progress, Tagalog gloss) — fictional filer Maya, sole-trader illustrator in London, £18,400 turnover, partner under £12,570 threshold, worked from home some days. - "US Federal 1040 2025 — first-year food-truck operator" (started, Spanish gloss, es-MX) — fictional filer Carlos, food-truck operator in Austin, $42,000 gross receipts, $11,800 in deductible expenses. - "Republic of Ireland Form 11 2025 — first-time landlord" (paused, Polish gloss) — fictional filer Tomasz, IT contractor in Dublin renting one flat in Drumcondra, €14,400 rental income, mortgage interest restricted under Section 24-equivalent. - "Philippines BIR 1701A 2025 — returning OFW caregiver" (paused, Tagalog gloss) — fictional filer Lualhati, returning caregiver in Manila, ₱180,000 gross professional income, electing the 40% Optional Standard Deduction. - "Australia individual return 2024-25 — international PhD student" (started, Tamil gloss, ta-IN) — fictional filer Aravind, PhD candidate in Melbourne, AUD 38,200 stipend + tutoring income. - "Canada T1 General 2025 — Cantonese-only OAS recipient via granddaughter" (in tax-clinic mode, yue-Hant gloss) — fictional filer Mrs Chan, OAS + CPP + a small TFSA in Vancouver. **Sample decoded box in detail view (this is what the demo should show):** - **Form:** UK SA103S (Self-Employment short pages), 2025-26 - **Page:** 1 - **Box number:** Box 9 - **Box label (verbatim):** "Your total income from self-employment" - **Literal reading (plain English):** "This is the total of everything you were paid this tax year for the work you did for yourself. It does NOT include income from a regular employer (that goes on SA100 employment pages) or rental income (that goes on SA105). It includes the full amount your clients paid you before you took any expenses out." - **First-language gloss (Tagalog):** "Ito ang kabuuang halaga ng lahat ng natanggap mo sa taong ito ng buwis para sa trabaho na ginawa mo bilang self-employed. Hindi kasama dito ang sweldo mula sa regular na employer (yan ay sa SA100 employment pages) o ang kita mula sa pag-renta ng bahay (yan ay sa SA105). Kasama dito ang buong halaga na binayad sa iyo ng mga kliyente mo bago mo bawasan ang mga gastusin." - **Worked example (label):** "Working example for Maya, a sole-trader illustrator in London" - **Worked example (value):** "£18,400" - **Worked example (explanation):** "Maya invoiced six clients across the year and was paid £18,400 in total. Box 9 is the gross figure before any expenses, so the £6,200 of allowable expenses she had do NOT come off here — those go in the expenses boxes further down." - **Accountant flag:** false - **Source URL:** "gov.uk/government/publications/self-employment-short-sa103s" (from groundingMetadata, not from JSON body) **Sample accountant-flagged box (the demo's second case):** - **Form:** UK SA103S, 2025-26 - **Page:** 2 - **Box number:** Box 41 - **Box label (verbatim):** "Adjustments to profit or loss for basis period purposes" - **Literal reading (plain English):** "If this is your first year as a sole trader and you started in the middle of a tax year, the way your taxable profit lines up with the tax year may need adjusting. From 2024-25 onwards HMRC moved most sole traders to a tax-year basis, but the transition rules can change the figure that goes on Box 41." - **First-language gloss (Tagalog):** "Kung ito ang unang taon mo bilang self-employed at nagsimula ka sa kalagitnaan ng tax year, maaaring kailanganing i-adjust kung paano nag-aayon ang taxable profit mo sa tax year. Mula 2024-25, inilipat ng HMRC ang karamihan sa mga sole trader sa tax-year basis, pero ang mga transition rules ay maaaring magbago sa figure na ilalagay sa Box 41." - **Accountant flag:** true - **Topic (short):** "Basis period reform transition" - **Reason (one line):** "The transition from accounting-period basis to tax-year basis can require a one-off adjustment whose calculation depends on the prior year's accounting period, which I cannot see." - **Draft message for accountant:** "Hi — this is my first SA103S. I started self-employment in May 2024 with an accounting period that didn't line up with the tax year. HMRC's basis-period reform applies in 2024-25 and 2025-26. Can you help me work out whether Box 41 needs an adjustment, and if so, what figure?" - **Estimated accountant minutes to answer:** 30 **Sample input artefacts (for the build to demonstrate):** - The UK SA100 + SA103S notes as the grounded long-context input (the model retrieves these via google_search, not from a local file — show the citation links in the demo). - A profile JSON from the profiler: `{ "country": "HMRC_UK", "tax_year": "2025-26", "first_language_bcp47": "tl-PH", "has_employment_income": false, "has_self_employment_income": true, "has_rental_income": false, "partner_earns_under_threshold": true, "worked_from_home": true, "has_savings_income": false, "has_foreign_income": false, "resident_full_year": true }`. - An iPhone photo of an HMRC SA103S page 2 (for the optional multimodal path). **Sample voice copy:** - Onboarding: "Read your tax form in your own language. We translate the question. The answer stays yours." - Profiler intro: "I'm going to ask you a few plain-English questions about your year. There are no right answers — I'm trying to figure out which forms you need." - Processing: "Reading HMRC's notes for SA103S page 1…" / "Translating to Tagalog…" / "Drafting a worked example for an illustrator…" / "Checking whether anything needs an accountant…" - Empty session: "This session is waiting for its first form. Pick a country and a tax year to start." - Accountant-flag inline: "This box involves something I can't read for you with confidence. Here's a sentence you can email an accountant — copy it into your phone." - Worked example: "Here's how a fictional filer in your situation might fill this in. Your own answer is yours to decide." - Source link: "Source: gov.uk · Self-Assessment notes SA103S 2025-26" - Tax-year mismatch: "You picked 2025-26. The official notes I can find are for 2024-25. Use the 2024-25 notes anyway, or wait until HMRC publishes 2025-26 (usually April)?" **Sample "what changed this year" briefing:** - For UK Self-Assessment 2025-26: "Personal Allowance unchanged at [cited]. The Class 2 NIC change introduced for 2024-25 continues to apply. Basis-period transition adjustments may still arise for sole traders whose accounting period did not align with the tax year before 2024-25." - For US 1040 2025: "Standard deduction figures updated for 2025 [cited]. The Self-Employed Health Insurance Deduction continues to interact with Marketplace subsidies — talk to an accountant if this applies." ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of a kitchen table at evening with a tax form, a pen, a half-drunk cup of tea, and a phone propped against a salt shaker. Generate via Nano Banana 2 with a prompt emphasising "wooden table, warm desk-lamp light, hand of a person in their thirties resting on the form's edge, late evening, gentle out-of-focus tea mug, no faces in frame, soft shadow under the phone". - **App icon / wordmark:** Set in the display serif with the verbatim-label mono accent on the word "Decoder". No icon — just type. Slightly worn paper texture behind it. - **Empty-state illustration:** A simple line drawing of a single tax-form page with a coffee ring on the upper-right corner. Hand-drawn aesthetic, not a flat icon. No calculator. No magnifying glass. - **Profile-screen illustrations:** Small per-question hand-drawn icons (a hand-drawn house for the rental questions, a hand-drawn coffee thermos for the self-employed questions). Restrained, not playful. - **Demo form pages:** Show actual screenshots of the HMRC SA103S page (which is public-domain government content) alongside the model's decoded output. For the demo, fall back to a stylised representation of the form layout rather than a literal scan, to avoid trademark/branding entanglements with the tax authorities. Label the demo form clearly as a stylised representation. - **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. The "talk to an accountant" callout uses a small hand-drawn sticky-note shape, not a warning triangle. ### 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"). The country picker explains why a country isn't yet supported when the user requests it. - 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: "Pick a country to start", "Tell me about your year", "Open the first form" — never a generic "Add your first item". - Smooth scroll for in-page anchors. - All AI-generated content streams in token-by-token where supported, with a clear honest sub-step indicator before content starts arriving ("Reading HMRC's notes for SA103S page 1…", not "Thinking…"). - If a grounded call returns no allowlisted citations, the box is automatically flagged for accountant rather than rendered with a guess. - If an AI call fails, show a calm, specific error ("I couldn't reach HMRC's notes just now — try again, or skip this box for now") and offer retry. - Each box card has a small "explain in even simpler language" button that re-runs the literal-reading call with a "even simpler / for a 14-year-old" instruction; useful for users whose first language is more colloquial than written. - "Listen" button on each box plays the first-language gloss via TTS; the TTS plays in the in-page audio element with playback rate 0.85x–1.2x adjustable. - The "open the official guidance" panel respects `prefers-reduced-motion` (slide-in becomes instant). - The accountant-flag callout's "copy email" button copies the draft message and shows a small inline confirmation ("copied — paste into your email app"), never a toast notification. ## 11. Tech & responsive requirements - **Deterministic arithmetic — server-side TypeScript only.** Any money-split / settlement / total / VAT / unit-conversion math runs in TypeScript on the server using integer-cents arithmetic. The Gemini call only extracts entities (line items, payer, currency code, raw quantities) into structured JSON. Never let the model do the sum — Gemini will return plausible-looking arithmetic that does not actually balance. - **TTS markdown-stripping preprocessor:** before sending any user-authored markdown to `gemini-3.1-flash-tts-preview`, strip non-spoken markdown: `#`/`##`/`###` headings (keep the title text), `**bold**` (keep the inner text), `[label](url)` (keep `label`, drop URL), `` ``` `` fenced code blocks (skip entirely), `>` block-quote markers (keep the text), and `|` table pipes (read row-by-row as sentences). Insert `…` between sentences for a short pause and a blank line plus `—` between paragraphs for a long pause. The model does not understand markdown; raw markdown will be read aloud as literal characters ("asterisk asterisk"). - **File downloads on Safari / Firefox:** when offering local-disk save of any export (PDF, CSV, MP3, ZIP, JSON, image), fall back to `` with a blob URL — the File System Access API (`showSaveFilePicker()`) is Chromium-only. Detect with `'showSaveFilePicker' in window`; otherwise use the anchor-download path. - **Stack:** React + TypeScript + Tailwind CSS. Functional components + hooks. Use Shadcn UI primitives where appropriate. - **Build runtime:** AI Studio Build — full-stack with Cloud Run server-side functions. All Gemini API calls happen server-side; API key lives in Secrets Manager, never in client bundle. - **Model selection:** explicitly pin `gemini-3.5-flash` for the form-decoder, translator, accountant-flag classifier, worked-example, profile-to-forms, and photo-identifier calls. Pin `gemini-3.5-flash` for glossary lookups. Pin `gemini-3.1-flash-tts-preview` for narration. Set `thinkingLevel` explicitly per call. Omit `thinkingConfig` on the TTS call. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed sessions on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it; magic-link email for cross-device resume. - **Storage:** Firebase Storage only for the optional photo path; off by default. Pre-signed URLs only. Enable only after the user opts into the photo path; surface "0 photos stored" otherwise. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The form-by-form decoder must be readable on the 375 px viewport; box cards stack vertically with the first-language gloss directly under the plain-English gloss. - Use `clamp()` for fluid typography. Prefer container queries over media queries for component-level responsiveness. - Use `dvh` / `svh` instead of `vh`. Respect safe-area insets on iOS. - Zero horizontal overflow at any width. Zero layout shift on load. - Persist user data in Firestore. Use real-time listeners on the session view. - Optimistic UI on draft-note writes; reconcile on response. Draft notes are debounced to once per 600 ms of inactivity. - Long-context calls are stateless from the user's perspective; server-side, cache the grounded notes pack per (form_code, tax_year) tuple with a 24-hour TTL to keep the cost story honest. - RTL languages (Arabic, Urdu, Farsi, Hebrew) flip the box-card layout: the verbatim English form label stays LTR (it's quoted), the first-language gloss renders RTL with proper `dir="rtl"` and `lang` attributes. ## 12. Accessibility (WCAG 2.2 AA) - Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`. Each box card is an `article` with the verbatim label as its `header`. - All interactive controls reachable by keyboard with a visible focus ring. - Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. The amber "talk to an accountant" callout and the slate verbatim-label colour are explicitly verified. - All images have meaningful `alt` text. The demo form page's stylised representation has `alt` describing the artefact ("stylised representation of HMRC SA103S page 1, with Box 9 highlighted"). - Form fields have associated `