# 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. --- # Whiteboard → Slides ## 1. Project **Whiteboard → Slides** turns a stack of end-of-meeting whiteboard photos into a presentable deck — Google Slides or Keynote — in the sixty seconds between the workshop wrapping and the next stand-up starting. The job is universal. A team finishes a two-hour strategy session, a design crit, a sprint retro, a customer interview synthesis, or a post-mortem. The walls and the flipchart are full of arrows, sticky notes, swim-lanes, two-by-two matrices, kanban columns, force-field diagrams, fishbones, decision trees, the cluster of red dots from the dot-vote at the end. Someone takes three or four phone photos, says "I'll turn this into slides" — and then doesn't, because turning a whiteboard photo into a slide takes an hour per board if you redraw it cleanly, and the team needs the deck for stand-up in fifteen minutes. This app does it in sixty seconds. You drop in the photos. **Gemini 3.5 Flash** parses each whiteboard's semantic structure — which clusters of writing belong together, which arrows connect which boxes, which dot-votes landed on which idea, what the chronological order of the boards was. **Nano Banana Pro** (`gemini-3-pro-image`) redraws each diagram as a crisp 4K vector-style slide with legible typography — the same arrows, the same groupings, the same emphasis, just clean. Workspace integration writes the result directly to a new Google Slides file in the user's Drive, no OAuth handshake required (when the post-I/O 2026 no-OAuth Workspace path is enabled on the deploying account; the app falls back to standard Drive OAuth consent otherwise). Each slide gets a one-paragraph speaker note inferred from the structure, and the app proposes a slide order based on whether the boards were sequential (a process map) or parallel (a two-by-two of options). Open the resulting Slides URL in a new tab — the deck is ready, the diagrams are diagrams not photos, and you can re-order or edit any slide before stand-up. The original photo lives on the Slides speaker-notes panel as a thumbnail, so anyone reviewing later can verify nothing was added or removed. **The thirty-second demo a visitor experiences:** drop in three phone photos of whiteboards from a one-hour workshop. The app shows the parse — a per-board outline of the boxes, arrows, sticky notes, and dot-votes it found. Press **Generate deck**. Sixty seconds later the app opens a Google Slides file in a new tab with twelve slides: a title slide naming the session, ten slides redrawn one-to-one from the whiteboards as vector-style diagrams, and a closing slide with the dot-vote results and proposed next steps. Each slide has a one-paragraph speaker note. The original photos sit alongside the redrawn slides in a verification panel, so the user can confirm nothing was invented. **Tagline:** _Snap the whiteboard. Get back a Google Slides deck — diagrams stay diagrams, arrows get straightened, nothing gets invented._ ## 2. Target audience - Workshop facilitators wrapping a two-hour strategy session with three full whiteboards and a fifteen-minute window before the read-out - Design leads running sprint crits, customer-interview synthesis, and journey-mapping sessions on flipcharts - Engineering managers wrapping a sprint retro on a whiteboard who need a deck for the leadership read-out - Teachers and trainers who teach off a whiteboard and want to send students a clean copy of the boards afterwards - Product managers running customer-interview debriefs on a conference-room wall covered in sticky notes - Consultants who run discovery workshops at client sites and need to walk out with the boards turned into deliverables before they leave - Researchers running affinity-mapping sessions with hundreds of sticky notes who need a clean grouped diagram for the report - Founders pitching at a partner meeting who sketched the business on a whiteboard and need a deck by tomorrow - Coaches running working sessions on flipcharts who want to send the participant a clean PDF of "here's what we drew" - Architects and engineers who whiteboard system diagrams and need a clean version in the docs without redrawing in Lucid or Excalidraw ## 3. Core value propositions - **Whiteboards in, presentable slides out — in the time it takes to walk to the next meeting.** A photo of a flipchart is unprofessional to paste into a deck. Redrawing it in Slides by hand takes about an hour per board. This app makes the redraw take sixty seconds, with a redrawn vector-style diagram instead of a phone-camera artefact. - **Diagrams stay diagrams; arrows get straightened.** Hand-drawn boxes become clean boxes. Wobbly arrows become clean arrows pointing in the same direction. Sticky notes become clean cards in the same grid the team grouped them in. Dot-votes get tallied and shown as numerical chips on the relevant idea. Emphasis (underlines, all-caps, circled items) is preserved as **emphasis**, not as a literal underline-of-the-pen-stroke. - **Nothing gets invented.** The hard rule of this template is that the model NEVER adds content the whiteboard does not contain. It structures and polishes what is there; that is all. Every slide shows the original photo crop alongside the redrawn diagram in a verification panel so the user can confirm. If the parser cannot read a chunk of writing, the slide shows a placeholder card with `[unreadable — see original photo]` and the original photo crop is highlighted at that spot. No fabrication, ever. - **Workspace-native (no OAuth dance).** Post-I/O 2026, AI Studio Build can write directly to Google Slides in the user's Drive without the user clicking through an OAuth consent screen. The app inherits the Google sign-in the user already did at app launch. The output file lives in their Drive, in a folder called `Whiteboard → Slides`, in their own account, not in some intermediate "our cloud" cache. - **Speaker notes inferred from the structure, not invented.** Each slide gets a one-paragraph speaker note that describes the structure: "This slide shows the customer-onboarding funnel as three sequential stages (Discover → Sign-up → First value). The team marked Discover as the highest-risk stage with three dot- votes against it." The note describes; it does not extrapolate. - **Slide order is suggested, not fixed.** After parsing, the app proposes an order — usually the chronological order the photos were taken, with a title slide added at the front and a "decisions and next steps" slide added at the back if dot-votes were detected. The user re-orders by drag before generating, or re-orders in Slides afterwards. The app never insists on its order. - **Original photo always one tap away.** The Slides speaker-notes panel includes a thumbnail of the source photo for each slide and a "show original alongside" button in the app. The user can verify in two seconds that the redrawn slide matches what was on the wall. - **Hand-drawn intent preserved.** If the team drew an arrow from box A to box B with the arrowhead at B, the redrawn slide has the arrowhead at B. If they circled "PRIORITY" three times, the redrawn slide shows PRIORITY in larger weight, not literally a circle drawn around it. The model translates intent, not pen strokes. - **Hard rule on no content invention.** This is the food-safety- equivalent rule for this template: the model NEVER adds bullet points, headings, supporting text, or "best-practice" content that the whiteboard does not contain. A slide is allowed to have less content than a typical "good slide" — that is fine. Empty slides show what was on the whiteboard: empty, or sparse, or one word. ## 4. Features to build - Photo input — drag-and-drop, paste, phone camera capture, multi- file select. Up to twelve photos per session. - Per-photo parse view — the app shows each photo with the parsed structure overlaid: boxes coloured, arrows traced, sticky notes clustered, dot-votes counted, headings called out. The user can see what the model "saw". - Per-photo edit — the user can tap any parsed element and correct it before generation (re-type a heading, redraw a group boundary, delete a spurious arrow the parser invented from a smudge). - Slide order drag-and-drop — re-order the photos before generation; the order of photos determines the slide order. - Title slide auto-generated from the session name and date (which the user types at upload, default "Whiteboard session — [date]"). - Per-slide redraw — Nano Banana Pro generates each slide as a 4K vector-style image with legible typography matching the whiteboard's content. - Closing slide auto-generated — if dot-votes were detected, the app proposes a "decisions and next steps" slide summarising which ideas got the most votes. - Workspace write — direct write to Google Slides via the post-I/O no-OAuth Workspace integration. Output file lives in the user's Drive in a folder named `Whiteboard → Slides`. - Verification panel — for each generated slide, the original photo crop is shown alongside the redrawn slide. A "report mismatch" button surfaces any spot where the user thinks the redraw added or changed content. - Speaker-notes generation — one paragraph per slide, describing the structure, never extrapolating beyond it. - Keynote export — for users who don't use Google Workspace, the app exports a `.key` file via Keynote XML or falls back to a `.pptx` that opens in Keynote. - PDF export — a print-ready PDF of the slides for users who just want a clean PDF, not a deck. - Re-generate any slide — if the user doesn't like a particular redraw, they can re-roll that slide alone without re-running the whole deck. - Session library — past sessions are kept in Firestore so the user can re-open the Slides file or re-run the redraw with a different style. - Style presets — three redraw styles: **Clean** (default — vector outlines, sans-serif typography, white background), **Whiteboard** (preserves a faint whiteboard background and slightly hand-drawn feel), **Print** (high-contrast for printing, no fills). - Sticky-note colour preservation — if the team used pink for problems and green for solutions on real sticky notes, the redraw preserves the colour coding. - Multi-board flow detection — if the photos show a sequential process (process map across three boards), the app notices and proposes a "show flow across slides" page that connects them. - Two-by-two and matrix recognition — if a whiteboard is a 2x2 matrix (effort/impact, urgent/important), the redraw uses a proper grid layout, not a photo-traced trapezoid. ## 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) - **Whiteboard semantic parse** (`gemini-3.5-flash`, multimodal image input) — accepts one whiteboard photograph and emits a structured `WhiteboardParse` JSON object: every detected element (box, arrow, sticky-note, heading, list-item, dot-vote, freeform-text), its bounding-box coordinates on the photo, its readable text content, the group it belongs to, the connections it has to other elements, the colour of the marker / sticky note. This is the load-bearing call — everything downstream depends on the parse being accurate AND honest about uncertainty. - **4K vector-style slide redraw** (`gemini-3-pro-image` — Nano Banana Pro) — accepts the `WhiteboardParse` JSON plus a 14-image style guide (the chosen redraw preset plus example outputs) and emits a single 4K image of the redrawn slide. Nano Banana Pro's legible- text-in-image capability is the load-bearing post-I/O 2026 ability — it can render the parsed text content of the whiteboard at presentation-grade typography quality, not as wobbly hand-lettering. Without this we'd be stuck pasting photos. - **Multi-board flow inference** (`gemini-3.5-flash`, long-context, reads all parsed boards together) — given the array of `WhiteboardParse` objects, infers whether the boards are sequential (a process map across multiple boards), parallel (a two-by-two with each quadrant on a different board), or independent (three separate exercises). Emits the recommended slide order and a flag on whether to insert flow-connector slides. - **Speaker-note generation** (`gemini-3.5-flash`, low thinking, per-slide) — accepts one `WhiteboardParse` plus the chosen slide order and emits a one-paragraph speaker note (3-5 sentences) describing the structure of the slide. The note describes; it does not extrapolate beyond what is in the parse. The system instruction is explicit about this. - **Title-slide and closing-slide synthesis** (`gemini-3.5-flash`, low thinking) — generates the title-slide content (session name, date, attendee count if provided) and the closing-slide content (top-voted ideas, proposed next steps) from the structured input only. Never invents next steps the team did not write down. - **Thinking levels** — `medium` for the whiteboard semantic parse (it has to reason about spatial relationships and which sticky belongs in which cluster). `low` for speaker-note generation and title/closing synthesis. `medium` for multi-board flow inference (it reasons across boards). No `high` thinking is used in this template — none of the calls benefit from deep reasoning. ### Backend services - **Auth — Required.** Firebase Auth with Google sign-in (auto- provisioned by AI Studio Build). The Google sign-in is also what authenticates the user to the Workspace integration that writes to Slides — post-I/O 2026, AI Studio Build inherits the user's Google identity to authorise Workspace writes without a second OAuth handshake. Apple sign-in is supported but disabled by default (Apple users can sign in with Apple but then need a Google account linked to use Slides export — flag this clearly). - **Database — Required.** Firestore for `users`, `sessions`, `parsed_boards`, `generated_slides`, `slide_orders`, `exports`. - **File storage — Required.** Firebase Storage for original whiteboard photographs (preserved at upload quality) and for generated 4K slide images. **Storage is NOT auto-provisioned by AI Studio Build today** — enable it in the Firebase console. Pre-signed URLs only. - **Workspace integration — Required.** Direct read+write to Google Slides, Drive, and (optional) Docs. Post-I/O 2026 this works without a separate OAuth consent — the AI Studio Build runtime forwards the user's Google identity. **Caveat for builders:** this is a post-I/O 2026 capability and rolled out 2026-05-19; if building before then, fall back to the manual OAuth path (Google OAuth client ID, scopes `https://www.googleapis.com/auth/presentations` and `https://www.googleapis.com/auth/drive.file`). - **Email — Optional.** Magic-link share emails for "send this deck to a colleague who isn't on the Workspace" — not required for v1. - **Payments — Not needed for v1.** Free for personal use. A future team tier might charge a small subscription via Stripe; not built in v1. - **External APIs:** Gemini API for all intelligence. Optional: a Keynote-export library for users who prefer Keynote over Slides. No required external API beyond Gemini. **Environment variables:** every secret (Gemini API key, Firebase service-account JSON, Workspace integration credentials if not using the AIS Build forwarder) lives in environment variables — never in client bundle. Include a `.env.example`. **Auth + data privacy reminders:** never log secrets · never store passwords in plain text · use HTTPS everywhere · honour 'delete my session' inside the UI · explicit opt-in for any analytics · the user's whiteboard photographs and generated decks are never sent 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 generated Slides file lives in the user's own Drive, not in app-owned storage. **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. Image-gen and TTS-style calls do not take `thinkingConfig` — omit the field entirely for those. 3. **Seed the JSON Schema as a fenced TypeScript / Zod block** in the system instruction or `responseSchema` field. The literal schemas are below. **Convert the Zod 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. None of the calls in this template use `google_search` — the parse is fully self-contained. The Workspace write is via the AIS Build Workspace integration, not a Gemini tool call. If a future "research what this acronym on the whiteboard means" feature is added, it would need its own grounded call with `responseSchema` disabled. 6. **State negative constraints explicitly** — they are listed below. They are NOT "be careful" suggestions; they are hard rules the model must follow. The single most important one is **never invent content the whiteboard does not contain.** ### Per-call model + tools matrix | Call | Model | thinkingLevel | Tools enabled | |------|-------|---------------|---------------| | Whiteboard semantic parse → `WhiteboardParse` | `gemini-3.5-flash` | medium | (none) | | Multi-board flow inference → `SessionFlow` | `gemini-3.5-flash` | medium | (none) | | Speaker-note generation (per slide) | `gemini-3.5-flash` | low | (none) | | Title-slide synthesis | `gemini-3.5-flash` | low | (none) | | Closing-slide synthesis (decisions + next steps from dot-votes) | `gemini-3.5-flash` | low | (none) | | 4K vector-style slide redraw | `gemini-3-pro-image` | n/a | n/a (image-gen) | | Empty-state / onboarding hero illustration | `gemini-3.1-flash-image` | n/a | n/a (image-gen) | *Note for builders:* on image-generation calls, omit `thinkingConfig` entirely — the field is not supported on those models. The `n/a` cells in this matrix are documentation only; do not serialise them into the request body. Multi-image input (multiple whiteboard photos in one parse call) is via the Gemini Developer API Files API (`files/*` resource name, e.g. `files/abc123xyz`, passed via `fileData: { fileUri, mimeType }`) or `inlineData` (base64); do NOT pass Firebase Storage public URLs to `generateContent` — the API does not fetch them server-side. `gs://` URIs belong to Vertex AI / Cloud Storage — a different surface, not accepted by the Developer API. **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 arrays and removes regex patterns before serializing. Validate those constraints in middleware AFTER parsing. ### Primary structured-output schemas (seed verbatim in the prompt) ```typescript import { z } from "zod"; const BoundingBox = z.object({ x_norm: z.number().min(0).max(1), // normalised 0-1 across photo width y_norm: z.number().min(0).max(1), // normalised 0-1 across photo height w_norm: z.number().min(0).max(1), h_norm: z.number().min(0).max(1), }); const MarkerColour = z.enum([ "black", "blue", "red", "green", "purple", "orange", "brown", "other", "unknown", ]); const StickyColour = z.enum([ "yellow", "pink", "green", "blue", "orange", "white", "other", "unknown", "not_a_sticky", ]); const ElementType = z.enum([ "heading", // a board title or section heading "box", // a box / rectangle / container "circle", // a circled idea (often a priority marker) "arrow", // a connecting arrow between elements "sticky_note", // a Post-it style note "list_item", // a bullet or numbered item "freeform_text", // standalone writing not in any container "diagram_label", // a label on an arrow or axis "axis_label", // an axis on a matrix (e.g. "Effort" on a 2x2) "dot_vote", // a dot left by a dot-vote "underline", // an underline used as emphasis "circle_emphasis", // a circle drawn around existing text as emphasis "scribble_out", // crossed-out content (preserve as "struck-through") "unreadable", // visible writing the parser couldn't read "other", ]); const EmphasisLevel = z.enum([ "none", "underline", "all_caps", "circled", "boxed", "starred", "double_underline", "highlighted", "scribble_out", ]); const Connection = z.object({ from_element_id: z.string(), to_element_id: z.string(), arrow_direction: z.enum([ "one_way", "two_way", "no_arrowhead", "branching", "unknown", ]), arrow_label_text: z.string().nullable(), // label written on or near the arrow }); const ParsedElement = z.object({ element_id: z.string(), // local to this board type: ElementType, bbox: BoundingBox, text_content_verbatim: z.string(), // exactly what was written, no completion text_confidence: z.number().min(0).max(1), marker_colour: MarkerColour, sticky_colour: StickyColour, emphasis: EmphasisLevel, group_id: z.string().nullable(), // shared id for clustered elements group_label: z.string().nullable(), // optional cluster heading dot_vote_count: z.number().nullable(), // when type is dot_vote OR when dot-votes are attached to another element is_struck_through: z.boolean(), flagged_for_user_review: z.string().nullable(), // reason if needs review }); const WhiteboardParse = z.object({ board_id: z.string(), source_photo_uri: z.string(), // Files API `files/*` resource name (Developer API) photo_orientation_correction_degrees: z.number(), // 0, 90, 180, 270 detected_board_type: z.enum([ "freeform_brainstorm", "process_map_sequential", "two_by_two_matrix", "four_quadrant_matrix", "kanban_columns", "fishbone_diagram", "affinity_map", "decision_tree", "swim_lane", "force_field", "user_journey", "list_or_outline", "unknown", ]), board_heading_verbatim: z.string().nullable(), elements: z.array(ParsedElement), connections: z.array(Connection), parse_confidence_overall: z.number().min(0).max(1), unreadable_regions: z.array(BoundingBox), parser_notes: z.array(z.string()), // honest notes about uncertainty }); const SessionFlow = z.object({ session_id: z.string(), boards_in_order: z.array(z.string()), // recommended board order relationship: z.enum([ "sequential_process", // boards 1 → 2 → 3 "parallel_exercises", // unrelated activities "matrix_quadrants_across_boards", // each board is one quadrant "before_after_pair", "expanded_detail", // board 2 is a zoom-in of board 1 "unknown", ]), insert_title_slide: z.boolean(), insert_closing_slide: z.boolean(), closing_slide_reason: z.string().nullable(), // e.g. "dot-votes detected on board 3" flow_inference_confidence: z.number().min(0).max(1), }); const SpeakerNote = z.object({ slide_id: z.string(), paragraph: z.string(), // 3-5 sentences describes_only_what_is_in_parse: z.boolean(), // assertion the model must set true cited_element_ids: z.array(z.string()), // elements from the parse this note references }); const GeneratedSlide = z.object({ slide_id: z.string(), source_board_id: z.string().nullable(), // null for title / closing slides slide_type: z.enum([ "title", "redrawn_board", "flow_connector", "closing_decisions", ]), image_uri: z.string(), // Files API `files/*` resource name for the 4K Nano Banana Pro output speaker_note: SpeakerNote, original_photo_thumbnail_uri: z.string().nullable(), redraw_style: z.enum(["clean", "whiteboard", "print"]), mismatch_reported_by_user: z.boolean(), }); type WhiteboardParse = z.infer; type SessionFlow = z.infer; type SpeakerNote = z.infer; type GeneratedSlide = z.infer; ``` ### Common failure modes (and how to avoid them) - Parser invents content it cannot read — the model wants to be "helpful" by completing a half-erased word. System instruction must require `text_content_verbatim` to be exactly what's visible, plus `unreadable` element type for anything the parser cannot read confidently. If `text_confidence < 0.6`, set `flagged_for_user_review` with a one-sentence reason. - Arrow direction inverted — the parser sees an arrowhead at the wrong end. System instruction must require the parser to explicitly identify the arrowhead end and set `arrow_direction` accordingly. If ambiguous, set `unknown` and flag for review. - Sticky-note colour misread — yellow stickies in warm lighting look orange. System instruction must include a colour-disambig block. If confidence is low, set `sticky_colour: "unknown"`. - Group boundaries hallucinated — the parser invents clusters that the team did not draw. Rule: only assign `group_id` if there is visual evidence of grouping (proximity + a drawn boundary, or a cluster heading written above). Without evidence, `group_id: null`. - Dot-vote count miscounted on a busy board — the parser undercounts or overcounts. Treat dot-votes as their own elements AND attach a count to the element they sit on; the server cross-checks the sum of dot-vote elements equals the sum of attached counts. If not, flag the board for user review. - Multi-board flow incorrectly inferred — the parser sees three unrelated exercises as a sequential process. The flow inference call must default to `parallel_exercises` when `flow_inference_confidence < 0.7`, and let the user override. - Nano Banana Pro adds decorative elements not in the source — for example, generating a "BEST PRACTICES" callout card the whiteboard did not contain. The image-gen system instruction must list the `WhiteboardParse` elements as the *only* allowed content of the slide and forbid additional decorative content. - Nano Banana Pro misrenders text from the parse — typo in the redrawn heading. The system instruction sends the verbatim text per element AND instructs the model to render it character-for- character. The server post-processes by OCR'ing the generated image and diffing against the parsed text; if any element's text was misrendered, the slide is re-generated up to twice. After two failures, the slide falls back to a layout with the original photo as a thumbnail in the corner. - Speaker-notes extrapolate beyond the parse — e.g. the parse says "Onboarding: 3 days" and the speaker note says "Onboarding takes 3 days, which is industry-average". System instruction forbids industry-average claims, best-practice references, or any information not in the parse. `describes_only_what_is_in_parse` must be `true` and `cited_element_ids` must be a strict subset of the parse's elements. - Closing slide invents next steps the team did not write down — e.g. the team voted on three ideas; the closing slide proposes next steps that were not on the whiteboard. The closing-slide system instruction restricts content to: (a) the top-voted ideas by vote count, with the verbatim text from the parse, and (b) any explicit "next steps" or "decisions" cluster from the parse — never inferred next steps. - Workspace write silently fails for users on a personal Gmail account (the AIS Build Workspace integration is for Workspace accounts only). The error must be surfaced clearly with a fallback to PowerPoint (.pptx) download. - Photo orientation wrong — the parser tries to read sideways text. The system instruction must include a "detect orientation first and correct" step; `photo_orientation_correction_degrees` is written back so the UI can show the photo in the corrected orientation alongside the redraw. ### Negative constraints (hard rules) - Do NOT invent content the whiteboard does not contain. This is the load-bearing rule of this template. The model never adds bullet points, headings, supporting text, "best-practice" content, "industry-standard" references, or any content not visible in the parse's elements. A sparse whiteboard produces a sparse slide. An empty whiteboard produces an empty slide. The user did the workshop; the app polishes; the app does not write. - Do NOT auto-complete partial words. If the whiteboard shows "Custmr Onb" and that's all that's readable, the slide shows "Custmr Onb", not "Customer Onboarding". The user re-types the full word in the parse-edit step if they want it spelled out. - Do NOT translate the whiteboard's language. If the team wrote in Spanish, the slide is in Spanish. If the team wrote in mixed English+French, the slide is in mixed English+French. Translation is a separate, user-initiated action — never default. - Do NOT change arrow directions. If the team drew the arrow pointing left, the slide arrow points left. If they drew a two-way arrow, the slide shows two arrowheads. - Do NOT regroup elements the team did not group. If two boxes are near each other but the team did not draw a boundary or a label around them, they are NOT one group on the slide. - Do NOT propose "next steps" the team did not write. The closing slide only shows next steps that exist on the whiteboard as explicit list-items or in a "next steps" / "actions" cluster. - Do NOT auto-publish the Slides file. The output is created in the user's Drive but the share permission is "private to owner" by default. The user explicitly shares it themselves. - Do NOT use the user's whiteboard photos, parses, or decks to train or fine-tune any model. Use the Gemini API on the paid tier, where Google does not use your content for model training, per the Gemini API Additional Terms. The capabilities-info panel says this in plain English. - Do NOT label content as "AI-generated" within the deck itself. The deck is the team's content, polished — not an AI artefact. The verification panel inside this app clearly shows the generation provenance; the exported deck does not need a "Made with AI" watermark. - Do NOT alter the colour-coding of sticky notes. If a team used pink for problems and green for solutions, the redraw preserves this. If the parse cannot confidently determine sticky colour, it uses a neutral colour and flags for review — it does not guess. - Do NOT cache the user's photos beyond the session lifetime. If the user deletes the session, the photos and the parsed data are deleted from Firestore and Firebase Storage within the same request. - Do NOT silently exceed the Files API multimodal input cap. If the user uploads more than twelve photos in one session, the upload step shows a clear error and asks them to split into multiple sessions. ### 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: Whiteboard semantic parse → `WhiteboardParse` Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You are parsing a single photograph of a whiteboard, flipchart, or sketchpad page taken at the end of a meeting or workshop. Your output is a structured WhiteboardParse JSON object describing every distinct element on the surface, their positions on the photo, the text they contain (verbatim), their grouping, and the connections between them. The photograph may be: - a whiteboard wall with marker writing and sticky notes - a flipchart page on an easel with marker writing - a sketchpad page with pen writing - a printed grid (e.g. a pre-printed 2x2 template) with sticky notes and marker overlays Lighting will be uneven (overhead fluorescents, conference-room windows, phone flash). The photo may be slightly tilted, slightly distorted (the photographer was not perpendicular to the surface). Glare may obscure parts of the surface. People's reflections may appear in glossy whiteboards. Step 1 — Orientation. Determine whether the photo is correctly oriented (text readable at 0 degrees) or rotated 90, 180, or 270 degrees. Set photo_orientation_correction_degrees to the rotation needed to make text upright. Subsequent steps assume the corrected orientation. Step 2 — Board-type classification. Classify the board's overall structure into one of: freeform_brainstorm, process_map_sequential, two_by_two_matrix, four_quadrant_matrix, kanban_columns, fishbone_diagram, affinity_map, decision_tree, swim_lane, force_field, user_journey, list_or_outline, unknown. If you cannot classify with confidence, use unknown — do not guess. Step 3 — Element detection. Identify every distinct element on the board. For each element: - assign a local element_id (stable across this parse) - classify its type: heading, box, circle, arrow, sticky_note, list_item, freeform_text, diagram_label, axis_label, dot_vote, underline, circle_emphasis, scribble_out, unreadable, other - record its bounding box as normalised coordinates (0..1) on the oriented photo - record its text_content_verbatim — exactly what is written, no word completion, no spelling correction, no language translation - record text_confidence (0..1) reflecting OCR clarity - record marker_colour for marker-written elements (black, blue, red, green, purple, orange, brown, other, unknown) - record sticky_colour for Post-it style notes (yellow, pink, green, blue, orange, white, other, unknown, not_a_sticky) - record emphasis level (none, underline, all_caps, circled, boxed, starred, double_underline, highlighted, scribble_out) - record group_id ONLY when there is visual evidence of grouping (a drawn boundary OR a cluster heading immediately above OR proximity AND consistent colour); otherwise null - record dot_vote_count when the element has dot-votes attached - record is_struck_through when the element is crossed out - if text_confidence < 0.6 OR colour/grouping is ambiguous, populate flagged_for_user_review with a one-sentence reason Step 4 — Connection detection. For each arrow on the board: - identify the from_element_id (the element the arrow originates from) and to_element_id (the element the arrow points to) - identify the arrow_direction: one_way, two_way, no_arrowhead, branching, unknown - if a label is written on or near the arrow, capture it as arrow_label_text Step 5 — Unreadable regions. If any region of the whiteboard has visible writing you cannot read confidently, add it to unreadable_regions as a bounding box. The downstream redraw will preserve the original photo crop at that spot rather than fabricate content. Step 6 — Parser notes. Populate parser_notes with one short entry per uncertainty you encountered. Examples: - "Top-right corner has glare; one possible sticky note is hidden" - "Three dot-votes counted on the 'pricing' card but a fourth dot may overlap with the 'support' card" - "Arrow from 'Discovery' to 'Sign-up' has no clear arrowhead; inferred direction from spatial sequence" Hard rules: - text_content_verbatim is verbatim. NEVER auto-complete partial words. NEVER correct spelling. NEVER translate language. If the team wrote "Custmr", record "Custmr". - If you cannot read a word, classify the element as unreadable (or set text_content_verbatim to "[unreadable]" and add the bbox to unreadable_regions). - group_id is null unless there is visual evidence of grouping. Proximity alone is not enough — a drawn boundary or cluster heading is required. - dot_vote_count must be the actual count visible. Do not estimate. If dots overlap, flag for user review. - arrow_direction reflects the drawn arrowhead. If you cannot tell, use unknown — do not infer from spatial layout alone. - parse_confidence_overall is your honest estimate of how trustworthy the parse is. Below 0.7 → the UI surfaces "review parsed elements before generating". Output ONLY the WhiteboardParse JSON matching the provided schema. No commentary. JSON only. ``` --- ### Call: Multi-board flow inference → `SessionFlow` Model: `gemini-3.5-flash` · thinkingLevel: medium · Tools: (none) ``` You receive an array of WhiteboardParse objects in the order the photos were uploaded. Your task is to infer the relationship between the boards and recommend a slide order. Possible relationships: - sequential_process — the boards depict steps in a process (e.g. board 1: Discovery, board 2: Decision, board 3: Delivery) - parallel_exercises — the boards are independent activities done in the same session - matrix_quadrants_across_boards — each board is one quadrant of a larger matrix - before_after_pair — two boards depicting the state before and after a change - expanded_detail — one board is a zoom-in of an element from another board - unknown — you cannot infer with confidence Signals to use: - shared headings across boards (e.g. "Customer Journey — Phase 1", "Customer Journey — Phase 2") - numbered headings ("1.", "2.", "3.") - arrows that visually leave one board and continue on the next - consistent element types across boards (all process maps, all brainstorms) - explicit cross-references in text ("see board 2") If flow_inference_confidence falls below 0.7, default to parallel_exercises and recommend boards_in_order as the order they were uploaded. The user will re-order via drag if needed. Set insert_title_slide to true if more than one board is present (a multi-board session benefits from a title slide). Set insert_closing_slide to true if any board contains dot-votes OR a "next steps" / "decisions" / "actions" cluster. Populate closing_slide_reason explaining why ("dot-votes detected on board 2", "actions cluster on board 3"). Hard rules: - Do NOT invent a sequence the boards do not support. If the boards are unrelated, return parallel_exercises with honest low confidence. - boards_in_order is a permutation of the input board_ids — no additions, no removals, no duplicates. - The closing-slide content is filled later by a separate call; this call only flags whether a closing slide should be inserted. Output ONLY the SessionFlow JSON matching the provided schema. No commentary. JSON only. ``` --- ### Call: Speaker-note generation (per slide) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive a single WhiteboardParse (or, for title/closing slides, a synthesised structure) and your task is to write ONE paragraph (3-5 sentences) describing the structure of the slide, suitable for the speaker-notes panel of a Google Slides deck. What the paragraph contains: - a one-sentence overview of what the slide depicts (e.g. "This slide shows the customer onboarding funnel as three sequential stages.") - two-to-four sentences describing the structure: the elements, their groupings, the connections, the emphasis applied by the team (e.g. "The team marked 'Discovery' with three dot-votes, indicating it as the highest-risk stage. An arrow connects 'Sign-up' to 'First value', labelled '3 days'.") What the paragraph does NOT contain: - best-practice claims ("This is industry-standard for SaaS onboarding…") - inferred next steps ("The team should focus on Discovery…") - recommendations the team did not make - statistics or comparisons not present in the parse - any content not in the WhiteboardParse's elements Set describes_only_what_is_in_parse to true to assert compliance. Populate cited_element_ids as a strict subset of the parse's element_ids that this note references — if you mention "Discovery" in the note, the element_id for the "Discovery" element must be in cited_element_ids. Hard rules: - 3-5 sentences. No more, no less. - No bullet points in the paragraph. Prose only. - No best-practice or industry-comparison language. EVER. - No translated content; preserve the team's original language. - Reference only elements present in the parse. Output ONLY the SpeakerNote JSON matching the provided schema. No commentary. JSON only. ``` --- ### Call: Title-slide synthesis Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive the session name (user-provided), the session date, and the array of WhiteboardParse board_headings. Your task is to produce title-slide content: { "title": "", "subtitle": "", "date_iso": "", "board_count": } Hard rules: - title is the session name verbatim. Do not edit or "improve" it. - subtitle is at most 8 words. It MUST be derived only from board headings or the session name — never invented. If no signal, return an empty string. - Do NOT add aspirational language ("Charting our future", "Building tomorrow's product"). The subtitle is functional, not promotional. Output the JSON exactly as specified. No commentary. ``` --- ### Call: Closing-slide synthesis (decisions + next steps from dot-votes) Model: `gemini-3.5-flash` · thinkingLevel: low · Tools: (none) ``` You receive the array of WhiteboardParse objects with dot-vote elements identified, AND any explicit "next steps" / "decisions" / "actions" clusters from the parse. Your task is to produce closing-slide content: { "heading": "Decisions and next steps", "top_voted_items": [ { "element_id": "...", "text_verbatim": "...", "vote_count": 5 }, ... ], "explicit_next_steps": [ { "element_id": "...", "text_verbatim": "..." }, ... ] } Hard rules: - top_voted_items is the elements with the highest dot_vote_count, ordered descending. Take the top 5. If fewer than 5 elements have dot-votes, take whatever exists. - explicit_next_steps is a list of ONLY those elements explicitly in a cluster labelled "next steps", "actions", "decisions", "to-do", or equivalent. NEVER inferred next steps. - text_verbatim is the element's text exactly as on the whiteboard. No editing, no completion, no translation. - Do NOT add any items not present in the parse. - If no dot-votes and no explicit next-steps cluster exist, the closing slide should NOT be inserted — the inferring call should have set insert_closing_slide to false. If it is true but no content exists, return empty arrays. Output the JSON exactly as specified. No commentary. ``` --- ### Call: 4K vector-style slide redraw Model: `gemini-3-pro-image` · n/a · n/a ``` You generate a single 4K image of a slide that redraws the whiteboard's content as a clean vector-style diagram with legible typography. You receive: - the WhiteboardParse JSON for the source board - the chosen redraw_style ("clean", "whiteboard", "print") - a 14-image reference style guide (one example output per board_type + style combination) - the redraw aspect ratio (16:9 default; 4:3 optional) Render rules: - Every element in the WhiteboardParse appears on the slide, positioned approximately where it was on the whiteboard (preserve the spatial structure). - Render text_content_verbatim character-for-character. Do NOT auto-complete, correct spelling, translate, or paraphrase. If text_content_verbatim is "Custmr Onb", render "Custmr Onb". - For each element type: - heading: large weight, top of slide or anchored to position - box: clean rectangle with the text centred - circle: clean circle (or oval if the original was oval) - arrow: clean straight or curved arrow respecting arrow_direction; arrowhead at the to_element_id end - sticky_note: rounded rectangle in the sticky_colour, with text inside - list_item: bullet or numbered as on the whiteboard - freeform_text: text without a container, positioned as on the source - diagram_label / axis_label: small italic label - dot_vote: small filled circle near the voted element OR a numeric chip ("3 votes") if dot_vote_count is on the element - emphasis: render as a heavier weight or italic, NOT as a literal pen-drawn underline / circle around the text - scribble_out: render with strikethrough text - unreadable: render a placeholder card with the text "[unreadable — see original]" at the position - Group elements that share a group_id with a soft container (rounded rect with a 1-pixel border, or a tinted background) and show the group_label as a heading above the container if present. - Colour: - "clean" style: white background, dark grey text, accent colour matching the marker_colour of the element - "whiteboard" style: faint off-white whiteboard background with subtle shadow; text in slightly-imperfect weight - "print" style: pure white background, all elements outline- only in black, no fills, suitable for monochrome printing - Sticky-note colours are preserved across all styles. A yellow sticky stays yellow; a pink sticky stays pink. Hard rules — do NOT violate: - DO NOT add elements not present in the WhiteboardParse. No decorative shapes, no "BEST PRACTICES" callouts, no inferred headings, no clip-art icons, no template watermarks. - DO NOT auto-complete text. The text on the slide is text_content_verbatim, character-for-character. - DO NOT change arrow directions. If arrow_direction is "one_way" with arrowhead at to_element_id, that's where the arrowhead goes. - DO NOT translate any text. - DO NOT add background imagery, gradients, or decorative borders unless the style guide explicitly calls for them. - DO NOT render the original photo. The slide is a clean redraw, not a tracing. Output: a single 4K image (3840x2160) of the slide in 16:9 aspect ratio (or 4:3 if specified). No commentary. ``` --- ### Call: Empty-state / onboarding hero illustration Model: `gemini-3.1-flash-image` · n/a · n/a ``` You generate a single image for the welcome screen or empty-state of the app. Images depict realistic workshop scenes: - a conference-room whiteboard at the end of a meeting, covered in marker writing and sticky notes, mid-afternoon light, no people in frame - a flipchart on an easel with handwritten brainstorm content, warm overhead light, a notebook and a phone on the table - a sketchpad open on a desk with hand-drawn diagrams, soft natural light through a window Prompt anchors that work well: - "a conference-room whiteboard wall, end-of-meeting state, covered in black-marker writing organised in three clusters, yellow and pink sticky notes grouped in the centre, soft overhead fluorescent light, no people in frame, slight asymmetry" - "a flipchart page on a wooden easel, hand-drawn process map in blue marker, four boxes connected by arrows, two red sticky notes attached, late afternoon light through a window, no people" - "an open A4 sketchpad on a desk, hand-drawn 2x2 matrix with axis labels in marker, eight items placed in the quadrants, a phone resting on the corner, warm desk lamp" Hard rules: - Photographic, realistic. Not cartoon, not flat illustration. - No people in frame. - No commercial branding visible (no real company logos, no product labels). - Warm, slightly imperfect lighting — avoid the glossy AI-render look. - Aspect ratios: 3:2 for hero, 1:1 for empty states. ``` ## 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. - **Two-hour strategy session, three whiteboards, fifteen-minute window.** A team finishes a strategy off-site with three full whiteboards: "Where we are", "Where we want to be in 12 months", "What gets in the way". Photos go in; the app classifies all three as `freeform_brainstorm`, identifies the sequential relationship (current → future → obstacles), proposes a five-slide deck (title + three redrawn boards + a closing slide with the five top-voted obstacles), and writes to Slides. The team has the deck before the next meeting starts. - **Sprint retro on a whiteboard, leadership read-out at 11 AM.** A scrum master photographs the retro board with four columns (Continue, Stop, Start, Action items). The app classifies it as `kanban_columns`, redraws each column cleanly with the team's notes, generates a closing slide listing the explicit "Action items" (NOT inferring new ones), and writes to Slides. The leadership read-out gets a deck that respects the team's words exactly. - **Customer-interview synthesis on a sticky-note wall.** A product manager runs a synthesis session: a conference-room wall covered in pink stickies (pain points) and green stickies (delights), grouped into clusters with hand-drawn boundaries and cluster labels. The app preserves the colour coding (pink stays pink, green stays green), respects the cluster groupings the team drew, and produces a multi-slide deck — one slide per cluster. - **Design crit on a flipchart, four versions of a screen sketched side by side.** A design lead's flipchart shows four mockup variants of a homepage with notes around each. The app classifies it as a `four_quadrant_matrix`-ish layout, preserves each mockup's relative position, and produces a four-up slide with the team's annotations beside each variant. - **Affinity mapping with hundreds of stickies.** A researcher runs a three-hour affinity-mapping session producing a wall with two-hundred sticky notes grouped into twelve clusters. The app's multi-image input handles three photos covering the wall, parses every sticky, respects the twelve cluster boundaries, and produces a twelve-slide deck with one cluster per slide plus an overview slide showing all twelve cluster headings. - **The 2x2 matrix that runs in every workshop.** Effort vs Impact, Urgent vs Important, Reach vs Confidence — the app detects `two_by_two_matrix` and redraws it as a clean four-quadrant grid with the team's items placed in the correct quadrants, axes labelled, and quadrant colour-coded (high-effort/high-impact is the gold quadrant, low-effort/high-impact is the green quadrant). - **Process map across three whiteboards.** A consultant's discovery workshop produced three whiteboards depicting a customer journey in three phases. The app's flow-inference call identifies `sequential_process`, produces three redrawn slides in the correct order, and inserts a flow-connector slide between each showing the arrow continuing from one board to the next. - **Fishbone diagram for a post-mortem.** An engineering team's post-mortem produced a fishbone with five spines (People, Process, Tools, Code, External). The app classifies it as `fishbone_diagram`, redraws the spines cleanly, and lists every contributing factor on each spine — without adding any factor the team did not write. - **The smudged corner.** A whiteboard photo has glare in the top-right that obscures one sticky note. The app's parser flags it as an unreadable region, the redrawn slide shows the rest of the board redrawn cleanly and the smudged corner as a placeholder card with `[unreadable — see original]` at that spot. The verification panel highlights the unreadable region on the original photo so the user can re-type the sticky's content into the parse-edit step before regenerating. - **The half-erased word.** A whiteboard from yesterday's meeting has "Custmr Onbo——" with the last part erased. The parser preserves "Custmr Onbo——" character-for-character. The user edits it in the parse step to "Customer Onboarding" before generation, OR leaves it as-is and the slide shows the partial word verbatim — the user's choice. - **Mixed-language whiteboard.** A team in Montreal wrote in mixed English and French ("La vision: customer-first delivery"). The parser preserves both languages verbatim. The slide is generated with both languages exactly as written. Translation is a separate, user-initiated action (not in v1). - **Hand-drawn arrow ambiguity.** A team drew an arrow without a clear arrowhead between two boxes. The parser sets `arrow_direction: unknown` and flags for user review. The user taps the arrow in the parse-edit step and chooses one_way / two_way / no_arrowhead. The slide reflects the user's choice. - **The single-slide remix.** A user already has a deck and just wants ONE slide replaced — they re-roll that one slide after editing the parse, without re-generating the whole deck. ## 6. Page structure Build the following screens / sections in this order. 1. **Welcome / sign-in.** A photographed-looking shot of a conference-room whiteboard covered in marker writing and sticky notes at the end of a meeting, soft overhead light, no people. One paragraph: "Whiteboard → Slides turns a stack of phone photos from your meeting whiteboards into a clean Google Slides deck — diagrams stay diagrams, nothing gets invented. Drop in your photos and you'll have a presentable deck in sixty seconds." Single Google sign-in button; Apple sign-in next to it (with the Workspace-only caveat noted in-line). Below: "Try with the sample workshop" → loads the demo session in section 8a. 2. **Sessions home.** The user's past sessions, most recent first. Each card shows the session name, date, board count, and a "Open Slides" link if a deck has been generated. Tap a session → detail view. New-session button at the top right. 3. **New session — upload.** A drag-and-drop zone for photos (up to twelve). Session name input (default "Whiteboard session — [today's date]"). Date picker. Optional attendee count. The "Parse photos" button is disabled until at least one photo is uploaded. 4. **Parse review.** For each uploaded photo, the parsed structure is overlaid on the photo. Boxes outlined in blue, arrows traced in green, sticky notes outlined in their detected colour, dot-votes shown as small circles, unreadable regions shaded grey. Tap any element to edit (re-type the text, change the type, delete, add a missing element). Confidence indicators: low-confidence elements show a faint underline; tapping reveals the parser's alternatives. The user can re-order the photos by drag (this is the slide order). A "Generate deck" button at the bottom triggers the full pipeline. 5. **Generating.** A progress view showing each step: "Parsing photos…" → "Inferring flow…" → "Generating slide 1 of 12…" → … → "Writing to Google Slides…". Each step shows an inline thumbnail of the redrawn slide as it completes. The view is honest about timing — sixty seconds for a typical three-board session. 6. **Deck preview.** A grid of the generated slides as thumbnails. Tap any slide for a full-screen view with the redrawn slide on the left and the original photo on the right (the verification panel). Each slide has a "Regenerate this slide" button. Below each slide: the speaker note in a readable paragraph, with cited elements highlighted on the slide thumbnail. 7. **Output actions.** A row of action cards: "Open in Google Slides" (primary, opens the freshly-written Slides file in a new tab), "Download as Keynote (.key)" (secondary), "Download as PowerPoint (.pptx)", "Download as PDF". A "Share with a colleague (magic link)" option. 8. **Verification panel.** Inside each slide view, a "Show original alongside" toggle compares the redrawn slide to the original photo. A "Report mismatch" button surfaces any spot where the user thinks the redraw added or changed content — this writes to a `mismatch_reports` Firestore collection so the prompts can be tightened. 9. **Settings & privacy.** Redraw style preference (Clean / Whiteboard / Print), default Slides folder name, "Delete this session" / "Delete my account" controls. Privacy panel restates the not-trained-on policy in plain English. A "Workspace integration status" indicator shows whether the no-OAuth Workspace path is active (post-I/O 2026) or whether the fallback OAuth path is in use. 10. **Footer.** "Made for the team finishing the meeting at 10:55 and presenting at 11:00." Privacy: "Your whiteboard photos, parses, and decks are yours. We never train on them. The Slides file lives in your Drive, not ours." 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 Whiteboard → Slides." - Subhead: "A clean Google Slides deck from your whiteboard photos — in sixty seconds. Diagrams stay diagrams. Nothing gets invented." - One paragraph (≤ 60 words) explaining who this is for and the hard rule: this app NEVER adds content the whiteboard does not contain. It structures, it polishes, it preserves the team's words and arrows; it does not write. - Visual: a small annotated illustration showing a photo of a whiteboard on the left, the parsed structure overlay in the middle, and a redrawn slide on the right. **Slide 2 — Try it now.** - One short prompt: "Try with the sample workshop". - A live demo input pre-loaded with the three-board strategy- session seed content from section 8a. - 1-2 sentences pointing at *the specific page elements* where the Gemini magic happens (the per-photo parse, the flow inference, the 4K Nano Banana Pro redraws, the no-OAuth Slides write). **Slide 3 — How to remix this.** - Headline: "Make this yours." - Three short bullets: - "Swap the sample workshop in `/data/seed-session/` for your own photos." - "Adjust the redraw styles in `/server/prompts/redraw/` to fit your brand." - "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). **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. - Don't auto-reshow on return visits. - Don't include unrelated CTAs. ## 6c. Capabilities info button (persistent in header) Add a persistent `(i)` icon in the top-right of the header. Click → opens a modal/panel titled **"What powers this app"**. **Panel contents (in this order):** **Gemini capabilities used (the hero list):** - **Gemini 3.5 Flash (multimodal image, structured output)** — parses each whiteboard photo into a structured WhiteboardParse: every box, arrow, sticky note, dot-vote, heading, and connection. The parser is honest about what it cannot read — unreadable regions are flagged, not invented. - **Gemini 3.5 Flash (long-context across boards)** — infers the relationship between multiple boards (sequential process, 2x2 quadrants, parallel exercises) and recommends a slide order. - **Gemini 3.5 Flash (per-slide speaker notes)** — generates a one-paragraph speaker note for each slide, strictly limited to what is in the parse. No best-practice claims, no inferred next steps. - **Nano Banana Pro (`gemini-3-pro-image`)** — redraws each whiteboard as a 4K vector-style slide with legible typography matching the team's words character-for-character. This is the post-I/O 2026 capability that makes this template possible — before Nano Banana Pro, slide text rendered by image gen was unreliable. - **Workspace integration (no-OAuth)** — writes the generated deck directly to a new Google Slides file in your Drive, without a separate OAuth consent screen. The file lives in your Drive, in your account, not in ours. - **Firebase Auth** — Google and Apple sign-in. - **Firestore** — stores your sessions, parses, and deck records. - **Firebase Storage** — keeps your original whiteboard photos and generated slide images at full quality. - **Cost note** — a typical three-board session costs about $0.42 of Gemini API spend. The detailed breakdown is in 6d. - **Privacy note** — your whiteboard photos, parses, and decks are private to you. The Slides file lives in your Drive. 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. - **No-invention promise** — this is the load-bearing rule of this app. The model NEVER adds content the whiteboard does not contain. Every slide has a verification panel showing the original photo crop alongside the redraw so you can confirm. If you spot a mismatch, the "Report mismatch" button helps us tighten the prompts. **Backend services this app depends on:** - Auth: see section 4b - Database: see section 4b - Storage: see section 4b — REQUIRES manual enable in Firebase console; AIS Build does not auto-provision Storage today. - Workspace integration: post-I/O 2026 no-OAuth path; fallback OAuth client ID for pre-I/O environments or non-Workspace accounts. - Apple sign-in: optional, requires an Apple Developer account. Note: Apple-only users cannot use the no-OAuth Slides path and will be prompted to add a Google account before exporting. - 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) - `WORKSPACE_CLIENT_ID` — optional, only if the post-I/O no-OAuth forwarder is not available in your AIS Build environment - `WORKSPACE_CLIENT_SECRET` — optional, fallback path only **Cost + privacy notes:** - One short paragraph per cost-sensitive capability: Nano Banana Pro redraws are billed per image — a 12-slide deck is about $0.36 (12 × $0.03/image roughly). Parsing and speaker notes add about $0.06. - One short paragraph on privacy: where the data lives (your Firebase project for parses; your own Drive for the Slides file), how to delete it, what is never sent for training. **Documentation links:** - AI Studio Build docs - Gemini API multimodal image, structured output, long-context - Nano Banana Pro (gemini-3-pro-image) docs and legibility examples - Workspace integration docs (no-OAuth, post-I/O 2026) - Firebase Auth, Firestore, Firebase Storage docs **Accessibility:** same standards as the onboarding modal. **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) - **Whiteboard parse (Gemini 3.5 Flash, medium thinking)** — one whiteboard photo plus the system instruction averages ~3,500 input tokens (image + instruction) and ~2,200 output tokens (structured parse). At Gemini 3.5 Flash pricing ($1.50/M input, $9/M output) that is ~$0.025 per board. A three-board session → ~$0.075. - **Multi-board flow inference (Gemini 3.5 Flash, medium thinking)** — ~4,000 input tokens (three parses concatenated) and ~300 output tokens → ~$0.009 per session. - **Speaker-note generation per slide (Gemini 3.5 Flash, low thinking)** — ~1,200 input tokens and ~250 output tokens per slide → ~$0.004 per slide. A 12-slide deck → ~$0.048. - **Title-slide synthesis (Gemini 3.5 Flash, low thinking)** — small input/output → ~$0.002 per session. - **Closing-slide synthesis (Gemini 3.5 Flash, low thinking)** — small input/output → ~$0.003 per session. - **Nano Banana Pro slide redraw (`gemini-3-pro-image`)** — each 4K image generation is ~$0.03 (Nano Banana Pro is $2/M input, $12/M output; a 4K image render averages ~2,500 output tokens). A 12-slide deck → ~$0.36. - **Onboarding hero image (Nano Banana 2, `gemini-3.1-flash-image`)** — ~$0.005/image. Generated once per app install (cached client-side). - **Workspace write to Slides** — free (Workspace API quota covers it). - **Expected per-session cost (12-slide deck):** ~$0.50 — parse + flow + 12 redraws + 12 speaker notes + title + closing. - **Bulk: 100 sessions/month:** ~$50 — well within a small-team budget; the dominant cost is Nano Banana Pro at $0.03/slide, which is what makes the deck presentable. - **Storage:** Firebase Storage standard tier ~$0.026/GB/month. A whiteboard photo at 4 MB and a 4K redraw at 2 MB → ~6 MB per slide → ~72 MB per 12-slide session ≈ ~$0.002/month per session. ## 7. Design language - **Mood:** A working surface, not a SaaS product. The conference room with the whiteboard still half-erased at 5 PM, the flipchart on the easel after a discovery workshop, the sketchpad on the desk after a one-on-one. The moment between "we had the conversation" and "we shared the decisions". Not Miro. Not Figma. Not Lucidchart. A workbench app. - **Typography:** Clean grotesque for app chrome and parsed- element labels (Inter or Geist). Display sans for the redrawn-slide preview headings (Söhne or Geist Mono for the speaker-notes panel as an accent — to mark structural notes vs the team's words). The team's verbatim words inside the parsed-element overlay use a slightly less-finished weight to signal "this is what they wrote, not what we wrote". - **Palette:** Clean off-white background `#FAFAF7` for the parse-review and deck-preview surfaces, deep ink `#1B1B1F` for body text, indigo `#3949AB` for parsed-box outlines and primary actions (this is the "structural overlay" colour), emerald `#2E7D5B` for parsed arrows and connection lines, amber `#C8842A` for low-confidence flags and "review this" cues, faded red `#A33A2C` for mismatch reports and the no-invention disclaimer chrome. Sticky-note colours preserved (`#FFE066` yellow, `#F8AFA6` pink, `#A8D5BA` green, `#A8C6F0` blue, `#F5C16C` orange). Borrowed from a workshop facilitator's kit, not from SaaS design systems. - **Imagery:** Photographic. Whiteboards in real conference rooms at the end of meetings. Flipcharts on easels with marker writing. Sketchpads open on desks. No flat illustrations. No "AI sparkle" decorations. Generated via Nano Banana 2 with prompts emphasising real working surfaces, warm light, no people, no commercial branding. - **Hand-feel touches:** When a parsed element is tapped, the overlay rectangle gives a subtle ink-fill animation that mirrors marker-on-whiteboard. The redrawn-slide thumbnails drop into the deck-preview grid with a thin paper-shadow as if a slide were being placed on a desk. The verification panel toggle (showing original photo alongside redrawn slide) crossfades, not slides — the user is comparing, not navigating. - **Spacing:** consistent 4-px base. Generous whitespace — the parse review needs air around each element so the user can tap accurately. - **Radius:** consistent token set (6 / 12 / 20 px). Parsed element overlays use 6; the slide-preview cards use 12; the welcome card uses 20. - **Shadows:** subtle, layered, neutral-tinted. Avoid heavy drop-shadows. - **Motion:** purposeful — entrance fades, hover lifts, page transitions. Respect `prefers-reduced-motion`. The redrawn- slide reveal as each generation completes uses a fade in with a subtle scale (1.02 → 1.00) over 320 ms; reduced motion uses an instant swap. No theatrical "ta-da" animations. No confetti. - **States:** every interactive element has hover, focus, active, disabled. Loading uses skeletons not spinners. Empty states have helpful next-action guidance. ## 8. Content generation rules - Write **realistic, specific copy**. NO Lorem Ipsum. NO generic placeholders like 'Your tagline here'. - Invent plausible session names, board headings, sticky-note content, and dot-vote results that fit the domain (use the seed content in section 8a as a starting point). Lean on realistic workshop patterns — a discovery workshop produces a journey map, a retro produces a kanban-style board, a brainstorm produces a sticky-note wall — but never claim a fictional session came from a real organisation. - Tone: warm, direct, free of corporate language. This template is for a workshop facilitator, not a workshop-software vendor. - Headlines: punchy and concrete. No 'Empower your X' filler. No 'Revolutionize'. No 'Seamless'. No 'AI-powered'. - Body copy: short paragraphs (2-4 sentences). Use lists where appropriate. - Plain language. Avoid jargon — except where the facilitator already speaks the jargon ("affinity map", "dot-vote", "2x2", "retro" are fine; "synergy" and "ideate" are not). - Where the app outputs AI-generated content, never label it as "AI says" — let it speak naturally. Use small uncertainty cues only where epistemic honesty requires them (a low- confidence parsed element shows as faintly underlined; tapping reveals the parser's alternatives). ## 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):** - "Q3 Strategy off-site — 2026-05-18" (3 boards, status: generated) — three whiteboards: "Where we are", "Where we want to be in 12 months", "What gets in the way". The hero demo session. - "Sprint 47 retro — 2026-05-15" (1 board, status: generated) — a kanban-style retro board with four columns (Continue / Stop / Start / Action items). - "Customer interview synthesis — 2026-05-12" (2 boards, status: generated) — an affinity-mapping wall of pink and green sticky notes from six customer interviews. - "Onboarding redesign workshop — 2026-05-08" (4 boards, status: parsed, deck not yet generated) — a journey map, a 2x2 of effort/impact, a fishbone of friction sources, and a list of next steps. - "Architecture whiteboard — 2026-05-05" (1 board, status: generated) — a system diagram with boxes and arrows depicting a microservices architecture. **Sample session in detail view (this is what the demo shows):** - **Session name:** "Q3 Strategy off-site — 2026-05-18" - **Date:** 2026-05-18 - **Attendee count:** 8 - **Board 1 — "Where we are"** (`freeform_brainstorm`): - Heading: "Where we are — May 2026" - Elements: 14 sticky notes in three clusters: "Strengths" (6 green stickies including "Strong retention in enterprise segment", "Healthy NPS in customer-success cohort", "Sales pipeline up 32% vs Q1"), "Weaknesses" (5 pink stickies including "Onboarding drop-off at day 3", "Limited mid-market product–market fit signal", "Engineering velocity stalled on legacy module"), and "Open questions" (3 yellow stickies including "Do we raise or stay lean?", "Build vs buy on observability?", "When do we hire VP Eng?"). - Three dot-votes on "Onboarding drop-off at day 3" — the top-voted weakness. - **Board 2 — "Where we want to be in 12 months"** (`freeform_brainstorm`): - Heading: "Where we want to be — May 2027" - Elements: 11 sticky notes grouped into "Product", "Customers", "Team", "Financials". Examples: "Mid-market is 40% of new ARR", "Onboarding completion ≥ 70% by day 7", "Engineering team at 24, hiring is closed", "Profitable by Q3 2027". - **Board 3 — "What gets in the way"** (`fishbone_diagram`): - Heading: "Obstacles — fishbone" - Five spines: People (3 items: "Hiring takes 11 weeks", "Onboarding new hires takes 6 weeks"), Process (4 items: "Sprint planning is 3 hours weekly", "Code review queue averages 18 hours"), Tools (2 items: "CI is flaky", "Observability spend is up 80%"), Customers (3 items: "Mid-market sales cycle is 4 months", "Procurement is custom for every deal"), External (3 items: "FX headwinds", "Competitor X raised $200M", "Hiring market has tightened"). - Two dot-votes on "Hiring takes 11 weeks" — the second most-voted obstacle. - One explicit "Next steps" cluster at the bottom-right: "1. Run an onboarding-flow review by end of June. 2. Pilot a 6-week hiring process on the two open Eng roles. 3. Decide build-vs-buy on observability by end of Q3." **Sample parsed elements (a few illustrative):** - Element id `b1-e-007`, type `sticky_note`, text_verbatim "Onboarding drop-off at day 3", sticky_colour `pink`, emphasis `circled` (the team circled it during the dot-vote), dot_vote_count 3, group_id `b1-g-weaknesses`, text_confidence 0.94. - Element id `b1-e-014`, type `sticky_note`, text_verbatim "When do we hire VP Eng?", sticky_colour `yellow`, emphasis `none`, group_id `b1-g-open-questions`, text_confidence 0.91. - Element id `b3-e-021`, type `freeform_text`, text_verbatim "1. Run an onboarding-flow review by end of June.", emphasis `none`, group_id `b3-g-next-steps`, text_confidence 0.87. **Sample multi-board flow:** - `SessionFlow.relationship = sequential_process` - `boards_in_order = ["b1", "b2", "b3"]` - `insert_title_slide = true` - `insert_closing_slide = true` - `closing_slide_reason = "Dot-votes detected on boards 1 and 3; explicit next-steps cluster on board 3"` - `flow_inference_confidence = 0.86` **Sample speaker notes:** - Title slide note: "This slide opens the Q3 strategy off-site deck. The session ran on 2026-05-18 with eight attendees and produced three whiteboards: current state, twelve-month vision, and obstacles." - Board 1 (Where we are) note: "This board organises the team's view of the current state into three clusters: Strengths (six green sticky notes), Weaknesses (five pink sticky notes), and Open Questions (three yellow sticky notes). 'Onboarding drop-off at day 3' received three dot-votes, the highest tally on this board." - Closing slide note: "This slide summarises the highest-voted items across the session and the team's explicit next steps. 'Onboarding drop-off at day 3' received three dot-votes on board 1; 'Hiring takes 11 weeks' received two dot-votes on board 3. The next-steps cluster on board 3 lists three actions for the coming quarter." **Sample slide redraw outputs:** - Title slide: large display heading "Q3 Strategy off-site", subtitle "May 18, 2026 · 8 attendees · 3 boards", clean off-white background, indigo accent rule beneath. - Board 1 redraw: three columns labelled Strengths / Weaknesses / Open Questions; sticky notes in their correct colour (green / pink / yellow); the "Onboarding drop-off at day 3" pink sticky has three small filled circles next to it indicating the dot-votes; clean sans-serif typography rendering each sticky's text character-for-character. - Board 3 redraw: a clean fishbone diagram with five spines (People, Process, Tools, Customers, External); each contributing factor listed on its spine; the "Next steps" cluster in a separate box bottom-right with three numbered items rendered verbatim. **Sample input artefacts (for the build to demonstrate):** - A 4032×3024 photograph of a conference-room whiteboard covered in marker writing and sticky notes, slight glare in the top-right corner. - A 4032×3024 photograph of a flipchart on an easel with a hand-drawn 2x2 matrix in blue marker. - A 4032×3024 photograph of a sketchpad page with a fishbone diagram in pen. - A photograph of a whiteboard with a half-erased word ("Custmr Onbo——") in one corner. **Sample voice copy:** - Onboarding: "Drop your whiteboard photos. We'll build the deck." - Upload empty state: "No photos yet. Drag photos here, or tap to choose." - Parsing: "Reading your whiteboards…" / "Tracing the arrows in board 2…" / "Counting dot-votes on board 3…" - Generating: "Redrawing board 1…" / "Generating speaker notes for slide 4…" / "Writing to Google Slides…" - Generated confirmation: "Your deck is ready — 12 slides in your Drive. Open in Slides ↗" - No-invention reassurance (inline near the generate button): "We won't add anything that isn't on your whiteboards. If we couldn't read something, we'll show the original." - Verification panel toggle: "Show original alongside" - Mismatch report: "Something on this slide wasn't on the whiteboard? Tell us — we'll tighten the prompt." - Low-confidence parse note: "We weren't sure about this element. Tap to see what we considered." ## 9. Media & assets - **Hero image (landing screen):** A photographed-looking shot of a conference-room whiteboard covered in marker writing and sticky notes at the end of a meeting, soft overhead light, no people. Generate via Nano Banana 2 with a prompt emphasising "a conference-room whiteboard wall, end-of- meeting state, covered in black-marker writing organised in three clusters, yellow and pink sticky notes grouped in the centre, slight glare on the top-right, soft overhead fluorescent light, no people in frame, slight asymmetry, warm imperfect lighting". - **App icon / wordmark:** Set in the display sans. A simple arrow-graphic ligature between "Whiteboard" and "Slides". No emoji. No beer mugs. - **Empty-state illustration:** A simple line drawing of a whiteboard with one sticky note in the corner. Hand-drawn aesthetic. Generate once at build time via Nano Banana 2 (`gemini-3.1-flash-image`), 1:1 WebP at 1024×1024, prompt: "single hand-drawn ink line illustration of a wall-mounted rectangular whiteboard with a thin frame, one small square sticky note in the top-right corner with a single squiggle on it, off-white paper background, slight pen imperfection, no shading, no colour fill, no text, no commercial branding". Ship as a seed asset at `/public/samples/empty-state-whiteboard.webp`. - **Demo photos:** Generated per the prompts in section 8a — Nano Banana 2 (`gemini-3.1-flash-image`) prompts for "a flipchart on an easel with a hand-drawn 2x2 matrix in blue marker, four boxes labelled, late afternoon light", "a sketchpad open on a desk with a fishbone diagram in pen, eight items on five spines, warm desk lamp". Ship as seed assets at `/public/demo/.webp`. - **Slide-style reference guides:** 14-image style guides for Nano Banana Pro — one per (board_type × redraw_style) combination — kept in `/public/style-guides/-/01..14.webp` (1024×1024 WebP each). Board types: `whiteboard`, `flipchart`, `sketchpad`, `sticky_wall`. Redraw styles: `clean_corporate`, `warm_pencil`, `bold_marker`. 4 × 3 = 12 combinations × 14 references = 168 seed files. Generate each via Nano Banana 2 (`gemini-3.1-flash-image`) with a per- combination prompt of the form: "single reference image for redrawing a in style — clean professional slide composition, generous whitespace, no hand-written text content, just typographic placeholders; palette and texture; aspect ratio 16:9; no people, no commercial branding". Each combination's 14 references should vary subject (KPIs, bullets, 2×2 matrix, fishbone, timeline, flow, pyramid, funnel, table, quote card, chart, hero, agenda, conclusion) so the style guide is robust. - **Stock fallbacks:** If image generation fails, fall back to a single sample photographed whiteboard from `/public/samples/sample-whiteboard.jpg` (3:2 WebP, 2048×1365 — ship as a seed asset; recreate via Nano Banana 2 (`gemini-3.1-flash-image`) with the prompt: "photographic conference-room whiteboard wall, end-of-meeting state, covered in black-marker writing organised in three clusters, yellow and pink sticky notes grouped in the centre, slight glare on the top-right, soft overhead fluorescent light, no people in frame, slight asymmetry, warm imperfect lighting, no commercial branding"). Never to a generic AI-stock-photo of a generic office. - **Generated imagery:** prefer Nano Banana 2 for hero / empty- state imagery and Nano Banana Pro for slide redraws. Prompt for warmth, asymmetry, and slight imperfection — avoid the glossy 'AI render' look on the hero. - **Optimisation:** WebP/AVIF, `loading="lazy"`, explicit `width`/`height` to prevent layout shift. The 4K slide redraws are stored at full resolution in Firebase Storage and served as a 1920×1080 WebP variant for the deck-preview thumbnails. - **Icons:** `lucide-react` for UI. Use sparingly — never decorative-only. ## 10. Interactivity & states - Every interactive element has hover, focus, active, and disabled states. - Forms validate inline and show specific error messages (not "Invalid input"). "Photo too large — please use a photo under 12 MB" is the right shape. - 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: "Drag photos here, or tap to choose", "Open Google Slides", "Regenerate this slide" — never a generic "Add your first item". - Smooth scroll for in-page anchors. - All AI-generated content streams in token-by-token where supported, with a clear "thinking…" indicator before content starts arriving. For image-generation calls (slide redraws), show a progressive placeholder skeleton matching the slide aspect ratio. - If an AI call fails, show a calm, specific error ("We couldn't generate slide 5 — the parse confidence was low. Want to review the parsed elements and retry?") and offer retry. - Low-confidence parsed elements show as faintly underlined; tapping reveals the parser's alternatives ("we read this as 'Hiring' — could also be 'Hiking' or 'Hirinq'"). - Unreadable regions show as a grey-shaded box on the parse- review overlay; tapping shows the original photo crop full-size so the user can re-type the content into the parse-edit step. - The "Open in Google Slides" action opens the freshly-written Slides file in a new tab. If the no-OAuth Workspace path is unavailable, fall back to a "Download as .pptx" with a clear inline explanation. - The "Report mismatch" action surfaces a small modal asking the user to describe what's wrong; the report is logged to Firestore and triggers a future prompt-tightening review. - The verification-panel "Show original alongside" toggle crossfades the original photo and the redrawn slide; the transition takes 240 ms with `prefers-reduced-motion` falling back to instant. - The no-invention reassurance copy near the generate button is non-dismissable inline text (not a tooltip) — it is the load-bearing rule and deserves persistent visibility. ## 11. Tech & responsive requirements - **Stack:** React + TypeScript + Tailwind CSS. Functional components + hooks. Use Shadcn UI primitives where appropriate. Slide-preview rendering via standard `` with responsive sizing (the 4K redraws come back as image URLs, not vector data, so client-side editing is intentionally limited — users edit in Slides after generation). - **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 parse / flow / speaker-notes / title / closing synthesis, `gemini-3-pro-image` for slide redraws, and `gemini-3.1-flash-image` for hero / empty-state imagery. Set `thinkingLevel` explicitly per call. Omit `thinkingConfig` on image-generation calls. - **Database:** Firestore (auto-provisioned by AI Studio Build). Show the seed session on first launch. - **Auth:** Firebase Auth — Google sign-in by default; Apple sign-in next to it with the Workspace-only caveat noted. - **Storage:** Firebase Storage for original whiteboard photos (preserved at upload quality) and for generated 4K slide images. Pre-signed URLs only. Photos are server-side re-uploaded to the Gemini Developer API Files API for the parse call — the resulting `files/*` resource name (e.g. `files/abc123xyz`) is passed via `fileData.fileUri` to `generateContent`, NOT a Firebase Storage public URL, NOT a `gs://` URI (that's Vertex AI). - **Workspace integration:** post-I/O 2026 no-OAuth forwarder preferred; fallback OAuth client ID + scopes (`https://www.googleapis.com/auth/presentations`, `https://www.googleapis.com/auth/drive.file`) if the forwarder is not available. - **Mobile-first.** Verify layouts at 375 px (iPhone SE), 768 px (iPad), 1024 px, 1440 px+. The photo capture and parse- review steps must work well on a phone — users will often take the photos and run the parse on the same phone during the walk back to their desk. - 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 deck-preview view so the user sees each slide appear as it completes generation. - Optimistic UI on writes; reconcile on response. - Chunk image uploads to Firebase Storage so a slow connection can still complete; show per-file progress. ## 12. Accessibility (WCAG 2.2 AA) - Semantic HTML — `header`, `nav`, `main`, `section`, `article`, `footer`. - All interactive controls reachable by keyboard with a visible focus ring. - Color contrast ≥ 4.5:1 for body, 3:1 for large text and UI components. Sticky-note colours in the parsed-element overlay use a darker text colour to ensure contrast. - All images have meaningful `alt` text. The original whiteboard photos have `alt` describing the artefact ("a conference-room whiteboard at the end of the 2026-05-18 Q3 strategy off-site, three clusters of sticky notes visible, slight glare on the top-right"). The generated redrawn slides have `alt` summarising the slide content (derived from the speaker note). - Form fields have associated `