16 KiB
Date, Author, Tags, Status
| Date | Author | Tags | Status | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 2026-07-23 | Hermes + Adolfo (interview) |
|
planned — ready to scaffold |
Kids Book Studio — Final Plan (Interviewed)
Decisions Locked
From interview 2026-07-23:
- Users: You + Alicia reviewing/approving (PB native email/pass auth)
- Review workflow: Simple — approve / needs-rework + one comment per page (no threads)
- Data backend: PocketBase on .110 (family BaaS direction). All family custom software on PB.
- App hosting: Custom Svelte editor as Docker container on .110 next to Instatic sites, Caddy route
books.reynafamily.comorstudio.reynafamily.com. NOT inside Instatic CMS. - Generation worker: Python service on Mac mini .102 polling PB for
status=queuedjobs — runs Codex imagegen + local TTS, uploads results back to PB files. - Frontend stack: SvelteKit + Tailwind, Playfair for titles, Inter body, Mono for prompts. VOUS/Hillsong clean serif headers.
- Seed data: Auto-import Grace/Poppy existing package (21 pages) as first book
- Image gen: Codex imagegen (proven) + BYOK configurable model (OpenAI / Gemini / etc via API key in PB settings collection)
- Audio gen: Moving OFF Voicebox. Use existing reyna-cli TTS abstraction (
espeak-ng→say→edge-tts→REYNA_TTS_COMMAND) + small local model on .110 Docker (Piper/Kokoro) as future-proof. No Voicebox dependency. - Audio per page: One narration clip per page for now, schema designed for multi-clip dialogue later (clips[] array forward-compatible)
- Text: Support full caption + shorter spoken cut + edit history (caption_history, audio_text_history) for device timing tweaks
- Images: 320x240 only for now (ESP32 native). Full-res later as extra rendition
- New books: Start with Grace/Poppy only, then New Book wizard
What We're Building
A web editor to manage kids books — pages with script, generated audio, images, and the prompts used — so you can iterate on specific pages and make new books, with Tactility export.
System Architecture
┌─────────────────────────────────────────────────────────┐
│ .110 FamReynaServer │
│ ┌──────────────┐ ┌────────────────────────┐ │
│ │ PocketBase │◄─┤ Svelte Editor (Docker) │ │
│ │ :8095 or new │ │ books.reynafamily.com │ │
│ │ - books │ │ - Library grid │ │
│ │ - pages │ │ - Book strip + reorder │ │
│ │ - assets │ │ - Page 3-col editor │ │
│ │ - gen_jobs │ │ - Device 320x240 prev │ │
│ │ - users/auth │ │ - Export ZIP/PDF │ │
│ │ - settings │ │ │ │
│ └──────┬───────┘ └────────────────────────┘ │
│ │ ▲ │
│ │ │ (PB files + realtime) │
└─────────┼─┼──────────────────────────────────────────────┘
│ │
│ │ polls jobs, uploads results
│ │
┌─────────┼─┼──────────────────────────────────────────────┐
│ .102 Mac mini │
│ ┌──────▼──────┐ │
│ │ Gen Worker │ - Codex imagegen skill │
│ │ Python svc │ - reyna-cli TTS (edge-tts/fallback) │
│ │ │ - future: Kokoro local on .110 │
│ └─────────────┘ - resize + transcode 320x240 / mp3 │
└──────────────────────────────────────────────────────────┘
Network:
- PB reachable via Tailscale + Caddy reverse proxy
- Worker polls PB via private network (no public exposure needed)
- Assets stored as PB files (images, audio) — accessible via PB file URL
PocketBase Collections (schema)
books
- id (PB auto), slug (text unique), title (text), author (text), created (autoDate)
- style_bible: json { art_style, continuity_rules[], color_palette[], reference_images[] file[] }
- display: json { width, height } default 320x240
- default_voice: json { profile, language } e.g. {profile: "en-US-AriaNeural", language:"en-US"}
- cover_page_id -> pages (relation, cover thumbnail)
- status_overview: computed via pages (not stored)
pages
- id, book (relation -> books), page_number (number), status (select: draft|needs-rework|approved|generating|queued)
- caption (text, rich — full text), caption_history (json, array of {text, edited_at, by})
- audio_text (text — spoken cut, defaults to caption if empty), audio_text_history (json)
- review_comment (text), reviewed_by (relation users), reviewed_at (date)
- notes (text — author private notes)
- original_page (number optional, for imported books that skipped numbers)
- export_include (bool default true)
- Future: clips[] json for multi-speaker
image_assets (one active per page, history preserved via versioned files + history JSON)
- id, page (relation -> pages), is_active (bool)
- file (file, image) — PB stores both original gen + 320x240 variant?
- Actually: store original file, gen job creates 320x240 as second file field thumb or via hook
- prompt (text), negative_prompt (text), style_bible_applied (bool)
- reference_pages (relation pages[]), generation_params (json: {model, size, seed, provider})
- prompt_version (number auto-increment per page), notes (text)
- generated_at, generated_by (user)
- Simple: to keep history, we don't delete old image_assets, just flip is_active. Page shows history slider.
audio_assets
- id, page (relation -> pages), is_active (bool)
- file (file, audio mp3/wav), duration_sec (number)
- text_used (text — snapshot of audio_text at generation time)
- voice_profile (text), language (text), engine (text: edge-tts|piper|kokoro|custom)
- tts_params (json)
- generated_at, generated_by
generation_jobs (queue)
- id, page (relation pages), type (select: image|audio), status (queued|running|done|failed)
- input (json): {prompt, negative, style_bible, voice, text, etc}
- provider (text: codex-imagegen|openai|gemini|edge-tts|piper|kokoro)
- result_image / result_audio (relation to image_assets/audio_assets when done)
- error (text), attempts (number)
- created, started, finished (dates)
settings (singleton-ish, or one per family)
- id, image_providers (json: {openai_key_encrypted?, gemini_key? ...}) — or use env, not PB at first
- default_image_provider, default_audio_engine
- style_presets (json): preset name → {art_style, continuity_rules}
users (PB built-in auth collection: you + Alicia)
Job Flow
- User edits prompt in Page Editor → clicks Generate Image
- Frontend creates generation_jobs record (status=queued, input={prompt:+style_bible, page, provider})
- Frontend subscribes to job realtime → shows spinner on page
- Worker on .102 polls PB collection generation_jobs filter=status=queued (or realtime subscribe)
- Worker marks running, executes:
- For image: calls Codex imagegen OR OpenAI/Gemini depending on provider → saves PNG → resizes to 320x240 thumb → uploads to PB as new image_assets file
- For audio: calls reyna-cli tts.py synthesize_wav() → transcodes to mp3 16kHz 24k mono → uploads as audio_assets
- Worker creates image_assets/audio_assets record (is_active=false initially, then activate after user approval, or auto-activate if generation is direct iteration — TBD: auto-activate with history preserved)
- Worker updates job to done with relation to new asset
- Frontend realtime gets event, refreshes page, shows new image with history slider
- Alicia reviews: changes status to approved or needs-rework + comment
For MVP: steps 5 auto-activates new asset (flips previous is_active=false, new is_active=true), but history still full.
Frontend Screens (detailed)
Route / (Library)
- Grid of books: cover = first page active image_assets file, else placeholder. Title, author, pages count, % approved bar, last edited.
- Button: New Book → modal: title, slug (auto from title), author default Reyna Family, style preset selector (Grace-Poppy watercolor, Minimal, New custom), import tactility zip? toggle.
- Each card: Open, Export zip, Play Preview
- PB query: books list with expand cover_page_id -> image_assets
Route /books/:slug
- Header: title, edit pencil for style bible, % approved, Play Preview (autoplay browser reader 320x240 + audio), Export button.
- Page strip: horizontal scrollable, 160x120 thumbs (active image per page), status dot (green approved, yellow needs-rework, gray draft, blue generating). Drag to reorder (updates page_number). Click to open page editor. Plus button to add page.
- Style Bible panel (collapsible right drawer): art_style textarea, continuity_rules as editable list (add/remove), color_palette tags, reference image uploader (PB file).
- Bulk actions: checkboxes on strip → bulk status change, bulk prompt find/replace, bulk re-generate audio.
Route /books/:slug/pages/:pageNumber (Page Editor — core)
- Three columns:
- Left (Script): caption textarea with char count + history icon (show caption_history). audio_text textarea (with Fill from caption button + auto-pace helper that calls edge-tts? simple: button to copy caption and add breaks). Play audio button (uses active audio_assets file URL). Voice profile selector (text input or select from settings list of edge-tts voices). Duration display from active audio. Review section at bottom of this column: status select, review_comment.
- Center (Image): large preview 320x240 framed + expand to full original. Prompt textarea (Monospace, with toggle: "prepend style bible" on/off — when on, UI shows combined effective prompt). Negative prompt. Reference pages multi-select. Generate button (with provider selector: Codex / OpenAI / Gemini). History carousel below: scrollable thumbs of all image_assets for this page (ordered by created), each shows prompt tooltip, click to make active (swaps). Restore button.
- Right (Meta): Page number + up/down reorder, original_page mapping, notes textarea, export_include toggle, created/updated meta, generation job log (last job status/error).
Component: Device Preview Modal
- 320x240 black frame mimicking ESP32, image displayed, Play/Prev/Next buttons matching Tactility Book Player. Auto-advance when audio finishes (same as device).
Component: Export Modal
- Formats: Tactility v1 ZIP (manifest.json + pageXXX.png + mp3). Options: only approved? include draft? file naming page001 vs page_XXX. Download button.
Importer — Grace/Poppy Seed
Script scripts/import_grace_poppy.py:
- Reads
~/output/tactility-book-packages/grace-poppy-tactility-book-example/books/grace-poppy/manifest.json - Creates book record slug=grace-poppy
- For each page in manifest, in order:
- Create page record with caption from manifest, audio_text = caption, status = approved (since already reviewed), original_page, page_number sequential
- Copy image file to PB file upload as image_assets (prompt empty, notes="imported, prompt unknown", is_active true)
- Copy audio file to PB file upload as audio_assets (is_active true, voice_profile=Aiden, duration_sec from manifest)
- Also sets style_bible with known Grace continuity rules.
Run once on first deploy.
Tech Stack Final
- Backend: PocketBase (Go binary, Docker on .110, :8095, Caddy /books api)
- Frontend: SvelteKit + Tailwind, Vite. Hosted as Docker container on .110 next to Instatic (separate Docker Compose service), Caddy reverse proxy to
books.reynafamily.com(orstudio.reynafamily.com). Builds talk directly to PB via JS SDK @pocketbase/js-sdk. - Auth: PB native users collection (email/pass). Two users: you, Alicia. Frontend has login page, guards Library route.
- Realtime: PB realtime subscriptions for jobs and pages status.
- Worker: Python 3.11, runs on Mac mini .102, deps: pocketbase client, pillow, ffmpeg, edge-tts, codex CLI available. Loop: poll every 5s for queued jobs. Could also move to .110 later with Piper local Docker for audio.
- Storage: PB file storage (local filesystem .110). Images and audio files served via PB file URL with auth.
Project Layout (to scaffold)
kids-book-studio/
README.md
docker-compose.yml (frontend + optional worker-for-local-dev)
pocketbase/
Dockerfile / config
migrations/ (PB JS migrations for collections)
scripts/import_grace_poppy.py
frontend/
package.json, SvelteKit app
src/routes/...
src/lib/pocketbase.ts
src/lib/components/...
static/
worker/
requirements.txt
worker.py (poll loop)
tts_engine.py (wraps reyna-cli tts.py + piper/kokoro)
image_engine.py (codex + openai + gemini providers)
asset_utils.py (resize, transcode)
Caddyfile.snippet (for .110 Caddy)
docs/
plan.md (this file)
schema.md
PB Collections JSON Proposal (draft, to convert to migration)
See code above for fields. Will generate JS migration file for:
- books, pages, image_assets, audio_assets, generation_jobs, settings
Implementation Phases (revised)
Phase 0 — PB + Skeleton
- Create kids-book-studio repo, docker-compose, PB collections migration
- Frontend SvelteKit scaffold with PB SDK, login page, Library grid (lists books)
- Importer script run, verify Grace/Poppy shows in Library with 21 pages
Phase 1 — Read-only Book + Page Editor
- Book Overview strip with thumbs, Style Bible editor drawer
- Page Editor 3-col read-only/active asset display, history display
- No generation yet, but schema ready.
Phase 2 — Generation Worker
- Python worker on .102 that polls PB generation_jobs, executes edge-tts audio + optionally Codex imagegen
- Frontend Generate buttons + realtime spinner + history updates
- Image history versioning, audio history.
Phase 3 — Review + Export
- Alicia review flow: status + comment, PB realtime updates
- Export Tactility ZIP with correct manifest.json format (existing format compatible with Book Player app)
- Device Preview modal 320x240 autoplay
- Drag reorder pages, bulk actions.
Phase 4 — Polish + New Book
- New Book wizard, style presets, high-res renditions, reMarkable PDF export
- BYOK provider keys UI (settings page), local Piper/Kokoro on .110 Docker
- Direct push to ESP32 fleet optional
Future (not now)
- Multi-clip dialogue per page
- Character sheet with ref images
- Translation tracks (keep image, different audio language)
- LLM auto-pace performance text, consistency check
- Family review with kid read-only view
Open Risks
- Codex imagegen reliability from worker (has been flaky, needs -o file pattern from brain doc)
- PB file size for audio/images — okay, small assets
- Caddy route for new subdomain — need to add via api script pattern (see bin/caddy-api)
- Worker auth: PB service account token, not user token
Next Step (Ready to Build)
When user says "build it", start Phase 0: scaffold repo, PB docker + migration, frontend login + library + import Grace/Poppy.