Studio -> Magazines: library, contents tree grouped by section with
confidence/pages/listening-time/audio status, the Stage D review
screen (tick/untick layout blocks, correct title/author, approve or
reject), a reflowed article view with click-to-seek sentence
highlighting, a linked original-page view showing the source bounding
box, and a podcast panel (2-3 host pickers, script generation,
turn-by-turn playback).
Wired into the existing lazy-module system (module-manifest.json,
loader.js, nav.js) rather than a separate app. Reuses /api/tts-preview
for both narration and podcast turns -- no new audio pipeline.
Fixes found building this:
- The pane toggle used role="tab" without the full ARIA tabs pattern
(aria-selected, roving tabindex, arrow-key nav) -- fails WCAG 2.2 AA
and hid the buttons from role-based tooling. Replaced with a button
group + aria-pressed, matching the plain-click interaction it
actually has.
- Print layout cues ("(continued on page 8)") were being sent to TTS
verbatim -- narration would have spoken the parenthetical aloud.
Stripped from the narration copy only; layout.json keeps the
original for the review screen.
- has_podcast now comes from the articles listing so opening an
article without a generated script skips a doomed GET instead of a
routine 404 on every open.
Verified against the real API contract with Playwright (library,
review pre-selection, approval, sentence highlighting + PDF-page
linking, podcast approval gate, zero console errors across the flow)
and manually end-to-end against a real ingested fixture, screenshots
included. Full suite: 94 Python tests, 33/34 desktop Playwright tests
(one unrelated pre-existing flaky DOM-count assertion).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3.6 KiB
Project map
Living index of where things are and what state they are in. Update in the same commit as the change it describes.
Current phase
Magazine projects, P0 complete, P1 mostly complete (frontend reachable at Studio → Magazines). Audiobook/Rehearser/voice-library features are mature and in daily use.
Deployment
Self-hosted Docker on the DGX Spark, port 7890, image
ghcr.io/martin-b78/tts-voice-creator-clone-and-design-2.
Python and static files are bind-mounted, so code changes need only a container
recreate; rebuild and push only when Dockerfile or requirements.txt change.
Layout
| Path | Role |
|---|---|
server.py |
FastAPI app, router registration, auth middleware |
core/ |
domain logic — config, database, audio, voice, routing, magazine |
routes/ |
HTTP boundary, one module per feature area |
static/js/ |
frontend, one module per feature area (no build step for most) |
tests/ |
test_*.py (unittest) and *.spec.js (Playwright) |
architecture/ |
SOPs for subsystems whose design is not obvious from code |
workflows/ |
ComfyUI graphs for character portrait generation |
Feature areas
| Area | Server | Frontend | State |
|---|---|---|---|
| Voice library / clone / design | routes/library.py, core/voice.py |
library.js |
mature |
| Read Aloud (books, PDFs) | routes/reader.py |
reader.js |
mature |
| Audiobook casting | routes/conversation.py |
audiobook*.js |
mature |
| Rehearser | routes/rehearsals_db.py |
rehearser-*.js |
mature |
| Studio (phase wizard) | — | studio.js |
mature |
| TTS engines / routing | routes/tts.py, core/routing.py |
engines.js |
mature |
| STT | routes/stt.py |
— | mature |
| Magazines | core/magazine.py, routes/magazine.py |
magazine.js, s-magazine.html |
P0 + P1 |
Data model
Documents (books and magazines alike) live under
<CONFIG_DIR>/reader_library/<doc_id>/, discriminated by meta.json → kind:
kind: "pdf"/ text — Read Aloud book. Text is extracted in the browser and not persisted;meta.jsonholds counts and a flatidxresume position.kind: "magazine"— addslayout.json,articles.json,pages/,images/,podcast/, and a per-articleprogressmap. See architecture/magazine-ingestion.md.
Settings, presets, routes and the SQLite database also live under CONFIG_DIR,
which is the writable volume.
Environment
| Variable | Purpose |
|---|---|
CONFIG_DIR |
writable state (settings, library, database) |
VOICES_DIR / OUTPUT_DIR |
voice WAV library |
TTS_CONTAINER_NAMES |
containers restarted after a voice change |
DOCKER_HOST |
points at the socket proxy; the raw socket is not mounted |
llm_url / llm_model / llm_api_key |
settings.json, OpenAI-compatible LLM |
Pending decisions
- Container registry: ghcr costs quota against a private package (≈427 MB per push, 500 MB free on the Free plan). Local registry vs. ghcr vs. no re-pull is unresolved — the compose files currently point at ghcr.
- Magazine P1 scope: sentence-level highlighting confirmed as the starting point; word-level deferred to P2.
Known limitations
giteabackup remote is 157 commits behindorigin;maintracksgitea.- Magazine sidebar detection is not implemented (P2).
- No
npm run qaaggregate command yet; Python tests run viaunittest, browser tests viaplaywright test.
Next logical step
Magazine P1 remainder: article images/page crops shown during playback, a cross-article "play all" queue, and OCR-engine dispatch to the KDL/Baidu parsers (see architecture/magazine-ingestion.md). Otherwise P2 per TODO.md.