# 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 `