Skip to content

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

LayerDetails
Firebase Projecttheatre-management-9b387 — the single project for all On Book apps
AuthFirebase Auth. Same user pool. Existing Pro users are recognized instantly in Rehearsal. New Rehearsal users appear in the shared /users/{userId} collection.
FirestoreSame database. Rehearsal reads from production-level collections (for project-linked scripts) and writes to personal-namespace collections (see Data Boundaries).
Cloud StorageSame bucket. Rehearsal scopes personal uploads under personalScripts/{userId}/.
Cloud FunctionsDeployed 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:

typescript
// 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 / PathPurposeAccess Pattern
projects/{projectId} (root doc)Production name, subscription statuscollectionGroup('members') → navigate to parent project
projects/{projectId}/members/{userId}Verify membership for project-linked scriptsRead own membership doc
projects/{projectId}/data/structureActs, Scenes, Characters for project scriptsonSnapshot — same shard as Pro
Cloud Storage: projects/{projectId}/system/scriptBlocks.json.gzFull script block data for project-linked rehearsalsDownload 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.gzExtracted blocks for personal scriptsDownload once, cache locally
users/{userId}/subscriptions/voicePersonal subscription status (RevenueCat)One-time read at startup

What Rehearsal Writes

Collection / PathPurposeAccess Pattern
users/{userId}/rehearsalSessions/{sessionId}Session records with stats and line notesWrite on session end (Voice/Flashcard only — Italian Run is exempt)
users/{userId}/personalScripts/{scriptId}Metadata placeholder during uploadWrite on upload initiation
Cloud Storage: personalScripts/{userId}/uploads/{scriptId}.pdfRaw PDF upload → triggers processPersonalScriptWrite during upload flow

What Rehearsal Never Touches

Rehearsal has no write access to any production-level data:

  • projects/{projectId}/data/* — cast, schedule, props, sound, production
  • projects/{projectId}/members/* (except reading own doc)
  • projects/{projectId}/files/* — file repository
  • script_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        │
                                          └─────────────────────────┘
  1. The stage manager uploads a PDF through Pro's Script Import feature.
  2. Pro's pipeline (Document AI OCR → Gemini extraction) produces ScriptBlock[] and ShowStructure, saved to Firestore and Cloud Storage.
  3. Rehearsal discovers available project scripts via the user's membership in the members subcollection. It reads structure/current for acts, scenes, and characters, then downloads scriptBlocks.json.gz from Cloud Storage.
  4. 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        │
                                               └─────────────────────────┘
  1. The actor uploads a PDF from inside Rehearsal.
  2. The file lands in Cloud Storage at personalScripts/{userId}/uploads/{scriptId}.pdf.
  3. The processPersonalScript Cloud 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)
  4. Rehearsal monitors the job via a real-time listener on personalScriptJobs/{jobId} and shows a processing indicator until status becomes ready.

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 PDFs

Key types shared across both pipelines:

typescript
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)

PropertyValue
TypeonCall (Firebase Callable)
Regionus-central1
Timeout540s
Memory2 GiB
StatusPROPOSED — see backend contract

Input:

typescript
{ scriptId: string; storagePath: string; title: string }

Output:

typescript
{ success: boolean; jobId: string; blockCount: number; characterCount: number }

Behavior:

  1. Validates the caller owns the script at the given storage path
  2. Runs Document AI OCR using the shared core/ocr-helpers.ts
  3. Passes OCR pages through core/extract.ts (Gemini batched extraction)
  4. Writes ScriptBlock[] to Cloud Storage as gzipped JSON
  5. Writes metadata (title, status, character list, characterHints) to Firestore at personalScripts/{userId}/scripts/{scriptId}
  6. 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:

  1. Org check: Query collectionGroup('members') for the user → navigate to the parent projects/{projectId} document → check subscription.isActive. If any active production has a subscription, the user gets org-bundled access at no extra cost.
  2. Personal check: Read users/{userId}/subscriptions/voice for a RevenueCat-managed flag. If present and not expired, the user gets personalVoice tier.
  3. Default: Free tier — Flashcard only.

Tiers

TierLabelVoice Access
freeFree❌ Flashcard only
orgBundledProduction Member✅ Included via Pro subscription
personalVoiceVoice 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

javascript
match /personalScripts/{userId}/scripts/{scriptId} {
  allow read, write: if request.auth != null && request.auth.uid == userId;
}

Personal Script Jobs

javascript
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

javascript
match /users/{userId}/rehearsalSessions/{sessionId} {
  allow read, write: if request.auth != null && request.auth.uid == userId;
}

Storage Rules

javascript
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.md

This file tracks:

  • Cloud Functions needed (name, types, behavior)
  • Firestore collections and schemas
  • Security rule requirements
  • Storage path conventions
  • Status per item: PROPOSEDAPPROVEDIMPLEMENTEDDEPLOYED

Workflow

  1. Rehearsal session identifies a need → Updates backend-requirements.md
  2. Switch to Pro workspace → Implement the change following Pro patterns
  3. Build verificationcd functions && npm run build
  4. Commit with prefixfeat(rehearsal): add processPersonalScript
  5. Deployfirebase deploy --only functions:processPersonalScript
  6. Update contract → Mark status as DEPLOYED

Breaking Changes

When Pro refactors shared infrastructure:

  1. Check backend-requirements.md for dependent features
  2. Prefix commit: refactor: [BREAKING-OBR] description
  3. Add migration notes to the contract

Commit Convention

bash
feat(rehearsal): add processPersonalScript cloud function
fix(rehearsal): correct personal script storage path
refactor(rehearsal): extract shared extraction core

Filter Rehearsal-related changes:

bash
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:

bash
# 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 run

Testing Script Pipeline Changes

  1. Make changes to functions/src/script-import/core/ in the Pro repo
  2. Build for type-checking: cd functions && npm run build
  3. Deploy the specific function: firebase deploy --only functions:processPersonalScript
  4. 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: