On Book Rehearsal Integration
This page documents how On Book Rehearsal (the AI-powered mobile line coach) integrates with On Book Pro's backend infrastructure. It is aimed at developers working on either codebase who need to understand the shared surface area.
Overview
On Book Rehearsal is a Flutter (Dart) app shipping on iOS and Android. It lets actors upload scripts, select scenes and characters, and rehearse lines via three modes: Voice Rehearsal, Italian Run, and Flashcard. Voice and Italian Run use the Gemini Multimodal Live API for real-time, full-duplex audio conversation. Flashcard is entirely offline-capable.
Architecturally, Rehearsal is a sibling consumer of the same Firebase project that powers On Book Pro. Both apps share authentication, Firestore, Cloud Storage, and the script extraction pipeline. They differ only in which collections they read/write and in how production-level features (scheduling, attendance, blocking, etc.) are surfaced — Rehearsal doesn't touch them.
┌──────────────────────┐ ┌──────────────────────┐
│ On Book Pro (Web) │ │ On Book Rehearsal │
│ React / Vite │ │ Flutter (iOS/Android) │
└──────────┬───────────┘ └──────────┬────────────┘
│ │
└──────────┬───────────────────┘
│
┌───────────▼────────────┐
│ Firebase Project │
│ theatre-management- │
│ 9b387 │
├────────────────────────┤
│ • Auth (shared users) │
│ • Firestore │
│ • Cloud Storage │
│ • Cloud Functions │
└────────────────────────┘Shared Infrastructure
| Layer | Details |
|---|---|
| Firebase Project | theatre-management-9b387 — the single project for all On Book apps |
| Auth | Firebase Auth. Same user pool. Existing Pro users are recognized instantly in Rehearsal. New Rehearsal users appear in the shared /users/{userId} collection. |
| Firestore | Same database. Rehearsal reads from production-level collections (for project-linked scripts) and writes to personal-namespace collections (see Data Boundaries). |
| Cloud Storage | Same bucket. Rehearsal scopes personal uploads under personalScripts/{userId}/. |
| Cloud Functions | Deployed from the on-book-pro repo. Rehearsal calls a dedicated processPersonalScript callable and benefits from the shared extraction core. |
Identity
Rehearsal adds a lightweight activation marker to the shared user document:
// In /users/{userId}
{
app: {
activated: true,
activatedAt: Timestamp
}
}This lets Pro detect which users also have Rehearsal installed, without granting any additional permissions.
Data Boundaries
What Rehearsal Reads
| Collection / Path | Purpose | Access Pattern |
|---|---|---|
projects/{projectId} (root doc) | Production name, subscription status | collectionGroup('members') → navigate to parent project |
projects/{projectId}/members/{userId} | Verify membership for project-linked scripts | Read own membership doc |
projects/{projectId}/data/structure | Acts, Scenes, Characters for project scripts | onSnapshot — same shard as Pro |
Cloud Storage: projects/{projectId}/system/scriptBlocks.json.gz | Full script block data for project-linked rehearsals | Download once, cache locally via Drift |
personalScripts/{userId}/scripts/{scriptId} | Personal script metadata (title, status, characters) | Real-time listener for processing updates |
Cloud Storage: personalScripts/{userId}/scripts/{scriptId}/scriptBlocks.json.gz | Extracted blocks for personal scripts | Download once, cache locally |
users/{userId}/subscriptions/voice | Personal subscription status (RevenueCat) | One-time read at startup |
What Rehearsal Writes
| Collection / Path | Purpose | Access Pattern |
|---|---|---|
users/{userId}/rehearsalSessions/{sessionId} | Session records with stats and line notes | Write on session end (Voice/Flashcard only — Italian Run is exempt) |
users/{userId}/personalScripts/{scriptId} | Metadata placeholder during upload | Write on upload initiation |
Cloud Storage: personalScripts/{userId}/uploads/{scriptId}.pdf | Raw PDF upload → triggers processPersonalScript | Write during upload flow |
What Rehearsal Never Touches
Rehearsal has no write access to any production-level data:
projects/{projectId}/data/*— cast, schedule, props, sound, productionprojects/{projectId}/members/*(except reading own doc)projects/{projectId}/files/*— file repositoryscript_jobs/*— production pipeline job tracking- Any activity log, attendance, or budget data
This hard boundary means Rehearsal can never corrupt a production's state.
Script Pipeline
Scripts reach Rehearsal through two paths:
Path A: Project-Linked Scripts
┌─────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Stage Mgr │────▶│ On Book Pro │────▶│ Firestore: │
│ uploads PDF │ │ Script Import │ │ structure/current │
│ in Pro │ │ Pipeline │ │ + GCS scriptBlocks │
└─────────────┘ └──────────────────┘ └─────────┬───────────┘
│
┌────────────▼────────────┐
│ On Book Rehearsal │
│ reads structure + │
│ downloads blocks │
└─────────────────────────┘- The stage manager uploads a PDF through Pro's Script Import feature.
- Pro's pipeline (Document AI OCR → Gemini extraction) produces
ScriptBlock[]andShowStructure, saved to Firestore and Cloud Storage. - Rehearsal discovers available project scripts via the user's membership in the
memberssubcollection. It readsstructure/currentfor acts, scenes, and characters, then downloadsscriptBlocks.json.gzfrom Cloud Storage. - The actor selects a character, scene range, and mode. Script data is cached locally via Drift (SQLite) for offline Flashcard use.
Path B: Personal Scripts
┌──────────────┐ ┌───────────────────────┐ ┌──────────────────────┐
│ Actor uploads│────▶│ Cloud Storage: │────▶│ processPersonalScript│
│ PDF in │ │ personalScripts/ │ │ Cloud Function │
│ Rehearsal │ │ {uid}/uploads/{id}.pdf │ │ (shared core) │
└──────────────┘ └───────────────────────┘ └──────────┬───────────┘
│
┌──────────────▼──────────┐
│ Writes to: │
│ • Firestore metadata │
│ • GCS scriptBlocks │
│ • characterHints │
└─────────────────────────┘- The actor uploads a PDF from inside Rehearsal.
- The file lands in Cloud Storage at
personalScripts/{userId}/uploads/{scriptId}.pdf. - The
processPersonalScriptCloud Function fires, running the same extraction core as production scripts (Document AI OCR → Gemini batched extraction), but:- Saves results to the personal namespace (
personalScripts/{userId}/scripts/) - Includes
characterHints(AI-extracted personality descriptions for voice profiles) - Skips production-level post-processing (no props, sound cues, or activity logging)
- Saves results to the personal namespace (
- Rehearsal monitors the job via a real-time listener on
personalScriptJobs/{jobId}and shows a processing indicator until status becomesready.
Shared Extraction Core
Both paths use the same extraction module:
functions/src/script-import/core/
├── extract.ts # Core pipeline: OCR pages → ScriptBlock[] + ShowStructure
├── types.ts # Shared interfaces: ScriptBlock, ShowStructure, ExtractionResult
├── ocr-helpers.ts # Document AI batch processing utilities
└── shard-discovery.ts # GCS shard stability detection for large PDFsKey types shared across both pipelines:
interface ExtractionResult {
blocks: ScriptBlock[];
structure: ShowStructure;
characterHints: Record<string, string>; // characterId → voice profile description
failedBatches: Array<{ batchNum; startPage; endPage; error }>;
}
type ScriptTarget = 'project' | 'personal';The ScriptTarget discriminator tells the pipeline where to write results. The extraction logic itself is identical — both targets produce the same ScriptBlock[] and ShowStructure shapes.
Cloud Functions
All Cloud Functions that serve Rehearsal are deployed from the on-book-pro repository under functions/src/. Rehearsal has no functions of its own.
processPersonalScript (Callable)
| Property | Value |
|---|---|
| Type | onCall (Firebase Callable) |
| Region | us-central1 |
| Timeout | 540s |
| Memory | 2 GiB |
| Status | PROPOSED — see backend contract |
Input:
{ scriptId: string; storagePath: string; title: string }Output:
{ success: boolean; jobId: string; blockCount: number; characterCount: number }Behavior:
- Validates the caller owns the script at the given storage path
- Runs Document AI OCR using the shared
core/ocr-helpers.ts - Passes OCR pages through
core/extract.ts(Gemini batched extraction) - Writes
ScriptBlock[]to Cloud Storage as gzipped JSON - Writes metadata (title, status, character list,
characterHints) to Firestore atpersonalScripts/{userId}/scripts/{scriptId} - Tracks job progress in
personalScriptJobs/{jobId}
Subscription Model
Rehearsal's voice modes (Voice Rehearsal + Italian Run) are gated. Flashcard mode is always free.
canAccessVoiceModes(userId):
return belongsToProjectWithActiveSub(userId)
|| hasPersonalVoiceSub(userId)Resolution Strategy
The SubscriptionService in Rehearsal resolves voice access through a two-step check implemented via Riverpod:
- Org check: Query
collectionGroup('members')for the user → navigate to the parentprojects/{projectId}document → checksubscription.isActive. If any active production has a subscription, the user gets org-bundled access at no extra cost. - Personal check: Read
users/{userId}/subscriptions/voicefor a RevenueCat-managed flag. If present and not expired, the user gets personalVoice tier. - Default: Free tier — Flashcard only.
Tiers
| Tier | Label | Voice Access |
|---|---|---|
free | Free | ❌ Flashcard only |
orgBundled | Production Member | ✅ Included via Pro subscription |
personalVoice | Voice Pro | ✅ Standalone via RevenueCat |
If an actor is removed from a production, bundled access revokes automatically because the membership query will no longer match.
Firestore Security Rules
All rules are deployed from on-book-pro/firestore.rules. Key additions for Rehearsal:
Personal Scripts
match /personalScripts/{userId}/scripts/{scriptId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}Personal Script Jobs
match /personalScriptJobs/{jobId} {
allow read: if request.auth != null && resource.data.userId == request.auth.uid;
allow create: if request.auth != null;
// Updates managed by Cloud Functions (Admin SDK)
}Rehearsal Sessions
match /users/{userId}/rehearsalSessions/{sessionId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}Storage Rules
match /personalScripts/{userId}/{allPaths=**} {
allow read, write: if request.auth != null
&& request.auth.uid == userId
&& request.resource.size < 50 * 1024 * 1024; // 50MB
}Key principle: All personal data is strictly owner-isolated. No other user can read or write another user's personal scripts, sessions, or uploads.
Cross-Repo Development Workflow
Because Rehearsal and Pro share backend infrastructure but live in separate repos, changes follow a contract-driven workflow.
The Contract
The single source of truth for what Rehearsal needs from the backend:
on-book-rehearsal/.agent/contracts/backend-requirements.mdThis file tracks:
- Cloud Functions needed (name, types, behavior)
- Firestore collections and schemas
- Security rule requirements
- Storage path conventions
- Status per item:
PROPOSED→APPROVED→IMPLEMENTED→DEPLOYED
Workflow
- Rehearsal session identifies a need → Updates
backend-requirements.md - Switch to Pro workspace → Implement the change following Pro patterns
- Build verification →
cd functions && npm run build - Commit with prefix →
feat(rehearsal): add processPersonalScript - Deploy →
firebase deploy --only functions:processPersonalScript - Update contract → Mark status as
DEPLOYED
Breaking Changes
When Pro refactors shared infrastructure:
- Check
backend-requirements.mdfor dependent features - Prefix commit:
refactor: [BREAKING-OBR] description - Add migration notes to the contract
Commit Convention
feat(rehearsal): add processPersonalScript cloud function
fix(rehearsal): correct personal script storage path
refactor(rehearsal): extract shared extraction coreFilter Rehearsal-related changes:
git log --oneline --grep="rehearsal"Developing Locally
Running Both Apps Against the Same Backend
Both apps connect to theatre-management-9b387 by default. No emulator configuration is needed for basic integration testing:
# Terminal 1: On Book Pro (localhost:5174)
cd "F:\App Projects\on-book-pro"
npm run dev
# Terminal 2: On Book Rehearsal (iOS Simulator or Android Emulator)
cd "F:\App Projects\on-book-rehearsal"
flutter runTesting Script Pipeline Changes
- Make changes to
functions/src/script-import/core/in the Pro repo - Build for type-checking:
cd functions && npm run build - Deploy the specific function:
firebase deploy --only functions:processPersonalScript - In Rehearsal, upload a test PDF and observe the processing pipeline
Safety Rules
- Never deploy Rehearsal backend changes without building first (
npm run build) - Always run existing Pro tests — Rehearsal functions share infrastructure
- Firestore/Storage rules are shared — test both apps' access patterns
- Personal script data is fully isolated — changes to personal paths cannot affect production data
Architecture Reference
For deeper detail on the systems Rehearsal depends on:
- Script Ingestion Pipeline — OCR, Gemini extraction, context caching
- Cloud Sync Architecture — Firestore sharding, offline patterns
- Authentication — Auth flow and user model
- State Management — How Pro manages shared state (for context)