Update CLI command spawning to explicitly set the working directory to the workspace root. This ensures subprocesses execute in a consistent environment, improving reliability in monorepo setups.
Adopt a consistent findWorkspaceRoot helper across bootstrap, storage, and database modules to reliably locate the monorepo root. Update environment file loading and docstore path construction to use the workspace root, ensuring correct file resolution in multi-package setups. This addresses issues with relative paths and improves compatibility in monorepo and containerized environments.
Update resolveDocstoreDir to handle multiple environments:
- Prefer monorepo root for local development
- Check process.env.PWD for containerized bootstrap
- Fallback to standard /app/docstore path
- Default to process cwd
Enhances robustness of docstore path resolution across deployment scenarios.
Introduce compose.local-slim.yml and compose.local-full.yml for local builds of
OpenReader and compute-worker. Update deployment documentation to describe new
compose variants and usage. Adjust package.json scripts to support local-slim
and local-full workflows. Rename compose files and update stack names for
clarity.
Fix/refactor reconciliation logic to wrap getOpStateRecord and listOpStates methods
from the state store, ensuring they are invoked as functions rather than accessed
as properties. This improves type safety and prevents potential runtime errors
from improper method usage.
- Extract database schema and migrations into @openreader/database
- Extract startup and orchestration scripts into @openreader/bootstrap
- Move compute-worker into packages/compute-worker
- Remove runtime dependency on drizzle-kit
- Update Dockerfile to deploy isolated packages without merging node_modules
Introduce a comprehensive Docker Compose documentation page covering slim, full, and local-build stack examples. Add example Compose files under docker/examples/ for streamlined deployment. Update local development docs to reference the new Compose guide. Revise package.json scripts to support the new Compose workflows and remove legacy compute-worker compose commands. Update sidebar to include the Docker Compose documentation for improved discoverability.
- Move operation contracts from api/contracts to operations/contracts for clearer separation
- Delete legacy API modules: contracts.ts, operation-keys.ts, public-operation.ts
- Add new modular files: compute-operation.ts, http-hooks.ts, routes.ts, operations/keys.ts, operations/reconciliation.ts
- Refactor all imports to use new operations/contracts and keys modules
- Rename protocol types from PublicOperation to ComputeOperation for clarity
- Update OpenAPI schema and all references to use ComputeOperation/Event naming
- Extract whisper decoder helpers to inference/whisper/decoder.ts
- Add new infra: nats-session.ts for NATS session management
- Update tests and storage logic to use new contracts and keys modules
- Refactor worker loop and orchestrator to use modular job definitions
- Update client and protocol in src/lib/server/compute-worker to use new ComputeOperation types
This refactor modularizes compute-worker operation logic, clarifies type naming, and improves maintainability by separating contracts, keys, and reconciliation logic. No breaking changes to external API, but all internal references and tests are updated for the new structure.
- Remove legacy inference and platform files from `src/inference/` and migrate responsibilities to new modular locations under `src/infrastructure/`, `src/inference/pdf/`, and `src/inference/whisper/`
- Consolidate environment/config logic into `src/infrastructure/config.ts`
- Move docstore and ffmpeg platform utilities to `src/infrastructure/platform.ts`
- Refactor PDF and Whisper inference code to use new document layout, layout model, and timestamp utilities
- Update API contracts to define and export types and constants previously scattered in inference/types
- Adjust all imports in jobs, storage, and tests to reference new module structure and type locations
- Inline parser version and encoding logic into API contracts
- Remove obsolete files and update test fixtures for new type locations
This update improves codebase modularity, maintainability, and separation of concerns. No changes to inference or orchestration functionality.
BREAKING CHANGE: inference module structure, type imports, and config utilities have changed; downstream code must update imports and integration points.
- Remove legacy compute-worker monolith under `src/compute/`, including PDF and Whisper inference, control-plane, and runtime orchestration
- Move PDF and Whisper inference logic to new `src/inference/` module
- Move control-plane, orchestrator, and state machine logic to `src/operations/`
- Move NATS/JetStream adapters to `src/infrastructure/`
- Move job orchestration, progress, and artifact persistence to `src/jobs/`
- Update imports throughout tests and main app to reference new module structure
- Update Dockerfile and scripts to use new asset paths
- Add new entrypoints: `src/api/app.ts` and `src/api/contracts.ts`
- Remove obsolete files and update `.gitignore` for new dev artifacts
This refactor modularizes the compute-worker, improving maintainability and separation of concerns. No functional changes to inference or orchestration logic.
BREAKING CHANGE: compute-worker internal APIs, directory structure, and imports have changed; downstream code must update imports and integration points.
- Removed deprecated functions related to document parsing and blob storage in blobstore.ts.
- Introduced new PDF rendering logic in pdf-preview-renderer.ts and pdf-preview-pdfjs-runtime.ts.
- Updated previews-render.ts to utilize the new PDF rendering functions.
- Refactored user-whisper-align-job.ts to use the compute-worker client for alignment requests.
- Enhanced artifact.ts and operation.ts to validate parsed PDF artifacts and resolve current PDF parses.
- Updated snapshot.ts to align with new worker operation types.
- Adjusted runtime-config.ts to check for compute-worker availability.
- Modified types in parsed-pdf.ts and tts.ts to reflect changes in the compute-worker protocol.
- Added unit tests for PDF artifact validation and compute-worker client contract.
- Removed obsolete pdf-op-key.vitest.spec.ts test file.
Track previous session existence to avoid minting a new anonymous user when a
session ends and the user is redirected to the sign-in page. This prevents
clobbering account-scoped preferences by ensuring bootstrapping does not occur
during the logout redirect flow.
Add periodic idle status logs with activity context to improve monitoring and
debugging of worker idle/disconnect behavior. Track last activity reason and
include it in status logs and idle disconnect eligibility checks. Update
activity marking calls to specify reason for more granular observability.
Review-driven: thread ttsUpstreamTimeoutMs into the speech-sdk branch as an
overall budget across SDK retries, and fail fast on model ids missing the
provider/model form instead of falling through to provider defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds 'speech-sdk' as a fifth built-in TTS provider backed by @speech-sdk/core
(Apache 2.0). Models use the provider/model format; requests go from the
server directly to the provider's API with the user's own key. Existing
providers, defaults, and config are untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Configure webpack to treat @napi-rs/canvas as an external dependency under
the 'canvas' module name when building the server bundle. This adjustment
ensures proper module resolution and avoids bundling native dependencies
directly, improving deployment reliability for environments requiring
@napi-rs/canvas.
Add explicit host validation to only allow audio output URLs from
replicate.delivery and its subdomains, blocking potential SSRF attacks
from malicious model outputs. Update tests to cover allowed and
disallowed host scenarios.
Update DocumentPreview text preview to use a CSS color-mix radial pattern
with increased opacity and a masked vignette effect. Improves visual
clarity and consistency with design tokens.
Switch SSRF protection in web-loader to use @microsoft/antissrf, removing
custom IP range logic. This centralizes and future-proofs SSRF mitigation by
leveraging maintained policy sets and DNS-aware enforcement. Error handling in
the import-url API route is updated to recognize new policy error messages.
Dependency added to package.json.
Add detection for CGNAT (100.64.0.0/10) and benchmarking (198.18.0.0/15) ranges
in IPv4 private IP checks. Update IPv6 site-local detection to include full
range. Refactor fetchWithLimit to return both HTML and the resolved final URL,
ensuring relative URLs are resolved against the actual fetched location.
Enhance SSRF mitigation in web-loader by expanding private IP detection for both
IPv4 and IPv6, including checks for multicast and broadcast addresses. Update
filename sanitization logic in UploadMenuDialog to collapse consecutive
disallowed characters and trim underscores, ensuring cleaner filenames. Remove
redundant userId check in import-url API route for streamlined authentication.
Add input field for specifying a custom document title when importing from a web URL.
Use the provided title for the generated markdown file name and success message,
falling back to the scraped title if left empty. Reset the title field after import
completion for improved UX.
Replace ModalFrame and related modal patterns in settings and upload dialogs with
the new SidebarDialog component to establish a consistent sidebar-based dialog
layout. Refactor SettingsModal and UploadMenuDialog to use SidebarDialog, updating
header, navigation, and content handling accordingly. Update DocumentList to use
DocumentUploader in compact mode for sidebar upload action. Export SidebarDialog
from the UI module. Minor fixes in web-loader for IP parsing and option naming.
This change standardizes dialog UI structure and improves maintainability.
Introduce a unified upload menu dialog enabling users to upload files,
create markdown/text documents, or import content directly from web URLs.
Implement a secure server-side loader for fetching and parsing web pages
with SSRF protection, content-type checks, and size limits. Add new API
route for importing documents from URLs, and update dependencies to
support HTML parsing and markdown conversion.
- Add UploadMenuDialog component with file, create, and URL import tabs
- Implement SSRF-mitigated web-loader using linkedom, readability, turndown
- Add /api/documents/import-url endpoint for web imports
- Update DocumentList to use new upload dialog
- Add BrowserIcon and supporting UI elements
- Update dependencies: @mozilla/readability, linkedom, turndown
- Expose importUrl client API
This improves document onboarding flexibility and enhances security for
web-based imports.
Add unit test verifying that U+00B7 middle dot is not stripped from Catalan
words during audio text preprocessing, reflecting multilingual requirements.
Move canonical audio text preprocessing logic to new shared module `audio-text.ts`
to ensure consistent normalization across worker, client, and NLP code. Update
all consumers to import and use the shared rules, eliminating duplication and
preventing drift. Expand glyph stripping to handle additional decorative
characters. Add unit tests for new cases and update documentation accordingly.
Add SSE `retry:` directive to suggest a 2-minute reconnect delay when the worker
enters idle sleep and drops silent EventSource streams. This prevents clients
from immediately reconnecting and re-waking the container, reducing unnecessary
resource usage. Update SSE encoding to support the `retry` field and add tests
for correct behavior. Improve idle disconnect logging for better observability.
Update the EPUB highlighting cache key to include aligned word texts, ensuring
that changes in alignment or word content invalidate stale cached spans even if
word counts remain unchanged. Clear the word-region cache when rendered text
maps are replaced or reset, preventing incorrect highlights after remapping.
Also add locale tracking to HTML sentence highlighting state to support
language-aware word segmentation during alignment.
Replace EPUB-specific word highlight mapping with shared token-sequence alignment
logic used by HTML and PDF viewers. Remove bespoke alignment code and tests in
favor of a single primitive (`locateAlignmentWordSpans`) that robustly maps
spoken words to rendered text regions, tolerant of transcription and formatting
differences. Update EPUB highlighting to cache per-segment word-region spans for
efficient re-use. Add unit tests for the new alignment logic.
Switch mp3 transcoding to output a seekable temp file instead of piping to stdout.
This allows ffmpeg to backpatch the Xing/Info header with correct total-duration
metadata, which is required for HTML5 audio elements to handle VBR mp3 playback
accurately. Prevents playback issues caused by missing or incorrect duration,
such as premature or stalled `ended` events.
Update documentation to specify that OpenReader uses the Kokoro voice set
for Kokoro models and the standard OpenAI voices for other models when no
voice listing endpoint is available. Improves clarity on provider fallback
behavior.
Align non-kokoro custom-openai models to use the canonical OpenAI voice set
instead of a separate custom set. Updates logic and tests to reflect this
standardization for improved consistency across OpenAI-compatible providers.
Avoid bootstrapping anonymous sessions when users are on sign-in or sign-up
pages to prevent overwriting account-scoped preferences after login. Adjust
loading logic to account for this scenario.
Update documentation to specify that only the speech endpoint is required for
OpenAI-compatible TTS providers, with voice listing now optional and auto-discovered.
Document support for multiple audio formats (mp3, wav, ogg, flac) and note that
API keys are optional for local or unauthenticated servers. Add Supertonic to
the list of compatible implementations and improve troubleshooting guidance.
feat(tts): support OpenAI-compatible servers that emit wav instead of mp3
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **New Features**
* Normalize all incoming TTS audio to MP3 so stored/served audio is consistently MP3.
* Broader discovery of voices from OpenAI‑compatible endpoints by probing multiple voice routes and respecting provider defaults.
* **Reliability**
* Retry flow when providers reject explicit response format to improve compatibility.
* Improved audio format detection, safer transcoding with cancellation and cleanup, and logging when normalization occurs.
* **Tests**
* Added unit tests covering audio format detection across multiple codecs.
* **Documentation**
* Added a Supertonic TTS provider setup guide.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Update OpenAI SDK instantiation to provide a placeholder apiKey when none is
supplied, enabling support for local or unauthenticated OpenAI-compatible
TTS servers. Ensure Authorization header is cleared when not needed.
Include documentation for configuring the Supertonic TTS provider. Update
the main TTS providers list and sidebar to reference the new guide, ensuring
users can easily find setup instructions for Supertonic.
Update audio format sniffing to require a full 10-byte ID3 header for mp3 detection and add stricter validation of MPEG frame headers to avoid false positives. Refactor ffmpeg process spawning to register abort listeners before module import, ensuring abort signals are handled promptly and preventing orphaned processes. Expand tests to cover edge cases in mp3 header detection.
The wav->mp3 normalization path re-encoded pristine high-fidelity TTS (e.g.
Supertonic's 44.1 kHz wav) at 64k CBR, which lowpasses hard and introduces
high-frequency artifacts that cause listening fatigue. Switch the live/segment
transcode to libmp3lame VBR -q:a 2 (~170-210 kbps for speech), effectively
transparent. The audiobook export still re-encodes to 64k for file size.
extractVoiceNames collapsed an explicit `{ voices: [] }` response to null,
which broke the existing contract of treating an empty list as a valid
"no voices" answer. Distinguish a genuinely empty array (honored as []) from
an array whose items yielded no usable names (unrecognized shape -> null, so
the next endpoint or defaults apply).
Distribute replacement characters across original URL spans to preserve positional mapping in highlights. In sentence alignment, add fallback to avoid misaligned word highlights when the sentence cannot be located, ensuring robustness in TTS word highlighting.
Introduce `normalizeToMp3` utility to convert wav/ogg audio buffers from TTS providers
to mp3, ensuring consistent mp3-only handling in cache and pipelines. Add logic to
detect and adapt to OpenAI-compatible servers that do not support explicit
`response_format`, falling back to default output and normalizing as needed.
Refactor voice resolution to probe multiple common endpoints and robustly extract
voice/style names from various payload shapes, improving compatibility with custom
OpenAI-compatible servers.
- Add `audio-format.ts` for audio normalization utilities
- Add tests for audio format normalization
- Update TTS provider request logic to apply normalization and handle response format negotiation
- Enhance voice endpoint probing and extraction logic for custom servers
Refactor EPUB and HTML word highlighting to use a shared, position-preserving
text normalization pipeline for mapping TTS alignment offsets to DOM positions.
Replace the previous fuzzy token-matching logic with a direct char→DOM map
based on the canonical "audio" text form, ensuring highlights stay in sync
across languages and with Unicode-aware hyphenation handling.
- Add `highlight-char-map.ts` for shared normalization logic
- Update normalization to handle Unicode hyphenation consistently across
compute, client, and shared layers
- Remove unused fuzzy token mapping and cache logic from EPUB highlighting
- Update tests for new highlight mapping and normalization behavior
BREAKING CHANGE: Removes legacy fuzzy token-based word highlight mapping in favor of direct char offset mapping; highlight cache and tokenization APIs are removed.
Enhance EPUB TTS canonical windowing by guarding against stale async completions
during rapid page turns, preventing outdated segment/highlight state from
overwriting the active page. Update SegmentsSidebar to robustly handle errors
in canonicalization, ensuring sidebar state is cleared on failure and avoiding
residual mappings from previous pages. Refine canonical window documentation to
clarify non-overlapping segment partitioning per page. Add a language-aware
cache test to verify correct canonical plan isolation across languages.
Consolidate EPUB TTS segment planning around a single canonical windowing model.
Introduce `epub-canonical-window.ts` to provide stable, viewport-independent
segment windows for both playback and sidebar. Refactor segment planning,
prefetching, and playback handoff to use canonical ordinals and keys, ensuring
that segments straddling page breaks are uniquely identified and never replayed
across pages. Update TTSContext, SegmentsSidebar, and related hooks to support
windowed canonical segments and precise ordinal continuity. Extend types and
interfaces to carry canonical segment metadata and windowing context.
Add comprehensive unit tests for canonical window selection, segment
materialization, and cache behavior.
BREAKING CHANGE: EPUB TTS segment planning, playback, and sidebar now rely on
canonical windowing, affecting segment key generation and playback logic.
Replace all references to `openssl rand -hex 32` with `openssl rand -base64 32`
in environment variable examples and documentation. This change standardizes
the recommended method for generating strong random secrets, improving
compatibility and clarity for users configuring authentication and cron secrets.
* refactor(user): overhaul user data cleanup and export for cascading deletes and TTS segment support
Revise user data cleanup logic to ensure proper cascading deletion of user-related
database rows and S3 objects, including shared document and preview artifacts.
Introduce explicit checks for last ownership before removing shared resources.
Add TTS segment cache and audio cleanup to document and user deletion flows.
Expand user data export to include TTS segment entries and audio, job events,
document settings, and linked auth sessions. Update schema with ON DELETE CASCADE
for userTtsChars and userJobEvents. Add new migration scripts and comprehensive
unit tests for cleanup and export scenarios.
* test(user): expand cleanup and export coverage for shared docs and TTS segment files
Add tests for user data cleanup with shared document ownership and for TTS segment
variant export scenarios, including cases with duplicate audio keys and storage
disabled. Refactor cleanup logic to use document row types and transactional
ownership removal with last-owner checks. Update TTS segment cache clearing to
report deleted segment count by unique entry. Adjust export logic to always
include all TTS segment variants and conditionally export file buckets based on
storage availability. Update migration to use NOT VALID/VALIDATE for new
cascading constraints.
* refactor(documents): centralize owned document deletion and add mutation locking
Introduce `deleteOwnedDocument` utility to encapsulate all logic for removing a user's ownership of a document, including TTS segment cache cleanup, preview artifact removal, and S3 blob deletion for last-owner cases. Add `withDocumentMutationLock` to serialize concurrent mutations on the same document, using advisory locks in Postgres and a local queue fallback. Refactor all API routes and user data flows to use these utilities, ensuring transactional safety and preventing race conditions during document deletion or transfer. Update tests for new flows and add coverage for document cleanup sequencing and locking behavior.
* feat(data): enhance anonymous claim to support document settings and TTS segment copy
Expand the anonymous data claim process to include document settings transfer and TTS segment S3 prefix copying. Remove foreign key constraints from user_tts_chars to allow non-user buckets. Update claim modal and onboarding flow to display claimed document settings. Refactor TTS char count transfer to merge all dates and fix upsert logic. Add S3 copy utility for TTS segments and corresponding tests. Update migrations and schema to reflect relaxed constraints.
* refactor(data): improve error handling and rollback for document and TTS segment operations
Enhance robustness of document deletion and TTS segment transfer by improving
error logging, partial rollback, and degraded state reporting. Add best-effort
cleanup and recovery mechanisms to prevent orphaned data and ensure diagnostic
information is captured for unexpected failures. Update user data cleanup to
log and swallow restoration errors without masking primary failures. Refine
claim logic to handle unmapped TTS audio keys safely.
* refactor(data): unify document mutation locking and transaction handling
Replace mutation-lock with document-lock to centralize document mutation
serialization and database transaction management. Introduce runInDbTransaction
utility to abstract SQLite/Postgres transaction differences. Update document
deletion, user data cleanup, and rate limiter logic to delegate transaction
handling and locking to shared helpers. Remove dialect-specific branching and
inline transaction logic for improved maintainability and testability.
BREAKING CHANGE: withDocumentMutationLock is removed in favor of withDocumentLock and runInDbTransaction
* fix(documents): improve rollback error handling in deleteOwnedDocument
Enhance error handling during document deletion by ensuring that failures
in restoring document ownership do not obscure the original error. Log
degraded events when ownership restoration fails after a deletion error,
providing additional context for debugging and monitoring. This change
improves reliability and traceability of document deletion operations.
* feat(tasks): introduce scheduled task engine and admin UI for background jobs
Add a general-purpose scheduled task system with a persistent registry and status tracking, supporting background maintenance jobs such as orphaned blob reaping, expired upload cleanup, job event pruning, and TTS usage retention. Implement a new `scheduled_tasks` table, task engine, and handlers for each maintenance operation. Integrate an admin UI panel for monitoring, manual runs, and configuration of tasks. Update document and user data cleanup flows to delegate shared blob and preview deletion to the scheduled reaper. Add Vercel cron integration for serverless environments.
BREAKING CHANGE: Document and user storage cleanup now relies on background scheduled tasks for shared blob and preview deletion; immediate inline deletion is no longer performed.
* refactor(user): streamline document and TTS segment transfer logic
Simplify user document transfer by consolidating storage and metadata handling. Remove inline deletion of shared blobs; only metadata is moved, and TTS segment transfer is controlled via options. Add `skipStorage` option for test scenarios to bypass storage operations. Update tests to reflect new transfer behavior.
* chore(instrumentation): delegate node-specific setup to separate module
Move Node.js-specific instrumentation logic to a dedicated file. Update
registration to dynamically import the node module only when running in a
Node.js environment. This separation clarifies environment-specific behavior
and improves maintainability.
* ci(config): update scheduled task cron to run daily at midnight
Change cron schedule for /api/admin/tasks/tick from hourly to once daily at
midnight to reduce task frequency and align with updated operational
requirements.
* refactor(tasks): enforce positive interval and improve scheduled task updates
Add database-level check constraints to ensure scheduled task intervals are always
positive for both Postgres and SQLite. Update admin API and UI to support sub-minute
intervals and stricter validation. Refactor scheduled task update logic to upsert
rows, ensuring tasks can be updated even if not yet present in the database.
Improve temporary upload cleanup to delete in paginated batches. Enhance tests to
cover new constraints and update behaviors.
* chore(docker): remove ffmpeg-static from runtime dependencies in image build
Eliminate ffmpeg-static from the production node_modules during Docker image
assembly to streamline the deployment artifact and avoid bundling unused binaries.
* refactor(admin): redesign task panel UI with Card layout and running indicator
Replace the bordered div layout in AdminTasksPanel with the Card component for
improved visual hierarchy. Add a dynamic running indicator using a custom
RunningDot component to clearly show active tasks. Update control alignment and
button states for better usability. Remove Badge-based status display in favor
of a more streamlined appearance.
* feat(tasks): add document blob lease for safe orphan reaping and improve scheduled task robustness
Introduce a document blob lease mechanism to prevent race conditions between document registration and orphaned blob cleanup. The new `document_blob_leases` table ensures that only one process can claim a document blob for mutation or deletion at a time. Update the orphan reaper to acquire a lease before deleting blobs and to re-check for ownership after acquiring the lease, avoiding accidental deletion of in-flight uploads.
Enhance scheduled task infrastructure with per-task fencing tokens to prevent stale runners from overwriting newer results, enforce runtime limits with abort signals, and expose scheduler mode and minimum interval to the admin panel and API. Adjust task handlers to accept a context with abort support, and update documentation and environment variable references for the new cron secret and scheduling behavior.
BREAKING CHANGE: Scheduled tasks now require a `document_blob_leases` table and updated handler signatures. Vercel deployments must set `CRON_SECRET` for scheduled maintenance.
* refactor(admin): add loading skeleton to tasks panel for improved UX
Introduce a TasksSkeleton component to display animated placeholders while scheduled tasks are loading. Replace direct rendering of empty task rows with the skeleton when data is pending, enhancing perceived responsiveness and user experience in the admin tasks panel.
* test: clean up playwright anonymous users
* test: make blob lease expiry deterministic
* fix(documents): improve blob lease release error handling and test stale lease scenario
Handle errors during blob lease release by logging warnings instead of allowing them to mask original results. Update unit tests to verify that releasing a stale lease does not affect a replacement lease.
* refactor(documents): implement exponential backoff with jitter for blob lease retries
Replace fixed retry delay with capped exponential backoff and jitter to reduce
contention and thundering herd effect when acquiring document blob leases. This
improves lease acquisition fairness and efficiency under high load.
Adjust language input resolution in TTS generation to ensure Replicate Kokoro
model receives the appropriate language code mapping even when the language
property is absent or falsy. Update tests to cover this scenario and verify
correct input construction.
Eliminate documentation of TTS and compute PDF parsing rate limiting options from the environment variables reference, as these are managed exclusively via runtime configuration in the admin UI and do not correspond to environment variables. This clarifies the distinction between environment-based and runtime-managed settings.
Update provider seeding logic and documentation to allow creation of admin/shared providers without requiring an API key. Adjust environment variable handling so that a blank or missing API_KEY is valid when API_BASE is set, enabling support for upstream TTS providers that do not require authentication. Remove defaulting to 'none' for API keys in API routes and ensure headers are omitted when no key is present. Add and update tests to verify correct handling of keyless providers.
BREAKING CHANGE: Providers can now be seeded without an API key; API_KEY is no longer required if API_BASE is set. Existing deployments relying on a non-empty API_KEY for seeding should review their environment configuration.
Update preference normalization logic to explicitly reject arrays when
validating record types, ensuring only plain objects are accepted. Adjust
ConfigProvider effect conditions for clarity and correctness. Add unit tests
verifying that arrays are not accepted as valid preference records or saved
voices.
Implement logic to resolve and inject Replicate Kokoro model-specific language
codes based on language tag and voice, using new mapping utilities. Update
`buildReplicateInput` to set `language_code` for the built-in Kokoro model.
Expand language utilities with code mapping and expose `resolveReplicateKokoroLanguageCode`.
Enhance tests to cover language code resolution and input mapping for Replicate
Kokoro, and verify language input key discovery for built-in models.
Move provider normalization logic into a dedicated module to ensure consistent
handling of provider defaults and user preference inheritance across client and
server. Update layout components to mount ConfigProvider only at the shared
layout level, preventing unnecessary remounts and hydration issues during
navigation. Adjust app config defaults so user provider settings are empty by
default, always inheriting the admin-configured provider unless explicitly set.
Add tests for normalization and sync logic to ensure robust provider resolution.
Add multilingual reader and TTS support
- Detect, normalize, persist, and expose document language across EPUB, PDF, HTML, TTS segment, and audiobook workflows.
- Forward supported language inputs to TTS providers, warn on voice/document language mismatches, and prevent incompatible Kokoro voice selections.
- Centralize Unicode-aware token alignment so EPUB, HTML, and PDF highlighting handles multilingual text, including Japanese matching.
- Standardize shared select, checkbox, button, input, menu, and popover primitives across reader, settings, admin, authentication, and public UI surfaces.
- Update README, introduction docs, and landing-page copy to describe multilingual narration and highlighting.
Remove internal selectedVoices state and use a memoized value based on the voice
prop to determine the selected voices. Avoid side effects on selection display,
ensuring that voice selection persists even when available voices are refetched.
This addresses issues where transient backend states could cause unwanted resets
to the first voice option.
Update introduction documentation and README to consistently refer to
OpenReader as "open-source" and highlight its multilingual capabilities.
Refine language segmentation example in multilingual sample text and
expand test coverage for language settings. Adjust TTS segment manifest
to normalize language tags, improve document language update logic, and
fix provider/model compatibility checks for instructions and language
input. Enhance reliability of language support detection for TTS
generation and Replicate provider integration.
Add a fast linear scan to resolve exact token sequence highlights before
falling back to the fuzzy window search, significantly improving
performance for large documents. Update unit tests to verify that
exact matches are found efficiently without invoking expensive
comparisons.
Expand README and introduction to highlight multilingual features,
including language-aware narration and highlighting. Update landing
page to reflect multilingual capabilities in descriptions, keywords,
and UI labels.
Eliminate the Playwright test matrix to run all browser projects in a single job.
Consolidate report artifact naming and streamline workflow configuration for
faster and more maintainable CI execution.
Move pdf.js worker and standard font resolution logic into dedicated
utility functions for improved maintainability and clarity. Replace
inline workerSrc setup with a reusable configurePdfjsNodeRuntime helper.
Update Next.js config to use a shared asset trace list for pdf.js runtime
files, reducing duplication and simplifying future updates.
Add 'pdfjs-dist' to serverExternalPackages in Next.js config to prevent bundling,
ensuring runtime resolution of on-disk assets like standard_fonts. Update
tracing includes for API preview route to explicitly cover worker and font
resources loaded dynamically.
Move encodeParserVersion to a dedicated module for unified access and remove local duplicates. Update all consumers to import from the new entrypoint. Tighten artifact readiness checks in both client and API route to ensure 'ready' is only reported when the artifact is accessible, with retries and stricter validation. Add tests for edge cases and operation state validation.
Standardize import of PDF_PARSER_VERSION across server modules to reference
@openreader/compute-core/api-contracts. This clarifies versioning boundaries
and improves maintainability by consolidating contract exports.
Eliminate parseState and parsedJsonKey columns from the documents table in both
Postgres and SQLite schemas, including related migration scripts and type
removal in document registration logic. This aligns the schema with the new
worker-based PDF parse model and reduces legacy field clutter.
Introduce new unit tests covering the PDF parse client lifecycle and worker-based
API routes. Tests verify client behavior for not-ready and ready parse states,
operation initiation, SSE event handling, and route validation. Also add
coverage for worker event proxy and worker flow routes. Remove all legacy
parseStatus and parsedJsonKey fields from document types, API, and gallery
view components to align with the new worker-owned PDF parse model.
This change removes legacy server-side PDF parse state management and transitions
to a fully worker-owned model for PDF parsing operations. Key updates include:
- Deletes all code related to server-managed parse state, including:
- parse-state.ts, parse-state-backfill.ts, parse-state-healing.ts,
parsed-pdf-reuse.ts, pdf-parse-operation.ts, and related job logic
- Removes the user-pdf-layout-job queue and associated job logic
- Refactors API routes for parsed PDF documents and events to use the new
worker-owned PDF parse operation flow under src/lib/server/pdf-parse/
- Updates S3 parsed PDF artifact keying to include parser version for
deduplication and compatibility
- Refactors client API and hooks to handle new error and progress reporting
- Removes all tests for the legacy parse state and job system, updating remaining
tests to mock the new worker-owned flow
BREAKING CHANGE: Server no longer manages per-user PDF parse state or jobs.
All PDF parsing is now managed by the compute worker and new artifact keying.
Legacy parse state and jobs are no longer supported. Existing parsed PDFs may
need to be reprocessed for compatibility with the new model.
Update folder tests to use an exported escapeRegExp helper when constructing
regex patterns for file name matching. This prevents false positives or errors
when file names contain special regex characters, improving test reliability.
## Summary by CodeRabbit
* **New Features**
* Expose PDF parser version system-wide; parser-version-aware parsing, reuse, and caching. Added tokenized temp upload flow, presign/finalize endpoints, and server-side DOCX→PDF conversion.
* Playback readiness propagated to TTS players/pages; unified upload/delete APIs and simplified uploader/sidebar UX.
* **Bug Fixes**
* Normalize stored parse state to current parser version to avoid stale readiness and improve reuse.
* Prevent premature playback starts and redundant preparation.
* **Tests**
* Added unit and E2E tests for parser-version handling, op-key inclusion, parse-state behavior, and canonical upload reuse.
Replace dual HTML5/Touch backend logic with a single TouchBackend configured to
handle both mouse and touch events, ensuring consistent drag-and-drop behavior
across all devices. Introduce a custom DocumentDragLayer to render drag previews
universally, as the touch backend does not provide a native drag image.
Update document tile, gallery, and list views to suppress native long-press
previews on iOS and prevent accidental document opening after drag actions.
Revise drag-and-drop test helpers to simulate pointer gestures compatible with
the new backend, improving reliability of automated tests.
Update DocumentUploader to upload all accepted files without filtering by type,
enabling consistent handling of DOCX and other supported formats. Add a default
timeout for DOCX-to-PDF conversion to prevent hanging LibreOffice processes.
This improves reliability and ensures uploads do not silently stall.
Remove the separate docx-to-pdf upload API and integrate DOCX conversion directly into the blob upload finalize route. Update client and uploader logic to treat DOCX as a supported upload type and streamline batch state handling. Adjust document caching and test helpers to align with the new unified flow. Add server-side DOCX-to-PDF conversion utility and corresponding unit tests.
This change simplifies the upload pipeline, consolidates conversion logic, and improves maintainability.
Avoid enqueuing duplicate parse jobs for PDFs that are already parsed or in terminal states. Update docx-to-pdf upload to reuse existing parsed PDF results when possible, skipping unnecessary parse operations and job enqueuing. Adjust parse-state-backfill to only enqueue jobs for pending or running states. Refine client upload logic to better infer document type from MIME when file names are missing. Enhance usePdfDocument to handle terminal parse states more robustly.
This change ensures idempotent parse job creation and reduces unnecessary worker load, while improving document upload and parse state handling.
Introduce startPdfParseOperation to encapsulate worker operation creation, job rate tracking, and parse state initialization for PDFs. Replace scattered operation setup logic in register-upload, docx-to-pdf upload, and parse-state-backfill with this unified helper. Update job enqueuing to propagate initial opId/jobId/status for accurate state tracking.
Change getParsedPdfDocument to throw on non-ready states and remove polling logic from usePdfDocument, shifting readiness detection to event streams. Switch API responses for non-ready parses from 202 to 409 to clarify client expectations and simplify error handling.
Add targeted tests to verify legacy backfill behavior and ensure no worker operation is created for pure data fetches of pending legacy PDFs.
BREAKING CHANGE: API now returns 409 Conflict for non-ready parsed PDFs instead of 202, and clients must subscribe to event streams for parse progress.
Replace type-specific document add/remove functions with generalized uploadDocuments and deleteDocument methods. Consolidate file upload handling in DocumentUploader for improved maintainability. Introduce evictCachedDocument to streamline cache eviction across document types. Update DocumentContext to support batch uploads and single-method deletion, simplifying context API and reducing duplication.
Add detection for precondition failures during blob copy to prevent race conditions when finalizing uploads. Refactor upload finalization to use Promise.all for concurrent processing. Extract parse state and parsed JSON key logic to variables for clarity and to avoid duplication in document registration.
Transition document upload flow to use presigned token-based temporary uploads. Add finalize endpoint to register uploaded documents after blob transfer, decoupling document ID assignment from upload initiation. Update client and server logic to operate on tokens, improve security, and enable canonical ID reuse for identical uploads. Update tests to verify deduplication behavior.
Ensure TTS text and playback readiness state are properly reset when
the current document data or name changes, or when clearing or setting
the current document. Update effect dependencies and logic to prevent
stale playback state and improve user experience when switching or
removing documents.
Add PDF_PARSER_VERSION constant and propagate parser versioning throughout
the PDF parsing, job, and API layers. Implement normalization of parse state
to ensure compatibility with the current parser version, and enable reuse of
parsed PDF results when possible. Add isPlaybackReady state to document hooks
and TTS player, improving playback UX by disabling controls until content is
ready. Update tests to reflect new playback readiness logic.
BREAKING CHANGE: PDF parse state and job logic now require explicit parserVersion;
older parse states may be treated as pending until reprocessed.
Add explanation and example for the API_KEY environment variable in the
Docker quick start instructions. Clarify its role in seeding the default
provider's key and post-boot key management.
Eliminate the unused --delete-local option and related code from the
filesystem migration script to prevent accidental local data removal.
Introduce a cleanupClaimedLegacyFsSources utility for removing legacy
filesystem sources after claim, and integrate it into the user claim
flow. Add unit tests to verify the cleanup logic.
Introduce a backfill handler to restore missing PDF parse operations for
documents in legacy 'pending' or 'running' states lacking an opId.
Update parsed route handlers to invoke this logic, ensuring correct
operation association for affected documents. Add regression tests to
validate backfill behavior for legacy scenarios.
Replace dynamic require.resolve lookup with direct path construction to
standard_fonts directory under node_modules. Removes use of createRequire
and streamlines runtime font path resolution logic for PDF parsing.
Refactor Dockerfile to optimize layering, embed entrypoint migration tools and compute worker as deployable bundles, and merge only required dependencies for runtime scripts. Add docker/entrypoint-migration-tools package and update workspace configuration. Refactor PDF parsing to resolve standard font data path robustly at runtime. Update dependencies and output config for Next.js standalone mode. Enhance entrypoint script to launch embedded compute worker from deployed bundle.
- Dockerfile now deploys migration tools and compute worker as separate bundles
- compute/core/pdf uses new pdfjs-runtime utility for robust font path resolution
- docker/entrypoint-migration-tools added for migration script dependencies
- scripts/openreader-entrypoint.mjs launches embedded worker from deployed bundle
- next.config.ts enables standalone output and includes standard_fonts in tracing
- Dependency updates across package.json and compute/worker/package.json
BREAKING CHANGE: Docker image structure and runtime entrypoint logic have changed; custom deployment scripts or Docker overrides may require updates.
* refactor(ui): replace anchor tags with next/link in SidebarNavLink and UserMenu
Update SidebarNavLink to use next/link for navigation instead of anchor tags,
ensuring proper routing and improved accessibility in Next.js. Refactor UserMenu
to remove legacy Link wrappers and directly use SidebarNavLink for signin and
signup links. This streamlines navigation components and aligns with Next.js
best practices.
* fix(pdf): handle non-zero viewport origins and improve layout model ordering
Update normalizeTextItemsForLayout to correctly apply viewport transforms,
including non-zero page origins, ensuring accurate mapping of PDF text items
to top-left coordinates. Refactor runLayoutModel to implement a custom order
sequence builder for layout regions using model order logits, improving
region ordering consistency with model semantics. Update related tests to
cover viewport transform edge cases.
* refactor(pdf): improve text normalization with font ascent and vertical overlap logic
Enhance text normalization by incorporating font ascent and descent data to more accurately position glyphs, especially for decorative initials. Update merge logic to better detect line membership using vertical overlap, ensuring drop caps and overlapping glyphs are merged correctly. Extend tests to cover these layout scenarios.
* style(range): redesign slider with precision gauge and ruler ticks
Revamp the range input to feature a minimalist "precision gauge" style.
Introduce a hairline rail, ruler notches for discrete steps, and a slim
needle thumb. Add CSS variables and logic for per-instance tick sizing
and coloring. Remove bulky inline class-based styling in favor of
centralized CSS for improved maintainability and visual clarity.
* refactor(ui): modularize PDF loader and range slider visuals
Move PDF layout scan visualization and range slider styles into dedicated CSS
modules, isolating their styles from the global scope. Integrate PdfLayoutScan
component into the PDF viewer loader UI for animated parse progress. Refactor
progress bars to use a reusable progress-fill class with animated sheen effect.
Update range input to use CSS module for precision gauge styling.
* style(reader): remove grid overlay from PdfLayoutScan visualization
* refactor(api): add staleness detection for inflight worker operation states
Integrate isWorkerOperationStateStale checks into document parse API endpoints to ensure inflight worker operation states are not reused if stale. Introduce helper for staleness detection and corresponding unit tests. Enhance SSE event streaming with keepalive intervals and improve progress acknowledgment error handling.
* feat(worker): recover and fail stale in-flight pdf ops on startup
Add orphaned operation recovery logic to detect and mark stale in-flight pdf_layout jobs as failed during worker startup. Extend OperationStateStore with listOpStates for state enumeration. Update tests and documentation to cover recovery behavior and new environment variable COMPUTE_PDF_JOB_ATTEMPTS.
* refactor(worker): distinguish staleness thresholds for running and queued pdf ops
Update orphan recovery logic to apply separate timeouts for 'running' and
'queued' pdf_layout operations. Adjust tests to verify that only stale
'running' operations are failed, while stale 'queued' operations remain
untouched.
* feat(worker): extend orphan recovery to handle whisper_align ops and improve logging
Update orphan recovery to detect and fail stale 'running' whisper_align operations
in addition to pdf_layout. Refactor recovery logic to generalize staleness checks
across operation kinds and enhance log output with detailed operation info.
Expand tests to verify correct handling of both whisper_align and pdf_layout
operations in running and queued states.
* refactor(control-plane): introduce revision-based CAS for operation state updates
Add revision tracking and compare-and-set (CAS) semantics to operation state
stores, enabling atomic state transitions and preventing lost updates. Extend
the OperationStateStore interface with getOpStateRecord and compareAndSetOpState
methods. Update orchestrator and worker runtime to utilize CAS for marking
operations as failed only if the state is unchanged. Enhance in-memory,
JetStream, and test control plane implementations to support revision logic.
This change improves concurrency safety and correctness of operation state
management across distributed components.
* feat(ui): add parse failure state to PDF layout scan animation
Display a distinct "parse halted" visual state in the PDF layout scan
component and PDF viewer page when parsing fails. The loader animation
is replaced by a static, dimmed page with an alert glyph and updated
styling, ensuring users are not misled by an active animation after a
failure. CSS and component logic updated to support the new state.
* feat(worker): extract orphaned operation recovery to module with periodic sweep
Move orphaned operation recovery logic into a dedicated orphan-recovery module,
introducing a periodic sweep timer that triggers recovery every 15 seconds while
the worker is connected. Refactor runtime to delegate orphan detection and
handling to the new module, improving modularity and maintainability. Add
unit tests for orphan-recovery to ensure correctness.
* fix(ui): adjust PDF viewer layout and update parse loader description
* refactor(pdf): streamline layout model region extraction and update test coverage
- Replace custom order sequence logic with softmax-based class selection in runLayoutModel
- Remove unused sigmoid and buildOrderSequence functions
- Simplify detection loop to filter and map regions directly
- Add targeted tests for layout model extraction logic
- Update CSS animation naming for consistency
- Clarify test description and add inline comments for orphan recovery scenario
* fix(pdf): add strict validation for layout model output shapes and extend test coverage
Add explicit error handling for invalid or inconsistent pred_boxes and logits array lengths in runLayoutModel to prevent silent failures. Expand test suite to verify correct region filtering and error scenarios, ensuring only labeled regions are returned and malformed outputs are handled robustly.
* docker fixes
* fix(worker): default COMPUTE_PREWARM_MODELS to false and update docs, env, and compose
Set COMPUTE_PREWARM_MODELS to false by default in runtime, docker-compose,
and .env.example to avoid pre-downloading ONNX models during worker startup.
Update Dockerfile and deployment documentation to reflect this change and
clarify configuration for model prewarming.
* fix(docker): downgrade pnpm version to 10.33.4 for compatibility
---------
Co-authored-by: Richard R <me@richardr.dev>
Eliminate the enableDestructiveDeleteActions feature flag from runtime
configuration, admin panel, environment docs, and user settings modal.
Remove all references, toggles, and documentation for this flag. This
simplifies the runtime config and user interface by consolidating
destructive actions under account deletion only.
BREAKING CHANGE: The enableDestructiveDeleteActions runtime flag is no longer supported. Any configuration or code depending on this flag must be updated.
Revise the compute worker deployment documentation to clarify usage
scenarios, streamline environment variable instructions, and improve
separation between embedded and standalone deployment modes. Remove
outdated references and reformat for better readability.
Apply "relative" class to IconButton components in TTSPlayer to enable
positioning of child elements or overlays. This prepares the UI for
potential enhancements such as loading indicators or tooltips.
* phase 0: token foundation
* phase 1: motion language
* phase 2: primitives and semantic tokens
* phase 3: depth and rhythm polish
* phase 4: enforce design system lint rules
* phase 5: split ui primitives into modules
* phase 7: refactor app surfaces to ui layer
* phase 9: enforce ui architecture imports
* phase 10: add ui system harness
* fix compact reader auth control
* fix pdf loader flash
* Converge sidebar and reader controls
* refactor: remove initial loader state and related logic from PDFViewerPage
* Remove legacy UI shim imports
* Converge modal and drawer frames
* Migrate secondary modals to shared frame
* Move settings modal onto shared frame
* Use shared cards in audiobook settings
* Converge choice and popover surfaces
* Converge reader navigation buttons
* Refactor UI components to use consistent button and icon styles across the application
* refactor(ui): unify button usage in settings, admin, and doclist components
Replace native button elements with shared Button, ChoiceTile, and IconButton
components for consistent UI behavior and styling. Update classNames and
props to match new component APIs. Adjust FinderSidebar to use utility
function for conditional class merging.
* feat(ui): introduce shared Listbox components for unified select and dropdown styling
Replace direct usage of Headless UI Listbox primitives with new SharedListboxButton,
SharedListboxOption, and SharedListboxOptions components across AudiobookExportModal,
FinderToolbar, VoicesControlBase, and select UI. Refactor related imports and classNames
to centralize dropdown styling and logic. Simplify UserMenu button markup for improved
consistency.
This change consolidates dropdown/select UI patterns, reduces duplication, and
improves maintainability by providing a single source of truth for Listbox styling
and behavior.
* refactor(ui): consolidate button, menu, popover, and range primitives for unified usage
Remove legacy UI harness and dev/demo files. Replace scattered button, menu, popover, and range input utilities with shared, composable primitives: Button, ButtonLink, ButtonAnchor, MenuActionItem, MenuItemsSurface, PopoverSurface, PopoverTrigger, and RangeInput. Update all usages across app, admin, player, and document components to use these new primitives, eliminating duplicated class logic and improving consistency. Remove obsolete utility files and class exports. This change streamlines UI code, centralizes styling, and reduces maintenance overhead.
* feat(ui): redesign range input with dynamic progress styling and improved accessibility
Revamp the range input component to support dynamic progress indication using CSS custom properties and linear gradients. Add logic to compute and set the progress percentage based on current value, min, and max. Refine focus and disabled states for better accessibility and usability. Update styling for both WebKit and Mozilla engines to ensure consistent appearance. This change enhances visual feedback and modernizes the range slider UI.
* style(ui): update range input to use secondary accent color for progress
Switch range input progress styling from primary to secondary accent color
for both WebKit and Mozilla engines. Remove drop shadow from slider thumb
for a cleaner appearance. This change aligns the component with the updated
design palette and simplifies visual effects.
* refactor(app): remove unused Link imports from public pages and components
Eliminate redundant imports of the Link component from Next.js in several
public-facing pages and components. These imports were no longer in use
after recent UI refactoring and consolidation of navigation elements.
This cleanup reduces bundle size and improves code clarity.
* chore(ui): remove unused export of segmented control classes from select component
Eliminate unnecessary export statements for segmentedButtonClass and
segmentedGroupClass in the select component to streamline the module's
public API and reduce potential confusion.
* test(accessibility): improve confirm dialog test coverage and refactor media state helper
Expand accessibility tests for ConfirmDialog to assert dialog semantics,
ARIA attributes, and visible destructive actions using test IDs. Refactor
expectMediaState helper to check both UI control state and underlying
media signals for more robust playback state detection.
* refactor(ui): improve segmented control accessibility and update danger color tokens
Update SegmentedControl to support full keyboard navigation and focus management,
enhancing accessibility. Replace string indicator in Select with icon, and update
button danger variant to use new --danger-strong variable for hover states. Add
danger-strong token to Tailwind config and globals. Refine dropzone disabled
behavior, adjust focus ring for better contrast, and apply minor UI consistency
tweaks across components.
* feat(ui): convert SidebarNavLink to forwardRef component
Refactor SidebarNavLink to use React.forwardRef, enabling parent components
to access the underlying anchor element's ref. Update prop typing and
function signature accordingly for improved composability and integration
with higher-order components.
Move origin extraction to shared utility for consistent validation across
server and client modules. Update layout metadata to use the shared origin
helper, improving maintainability and deployment flexibility. Add unit tests
for layout metadata to ensure correct origin handling.
Switch landing and layout metadata to use BASE_URL from environment for
deployment flexibility. Refine privacy page environment check for robustness.
Update ARIA attributes on landing preview for better accessibility. Adjust
terminal and visually hidden styles for improved formatting and screen reader
support.
Update landing page descriptions to emphasize native file rendering and
layout-aware parsing. Adjust Docker quick start instructions to remove
redundant volume mount and highlight required environment variables for
self-hosting.
Redesign the public landing and privacy pages with a new layout structure,
introducing an aurora background, grain overlay, and updated navigation.
Revise hero section with animated word-level highlighting and refreshed
copy. Update CSS to use semantic theme variables, color-mix, and new
background effects. Adjust navigation, footer, and action button order for
clarity. Update privacy page button label for consistency.
Revise landing page metadata to specify Replicate TTS and clarify export as
audiobook workflows. Simplify hero panel glow effect in CSS for visual
consistency.
Reorganize privacy policy content to improve clarity on service provider roles,
analytics consent, and encryption limitations. Update section headings and
descriptions for accuracy. Enhance CookieConsentBanner and PrivacyModal to
reflect optional analytics and clarify storage, operator access, and password
handling. Improve CSS for policy section headings and lists for better
readability.
Move all button-related class utilities and types from formPrimitives to a new
ui/buttonPrimitives module. Update all component imports to reference the new
location, ensuring consistent button styling and easier future maintenance.
Remove redundant button code from formPrimitives and re-export as needed for
admin UI compatibility. Add public.css for landing and privacy layout styles.
Eliminate the unused authEnabled property from context providers, hooks,
components, and API responses. All logic and UI now assume authentication is
required, simplifying prop signatures and reducing branching. Update related
types, context values, and function calls to reflect this change. This streamlines
the authentication flow and removes unnecessary configuration.
Eliminate all code, tests, and utilities related to the legacy 'unclaimed' user
scope. All API endpoints, document and audiobook storage, and access logic now
require a valid authenticated userId and only operate on resources owned by that
user. Remove related helper functions, test cases, and conditional flows for
anonymous/unclaimed data. Update types, client APIs, and UI logic to reflect
that only 'user' scope is supported. This simplifies ownership checks and
removes ambiguity around document and audiobook access.
Eliminate all code paths, configuration, and documentation related to running
without authentication. Require AUTH_SECRET and BASE_URL at startup, updating
middleware, server logic, and runtime checks to assume auth is always enabled.
Simplify onboarding, settings, and test helpers to reflect mandatory auth.
Update environment examples, Docker and deployment docs, and CI/test configs.
Remove no-auth-specific UI flows, test cases, and feature toggles.
Update .env.example and environment variable documentation to clarify that
FFMPEG_BIN can override the ffmpeg binary path for general audio processing,
not just audiobooks. Note that it is also used by compute worker Whisper audio
decode in addition to audiobook routes.
Remove all references to legacy RUNTIME_SEED_* environment variables from documentation and codebase. Update docs and .env.example to document the new RUNTIME_SEED_JSON and RUNTIME_SEED_JSON_PATH variables for first-boot runtime config and provider seeding. Refactor admin seed logic and runtime config schema to eliminate env-var-based seeding in favor of JSON-based initialization. Update admin panel UI and badges to reflect new seed sources. Remove obsolete env parsing logic and tests for RUNTIME_SEED_* flags. Add new tests for JSON seed behavior.
BREAKING CHANGE: RUNTIME_SEED_* environment variables are no longer supported; use RUNTIME_SEED_JSON or RUNTIME_SEED_JSON_PATH for first-boot runtime config and provider seeding.
Remove TTS cache and upstream tuning environment variables in favor of admin-managed
runtime settings. Add new admin panel controls for TTS retry attempts, upstream timeout,
audio cache size, and cache TTL. Update API routes and TTS generation logic to consume
these runtime-configurable values, enabling live adjustment without redeploy. Update
documentation to reflect the removal of related env vars and the new admin workflow.
- Add Delete/Backspace keyboard shortcut for document deletion in GalleryView
- Use buttonClass utility for consistent button styling
- Simplify GalleryViewIcon SVG for improved clarity and alignment
These changes streamline user interactions and enhance visual consistency in the document gallery view.
- Adjust GalleryView container structure and paddings for better mobile scrolling
- Refine document preview and details sizing for smaller screens
- Update ListView grid columns and icon sizing for improved mobile fit
- Ensure icons do not shrink and maintain alignment on all devices
These changes enhance usability and visual consistency of document views on mobile devices.
- Stream request body in blob upload fallback route to enforce size limits
without buffering entire payload
- Enhance admin provider panel error handling for multi-status responses
- Adjust positive integer validation to require value >= 1
- Use full rate limit config for job event recording and prune by largest window
- Fix PDF layout job event userId usage in docx-to-pdf upload route
- Add missing windows array fallback in documents register route
- Minor CI workflow and env example corrections
- Update audiobooks blobstore test to use beforeAll directly
These changes improve efficiency, correctness, and maintainability across API, admin, rate limiting, and test modules.
update bottom padding on banners, status bars, and player controls to use
env(safe-area-inset-bottom) for better support on devices with display
cutouts or home indicators. also add viewportFit: cover to layout for
full-viewport rendering on mobile browsers.
extract SettingsTrigger component to separate modal open control from SettingsModal.
update HomeContent to manage modal state and pass open/onOpenChange props.
remove redundant trigger button from SettingsModal for improved composability.
removes embedded trigger logic from SettingsModal, enabling external state
management and better integration with various UI layouts. also cleans up
playwright config by centralizing testIgnore for unit tests.
move TTS and PDF parsing rate limit toggles and max upload size input into a new
"Rate limiting" section in the admin panel for improved organization. update
documentation to reflect the new grouping and clarify the relationship between
these settings.
change RUNTIME_SEED_DISABLE_COMPUTE_LIMIT default to true in env example,
documentation, and runtime config schema to align with TTS rate limit behavior.
clarify admin panel as the source of truth for enabling PDF parsing rate limits.
implement a generic user_job_events table for tracking compute job creation
enforce configurable burst and sustained limits for PDF layout parsing
add admin panel controls for compute rate limiting and max upload size
update API routes to apply and record rate checks for PDF parse jobs
document new environment variables and admin settings for compute limits
improve IP extraction logic for rate limiting accuracy
add tests for request IP extraction and test namespace gating
This change introduces a robust mechanism to throttle expensive compute operations, such as PDF parsing, on a per-user basis. It provides both burst and sustained rate controls, with admin-tunable parameters and clear user feedback on throttling. The job event ledger enables accurate concurrency and rate enforcement, while new documentation and tests ensure maintainability and clarity.
move worker startup logic from server.ts to runtime.ts for better modularity
add worker-loop-policy.ts for loop control abstraction
delete legacy unit tests under tests/unit/ related to compute worker and control-plane
add vitest config and test directories for compute-core and compute-worker
add vitest workflow for CI
update package.json scripts for new test commands and add vitest as dev dependency
BREAKING CHANGE: worker entrypoint is now compute/worker/src/runtime.ts instead of server.ts; legacy Playwright-based unit tests for compute worker are removed in favor of Vitest
- Delete EditableRow from formPrimitives and its usage in AdminFeaturesPanel
- Remove EditIcon from icon set as it is no longer referenced
- Simplify AdminFeaturesPanel state and draft handling
- Update AdminProvidersPanel to use distinct query key for default provider
- Ensure query invalidation covers new default provider key
This streamlines the admin codebase by eliminating obsolete editing UI and related icon, and clarifies provider query logic.
- Remove legacy TTS rate limit environment variables and migrate all related configuration to runtime settings managed via the Admin UI
- Add runtime config keys for TTS rate limiting enable/disable and per-user/IP daily quotas
- Refactor rate limiter and API routes to use runtime config for thresholds and enablement
- Update AdminFeaturesPanel to allow editing TTS rate limiting and quota values in the UI
- Add shared-provider-selection utility for consistent provider selection logic
- Update documentation to reflect new runtime/admin configuration and remove obsolete env var docs
- Add unit tests for rate limit runtime settings and provider selection
BREAKING CHANGE: TTS rate limiting is now controlled via Admin → Site features; environment variables for TTS rate limiting are no longer supported and will be ignored.
Add SidebarUploadLoader component to DocumentList for displaying real-time
upload progress in the sidebar. Extend DocumentUploader to emit detailed
batch state updates via onUploadBatchChange, enabling granular feedback
on current file, phase, and completion status. Improves user awareness of
document upload and conversion processes.
Introduce onRequestSidebarClose prop to FinderWindow and implement its
usage in DocumentList to enable closing the sidebar on mobile via
backdrop tap or Esc key. Enhances mobile usability by allowing users to
dismiss the sidebar more intuitively.
Update FinderSidebar to remove background color on inactive hover for
improved visual clarity. Extend the DocumentSkeleton warning timeout to
10 seconds to allow more time before displaying the alert. Refine test
description for icons grid layout to clarify auto-fit behavior.
Enhance the document icons grid to better handle single-row layouts by
dynamically suppressing column stretching when the number of documents fits
in a single row. Introduce maxColumnsForIconGrid and related logic to
calculate optimal column counts based on container width and icon size.
Update grid style computation to allow optional suppression of single row
stretch, improving alignment and appearance for small document sets.
Add unit tests for icon grid layout calculation and column logic to ensure
correctness across different icon sizes and container widths.
Introduce per-page progress events for PDF parsing by adding onPageStarted
hooks throughout the compute worker and core PDF pipeline. Add
compute/worker/src/pdf-progress.ts to encapsulate progress event builders.
Update server logic to emit page start and page parsed events, improving
client feedback during long-running PDF jobs.
Add src/lib/server/compute/abort-like-error.ts and integrate isAbortLikeError
to robustly detect and suppress expected aborts in API event streaming.
Update API route to ignore abort-like errors on worker proxy crash.
Add unit tests for abort-like error detection and PDF progress event
generation. Refactor PDF parse logic to allow empty merged regions without
throwing, ensuring downstream flows remain resilient to edge cases.
These changes improve observability, error handling, and reliability for
document parsing and event streaming.
Extract onboarding flow step resolution and async runner logic into a
dedicated module for better separation of concerns and maintainability.
Refactor context/provider to use new helpers and simplify settings modal
control. Add unit tests for onboarding step resolution and async runner
coalescing. Update e2e helpers and selectors for onboarding modals.
Adjust environment file handling for CI and Docker contexts.
- Add .dockerignore for build hygiene
- Refactor onboarding context and modal logic
- Add src/lib/client/onboarding-flow.ts with core onboarding helpers
- Add onboarding-flow unit tests
- Update Playwright config and scripts for CI env loading
- Improve test selectors for claim modal
- Update .gitignore to exclude all .env* except .env.example
This change increases onboarding logic modularity and reliability, and improves CI/test environment handling.
Introduce a document identity key combining type and id for consistent
identification across document operations, including selection, drag-and-drop,
and folder management. Refactor drag item structures to use identity objects
instead of plain ids, preventing cross-type collisions and improving merge
accuracy. Update Dexie recently opened map to use identity keys. Enhance
document size formatting for small files and improve error feedback in the
uploader. Clean up redundant props and standardize icon SVG handling.
Eliminate the deprecated "columns" view mode from the document list, toolbar,
icons, and skeleton components. Update all related types and logic to only
support "icons", "list", and "gallery" views. Standardize user-facing labels
from "HTML" to "Text" for improved clarity across the sidebar, segment
locator labels, and document counts. Clean up associated code and types to
reflect these changes.
Shift app-level actions (settings and user menu) from the top bar to the bottom
of the sidebar for improved navigation consistency. Introduce a new "sidebar"
variant for the UserMenu component with tailored styling and behavior for
sidebar placement. Update SettingsModal to support a customizable trigger label
and styling for sidebar integration. Refactor FinderSidebar to accept an
optional bottomSlot, enabling flexible placement of sidebar controls.
Move grid style and template calculation for icon views into a shared
iconsGrid.ts utility. Replace inline grid logic in IconsView and DocumentListSkeleton
with calls to the new iconsGridStyle function for consistency and maintainability.
Revise test helpers and specs to use stricter role-based selectors for document
actions, including delete buttons and document links. Improve accessibility and
upload tests by asserting document presence via explicit file lists instead of
generic text checks. Remove redundant summary assertion from delete flow.
Eliminate unused loading state and related effects from document tile and list
row components. Standardize navigation by disabling prefetch on all document
open links in doclist views for consistent client-side routing. Cache
document list state on load to improve state restoration and initialization.
Introduce a "Remove All Folders" action accessible via a new sidebar menu,
allowing users to clear all folders at once. Add a confirmation dialog to
prevent accidental removal. Update the FinderSidebar to include a menu button
with the new action, and implement supporting state and handlers in the
document list. Add DotsHorizontalIcon for menu UI. Expand folder tests to
cover the new bulk removal flow.
Update gallery view document tiles with refined border, shadow, and background
styles for better visual feedback and accessibility. Enhance image preview
component to immediately reveal cached images by checking decode state on mount,
preventing opacity glitches on remount. Adjust gallery rail scrolling to reset
on document list changes for consistent navigation.
Eliminate folder grouping logic and related UI from all document list views.
Switch to a flat document array model for rendering, selection, and filtering.
Remove folder collapse/expand state and folder drag-and-drop handling. Update
sidebar and toolbar components to remove folder creation and management
controls. Simplify props and internal state across all views for consistency.
Introduce recentlyOpenedAt field to document types and fetch per-document
recently opened timestamps from local cache. Update the "Recently Opened"
sidebar filter to sort by this value and show only documents with recent
activity. Adjust grid layout logic for icons view to better handle small
numbers of documents. Update sidebar label for clarity.
Replace inline icon size buttons with a dropdown menu that appears when
the icons view toggle is active. This change improves toolbar clarity by
grouping related controls and enhances accessibility with better focus
handling. The update also streamlines the button structure for view mode
selection.
Replace fixed Tailwind grid column classes with a responsive CSS grid
that leverages tile width variables per icon size. Grid columns now
adapt automatically via `auto-fit` and `minmax`, and grid gaps are
centralized for consistency. This update enhances layout flexibility,
maintainability, and responsiveness in the icons view.
Enhance mobile and desktop sidebar behavior by introducing a separate state for mobile sidebar visibility and ensuring it does not persist from desktop state. Update FinderToolbar to visually indicate sidebar open state and make the toolbar sticky for better accessibility. Refine FinderSidebar width handling for improved responsiveness. Remove redundant border in mobile sidebar dialog for a cleaner appearance. These changes provide a more consistent and intuitive sidebar experience across viewports.
Simplify document list state handling by centralizing loading and empty logic into DocumentList.
Remove redundant state management from HomeContent. Refactor DocumentListSkeleton to support all view modes and icon sizes for improved visual consistency during loading. Enhance ColumnsView styling for folder and document rows, improving selection, hover, and drag feedback. Use useMemo for visible order calculation to optimize rendering.
These changes streamline the document list rendering flow, unify skeleton loading visuals, and improve the clarity and responsiveness of the columns view interface.
Integrate DocumentUploader in overlay mode to wrap all document views, enabling drag-and-drop uploads across the entire list area. Refine file size formatting to support KB, MB, and GB units in all views and tiles. Standardize button scaling and padding for consistent UI rhythm. Update DocumentPreview and detail panels to display both type and human-readable size. Add overlay drag visual feedback for uploads.
These changes improve usability, visual clarity, and consistency in the document list interface.
Revamp the document list experience by introducing a Finder-style window
interface with multiple views (icons, list, columns, gallery) and a new
sidebar filter system. Remove legacy folder and list item components in
favor of modular, windowed views. Integrate react-dnd-touch-backend and
custom DnD context for improved drag-and-drop, including mobile support.
Update uploader and dialog styles for consistency. Extend document types
to support new view modes, icon sizing, and sidebar state.
Add useUnmountCleanupRef custom hook to centralize and simplify unmount
cleanup logic in document page components. Refactor EPUB, HTML, and PDF
page components to utilize this hook, replacing repetitive ref and effect
patterns for invoking cleanup functions on unmount. This reduces code
duplication and improves maintainability.
Remove redundant clearCurrDoc calls from navigation links and ensure document cleanup occurs on component unmount for EPUB, HTML, and PDF pages. Simplify PDF back navigation by eliminating sidebar delay logic. Refactor DocumentListItem to avoid unnecessary router usage and consolidate document link handling. These changes improve maintainability and consistency in document lifecycle management across the UI.
Eliminate the errorCode property from server logger calls, error contract types, and API error bodies throughout the codebase. This reduces duplication and streamlines error reporting, relying on structured event names and error classes for identification. All affected API routes, server logic, and logging utilities have been updated for consistency. Documentation directory added for future reference.
Update TTS segment ensure route to distinguish aborted and failed segment generation, providing more granular logging and context. Replace ambiguous message variables with clearer detail fields, and improve log event naming for abort scenarios. Adjust ESLint logger call selectors for maintainability and consistency, consolidating selector logic and enforcing stricter server logger usage patterns. These changes improve observability and error traceability in TTS segment operations.
Introduce `serverLogger` utility based on pino for consistent, structured logging across all server and API modules. Replace direct console logging with `serverLogger` and add request-scoped logging helpers. Update environment variable handling, documentation, and deployment guides to reflect new logging configuration (`LOG_FORMAT`, `LOG_LEVEL`, `COMPUTE_LOG_LEVEL`). Enforce no-console in server code via ESLint and add pino/pino-pretty dependencies.
This change standardizes log output, improves observability, and prepares the codebase for ingestion by log platforms.
Integrate opId handling throughout PDF layout parsing by updating client hooks, API routes, and event streams to support operation ID propagation. Add server utility for worker operation creation to centralize opId management. Enhance polling and SSE subscription utilities to accept and return opId, improving traceability and coordination of parse operations across the client and server.
Define maxDuration as 300 seconds for the parsed events API route to explicitly
control execution timeouts during long-running PDF layout processing. This
ensures more predictable serverless behavior for streaming operations.
Introduce detailed console logging across document parsing API routes and worker
operation state fetches. Logs now capture stream openings, error conditions,
worker state unavailability, and invalid responses, improving observability and
debuggability for PDF parsing and ONNX layout processing flows.
Expand documentation and example env files to explicitly distinguish which
environment variables belong in the app/root `.env` versus the `compute/worker/.env*`
files when deploying in external worker mode. This clarifies routing/auth
settings versus worker runtime configuration, reducing misconfiguration risk.
Add compute/core/package.json and compute/worker/package.json to Docker build
context to ensure all workspace dependencies are correctly installed during
image creation. This change prevents missing dependency issues for subpackages
when building in isolated environments.
Update all configuration, manifest, and documentation references to use the q4
Whisper ONNX model files instead of int8. Adjust expected file names, hashes,
and environment variable descriptions to reflect this new default. This aligns
runtime, deployment, and developer documentation with the updated model artifact
expectations for improved consistency.
Add worker operation state polling to document parse routes, enabling real-time
status and progress updates from the ONNX layout worker. Introduce
`fetchWorkerOperationState` utility and status mapping logic to synchronize
parse status with worker job state, improving accuracy for pending, running,
and failed operations.
Add robust fatal shutdown handling for embedded services (weed mini,
nats-server, compute-worker) to ensure all services are terminated if any
critical process exits unexpectedly. Introduce flags to prevent duplicate
shutdowns and improve error reporting for better reliability in process
management.
Refactored docstore directory logic to dynamically locate the monorepo
root by searching for a pnpm-workspace.yaml marker. If found, the
docstore directory is anchored at the monorepo root; otherwise, it
defaults to the current working directory. This enhances consistency
when running in different environments.
Also updated process shutdown and SeaweedFS launch arguments in the
entrypoint script for improved reliability and log filtering.
Eliminates the START_EMBEDDED_COMPUTE_WORKER environment variable from configuration,
documentation, and entrypoint logic. Embedded compute worker startup is now strictly
determined by the absence of COMPUTE_WORKER_URL. If embedded mode is triggered,
the presence of the nats-server binary is mandatory; otherwise, an error is thrown.
Documentation and example env files have been updated to reflect this streamlined
behavior and clarify requirements for both embedded and external compute worker setups.
Update tsconfig.json to include a path mapping for
@openreader/compute-core/api-contracts, enabling cleaner imports and
improving modularity for API contract usage across the codebase.
Move shared type contracts from `contracts` to new `api-contracts` module in `compute/core` for clearer API boundaries. Remove legacy `contracts`, `pdf`, and `whisper` index files. Update all imports to use `api-contracts`. Refactor server job logic by replacing `parsePdfJob.ts` with modular `user-pdf-layout-job.ts` and `user-whisper-align-job.ts`, updating all relevant API routes and compute integration. This improves maintainability and separation of concerns across compute and server layers.
Add useOnboardingCoordinator hook and onboarding-state registry to manage
onboarding-related state such as privacy acceptance and first-visit tracking.
Refactor SettingsModal to utilize the new onboarding state abstractions,
removing legacy privacy gating logic and improving maintainability.
Includes unit tests for onboarding-state registry to ensure correctness.
Move TTS segment cache invalidation to a fire-and-forget promise after
document update to prevent cache errors from blocking parse readiness.
Log warnings for both cache invalidation failures and warnings, but do not
interrupt the main job flow. This improves job robustness and avoids
unnecessary failures due to cache issues.
Consolidate privacy acceptance checks into reusable hooks and callbacks within
SettingsModal. Replace scattered gating with a single entry point for opening
settings or changelog views, ensuring consistent enforcement. Update test
helpers to robustly dismiss onboarding and settings overlays by avoiding
state race conditions.
Enhance worker operation handling by adding detailed logging for operation
acceptance, reuse, replacement, and stale detection, including opKey hashes and
reasons for replacement. Remove the alignMutex from Whisper alignment logic to
simplify concurrency control and reduce unnecessary serialization of alignment
requests. These changes improve observability and throughput for alignment and
layout parsing jobs.
Introduce COMPUTE_NATS_REPLICAS environment variable to control the number of
replicas for JetStream streams and key-value buckets. Only values 1, 3, or 5
are accepted, defaulting to 1 for other inputs. Update worker server logic and
deployment documentation to describe and support this configuration.
Add lazy NATS connection lifecycle and idle disconnect logic to the worker,
enabling automatic disconnection after 120s of inactivity to support Railway
container sleep. Update deployment docs to describe this behavior, including
caveats and reconnection flow.
Allow '@openreader/compute-core/types' as a permitted subpath in ESLint
import rules. Update the lint message to reflect the new allowed subpath,
ensuring consistency with recent type centralization efforts.
Move and re-export all PDF parsing and TTS-related types from
@openreader/compute-core/types, eliminating local type duplication.
Update imports throughout the codebase to use the new centralized type
module. Remove obsolete worker-contract file and update tsconfig paths
and compute-core exports for the new types entry point. Refactor API
routes and job logic to use the new type imports and shared parse-state
healing utility. This streamlines type management and improves
consistency between compute and app layers.
Introduce automatic healing for stale PDF parse states in API routes. Add
`isDocumentParseStateStale` utility and use a shared `COMPUTE_OP_STALE_MS`
environment variable (with config getter) to control the stale window for both
worker op replacement and app-side parse-state healing. Update documentation and
environment variable references to reflect new config.
Enables automatic marking of stuck parses as failed, improving reliability and
retry behavior in distributed compute environments.
Update PDF viewer and document hook to improve parse status management:
- Set parseStatus to null on clear instead of 'pending' for accurate state
- Explicitly set initial parseStatus and reset parseProgress when loading a new document
- Adjust loader display logic to check for resolved parse status before showing expanded loader
Prevents UI inconsistencies and ensures parse progress is reflected correctly during document transitions.
- Replace static JSON imports with runtime manifest loading in pdf and whisper model modules for compatibility with ESM and bundlers
- Refactor Next.js config to clarify compute mode logic and improve worker bundling conditions
- Update test imports to use package entrypoints instead of relative paths
- Remove redundant whisper alignment/model tests now covered elsewhere or by integration
- No breaking changes to public API or model handling logic
Replace static timeout constants with functions that derive alignment and ffmpeg
decode timeouts from compute timeout config. This enables adaptive timeout
behavior based on runtime configuration, improving robustness under varying
resource constraints. Update error handling and internal APIs to propagate
timeout values accordingly.
Remove unused Node.js engine constraint from package.json for broader compatibility.
Remove unnecessary UI reload and modal interactions during audiobook export
cleanup to reduce flakiness under load. Set explicit timeout for long-running
MP3 export test to ensure consistent execution. These changes enhance test
stability and maintainability.
- Deleted mergeTextWithRegions.ts, LICENSE.txt, manifest.json, parsePdf.ts, renderPage.ts, runLayoutModel.ts, stitchCrossPageBlocks.ts, and types.ts as they are no longer needed.
- Updated unit tests to reflect new import paths from @openreader/compute-core.
- Adjusted tsconfig.json to include new paths for pdf-layout module.
Align COMPUTE_PDF_JOB_ATTEMPTS default value with new ONNX-based PDF layout
parsing approach by setting it to 1 in environment examples, worker config,
and deployment documentation. This reflects improved reliability and reduces
unnecessary retries for PDF processing jobs.
Standardize compute job concurrency configuration by introducing a shared
COMPUTE_JOB_CONCURRENCY setting, replacing separate PDF and Whisper concurrency
controls. Add cross-platform CPU core detection and thread budgeting utilities,
and update both ONNX model execution and concurrency limiters to use dynamic
thread allocation per job. Refactor environment variable docs and examples to
reflect unified concurrency management. Streamline PDF.js font path resolution
for improved reliability across environments.
Introduce idle and hard cap timeouts for PDF layout parsing in the compute worker,
ensuring long-running jobs are capped and progress is tracked with explicit
timeouts. Refactor NATS JetStream client initialization to use a configurable
API timeout. Update PDF viewer UI to treat parse state as 'pending' by default,
improving clarity during document preparation and parsing. Refine parse loader
labels and progress logic for more accurate user feedback.
Update compute worker to use separate environment variable for PDF job attempts
(`COMPUTE_PDF_JOB_ATTEMPTS`) and set Whisper job max deliveries to 1, clarifying
and separating retry logic between job types. Adjust `.env.example` and deployment
docs to comment out defaults and document new/renamed variables for advanced tuning.
In TTS segment ensure route, introduce explicit timeouts for Whisper alignment
operations, with configurable durations for local and worker modes, improving
robustness and error handling for long-running alignments.
Revise PDF viewer to introduce an explicit 'unknown' parse state and improve
loader and status messaging for better user clarity. Refactor Spinner to use
'currentColor' for border styling and simplify conditional rendering based on
className. Expand compute worker .env.example with explicit concurrency,
timeout, and job attempt variables for enhanced configuration transparency.
Enhance PDF parse loader transitions by introducing a delayed expansion for detailed progress, reducing UI flicker during parse-to-render handoff. Refactor PDFViewer to ensure document readiness is signaled only once per load, preventing redundant callbacks and improving synchronization between parsing and rendering states.
Introduce detailed progress reporting for PDF layout parsing, exposing phase and page-level updates to clients. Replace legacy parseStatus with a structured parseState field in the database schema, updating all relevant backend and API logic. Add SSE endpoint for real-time parse progress updates. Update client hooks and UI to reflect granular progress and improve user feedback during document parsing. Includes migration scripts and new parse-state utility module.
Migrate worker and backend contract to an operation-centric model, introducing opKey-based deduplication for Whisper alignment and PDF layout parsing. Add new operation request/state types and update worker endpoints to accept generic operation requests and expose operation status via SSE. Refactor client and server logic to support operation flows. Update deployment docs, environment examples, and Dockerfile to reflect new architecture and dependency management.
Add ability to fetch parsed documents using a stored S3 key, enabling more flexible blob access patterns. Update API route to prefer `parsedJsonKey` if present, falling back to legacy lookup. Introduce `COMPUTE_WORKER_WAIT_TIMEOUT_MS` environment variable for customizing worker job wait time, with a new default of 120 seconds. Update documentation and examples to reflect these changes.
Align compute worker to use PORT environment variable consistently across
Dockerfile, docker-compose, and server code, replacing COMPUTE_WORKER_PORT.
Update .env.example and deployment documentation to clarify platform-specific
behavior, especially for Railway and similar environments. This simplifies
configuration and reduces platform-specific issues.
Update worker server to prioritize platform-specific PORT environment variable
when setting the compute worker port. Add robust normalization for
COMPUTE_WORKER_URL, ensuring proper scheme and validation, and trim trailing
slashes for consistency. This enhances reliability in diverse deployment
environments.
Update backend creation logic to consistently determine compute mode using
a local variable, ensuring correct handling when the mode is undefined. This
improves clarity and robustness in backend selection.
Remove 'ffmpeg-static' from server external packages in Next.js config and
specify Node.js 22.x as required engine in package.json to align with
deployment environment.
Update Next.js configuration to conditionally set 'output' only when not on
Vercel, and always define 'outputFileTracingExcludes' with dynamic includes
based on compute mode. This ensures compatibility with Vercel deployments and
improves local/server build behavior.
Update TTS context to include configProviderType, supportsInstructions, and
ttsInstructions in the provided value, improving downstream access to provider
capabilities and instruction handling.
Update compute backend interfaces and job logic to consistently handle parsed
PDF layout results as either direct data or S3 object key references. Refactor
result types, local and worker backends, and job processing to support this
pattern. Improves flexibility for large document parsing and object storage
integration.
Add `COMPUTE_JOBS_STREAM_MAX_BYTES` and `COMPUTE_JOB_STATES_MAX_BYTES` to
environment example, worker server configuration, and deployment docs. These
variables allow tuning JetStream resource caps for job queue and state storage
to better fit deployment requirements.
Restructure compute core by extracting job contracts and runtime logic into separate
modules (`contracts.ts`, `local-runtime.ts`) for improved modularity and clearer
API boundaries. Update exports and imports across worker, server, and local
compute backends to use the new modules. Enhance Next.js config to support
dynamic backend selection via build-time constants and aliasing, enabling
seamless switching between local and worker compute modes. Adjust TypeScript
paths and Dockerfile entrypoint to align with the new structure.
Add a comprehensive Synadia Cloud and Railway setup guide to the compute worker
deployment docs, including step-by-step environment variable configuration and
example image tags. Update Vercel deployment instructions with a quick start for
external compute worker integration via Railway and Synadia NGS. Document new
`.env.prod` pattern in .gitignore for production environment management.
Remove legacy .npmrc config. Revise root and worker Dockerfiles to enhance
layer caching, explicitly copy only required build artifacts, and streamline
runtime dependencies. Switch to pnpm workspace-aware install for compute worker
to ensure correct dependency resolution. Set Next.js output to standalone mode
for optimized server builds. Update docker-compose sync targets for consistency.
Expand compute worker documentation to explain NATS authentication using
either a credentials file or raw credentials string, including Synadia Cloud
integration steps. Update environment variable examples and server connection
logic to support both `NATS_CREDS_FILE` and `NATS_CREDS`. Add `.creds` to
.gitignore to avoid accidental credential leaks.
This improves deployment flexibility for cloud and on-prem environments by
supporting multiple authentication methods for NATS JetStream.
Replace Redis and BullMQ with NATS JetStream and KV for job queuing and state management
in the compute worker service. Update environment variables, Docker Compose, and documentation
to reflect the new message queue backend. Adjust dependencies and code to use NATS-based
work queue and key-value storage for durable job processing.
This change improves scalability and reliability of distributed compute workloads by leveraging
NATS JetStream's work queue and persistence features. Documentation and configuration examples
are updated to guide deployments using the new backend.
Introduce clearSegmentCaches to TTS context for explicit cache invalidation after segment clearing. Enhance SegmentsSidebar scroll logic to better distinguish user-initiated and programmatic scrolls, improving UX during segment updates.
Eliminate the smartSentenceSplitting configuration flag from application state, UI, and all TTS and audiobook adapter logic. Consolidate TTS segment planning to always include context units, streamlining code and reducing conditional branches. Update tests and type definitions to reflect the removal of this feature toggle.
Update TTS segment prefetching logic to handle arrays of CanonicalTtsSourceUnit for upcoming and next locations, enabling more precise TTS segment mapping. Expand cache key construction to include providerType and instructions, improving cache differentiation for TTS requests. Adjust related types and usages to support richer segment metadata propagation.
Introduce sourceUnitsFromParsedPage helper to extract CanonicalTtsSourceUnit
objects from parsed PDF pages, filtering by block kind and text content. Update
TTS state setter to include extracted source units when available, enabling
improved TTS segment mapping and future extensibility.
Move download logic into reusable downloadViaTrigger function. Add retries and
button enabled checks to downloadFullAudiobook for improved robustness against
transient UI or backend delays.
Replace text-based assertion for PDF title with role-based heading check and
add assertion for visible page navigation button. Improves test robustness
against UI changes and ensures accessibility compliance.
Unify and improve layout settling logic across EPUB, HTML, and PDF viewers by
adding delayed recomputation and guarding against zero-height container during
transient states. Refactor PDF document loading to include retry logic with
success detection, ensuring robust handling of transient failures. Update
Playwright test helpers to provide more reliable PDF viewer readiness checks
and introduce request retry utilities for backend API calls. Add targeted unit
tests for single-section chapter fallback in the PDF audiobook adapter. Update
test files and helpers to align with new layout and export logic.
Refactor compute backend initialization to use asynchronous loading and promise caching, replacing previous synchronous singleton pattern. Update all consumers—including TTS segment alignment and PDF layout parsing jobs—to await compute backend resolution before invoking methods. This change improves compatibility with dynamic imports and future-proofs backend selection logic.
Eliminate unused PDF text extraction logic and margin-based filtering from the client library. Refactor API routes to generalize parse status normalization, removing explicit references to deprecated "unsupported" status and type variants. Update usages to accept string parse statuses for improved flexibility. Simplify document state dependency tracking in the PDF document hook.
Eliminate the "none" compute mode and all related code paths, including the NoneComputeBackend, "unsupported" parse status, and PDF margin settings. Parsing is now always available if the app starts successfully, and configuration is limited to "local" or "worker" compute modes. Update types, API routes, client adapters, and documentation to reflect this simplification.
BREAKING CHANGE: "none" is no longer a valid COMPUTE_MODE; only "local" and "worker" are supported. "unsupported" parse status and PDF margin settings are removed.
Introduce WorkerJobTiming interface to capture queue wait, S3 fetch, and compute
durations for compute worker jobs. Update job result and status response types
to include timing data. Enhance worker implementation to record and attach
timing metrics for alignment jobs, improving observability of job processing
latency. Update exports and type usage to support new timing fields.
Revise project highlights in README and introduction to emphasize ONNX-based
PDF layout parsing (PP-DocLayoutV3), segment-based TTS playback, and flexible
backend/storage options. Clarify compute modes and self-hosting features.
Update TTS provider descriptions for accuracy and conciseness.
Also expand tsconfig exclude list to omit compute worker sources from main build.
Introduce support for external compute worker mode (`COMPUTE_MODE=worker`) using a new `WorkerComputeBackend`. This enables offloading heavy ONNX Whisper alignment and PDF layout parsing to a standalone worker service (Redis + BullMQ), improving scalability and compatibility with serverless/limited environments.
- Add `@openreader/compute-core` as a shared package for ONNX inference and PDF parsing logic.
- Implement `WorkerComputeBackend` and worker contract/types for remote job execution.
- Update compute backend selection logic and remove previous worker mode guards.
- Extend `WhisperAlignInput` and `PdfLayoutInput` types to support object keys for remote data access.
- Refactor local compute backend to use `@openreader/compute-core` and support both buffer and object key inputs.
- Update job runner, TTS segment alignment, and PDF layout parsing flows to use new compute backend APIs.
- Add scripts, Docker workflow, and documentation for deploying and running the compute worker.
- Update environment variable docs and examples for worker mode, including storage requirements and configuration.
- Document published images and stack changes to reflect the new compute worker architecture.
BREAKING CHANGE: `COMPUTE_MODE=worker` now requires an external compute worker service and S3-compatible object storage. Embedded SeaweedFS (`weed mini`) is not supported in worker mode. See the new documentation for deployment and configuration details.
- Replace `OPENREADER_COMPUTE_MODE` and related variables with `COMPUTE_MODE`
- Replace `OPENREADER_*` PDF/Whisper model URLs with `PDF_LAYOUT_MODEL_BASE_URL` and `WHISPER_MODEL_BASE_URL`
- Remove legacy `NEXT_PUBLIC_*` runtime config seeds in favor of `RUNTIME_SEED_*`
- Update documentation, code, and environment references to match new variable names
- Remove deprecated `scripts/fetch-models.mjs` and related npm script
- Update runtime config SSR injection from `window.__OPENREADER_RUNTIME_CONFIG__` to `window.__RUNTIME_CONFIG__`
- Adjust Next.js config to use new compute mode env var and optimize output file tracing for ONNX dependencies
BREAKING CHANGE: Environment variable names for compute mode, model URLs, and runtime config seeding have changed. Update `.env` files and deployment configs to use `COMPUTE_MODE`, `PDF_LAYOUT_MODEL_BASE_URL`, `WHISPER_MODEL_BASE_URL`, and `RUNTIME_SEED_*` as appropriate. Legacy `OPENREADER_*` and `NEXT_PUBLIC_*` variables are no longer supported.
Replace the previous whisper.cpp-based word alignment with a fully ONNX-based
implementation using onnxruntime-node and @huggingface/tokenizers. Add new
Whisper ONNX model management, alignment mapping, and spectral analysis modules.
Remove all code and documentation referencing whisper.cpp, update environment
variables, Dockerfile, and docs to reflect ONNX-only alignment. Add unit tests
for alignment and ONNX model logic.
Enhance PDF cross-page block stitching to move only sentence continuations and
preserve remaining text on the following page. Update TTS segment planning to
support strict source boundary enforcement, ensuring segments do not cross
block or paragraph-title boundaries. Add new tests for both features and
refactor PDF text item normalization to filter out skewed or rotated runs.
Remove temporary working directory usage and in-place file writes from preview
generation, simplifying logic for both PDF and EPUB previews. Change default
preview image variant from 480px to 400px width, updating related constants and
cache schema version to maintain consistency across client and server code.
Implement block-level highlighting in PDF viewer by leveraging parsed document
structure and TTS segment locator. Extend highlightPattern API to accept options
for parsedDocument, locator, and block geometry mode. Update PDFViewer to use
block geometry highlighting when available, improving accuracy and alignment of
sentence and word highlights with ONNX-based DocLayoutV3 parsing. Refactor
highlighting logic to support both legacy span-based and new geometry-based
approaches.
Remove hardcoded defaults for preview variant and width in database schemas to allow more flexible preview generation. Bump preview cache schema version and add versioning to cache rows for consistency. Switch preview image generation to 480px JPEGs with updated file naming and content type. Refactor PDF preview rendering to support configurable output format and quality, and update all preview consumers to use unified "image" buffer property instead of "png". Add migration scripts and update metadata for both Postgres and SQLite.
BREAKING CHANGE: Preview variant and width defaults removed from database; preview cache and image handling updated across system.
Switch PDF layout parsing to use the PP-DocLayoutV3 ONNX model, replacing the previous Docling-based approach. Update all environment variables, model fetching, and manifest handling for the new model and its artifacts. Refactor block kind taxonomy throughout the codebase, tests, and UI to align with PP-DocLayoutV3 labels, including expanded and renamed block types. Revise document settings, block filtering, and stitching logic to support the new set of block kinds. Update documentation and environment variable references to reflect the model transition.
BREAKING CHANGE: PDF parsing now requires PP-DocLayoutV3 ONNX model and updated environment variables; block kind names and settings have changed throughout the system.
Migrate PDF parsing to use ONNX Docling layout model for structured block extraction, enabling improved TTS segmentation and chaptering. Add compute backend abstraction for heavy tasks (alignment, layout parsing) with configuration via `OPENREADER_COMPUTE_MODE`. Integrate block-level locators and document settings for per-document PDF parsing options. Update S3 storage for parsed PDF JSON, add migration and schema changes, and extend client and server APIs for parsed data and settings. Remove legacy word highlight flag in favor of compute capability detection.
- Add ONNX model download, local/none compute modes, and `onnxruntime-node` dependency
- Update PDF viewer and TTS pipeline to use parsed blocks and block-level locators
- Add document settings storage and APIs for per-document PDF options
- Update S3 storage layout for parsed PDF JSON
- Add admin/config/docs updates for new compute and parsing features
- Remove obsolete word highlight runtime flag and UI
BREAKING CHANGE: PDF parsing and word highlighting now require `OPENREADER_COMPUTE_MODE=local` and ONNX model; document settings and S3 layout updated; legacy word highlight flag removed.
Introduce sentence and word highlighting for HTML/TXT/MD documents, including new config options (`htmlHighlightEnabled`, `htmlWordHighlightEnabled`) and synced user preferences. Update the HTML viewer to support these highlights using custom spans, and add corresponding CSS styles. Extend document settings to allow toggling these features. Implement block-level locator sorting and display improvements. Add audiobook export support for HTML documents, including adapter and pipeline integration.
- Add `htmlHighlightEnabled` and `htmlWordHighlightEnabled` to config, user state, and Dexie storage
- Update HTML viewer to apply and manage highlights for sentences and words
- Add CSS for HTML highlight classes and scroll margin
- Extend document settings UI for HTML highlight toggles
- Support HTML audiobook export in modal and pipeline
- Improve block locator sorting and sidebar display for HTML
- Add HTML audiobook adapter and block parsing utilities
Extract release-based deploy triggers to new workflow for clarity and
reliability. Update docs-deploy.yml to streamline dispatch inputs and
remove release/workflow_run triggers. Extend build-changelog-feed.mjs to
support incremental changelog sync via workflow_dispatch with release tag
and action, enabling precise upserts and deletions of changelog entries.
Revert packageManager to pnpm@10.33.4 in package.json for compatibility.
Expand allowBuilds in pnpm-workspace.yaml to explicitly exclude canvas,
core-js, es5-ext, esbuild, sharp, and unrs-resolver from workspace builds.
These adjustments enhance build reliability and prevent issues with
problematic dependencies.
Update GitHub Actions workflows to latest action versions and improve pnpm usage by introducing per-package workspace configs. Add docs-site/pnpm-workspace.yaml and update Dockerfile/package.json for better monorepo support. Upgrade dependencies across main and docs-site packages for compatibility and security. Refine TypeScript types and hooks for improved type safety and maintainability.
Upgrade pnpm version to 11.1.2 across Dockerfile, workflows, and package.json.
Add pnpm-workspace.yaml to enable workspace features. Update all docs-related
scripts and CI jobs to use --ignore-workspace for isolated docs-site installs.
Set packageManager field in package.json for explicit version control.
This ensures consistent dependency management and prepares for multi-package
workspace support.
Expand admin panel documentation to detail the transition from live env var
reading to one-shot seeding for TTS providers in v3.0.0. Add warnings about
the impact of rotating AUTH_SECRET on encrypted admin provider keys and the
required manual steps after rotation.
docs(migrations): add schema migration history table
Document the sequence and purpose of each migration introduced in v3.0.0,
clarifying upgrade behavior from v2.2.0 and summarizing schema changes.
chore(env): remove commented default TTS model example from .env.example
Add explicit runtime checks to prevent runtime config functions from being called on the client. This guards against accidental client-side invocation by throwing errors if executed outside the server environment.
Move runtime config resolution logic to a new `runtime-config-rsc.ts` module for use in React Server Components. Update public layout and landing page to consume the RSC-specific resolver, improving compatibility and separation of concerns.
Introduce `enableUserSignups` runtime setting to allow administrators to control
whether new accounts can be created. Update environment variable and documentation
references to support this feature. UI elements for account creation are now
conditionally rendered based on this flag. Signup attempts are blocked server-side
when disabled, including email, OAuth, and anonymous upgrades.
Add `assertUserSignupAllowed` utility for consistent enforcement and corresponding
unit tests to verify policy behavior.
Refine radial gradient backgrounds in Settings and DocumentSettings modals to use
CSS color-mix with accent variable for improved theming. Adjust Settings modal
header spacing and layout for better alignment and consistency.
Refactor chapter API to use new coerceAudiobookGenerationSettings utility for
validating and migrating audiobook metadata. Add src/lib/server/audiobooks/settings.ts
with shared logic and introduce corresponding unit tests to ensure correct
settings migration and validation.
Move changelog version check effect from SettingsModal to a new
scheduleChangelogCheck utility for improved modularity and testability.
Add unit tests for changelog check scheduling behavior.
Integrate changelog version check on settings open, triggering the changelog
panel when a new app version is detected. Refactor user preferences storage to
support metadata via payload utilities, enabling version tracking and future
extensibility. Add API endpoint for changelog version check and related tests
for changelog and preferences payload logic.
Update SettingsModal to use conditional rendering for the changelog panel,
displaying the main settings navigation only when the changelog is closed.
Refactor mobile and desktop navigation layout to improve maintainability.
Add missing ChevronRightIcon import for icon consistency.
Introduce changelog feed manifest support, including client and shared
utilities for fetching and parsing changelog data. Add a new
Settings modal panel for viewing changelog entries, with version
detection based on the current app version. Expose a configurable
changelog feed URL in both environment variables and admin panel
runtime settings. Update documentation and deployment workflow to
support changelog feed generation and consumption. Include unit tests
for changelog utilities.
Update multiple UI panels to use skeleton components instead of loading text or spinners.
Standardize loading states in AudiobookExportModal, AdminFeaturesPanel, AdminProvidersPanel,
DocumentSelectionModal, and SegmentsSidebar for improved visual consistency and user feedback.
Eliminate the showAllDeepInfraModels runtime/admin config and all code paths
that allowed restricting Deepinfra's model catalog. Deepinfra now always
shows the full model list regardless of API key or environment variable.
Update documentation, environment examples, admin panels, runtime config,
provider catalog logic, and tests to reflect this change.
Move shared button, input, and listbox styles to a new formPrimitives module
used across admin panels, settings, and document settings. Update admin and
settings components to import these styles from the new location. Refactor
DocumentSelectionModal to use a direct files prop with loading and error
states, removing fetcher logic and internal fetch state. Add a custom hook
for library document queries. Clean up legacy admin/ui primitives and
standardize segmented controls and section layouts.
Integrate @tanstack/react-query in SettingsModal to cache and prefetch library
documents, improving import modal responsiveness. Refactor SegmentsSidebar to
use infiniteQuery for TTS segments manifest, enabling pagination, cache
invalidation, and consistent data fetching. Remove legacy fetch state logic in
favor of react-query state management.
Adopt new layout files for app, epub, html, and pdf routes to standardize
Next.js nested layout structure. Refactor SegmentsSidebar to accept an explicit
epubBookRef prop, decoupling it from internal context. Enhance EPUB resize hook
to ignore initial baseline rect and avoid unnecessary TTS interruptions on load.
Simplify Providers by removing legacy context nesting and conditional logic.
Adopt @tanstack/react-query for data fetching and cache management in admin
settings, provider management, shared provider hooks, and context providers.
Replace legacy useState/useEffect data loading with react-query's useQuery and
useMutation patterns. Refactor document, rate-limit, and shared provider
contexts to use query keys and cache invalidation for consistent state across
the app. Add QueryClientProvider to root providers. Update package.json to
include react-query dependency.
Remove fallbackProviderRef usage from settings and config context to streamline
provider resolution. Adjust normalizeLegacyProviderRef to retain 'default-openai'
unless a non-default fallback is explicitly provided. Update provider listing to
enforce deterministic ordering by updatedAt, createdAt, and slug. Expand unit tests
to verify provider ordering, legacy ref normalization, and selection logic.
Transition all TTS-related logic, types, and UI to use the new providerRef/providerType model in place of legacy ttsProvider fields. Introduce a centralized tts-provider-policy module to encapsulate provider/model capability checks, default value resolution, and compatibility logic. Update all API routes, contexts, hooks, components, and tests to use providerRef and providerType, ensuring consistent handling of built-in and shared TTS providers. Remove legacy defaultTtsModel config in favor of per-provider defaults and shared provider admin control. Add the showAllProviderModels runtime flag to restrict users to provider default models when desired.
BREAKING CHANGE: ttsProvider fields are replaced by providerRef/providerType throughout the codebase; defaultTtsModel config is removed in favor of per-provider defaults.
Add a dedicated Admin Panel documentation page detailing management of shared TTS providers and site features via the admin UI. Update all TTS provider guides, environment variable references, and deployment docs to clarify the new runtime configuration model: environment variables serve as first-boot seeds only, with ongoing management handled through the admin interface. Revise sidebars and cross-links to include the new admin panel docs and clarify the distinction between legacy env-based and admin-managed configuration.
Add database-backed runtime configuration for feature flags and TTS provider credentials, editable via a new admin UI panel. Replace static NEXT_PUBLIC_* and TTS provider env vars with admin-managed settings stored in the database and injected at SSR for client access. Implement admin-only panels for managing shared TTS provider credentials (with encrypted API keys) and live site feature flags. Add schema migrations, API routes, React contexts, and hooks for SSR-injected runtime config and live updates. Update client and server logic to resolve configuration from the database at runtime, enforcing admin restrictions and supporting migration from legacy env-based config.
BREAKING CHANGE: Feature flags and TTS provider credentials are now managed at runtime via the admin UI. Environment variables are only used for initial seeding and are ignored after first boot. Existing deployments must migrate configuration to the admin panel.
Eliminate the deprecated `/api/tts` route and its client usage, consolidating TTS generation logic under chapter and segment endpoints. Update client API utilities to remove direct TTS buffer fetching and improve error propagation for chapter generation failures. Extend TTS segment error metadata to include upstream rate limit and retry details, and standardize error typing in shared types. Adjust tests and helpers to reflect the new TTS mocking approach and endpoint structure. This streamlines TTS API surface and enhances error reporting consistency.
Move daily quota exceeded problem response logic to a dedicated utility in `lib/server/rate-limit/problem-response`. Refactor API routes to use the new builder and remove duplicated formatting code. Update all relevant imports for consistency. This improves maintainability and ensures uniform error responses across TTS endpoints.
Move `epub-word-highlight`, `tts-epub-handoff`, and `tts-epub-preload` from shared to client/epub to clarify their usage and improve bundling. Update all imports to use new client paths. Extend TTS and sidebar logic to support HTML document progress tracking and restoration, introducing a new location format for HTML and consistent handling for non-EPUB readers.
BREAKING CHANGE: EPUB TTS utilities relocated to `lib/client/epub`; update import paths. HTML progress persistence now uses a new format incompatible with previous versions.
Redesign TTS segment storage by decomposing the `tts_segments` table into two normalized tables: `tts_segment_entries` for unique segment identity and locator projection, and `tts_segment_variants` for per-settings audio variants. Update schema, queries, and API routes to use the new structure, including manifest pagination and segment resolution logic. Refactor segment locator handling to use stable projections and manifest cursors. Migrate tests and data cleanup utilities to support the new model.
BREAKING CHANGE: Database schema for TTS segments is now split into entry and variant tables; all code and consumers must use the new structure. Persisted data and APIs relying on the old `tts_segments` table are incompatible.
Implement strict validation for EPUB TTS segment locators, requiring stable spine coordinates (`spineHref`, `spineIndex`, `charOffset`) and rejecting legacy CFI-only locators. Introduce `segmentKey` as a normalized identity derived from segment text, used for robust merging and deduplication of synthesized and persisted segments. Update database schemas, API routes, and manifest logic to support the new locator format and segmentKey. Add helpers for locator resolution and segmentKey construction, with comprehensive unit tests for identity, normalization, and manifest grouping.
BREAKING CHANGE: Persisted EPUB TTS segment locators must now include stable spine coordinates; legacy CFI-only locators are no longer accepted. Consumers must handle the new `segmentKey` field for segment identity and merging.
Refactor TTS segment identification to support canonical segment keys, decoupling segment IDs from locator and index for more robust deduplication and handoff. Add `segmentKey` to segment types, manifest, and ensure route. Implement natural sorting for EPUB CFI locations and segment locators using new comparison utilities. Update sidebar and manifest helpers to use locator-aware sorting. Add foundational modules for EPUB word highlighting, TTS segment planning, and EPUB handoff logic. Expand types and tests to cover new canonical segment and locator behaviors.
BREAKING CHANGE: TTS segment IDs now support canonical segment keys; APIs and manifest consumers must handle `segmentKey` and updated locator comparison logic.
Introduce configurable segment preloading depth, sentence lookahead, and TTS segment max block length for both PDF and EPUB readers. Add new settings to user preferences, config context, and document settings UI. Refactor TTS segment splitting logic to support per-user max block length and propagate these options through PDF/EPUB adapters and TTS segment generation. Update API, manifest pagination, and segment deduplication for improved performance and correctness. Add tests for new NLP options and manifest logic.
BREAKING CHANGE: TTS segment splitting and manifest APIs now require max block length and preloading parameters; user config schema updated.
Introduce logic to provide previous text context for TTS sentence splitting,
enabling more accurate prefix stripping and improved handling of segment
boundaries. EPUB and PDF providers now supply previous segment text, and
TTSContext leverages this context to avoid redundant continuation logic.
This enhances TTS playback continuity and reduces repeated or truncated
sentences across segment transitions.
Update segment manifest API to group segments by both index and locator
attributes, ensuring distinct segment variants are not merged incorrectly.
Adjust SegmentsSidebar logic to select the most relevant segment per index
based on locator match and recency, improving sidebar accuracy when
multiple locator variants exist for a segment.
Update TTSContext to track manual pause epochs, ensuring explicit user
pauses are respected during async sentence splitting and playback state
restoration. Adjust TTSPlayer button disabling logic to allow pausing
while processing. Move and improve pauseTTSAndVerify helper for robust
cross-browser TTS pause assertions, replacing local test implementations.
Update expectDocumentListed to use polling for document link visibility.
Standardize aria-labels in DocumentHeaderMenu and ReaderSidebarShell to use
"Hide" and "Close" phrasing for improved accessibility and user experience.
Introduce SegmentsSidebar component and supporting reader UI for segment-level
navigation and inspection. Add TTS segments manifest and clear API endpoints to
enable efficient retrieval and management of segment variants. Update TTS
segment schema to include settings_hash for improved lookup and indexing,
and adjust indexes to use settings_hash instead of settings_json. Update
contexts and types to support segment variant display and interaction.
This enhances document navigation and TTS segment management for end users.
Switch tts_segments schema to use settings_json (jsonb for Postgres, text for SQLite)
instead of settings_hash. Update related indexes and migration SQL. Regenerate
Drizzle snapshots to reflect the new structure. This improves flexibility for
storing TTS settings and enables more robust segment lookups.
Introduce `playbackRunIdRef` and `invalidatePlaybackRun` to ensure that playback
state is correctly reset on abort and content changes. Update abort and navigation
handlers to call `invalidatePlaybackRun`, preventing duplicate or stale playback
runs, especially in scenarios with rapid restarts or browser-specific audio event
quirks. This addresses issues where playback could become stuck or behave
unexpectedly due to outdated in-flight guards.
Remove legacy TTS segment manifest and audio endpoints in favor of new presigned and fallback audio URL approach. Update segment manifest structure to include `audioPresignUrl` and `audioFallbackUrl` fields, deprecating the previous `audioUrl`. Refactor segment ensure logic and TTS context to utilize the new playback source model. Add server utilities for generating presigned S3 URLs and introduce new API routes for presign and fallback audio retrieval. Clean up obsolete client API functions and types to reflect the new manifest format.
Expand and clarify documentation for TTS segment storage, including key layout, cleanup, and CLI access in object storage. Add details on the `tts_segments` schema and migration process. Revise local development and Docker quick start guides with improved prerequisites, platform-specific setup instructions, and updated TTS provider references. Document new usage of `AUTH_SECRET` for TTS segment text fingerprinting.
Introduce TTS segment support with new database tables and migrations for both Postgres and SQLite. Implement segment ensure, audio, and manifest API endpoints, segment storage in S3, and segment authorization logic. Update TTS context and client API to use segment-based synthesis and retrieval. Add server-side helpers for segment ID, settings hash, locator normalization, and audio probing. Extend user data cleanup and teardown logic to remove TTS segment objects. Add unit tests for segment helpers.
BREAKING CHANGE: TTS synthesis now uses segment-based storage and APIs; database migrations required.
Document observed S3 `InternalError` regressions with SeaweedFS 4.19 in
object storage, local development, and Docker quick start guides. Clarify that
version 4.18 is pinned in CI and Docker builds for reliability while
compatibility is investigated.
Update CI workflow and Dockerfile to use chrislusf/seaweedfs:4.18 instead of
latest. This avoids upload regressions observed with version 4.19 and ensures
consistent builds.
Introduce pauseActiveHowl to centralize audio pause logic, ensuring consistent
cleanup and media session state updates. Update pause and togglePlay to use the
new helper, enabling accurate resume of paused audio and improved error
handling. Enhance Howl onpause event to synchronize playback state and clear
timeouts.
Move resolveVoices and resolveReplicateVoiceInputKey to a new server-side
voice-resolution module to improve separation of concerns and reduce shared
bundle size. Update all imports and related tests to use the new module.
Remove unused LRUMap and related caches from shared catalog. Update Replicate
cooldown logic to use per-scope LRU cache for improved concurrency handling.
Move getUpstreamStatus and getUpstreamRetryAfterSeconds to a shared utility
module for consistent upstream error handling across TTS endpoints. Strengthen
audiobook chapter API by introducing runtime validation for incoming settings
payloads, ensuring type safety and error reporting for malformed requests.
Replace in-memory Map caches with LRUMap for Replicate voice and schema
lookups, improving memory management and eviction logic.
Align default Replicate model to the versioned Kokoro model across UI, docs,
and server logic. Refactor TTS settings merging for audiobooks to ensure
consistent normalization. Improve Retry-After header handling for upstream
rate limits. Expand Replicate voice resolution to use model schemas when
available, with test coverage for custom and built-in models.
Update documentation to reflect new Replicate defaults, model selection,
and configuration guidance.
- Updated environment variables documentation to include Replicate as a TTS provider option.
- Added Replicate to the sidebar for TTS provider guides.
- Included Replicate as a dependency in package.json and pnpm-lock.yaml.
- Enhanced audiobook chapter generation to normalize native speed settings based on the TTS provider.
- Improved error handling in TTS API routes to provide retry information for rate-limited responses.
- Updated AudiobookExportModal to reflect native speed support for Replicate models.
- Modified SettingsModal to set default model for Replicate.
- Enhanced SpeedControl component to conditionally render native speed controls based on provider support.
- Updated TTSContext to utilize effective native speed for TTS requests.
- Implemented Replicate request handling in the TTS generation logic.
- Added new documentation for configuring Replicate as a TTS provider.
Add support for a persistent "custom" theme that users can edit and preview in settings.
- add CustomThemeColors type and storage helpers (getCustomThemeColors, setCustomThemeColors)
- apply custom CSS variables at runtime and provide applyCustomColors to live-update variables
- include fallback CSS for html.custom to avoid flash before hydration
- add ColorPicker component with curated swatches for each color role
- extend SettingsModal to support editing, previewing, and persisting custom theme colors; exclude 'custom' from regular theme lists and read custom colors when rendering previews
- tighten theme detection logic to treat custom background luminance as light/dark when needed
This enables users to define, persist, and see live updates for custom color palettes.
Centralized TTS provider/catalog and resolver utilities.
New audiobook generation pipeline with EPUB and PDF source adapters.
TTS settings view model and config-update helpers for synced preferences.
Improvements
Generalized TTS instruction support and provider/model-aware voice defaults.
Audiobook export delegated to pipeline for consistent progress, retry, and cancel handling.
UI test IDs added to privacy and migration modals.
Tests
New unit tests for TTS catalog and config helpers; export readiness and onboarding dismissal added to tests.
Chores
Package version bumped to 2.1.1.
Make voice-fetching more robust by always clearing the request timeout and stricter type-checking of remote data.
- move clearTimeout into finally blocks for Deepinfra and custom OpenAI voice fetches to guarantee the timeout is cleared on success, failure, or abort
- validate that custom endpoint returns an array of strings before accepting voices; otherwise fall back to defaults
This reduces potential timer leaks and avoids accepting malformed voice lists from custom endpoints.
Improve robustness across audiobook pipeline, TTS catalog, and preferences comparison.
- audiobooks: centralize abort creation with createAudiobookAbortError and replace repeated ad-hoc throws with it; avoid reprocessing non-completed chapters when collecting existing indices; update progress accounting by advancing processedLength and emitting onProgress after saving a chapter.
- tts: add a 10s AbortController timeout when fetching custom OpenAI voices, wire the signal to fetch, and ensure the timeout is cleared on success or failure; fall back cleanly when endpoint doesn't support voices.
- config: harden deepEqual by checking mismatched array vs object shapes early to avoid incorrect comparisons.
No user-visible behavior changes besides improved cancellation, progress reporting, and more resilient remote voice discovery.
Refactor test helpers to reliably dismiss onboarding and migration modals during
UI flows. Replace ad-hoc Settings-only dismissal with a unified dismissOnboardingModals
routine that targets privacy, migration, and settings dialogs via testids,
interacts with visible controls (checkbox, continue/skip/save buttons) and waits
for dialogs to hide. Increase resilience by repeating checks, adding short
settle delays, and tightening timeouts.
Also add data-testid attributes to PrivacyModal, SettingsModal and
DexieMigrationModal to make the selectors deterministic and reduce flakiness in
export/upload tests.
No production behavior changes.
- Move TTS provider resolution and voice-fetch logic into shared catalog and make
remote fetches more resilient (timeouts, abort handling, baseUrl normalization,
treat explicit empty custom-openai responses as valid).
- Add continuation guard to TTS feature set and update API voices endpoint error
logging to better surface auth/session failures.
- Extract and stabilize ebook pipeline and epub adapter:
- Cache prepared EPUB chapters to avoid repeated parsing and handle missing/empty
chapter titles safely.
- Fail early when created chapter lacks a bookId.
- Improve config/preferences handling:
- Replace JSON stringify comparisons with a deepEqual utility to compare defaults.
- Tighten ConfigContext effect dependencies and consolidate synced preference
queuing logic to include additional keys.
- Harden end-to-end test helpers and unit tests:
- Make settings dialog dismissal deterministic (use Enter/Escape and explicit
visibility/enabled checks) and simplify upload retry behavior.
- Add unit test to ensure custom-openai empty voice lists are preserved.
Why: Reduce runtime and test flakiness, centralize TTS logic for easier maintenance,
and make EPUB processing and preference diffing more robust. Breaking changes: none.
- Add waitForBackendDownloadReady helper to tests/export.spec.ts to wait for backend
chapter metadata and enabled full-download UI before attempting combines/downloads.
Use it in two export tests to reduce flakiness related to eventual backend storage visibility.
- Improve uploadAndDisplay helper in tests/helpers.ts to retry link click when transient
"intercepts pointer events" errors occur, ensuring test clicks succeed under flaky UI conditions.
Add dismissSettingsModalIfVisible to detect and close the Settings dialog before interacting
with page links (click Save or press Escape, wait for dialog to hide).
- Update SettingsModal.tsx useEffect dependencies to avoid referencing checkFirstVist in the
local state sync effect and add separate effect for checkFirstVist for clearer lifecycle.
Why: Reduce end-to-end test flakiness by waiting for backend readiness and making UI interactions
more robust; simplify SettingsModal effect dependencies to prevent unnecessary re-runs. Breaking changes: none.
- Introduce a shared tts-provider-catalog to centralize provider/model/voice defaults
and feature flags (supportsTtsInstructions, providerSupportsCustomModel, resolveProviderModels,
getDefaultVoices, etc.). Replace ad-hoc provider logic across server and client with catalog calls.
- Replace inline voice/model UI logic in SettingsModal, AudiobookExportModal, TTSContext,
generate server code, and voice hook to use catalog helpers (resolveTtsSettingsViewModel,
supportsTtsInstructions, getDefaultVoices).
- Extract audiobook generation responsibilities into a small client-side pipeline:
- Add createEpubAudiobookSourceAdapter and createPdfAudiobookSourceAdapter adapter modules
to prepare chapters from EPUB/PDF sources.
- Add runAudiobookGeneration and regenerateAudiobookChapter pipeline functions to handle
chapter preparation, progress tracking, request header construction, and retry/abort behavior.
- Wire adapters/pipeline into EPUBContext and PDFContext to simplify and centralize audiobook flow.
- Move config preference helpers out of ConfigContext into dedicated client modules:
- buildSyncedPreferencePatch (lib/client/config/preferences.ts)
- applyConfigUpdate and getVoicePreferenceKey (lib/client/config/updates.ts)
- Use these helpers in ConfigContext to keep update logic small and testable.
- Add TTS settings view model resolver (lib/client/settings/tts-settings.ts) and tests
for the tts-provider catalog and config helpers (tests/unit/tts-provider-catalog.spec.ts).
- Minor usages updated: AudiobookExportModal, SettingsModal, useVoiceManagement, TTSContext,
server tts generation, and various context files to consume the new modules.
Why: Reduce duplication, improve testability, and separate concerns for provider metadata,
UI view-model logic, and audiobook generation flow. Breaking changes: none.
Introduce safeguards for continuation handling in TTS flow to prevent looping and over-carrying text. Add constants to limit carried characters and words, and loop-guard thresholds. Implement normalizeBlockFingerprint to create compact fingerprints of page-first blocks and store them in pageFirstBlockFingerprintRef. Use fingerprint comparison and progress threshold to skip to next page when a loop is detected. Cap continuation slices by characters and words and ensure carried text matches the returned addition.
test(ui): make settings save button click more robust
In tests/helpers.ts wait for the Settings Save button to become visible (with a short timeout) before attempting to open Settings, and click the Settings button with force to avoid intermittent pointer-interception failures. Improve stability of setupTest by handling enter transitions and overlays.
Add a test to verify selected voice persists instead of resetting to default.
Also refactors export tests to be UI-driven rather than backend-dependent.
- Removed unused imports and types from TTS route.
- Consolidated TTS buffer generation logic into a new module.
- Implemented caching for TTS audio buffers using LRU cache.
- Updated TTS request handling to support instructions for specific models.
- Refactored audiobook chapter creation to use new TTS generation logic.
- Simplified error handling and response management in TTS API.
- Enhanced client-side logic to manage TTS requests and retries.
- Updated types to reflect changes in TTS request payload structure.
Add five new light theme options (lavender, rose, sand, sky, slate) with dedicated color palettes. Redesign Settings modal with sidebar navigation replacing tabs, add visual theme color selector showing preview swatches, and introduce new icons (KeyIcon, PaletteIcon, UserIcon, DocumentIcon). Also update PrivacyModal to render conditionally based on authEnabled and add privacy policy link. Update tests to reflect new UI structure.
Major overhaul: implement auth, sqldb for metadata, s3-like storage, and lessen reliance on fs
New Features
Added user authentication system with sign-in, sign-up, and account management
Added TTS rate-limiting with per-user character limits and daily resets
Added user data export functionality
Added support for PostgreSQL database and external S3-compatible object storage
Documentation
Launched comprehensive documentation site with deployment guides, configuration references, and TTS provider integration guides
Chores
Rebranded project from "OpenReader-WebUI" to "OpenReader"
Add ZIP-based user data download endpoint, enable AES-256 server-side S3 encryption for all stored documents and audiobooks. Rewrite privacy policy with detailed CCPA categories, service provider disclosures (Vercel/Neon/Railway), and user rights sections. Replace privacy modal with checkbox agreement flow.
Add Next.js middleware to handle session-based access control for protected
routes while maintaining access to public paths. Configure comprehensive
security headers including CSP, HSTS, and frame options in next.config.ts.
Also, reduce session cookie cache duration to 5 minutes to ensure frequent
revalidation against the database and fix a navigation issue in the EPUB
context when jumping to specific CFI locations.
Remove the foreign key constraint referencing the user table in both
Postgres and SQLite schemas. This decouples the usage tracking from
the primary user table.
Move database drivers and heavy server-side modules to be loaded
lazily via require() and dynamic import(). This reduces the initial
memory footprint and improves cold start performance for serverless
functions by avoiding loading unused dependencies on every request.
Implement a retry mechanism for document blob HEAD requests to handle
S3 read-after-write propagation delays. This prevents 409 errors
when the client calls the endpoint immediately after performing a
direct upload to S3 via a presigned URL.
Update the client-side preview status logic to gracefully handle failed
states and return fallback URLs. Add PDF worker tracing to the build
configuration for preview-related API endpoints and include server-side
error logging.
Implement a check for GitHub OAuth credentials to conditionally render
the GitHub sign-in button. This ensures the UI accurately reflects
available authentication methods based on environment configuration.
- Add `isGithubAuthEnabled` utility to verify server-side credentials
- Propagate GitHub auth status through context providers to the UI
- Conditionally display the GitHub sign-in button in the sign-in page
- Update documentation and examples to use hex encoding for `AUTH_SECRET`
- Add unit tests for the new GitHub authentication configuration logic
Increase timeouts for export and navigation tests to 60 seconds to
prevent flakiness. Remove brittle assertions checking for visible
overlays in setup helpers to ensure more stable test execution.
Replace ffprobe-static with ffmpeg-static for metadata extraction in
migration scripts. This removes the requirement for ffprobe binaries
across Docker, CI, and Vercel environments, simplifying the dependency
graph.
Enable WAL mode and set a busy timeout for SQLite to better handle
concurrent access and prevent 500 errors. Add error handling to
async calls in UI components and remove unused imports across several
files.
Implement AbortController in ConfigContext, PDFContext, and preference sync to prevent race conditions during session changes or rapid navigation. Refactor document preview routes into shared utilities and add LRU caching for text previews to optimize memory. Update documentation for Better Auth schema ownership and migration workflows. Fix issues with Postgres system user seeding and better-sqlite3 configuration.
- Implement ensureSystemUserExists to handle foreign key constraints for
unclaimed data when authentication is disabled.
- Add missing foreign key relationship between audiobook chapters and
audiobooks with cascade delete.
- Integrate a beforeDelete hook in auth config to trigger storage
cleanup when a user is removed.
- Regenerate initial migrations for both PostgreSQL and SQLite to
reflect schema updates.
- Refactor useAuthSession to call useSession unconditionally, adhering to React Rules of Hooks by using a stub client when auth is disabled.
- Update migration script to insert a system user to satisfy foreign key constraints during database transitions.
- Implement AbortController and timeouts in entrypoint endpoint polling to prevent hanging fetch requests.
- Use useMemo in PDFViewer to provide a stable file object, preventing unnecessary re-renders and warnings.
- Enhance state cleanup in EPUBContext and AuthLoader to handle missing metadata or disallowed sessions gracefully.
Enforce referential integrity by linking application tables to the auth user table across both PostgreSQL and SQLite schemas.
- Add foreign key references with ON DELETE CASCADE to all user-owned tables.
- Re-generate initial migrations for Postgres and SQLite providers.
- Update migration script to ensure the SQLite database directory exists.
- Remove explicit SSL configuration from the PostgreSQL connection pool.
- Updated SQLite journal metadata with new tag and timestamp.
- Refactored account deletion API to utilize Better Auth's built-in user deletion method for cascading cleanup.
- Enhanced database initialization to include authentication schemas for both SQLite and Postgres.
- Removed user, session, account, and verification tables from the Drizzle schema, as they are now managed by Better Auth.
- Created separate schema files for authentication in both Postgres and SQLite.
- Added SQL migration scripts for creating necessary tables in both database systems.
- Remove ffprobe-static dependency and associated configuration
- Reimplement ffprobeAudio to parse duration and title tags from ffmpeg output
- Update CI workflow to install SeaweedFS binary and set required environment variables
- Enhance E2E test setup to handle auth-protected endpoints during document cleanup
- Set FFMPEG_BIN and FFPROBE_BIN environment variables in Playwright workflow
- Reduce memory allocation for audiobook and whisper API routes to 1024MB
- Introduce `USE_ANONYMOUS_AUTH_SESSIONS` to make guest access an opt-in feature.
- Transition root-level pages into `(app)` and `(public)` route groups for improved access control.
- Adopt Figtree as the primary typeface and implement a pre-render theme initialization script.
- Decouple audiobook chapter processing into a standalone API route and harden resource ownership logic.
- Replace the legacy footer with a unified layout and add skeleton loading states for document lists.
- Update environment documentation and test suites to align with the revised routing and auth flows.
BREAKING CHANGE: The audiobook generation API has been restructured, moving specific chapter logic to `/api/audiobook/chapter`.
- Add document preview caching logic with in-memory and persisted storage.
- Implement S3 blobstore functions for managing document previews.
- Create rendering functions for PDF and EPUB cover images to JPEG format.
- Introduce database schema and functions for managing document preview metadata.
- Add unit tests for rendering PDF and EPUB previews.
- Implemented user preferences management with a new API for GET and PUT requests.
- Added user document progress tracking with a new API for retrieving and updating progress.
- Introduced database schema changes for user preferences and document progress.
- Enhanced EPUB and TTS contexts to support syncing user preferences and document progress.
- Added functions to handle transferring user preferences and progress during account linking.
- Updated client-side logic to schedule syncing of user preferences and document progress.
- Rename configuration.md to auth.md for clarity
- Rename storage-and-blob-behavior.md to object-blob-storage.md
- Split server library import into dedicated documentation page
- Rename intro.md to introduction.md
- Merge support.md into support-and-contributing.md
- Add interactive tabs to configuration and setup guides
- Improve cross-references and navigation between documentation pages
- Update sidebar structure and Docusaurus config for new file paths
Configure Next.js output tracing to properly include ffmpeg and ffprobe
static binaries for audiobook processing endpoints. This ensures the
required binaries are bundled correctly during deployment across all
audiobook API routes.
This commit restructures the audiobook generation and serving layer to rely exclusively on S3-compatible blob storage, removing the dependency on local filesystem paths.
- Bundle `ffmpeg` and `ffprobe` binaries via npm to ensure portability across environments.
- Update API routes to stream audio directly from blob storage instead of local disk.
- Remove legacy migration endpoints and filesystem-based indexing logic.
- Add startup scripts to facilitate the transition from local to remote storage.
BREAKING CHANGE: Audiobook functionality is now strictly dependent on S3 configuration. The previous filesystem-based storage method has been removed.
Add environment variables for fine-grained control over TTS rate limiting
and Better Auth behavior. Move documentation to external Docusaurus site
with automated deployment workflows.
- TTS rate limiting can now be enabled/disabled via TTS_ENABLE_RATE_LIMIT
- Customizable daily limits for anonymous/authenticated users and IP backstops
- Better Auth rate limiting can be disabled via DISABLE_AUTH_RATE_LIMIT
- Rename library import env vars to IMPORT_LIBRARY_DIRS/DIR
- Add docs-site with Docusaurus and GitHub Actions workflows
- Update README to reference external documentation
Replaces local filesystem document storage with S3-compatible object storage.
Adds embedded SeaweedFS 'weed mini' for local development and Docker deployments.
Updates document upload flow to use presigned URLs with a server fallback proxy.
Refactors auth configuration to use BASE_URL and AUTH_SECRET.
BREAKING CHANGE: Renamed BETTER_AUTH_URL to BASE_URL and BETTER_AUTH_SECRET to AUTH_SECRET. Removed legacy document upload/content endpoints. Requires S3 environment variables (auto-configured for embedded SeaweedFS).
- Replace client-side IndexedDB with server-first document storage
- Add document caching layer for offline capability and performance
- Implement document transfer during anonymous-to-authenticated account linking
- Add database indexes for improved query performance
- Update document contexts to use new caching system
- Refactor document upload and deletion APIs
- Add audiobook pruning for missing files
- Improve test isolation with namespace support
- Add unit tests for document cache and transfer functions
Ensure all metadata operations consistently use the database layer,
removing filesystem-only fallback paths and conditional DB checks.
- Simplified migration scripts to always run on startup
- Updated document/audiobook APIs to always query DB
- Added ensureDbIndexed() calls across all routes
- Extracted test namespace utilities to dedicated module
- Removed migration-manager.ts (functionality consolidated)
- Updated rate limiter to assume DB is always available
BREAKING CHANGE: Database is now required in all configurations.
When auth is disabled, SQLite is used by default at /app/docstore/sqlite3.db.
- Change audiobooks and audiobookChapters tables to use composite PK (id, userId)
- Migrate audiobooks from flat storage to user-specific directories under audiobooks_users
- Add support for claiming unclaimed audiobooks on account creation
- Improve auth rate limiting with retry logic and DISABLE_AUTH_RATE_LIMIT for tests
- Fix iOS/Safari audio playback with unlock mechanism and playback rate watchdog
- Update API routes to handle user-scoped audiobook access with fallback to unclaimed
- Transfer audiobooks when linking anonymous accounts to real accounts
- Remove foreign key constraint from audiobookChapters to support composite PK
- Add cascade delete to account and session foreign keys
Replace privacy popup with PrivacyModal and reorganize auth UI components
Remove signed-out persistence and centralize session handling in useAuthSession
Move document viewers and Kokoro voice helpers into dedicated modules
Relocate migration scripts to `drizzle/scripts`, update `start` and
`migrate`
to use the new path, and add a `generate` script for contributors.
Update README migration guidance for SQLite vs Postgres workflows.
BREAKING CHANGE: remove `migrate:force` and change migration script
paths; consumers relying on `scripts/migrate*.mjs` or `pnpm
migrate:force` must update to the new commands.
Add dedicated Drizzle config files for sqlite and postgres and generate
new migration outputs for each dialect.
Update migrate scripts to load env files and auto-pick the correct
config unless explicitly provided, and tighten API auth handling by
using shared auth context helpers and enforcing userId checks when auth
is enabled.
- Smooth react-pdf page turns with stable canvas staging and fade-in
- Retry transient empty page text extraction during fast page turns
- Avoid noisy TTS preload toasts, guard requests when quota is exhausted,
and improve Howler retry/unload behavior to prevent pool exhaustion
- Enforce auth FK cascade deletes and tighten rate-limiter bucket updates
using affected-row checks
- replace custom DB adapter with Drizzle setup (SQLite/Postgres schemas) and add `drizzle.config.ts` plus migrations in `drizzle/` and `drizzle_pg/`
- switch better-auth to drizzleAdapter, add auth helpers (`getAuthContext`, `requireAuthContext`, `requireAudiobookOwned`), and make `useAuth`/`useAuthSession` safe no-ops when auth is disabled
- persist documents/audiobooks in DB with ownership checks, unclaimed fallback, and ref-counted deletes; add FS scan helper for no-auth mode
- gate docx-to-pdf, library, voices, whisper, and migration endpoints behind auth when enabled
- add unclaimed data scan/claim flow with `/api/user/claim`, `ClaimDataModal`, and server-side scan/claim helpers
- refactor rate limiting and account deletion to use Drizzle tables (`user_tts_chars`, `user`)
- run migrations via `scripts/migrate-if-auth.mjs` (auto on `pnpm start` + `pnpm migrate`), remove Docker entrypoint and old better-auth migration file
- update README and lockfile for the new migration workflow and dependencies
Update modal components to start from top on mobile devices and center on larger screens, improving usability across screen sizes. Also refine transition handling in privacy popup for cleaner unmounting.
- Remove unused SettingsIcon and UserMenu imports from epub and pdf pages
- Remove unused UserMenu import from home page
- Remove unused isDev const from home page
- Update SettingsModal to only import clearSignedOut
- Fix apostrophe encoding in privacy popup description
- Add device ID and IP-based rate limiting to prevent abuse
- Refactor UI components for better header menus and settings
- Update privacy popup with detailed data usage info
- Improve PDF handling and caching to prevent react-pdf warnings
- Update README with new Docker instructions and environment variables
- Implement user sign-in, sign-up, and account management using better-auth
- Add rate limiting for TTS API with daily character limits for authenticated and anonymous users
- Integrate SQLite and PostgreSQL database support for user sessions and data persistence
- Update UI components to include authentication flows, user menu, and privacy popup
- Modify Dockerfile and package.json for new dependencies and entrypoint script
- Bump version to v1.3.0
* **Bug Fixes**
* More reliable PDF sentence and word highlighting during layout changes and rapid updates, preventing stale or overlapping highlights.
* **Enhancements**
* Revamped text normalization and TTS block splitting for better handling of long/complex content, EPUB texts, dialogue/quotes, and PDF artifacts.
* **Tests**
* Expanded unit tests covering block-splitting, EPUB behavior, oversized inputs, and boundary cases.
* **Chores**
* Package version bumped; test runner config updated to ignore unit tests for select browser projects.
- Add logic to split oversized sentences in `splitTextToTtsBlocksEPUB` using `splitOversizedText` to ensure blocks stay within `MAX_BLOCK_LENGTH`
- Remove `extractRawSentences` and `processTextWithMapping` functions as they are no longer needed
- Update tests to reflect the changes, including a new test for oversized sentence splitting and removal of related test suites
Introduce `splitTextToTtsBlocksEPUB` function to handle EPUB text splitting,
treating single newlines as paragraph boundaries for precise highlighting.
Update TTS context to conditionally use EPUB splitting based on document type.
Enhance PDFViewer to clear highlights when current sentence is null.
Add comprehensive tests for the new functionality and refactor existing ones.
Handle case where currentSentence is null by canceling retry loops and clearing stale highlights to prevent lingering highlights. Remove redundant check inside the highlight function.
- Rename and enhance text processing functions in nlp.ts for better handling of oversized texts, sentence boundaries, and PDF artifacts
- Update PDFViewer to add layout-aware highlighting with retry logic for sentence and word highlights
- Adjust PDFContext and TTSContext to use new normalized text functions
- Expand unit tests for new splitting behaviors, including long texts and punctuation preferences
Add support for building and publishing multi-architecture Docker images (amd64 and arm64) using a matrix strategy. Introduce a new 'prepare' job for metadata computation and a 'merge' job for creating manifest lists. Enable manual workflow dispatch with optional latest tagging. Improve caching and provenance handling for better efficiency and reliability.
Add 'force-dynamic' export to audiobook and documents API routes to ensure
dynamic rendering. Remove 'nodejs' runtime export from TTS route for consistency.
OpenReader WebUI is an open source text to speech document reader web app built using Next.js, offering a TTS read along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**. It supports multiple TTS providers including OpenAI, Deepinfra, and custom OpenAI-compatible endpoints like [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI) and [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI)
OpenReader is an open-source, self-host-friendly text-to-speech document reader built with Next.js for **EPUB, PDF, TXT, MD, and DOCX** with multilingual, synchronized read-along playback.
- 🌐 **Multilingual support** — choose a document language for language-aware narration and highlighting. Available languages depend on the configured TTS provider and voice.
- 🎧 **Audiobook export** in `m4b`/`mp3` with resumable chapter processing.
- 🗂️ **Flexible backend** — embedded SeaweedFS or S3-compatible storage, SQLite or Postgres, server library import, and device sync.
| Run Kokoro locally | [Kokoro-FastAPI](https://docs.openreader.richardr.dev/configure/tts-provider-guides/kokoro-fastapi) |
| Run KittenTTS locally | [KittenTTS-FastAPI](https://docs.openreader.richardr.dev/configure/tts-provider-guides/kitten-tts-fastapi) |
| Get support or contribute | [Support and Contributing](https://docs.openreader.richardr.dev/about/support-and-contributing) |
> **Note:** Requesting audio from the TTS API happens on the Next.js server not the client. So the base URL for the TTS API should be accessible and relative to the Next.js server. If it is in a Docker you may need to use `host.docker.internal` to access the host machine, instead of `localhost`.
## 🧭 Community
Visit [http://localhost:3003](http://localhost:3003) to run the app and set your settings.
- Questions and ideas: [GitHub Discussions](https://github.com/richardr1126/openreader/discussions)
> **Note:** The `openreader_docstore` volume is used to store server-side documents. You can mount a local directory instead. Or remove it if you don't need server-side documents.
## 📜 License
### 2. ⚙️ Configure the app settings in the UI:
- Set the TTS Provider and Model in the Settings modal
- Set the TTS API Base URL and API Key if needed (more secure to set in env vars)
- Select your model's voice from the dropdown (voices try to be fetched from TTS Provider API)
### 🗣️ Local Kokoro-FastAPI Quick-start (CPU or GPU)
You can run the Kokoro TTS API server directly with Docker. **We are not responsible for issues with [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI).** For best performance, use an NVIDIA GPU (for GPU version) or Apple Silicon (for CPU version).
<details>
<summary>
**Docker CPU**
</summary>
```bash
docker run -d \
--name kokoro-tts \
--restart unless-stopped \
-p 8880:8880 \
-e ONNX_NUM_THREADS=8 \
-e ONNX_INTER_OP_THREADS=4 \
-e ONNX_EXECUTION_MODE=parallel \
-e ONNX_OPTIMIZATION_LEVEL=all \
-e ONNX_MEMORY_PATTERN=true \
-e ONNX_ARENA_EXTEND_STRATEGY=kNextPowerOfTwo \
-e API_LOG_LEVEL=DEBUG \
ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
```
> Adjust environment variables as needed for your hardware and use case.
</details>
<details>
<summary>
**Docker GPU**
</summary>
```bash
docker run -d \
--name kokoro-tts \
--gpus all \
--user 1001:1001 \
--restart unless-stopped \
-p 8880:8880 \
-e USE_GPU=true \
-e PYTHONUNBUFFERED=1 \
-e API_LOG_LEVEL=DEBUG \
ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.4
```
> Adjust environment variables as needed for your hardware and use case.
</details>
> **⚠️ Important Notes:**
> - For best results, set the `-e API_BASE=` for OpenReader's Docker to `http://kokoro-tts:8880/v1`
> - For issues or support, see the [Kokoro-FastAPI repository](https://github.com/remsky/Kokoro-FastAPI).
> - The GPU version requires NVIDIA Docker support and works best with NVIDIA GPUs. The CPU version works best on Apple Silicon or modern x86 CPUs.
## Local Development Installation
### Prerequisites
- Node.js (recommended: use [nvm](https://github.com/nvm-sh/nvm))
- pnpm (recommended) or npm
```bash
npm install -g pnpm
```
- A TTS API server (Kokoro-FastAPI, Orpheus-FastAPI, Deepinfra, OpenAI, etc.) running and accessible
Optionally required for different features:
- [FFmpeg](https://ffmpeg.org) (required for audiobook m4b creation only)
```bash
brew install ffmpeg
```
- [libreoffice](https://www.libreoffice.org) (required for DOCX files)
```bash
brew install libreoffice
```
- [whisper.cpp](https://github.com/ggml-org/whisper.cpp) (optional, required for word-by-word highlighting)
```bash
# clone and build whisper.cpp (no model download needed – OpenReader handles that)
> Note: The base URL for the TTS API should be accessible and relative to the Next.js server
4. Start the development server:
With pnpm (recommended):
```bash
pnpm dev # or npm run dev
```
or build and run the production server:
With pnpm:
```bash
pnpm build # or npm run build
pnpm start # or npm start
```
Visit [http://localhost:3003](http://localhost:3003) to run the app.
## 💡 Feature requests
For feature requests or ideas you have for the project, please use the [Discussions](https://github.com/richardr1126/OpenReader-WebUI/discussions) tab.
## 🙋♂️ Support and issues
If you encounter issues, please open an issue on GitHub following the template (which is very light).
## 👥 Contributing
Contributions are welcome! Fork the repository and submit a pull request with your changes.
## ❤️ Acknowledgements
This project would not be possible without standing on the shoulders of these giants:
- [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M) model
- [Orpheus-TTS](https://huggingface.co/collections/canopylabs/orpheus-tts-67d9ea3f6c05a941c06ad9d2) model
The admin panel lets a designated set of users manage **shared TTS providers** and **site-wide feature flags** directly from the Settings modal — without touching env vars or redeploying.
It is gated behind authentication, so you must have auth enabled to use it ([Auth](./auth)).
## Designating admins
Set `ADMIN_EMAILS` to a comma-separated list of emails:
```env
AUTH_SECRET=... # required for auth
BASE_URL=... # required for auth
ADMIN_EMAILS=alice@example.com,bob@example.com
```
On every session resolution the server compares the user's email against this list and writes `user.is_admin = true` (or `false` for emails removed from the list). No restart is required to demote — the next page load picks it up.
When the logged-in user is an admin, an **Admin** tab appears in **Settings → sidebar** with two sub-tabs:
- **Shared providers** — server-side TTS provider instances visible to all users.
- **Site features** — runtime-editable replacements for what were previously build-time public env flags.
## Shared TTS providers
Each shared provider is one named instance bound to one of the four built-in provider types (`custom-openai`, `openai`, `replicate`, `deepinfra`). The admin form has:
| Field | Notes |
| --- | --- |
| **Slug** | URL-safe identifier exposed to users (e.g. `kokoro-prod`). Must not collide with a built-in id. Lowercase alphanumeric + hyphens. |
| **Display name** | Shown in the user's provider dropdown, suffixed with "(shared)". |
| **Provider type** | One of the four built-ins. Determines voice/model resolution. |
| **Base URL** | Optional. Falls through to the provider type's default when blank. |
| **API key** | Encrypted at rest with AES-256-GCM (key derived from `AUTH_SECRET` via scrypt). On edit, leave blank to keep the existing key. |
| **Default model** | Optional. Used as the initial model when a user selects this provider. |
| **Enabled** | Toggle to hide the provider from non-admin users without deleting it. |
When a non-admin user picks a shared provider in **Settings → TTS Provider**:
- The API key / base URL fields are hidden — those credentials never leave the server.
- The TTS request still goes through the user's browser, but the server replaces the slug with the matching admin row's decrypted key and base URL before calling the upstream provider.
- The user's per-request `x-openai-key` / `x-openai-base-url` headers are ignored for shared slugs.
Whether users can supply their own personal built-in provider keys is controlled by the site feature `restrictUserApiKeys`:
- `true`: users are restricted to shared providers only.
- `false`: users may also use per-user BYOK credentials for built-in providers.
### Auto-seeded "default-openai"
On first boot, if `admin_providers` is empty and `API_BASE` or `API_KEY` is set, OpenReader creates a single shared provider with:
- baseUrl from `API_BASE`, apiKey from `API_KEY` when provided (blank keys are supported)
- defaultModel set to `kokoro` (you can edit it in Admin → Shared providers)
After this seed runs, the legacy `API_KEY` / `API_BASE` env vars are no longer read by the TTS routes — the DB row is authoritative. You can rename, edit, disable, or delete this row like any other from the admin UI, and remove the env vars from your `.env` when convenient.
:::warning Upgrading from v2.2.0
In v2.2.0 and earlier, `API_KEY` / `API_BASE` were read live by the TTS routes on every request. As of v3.0.0 they are **one-shot seeds** consumed only on the first boot where `admin_providers` is empty. After upgrading, boot the app once and confirm a `default-openai` row exists in **Settings → Admin → Shared providers** with the correct base URL. If it is missing or wrong (e.g. the env vars were not set on first boot, or the table was already non-empty from a pre-release), create or edit the shared provider manually — TTS will not fall back to the env vars.
:::
## Site features
Runtime-editable settings, one row per key:
| Key | What it controls |
| --- | --- |
| `defaultTtsProvider` | Default provider id new users start with (built-in id or shared slug). |
| `changelogFeedUrl` | Public changelog manifest URL used by the Settings modal changelog panel. |
| `enableUserSignups` | Controls whether new accounts can be created. Existing accounts can still sign in when this is `false`. |
| `restrictUserApiKeys` | Restrict user-supplied API keys/base URLs; when `true`, only admin shared providers are allowed. |
| `enableTtsProvidersTab` | Whether the user-facing TTS Provider tab in Settings is shown. |
| `showAllProviderModels` | When `false`, users are restricted to each provider's default model (shared provider `defaultModel` or built-in provider default). |
| `enableAudiobookExport` | Show the audiobook export entry points on PDF/EPUB pages. |
| `enableDocxConversion` | Accept .docx uploads (converted to PDF server-side). |
Word-by-word highlighting and PDF layout parsing capability are controlled by compute-worker server env configuration, not an admin runtime flag.
Each row shows a source badge:
- **from seed** — the value was seeded on first boot (from `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH`).
- **admin** — explicit admin override. Use **Reset** on the row to clear it back to built-in default behavior.
- **default** — no seed/admin row exists; built-in default is active.
:::warning Security note for `restrictUserApiKeys`
Turning `restrictUserApiKeys` off allows user-supplied API keys to flow through this server. Use this only for trusted/self-hosted deployments where that tradeoff is acceptable.
:::
## Rate limiting
A dedicated **Rate limiting** group (within the same admin panel) collects the daily quotas, the PDF parsing throttle, and the upload size cap:
| Key | What it controls |
| --- | --- |
| `disableTtsRateLimit` | Disable the per-user/IP daily TTS character limits. When `false`, the daily-limit fields below it apply. |
| `disableComputeRateLimit` | Disable per-user PDF parsing rate limiting. When `false`, the burst/sustained limit fields below it apply. |
| `maxUploadMb` | Maximum size (MB) accepted for a single document upload. Enforced server-side and signed into the presigned S3 PUT. |
The **Disable TTS daily rate limiting** and **Disable PDF parsing rate limiting** toggles each reveal a collapsible group of numeric inputs when set to `false`:
- TTS: anonymous/authenticated per-user daily limits and anonymous/authenticated IP daily backstops.
- PDF parsing: burst limit + window (seconds) and sustained limit + window (seconds). The sustained window doubles as a concurrency cap.
## TTS upstream
At the end of the **Site features** tab, a dedicated **TTS upstream** group controls server-side request and cache tuning (DB-backed runtime settings, not env vars):
| Key | What it controls |
| --- | --- |
| `ttsUpstreamMaxRetries` | Maximum retry attempts for upstream TTS 429/5xx responses. |
| `ttsCacheMaxSizeBytes` | Maximum size of the in-memory TTS audio cache. |
| `ttsCacheTtlMs` | Time-to-live for cached TTS audio buffers. |
In v4 these settings are admin-only and are no longer configurable through environment variables.
## Scheduled tasks
The **Scheduled tasks** section controls background maintenance jobs such as expired-upload cleanup, orphaned-blob reaping, and rate-limit ledger pruning.
- Enable or disable each task, adjust its interval, or run it immediately.
- Runs use database-backed leases so multiple app instances do not normally execute the same task concurrently.
- A task that exceeds four minutes is aborted and recorded as failed. A crashed run can be reclaimed after its stale lease expires.
- Failures and the latest successful summary appear on the task card and in server logs.
Self-hosted Node.js deployments tick the scheduler in-process once per minute. Vercel uses the authenticated `/api/admin/tasks/tick` cron route; the checked-in Vercel Hobby schedule runs once daily, so intervals shorter than one day are unavailable there. See [Vercel Deployment](../deploy/vercel-deployment#5-scheduled-maintenance-tasks).
## Migrating off env vars
In v4, runtime site features are managed by admin settings and optional JSON seed. To minimize env surface area:
1. Deploy this version with your existing env values in place.
2. Boot the app once. Open Settings → Admin and verify:
- Seeded settings appear as **from seed** (if you supplied a runtime JSON seed).
- A `default-openai` row exists in **Shared providers** (if you had `API_BASE` or `API_KEY` set).
3. Remove any bootstrap env vars you no longer need from `.env`.
4. Redeploy. Behavior is unchanged — the DB is now the source of truth.
You can keep `API_BASE` / `API_KEY` if you intentionally want bootstrap fallback behavior on empty provider tables.
## How keys are protected
- API keys are encrypted in the `admin_providers` table with AES-256-GCM. The encryption key is derived from `AUTH_SECRET` via `scrypt`.
- The masked-list view (`GET /api/admin/providers`, used by the admin UI itself) returns `••••` + last-4 only — never plaintext or ciphertext.
- The public list endpoint (`GET /api/tts/shared-providers`, called by every user's browser) returns only `{ slug, displayName, providerType, defaultModel }`. Keys and base URLs are never exposed to the client.
- Non-admin users cannot enumerate admin providers' credentials or base URLs through any API.
:::danger Rotating `AUTH_SECRET` invalidates all stored admin provider keys
Because the encryption key for `admin_providers` is derived from `AUTH_SECRET`, changing `AUTH_SECRET` makes every stored API key undecryptable. After rotating it, shared providers will fail to authenticate upstream until you re-enter each provider's API key in **Settings → Admin → Shared providers** (edit the row and paste the key again). There is no automated re-encryption path. If you must rotate `AUTH_SECRET`, plan to re-enter admin provider keys immediately afterward.
:::
## Related
- [Auth](./auth) — required to use the admin panel.
- [TTS Providers](./tts-providers) — built-in provider catalog and per-user behavior.
This page covers application-level configuration for provider access and authentication.
## Auth behavior
- `BASE_URL` and `AUTH_SECRET` are required at startup in v4+.
- Keep `AUTH_TRUSTED_ORIGINS` empty to trust only `BASE_URL`.
- Anonymous auth sessions are disabled by default.
- Set `USE_ANONYMOUS_AUTH_SESSIONS=true` to enable anonymous session flows.
## Runtime modes
OpenReader has two common runtime modes:
- **Auth enabled, non-admin user**: user account/session features are available, but no admin controls.
- **Auth enabled, admin user**: full **Settings → Admin** access (shared providers + site features).
## Admin role
You can designate one or more users as admins via the `ADMIN_EMAILS` env var:
```env
ADMIN_EMAILS=alice@example.com,bob@example.com
```
Admins see a new **Admin** tab in **Settings** with two sub-tabs:
- **Shared TTS providers** — server-managed TTS provider instances with encrypted keys, visible to all users.
- **Site features** — runtime overrides for what were previously build-time public env flags (including account signup availability, default TTS provider, audiobook export, etc.).
Admin assignment is reconciled on every session resolution, so removing an email from `ADMIN_EMAILS` demotes the user on next login without a restart. See [Admin Panel](./admin-panel) for the full reference.
## Route behavior
- `/` is a public landing/onboarding page and remains indexable.
- `/app` is the protected app home (document list and uploader UI).
- If a valid session exists (including anonymous), visiting `/` redirects to `/app`.
- Protected app routes continue to require auth; when anonymous sessions are disabled and no session exists, users are redirected to `/signin`.
## Related docs
- For auth environment variables: [Environment Variables](../reference/environment-variables#auth-and-identity)
- For admin role and shared TTS provider config: [Admin Panel](./admin-panel)
- For TTS character limits and quota behavior: [TTS Rate Limiting](./tts-rate-limiting)
- For provider-specific guidance: [TTS Providers](./tts-providers)
- For storage/S3/SeaweedFS behavior: [Object / Blob Storage](./object-blob-storage)
- For database mode: [Database](./database)
- For migration behavior and commands: [Migrations](./migrations)
## Sync notes
### Auth enabled
- Settings and reading progress are saved to the server.
- Updates are not instant push-based sync; they use normal client polling/refresh behavior.
- If two devices change the same item around the same time, the newest update wins.
## Claim modal note
- You may still see old anonymous settings/progress available to claim from older deployments.
- Legacy `unclaimed` data is only surfaced through the claim flow; normal authenticated routes are scoped to your current user id.
This page covers database mode selection for OpenReader.
## Scope of this page
- Focus: SQL metadata, state, and relational tables.
- Not covered here: object key layout and blob transport details (see [Object / Blob Storage](./object-blob-storage)).
## Database mode
- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
- Postgres: enabled when `POSTGRES_URL` is set; recommended for production/distributed deployments.
## What the database stores
- Document and audiobook metadata/state used by server routes.
- Auth/session tables (`user`, `session`, `account`, `verification`) when auth is enabled — schema is auto-generated by Better Auth.
- TTS character usage counters (`user_tts_chars`) for daily rate limiting (when enabled).
- User settings preferences (`user_preferences`) when auth is enabled.
- User reading progress (`user_document_progress`) when auth is enabled.
- Document preview job/asset metadata (`document_previews`) for server-side PDF/EPUB thumbnails.
- TTS segment metadata (`tts_segments`) for server-side playback caching:
- Segment identity + settings hash
- Audio object key and duration
- Optional alignment payload for word highlighting
- Status/error state
- Text fingerprint/hash (plaintext segment text is not stored)
App-specific tables are manually maintained in Drizzle schema files, while auth tables are generated by the Better Auth CLI. Both are migrated together via Drizzle. See [Migrations](./migrations) for details.
## What the database does not store
- Raw document file bytes
- Audiobook audio bytes
- TTS segment audio bytes
- Generated preview image bytes
Those payloads live in object storage. SQL stores the metadata, references, and status.
## Related variables
- `POSTGRES_URL`
For database variable behavior, see [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
## Related docs
- [Migrations](./migrations)
- [Object / Blob Storage](./object-blob-storage)
- [Auth](./auth)
## State sync summary
- Settings and reading progress are stored in SQL and synced from the app.
- Sync is currently request-based (not realtime push invalidation).
- `@openreader/bootstrap` owns startup orchestration, storage migration, and optional embedded
SeaweedFS, NATS, and compute-worker processes.
- The Next.js app imports `@openreader/database` directly, but does not orchestrate migrations or
child processes.
Docker deploys bootstrap as an isolated runtime bundle under `/opt/openreader/bootstrap`; it does
not merge migration dependencies into the standalone Next.js app under `/app`.
## Startup migration behavior
By default, the shared entrypoint runs migrations automatically before app startup in:
- Docker container startup
- `pnpm dev`
- `pnpm start`
Startup migration phases:
- DB schema migrations (`pnpm migrate`)
- Storage/data migration (`pnpm migrate-fs`) for legacy filesystem content into S3 + DB rows
:::info
In most setups, you do not need to run migration commands manually because startup handles this automatically.
:::
### Schema history
Migrations are applied in order. All of the following ship in v3.0.0; an instance upgrading from v2.2.0 applies `0001`–`0004` in a single startup pass.
| Migration | Dialects | What it does |
| --- | --- | --- |
| `0001_tts_segments` | SQLite + Postgres | Creates the original single-table `tts_segments` used by server-side TTS segment caching. |
| `0002_add_segment_key_to_tts_segments` | SQLite + Postgres | Adds the `segment_key` column to `tts_segments` for stable locator-independent segment identity. |
| `0003_tts_segments_v2_split` | SQLite + Postgres | Replaces `tts_segments` with a normalized two-table model: `tts_segment_entries` (one row per document segment + locator identity) and `tts_segment_variants` (one row per settings combination, holding the cached audio key, status, and alignment). Drops the original `tts_segments` table — no released build (v2.2.0 or earlier) ever populated it, so there is no production data to migrate. |
| `0004_admin_panel` | SQLite + Postgres | Creates `admin_providers` (encrypted shared TTS provider rows) and `admin_settings` (runtime site-feature config), and adds the `is_admin` column to the `user` table. Backs the [Admin Panel](./admin-panel). |
To skip automatic startup migrations:
- Set `RUN_DRIZZLE_MIGRATIONS=false`
- Set `RUN_FS_MIGRATIONS=false`
:::warning
If you disable startup migrations, ensure your deployment process runs migrations before serving traffic.
:::
## Apply migrations
In most cases, you do not need manual migration commands because startup runs migrations automatically.
`pnpm migrate` applies migrations for one database target:
- Postgres when `POSTGRES_URL` is set
- SQLite when `POSTGRES_URL` is unset
```bash
# Run pending migrations for one target:
# - Postgres if POSTGRES_URL is set
# - SQLite if POSTGRES_URL is unset
pnpm migrate
# Run storage migration (filesystem -> S3 + DB)
pnpm migrate-fs
# Dry-run storage migration without uploading/deleting
pnpm migrate-fs:dry-run
```
`pnpm migrate` uses the programmatic Drizzle migrator from `@openreader/database`. Drizzle Kit is
not a production or startup dependency; it is used only to generate new migration files.
## Generate migrations
`pnpm generate` is a two-phase script for contributors and schema changes:
1. **Better Auth schema generation** — runs the Better Auth CLI twice (once for SQLite, once for Postgres) to produce auto-generated Drizzle schema files for auth tables (`user`, `session`, `account`, `verification`).
2. **Drizzle migration generation** — runs `drizzle-kit generate` for both configs in `packages/database`, producing SQL migration files from all schema files (app + auth).
:::note
Most users do not need to run `pnpm generate`. Use it when contributing or when you have changed Drizzle schema files and need new migration files.
:::
### Schema ownership
Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
- `packages/database/src/schema_auth_sqlite.ts`
- `packages/database/src/schema_auth_postgres.ts`
App-specific tables are manually maintained in the standard Drizzle schema files:
- `packages/database/src/schema_sqlite.ts`
- `packages/database/src/schema_postgres.ts`
Both sets of schema files are included in the Drizzle generation configs. Runtime migration
execution is owned by `@openreader/database`.
When app schema changes (for example `tts_segments`), keep these in sync:
Running `drizzle-kit generate` directly skips the Better Auth CLI step. If auth schema has changed upstream (e.g. after a Better Auth version bump), run `pnpm generate` instead to regenerate the auth schema files first.
This page documents how server library import works and how to configure it.
## What it does
Server library import lets you browse files from one or more server directories and import selected files into OpenReader.
- Import is user-driven via a selection modal
- Only selected files are imported
- Imported files become normal OpenReader documents
## FS / Volume Mounts
### App data mount
- Target: `/app/docstore`
- Recommended: yes, for persistence
- Purpose: stores app runtime data, metadata DB, and embedded storage state
- Mount string: `-v openreader_docstore:/app/docstore`
### Library source mount
- Target: `/app/docstore/library`
- Recommended: yes for this feature, use read-only (`:ro`)
- Purpose: exposes host files as import candidates in Server Library Import
- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
## Import flow
1. Open **Settings -> Documents -> Server Library Import**.
2. Select files in the modal.
3. Click **Import**.
Selected files are fetched from the server library endpoint and imported into OpenReader storage.
:::warning Shared Library Roots
Library roots are configured at the server level (not per-user). Any user who can access Server Library Import can browse/import from the same configured roots.
Imported documents are still saved to the importing user's document scope.
:::
## Supported file types
- `.pdf`
- `.epub`
- `.html`, `.htm`
- `.txt`
- `.md`, `.mdown`, `.markdown`
## Optional: Configure Library Roots
You only need this when the default mounted path is not what you want.
By default, OpenReader uses `docstore/library` as the import root. You can override that with environment variables:
- `IMPORT_LIBRARY_DIRS` (takes precedence): multiple roots separated by comma, colon, or semicolon
- `IMPORT_LIBRARY_DIR`: single root
See [Environment Variables](../reference/environment-variables#library-import) for variable details.
## Notes
- Library listing is capped per request (up to 10,000 files).
- When auth is enabled, library import endpoints require a valid session.
- The mounted library is a source; removing it does not delete already imported documents.
Run [KittenTTS-FastAPI](https://github.com/richardr1126/KittenTTS-FastAPI) locally and connect it to OpenReader using the `Custom OpenAI-Like` provider. Lightweight and CPU-friendly.
Use any OpenAI-compatible TTS service with OpenReader, including self-hosted servers not covered by a dedicated guide.
## Requirements
Your service only needs an OpenAI-compatible speech endpoint:
- `POST /v1/audio/speech` — **required**.
- Voice listing is **optional** and auto-discovered from `/v1/audio/voices`, `/v1/voices`, or `/v1/styles`. If none respond, OpenReader falls back to default voices — the Kokoro voice set for Kokoro models, otherwise the standard OpenAI voices (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`).
The endpoint may return `mp3`, `wav`, `ogg`, or `flac` — OpenReader normalizes non-mp3 audio to mp3 automatically. An API key is optional.
Known compatible implementations: [Kokoro-FastAPI](./kokoro-fastapi), [KittenTTS-FastAPI](./kitten-tts-fastapi), [Orpheus-FastAPI](./orpheus-fastapi), [Supertonic](./supertonic).
1. Add a shared provider with type `custom-openai`.
2. Set `API_BASE` to your service base URL (typically ending in `/v1`).
3. Set API key if your service requires authentication.
4. Set a default model/voice supported by your backend.
**Legacy bootstrap seed (optional, first boot only):**
```env
API_BASE=http://your-tts-server/v1
# API_KEY=optional-key-if-required
```
**Or in-app via Settings → TTS Provider:**
1. Set provider to `Custom OpenAI-Like`.
2. Set `API_BASE` to your service's base URL (typically ending in `/v1`).
3. Set `API_KEY` if your service requires authentication.
4. Choose a model and voice supported by your backend.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
:::warning TTS requests are server-side
`API_BASE` must be reachable from the **Next.js server**, not just the browser. In Docker, use container names or `host.docker.internal`.
:::
## Troubleshooting
If voices don't load, confirm the server is reachable from the Next.js runtime and that at least one of `/v1/audio/voices`, `/v1/voices`, or `/v1/styles` returns a valid response. If none do, OpenReader falls back to default voices — synthesis still works as long as `POST /v1/audio/speech` succeeds.
Use [speech-sdk](https://github.com/Jellypod-Inc/speech-sdk) (Apache 2.0) to reach additional cloud TTS providers (ElevenLabs, Cartesia, Hume, Deepgram, Google Gemini TTS, Inworld, and more) with your own provider API keys. Requests go from the OpenReader server directly to the provider's API; no extra account or proxy is involved.
Models use the `provider/model` format. The API key you enter belongs to the provider named by the model prefix: for `elevenlabs/eleven_multilingual_v2` enter an ElevenLabs key, for `cartesia/sonic-3.5` a Cartesia key, and so on.
2. Enter the API key for the provider you want to use.
3. Set default model to a matching `provider/model` (for example `elevenlabs/eleven_multilingual_v2`).
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
1. Set provider to `Speech SDK`.
2. Choose a model; enter the API key for that model's provider.
3. Choose a voice.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## Built-in models
- `openai/gpt-4o-mini-tts` (works with your existing OpenAI API key)
- `elevenlabs/eleven_multilingual_v2`
- `cartesia/sonic-3.5`
- `deepgram/aura-2`
- `google/gemini-2.5-flash-preview-tts`
- `inworld/inworld-tts-1.5-max`
You can also choose `Other` and enter any `provider/model` the SDK supports. Recognized prefixes: `openai`, `elevenlabs`, `cartesia`, `hume`, `deepgram`, `google`, `inworld`, `minimax`, `fish-audio`, `murf`, `resemble`, `fal-ai`, `mistral`, `xai`, `smallest-ai`.
## Voice IDs
ElevenLabs and Cartesia identify voices by opaque IDs. The built-in lists map to these shared library voices:
| ElevenLabs ID | Name | | Cartesia ID | Name |
| --- | --- | --- | --- | --- |
| `JBFqnCBsd6RMkjVDRZzb` | George | | `a0e99841-438c-4a64-b679-ae501e7d6091` | Barbershop Man |
| `IKne3meq5aSn9XLyUdCD` | Charlie | | `156fb8d2-335b-4950-9cb3-a2d33f0c0c2a` | British Lady |
| `XB0fDUnXU5powFXDhCwa` | Charlotte | | `694f9389-aac1-45b6-b726-9d9369183238` | California Girl |
| `Xb7hH8MSUJpSbSDYk0k2` | Alice | | `87748186-23bb-4571-8b8b-a73da9bf9c4f` | Commercial Lady |
| `iP95p4xoKVk53GoZ742B` | Chris | | `ee7ea9f8-c0c1-498c-9f62-dc2da49a6f98` | Friendly Reading Man |
| `nPczCjzI2devNBz1zQrb` | Brian | | `248be419-c632-4f23-adf1-5324ed7dbf1d` | Hannah |
| `onwK4e9ZLuTAKqWW03F9` | Daniel | | | |
| `pFZP5JQG7iQjIQuC4Bku` | Lily | | | |
| `pqHfZKP75CvOlQylNhV4` | Bill | | | |
## Notes
- One voice per request; Kokoro-style multi-voice mixing does not apply to this provider.
- Playback speed is applied client-side, so cached audio segments stay valid when you change speed.
- Providers without a built-in voice list fall back to a `default` entry, which lets the provider pick its default voice.
- Word-by-word highlighting works the same as with every other provider (alignment runs in OpenReader, not the provider).
- TTS requests are sent from the server, not the browser. The API key is never exposed to clients.
## References
- [speech-sdk on GitHub](https://github.com/Jellypod-Inc/speech-sdk)
Run [Supertonic](https://github.com/supertone-inc/supertonic-py) locally and connect it to OpenReader using the `Custom OpenAI-Like` provider. Supertonic is a fast, on-device TTS engine that ships its own OpenAI-compatible HTTP server.
:::note No Docker image
Supertonic does not publish a Docker image — it installs as a Python package and runs as a local HTTP server. These instructions assume OpenReader itself runs in Docker (the common case); see [Running OpenReader directly on the host](#running-openreader-directly-on-the-host) if you don't.
:::
## Run Supertonic
Install with `pip` (or `pipx` for an isolated install) and start the server:
The first run downloads the model (~400MB). Once it's up, the OpenAI-compatible endpoint is at `http://127.0.0.1:7788/v1/audio/speech` and interactive docs are at `http://127.0.0.1:7788/docs`.
- **Models:**`supertonic-3` (default) or `supertonic-2`.
- **Voices:** built-ins `M1`–`M5` and `F1`–`F5`, plus any custom voices you import. OpenReader discovers them automatically via the `/v1/styles` endpoint.
- **Audio format:** Supertonic emits `wav` by default; OpenReader transcodes it to mp3 transparently, so no extra configuration is needed.
## Connect to OpenReader
From a Docker container, your host machine is reachable at `host.docker.internal`, so the base URL is `http://host.docker.internal:7788/v1`. On Docker Desktop (macOS/Windows) this reaches the loopback-bound server above with no extra setup.
:::note Linux (native Docker Engine)
On native Linux Docker, `host.docker.internal` needs `--add-host=host.docker.internal:host-gateway` on the OpenReader container (or the equivalent `extra_hosts` entry in `docker-compose.yml`), and it routes to the host's bridge interface rather than loopback. Pick one:
- Run the OpenReader container with `--network host` (or `network_mode: host`), keep Supertonic on `--host 127.0.0.1`, and use `http://127.0.0.1:7788/v1` as the base URL.
- Or start Supertonic with `--host 0.0.0.0` so the bridge can reach it — keep it on a trusted network or behind a firewall.
1. Add a shared provider with type `custom-openai`.
2. Set base URL to `http://host.docker.internal:7788/v1`.
3. Leave API key blank — `supertonic serve` does not require one.
4. Set default model to `supertonic-3` (or `supertonic-2`).
**Legacy bootstrap seed (optional, first boot only):**
```env
API_BASE=http://host.docker.internal:7788/v1
```
**Or in-app via Settings → TTS Provider:**
1. Set provider to `Custom OpenAI-Like`.
2. Set `API_BASE` to `http://host.docker.internal:7788/v1`.
3. Leave `API_KEY` blank.
4. Choose model `supertonic-3` (or the model your deployment exposes).
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## Running OpenReader directly on the host
If OpenReader runs on the same machine (e.g. `pnpm dev`) rather than in Docker, skip `host.docker.internal` and use `http://127.0.0.1:7788/v1` as the base URL everywhere above.
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. There are three places provider configuration can live:
**Admin-managed shared providers** (Settings > Admin > Shared providers): DB-backed instances configured by an admin and visible to all users. Keys are encrypted at rest and never exposed to the client. Available only when [auth is enabled](./auth) and your account is in `ADMIN_EMAILS`. See [Admin Panel](./admin-panel).
**Per-user Settings modal** (Settings > TTS Provider): provider + API key stored in the user's browser and sent with every TTS request. This path is available only when the admin/runtime setting `restrictUserApiKeys=false`.
**Environment variables**: `API_KEY` and `API_BASE` exist as a one-shot first-boot seed that auto-creates a `default-openai` admin shared provider. After the first boot they are no longer read by the running app.
:::tip
If you're running a private/self-hosted instance and want per-user BYOK behavior, turn off **Settings → Admin → Site features → Restrict user API keys**. For first-boot automation, set `runtimeConfig.restrictUserApiKeys=false` in `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH`.
:::
## Providers
- **OpenAI**: Cloud. Base URL pre-filled (`https://api.openai.com/v1`). API key required.
- **Replicate**: Cloud. Base URL managed internally by OpenReader. API key required.
- **DeepInfra**: Cloud. Base URL pre-filled (`https://api.deepinfra.com/v1/openai`). API key required.
- **Speech SDK**: Cloud. Reaches additional providers (ElevenLabs, Cartesia, Hume, Deepgram, Google, Inworld, and more) directly with your own provider API keys via [speech-sdk](./tts-provider-guides/speech-sdk). No base URL. API key required (the key for the model's provider).
- **Custom OpenAI-Like**: Self-hosted or any custom endpoint. `API_BASE` must be set manually (typically ending in `/v1`). API key optional.
For `OpenAI`, `DeepInfra`, and `Replicate` you only need to supply an API key. For `Custom OpenAI-Like` you must also set `API_BASE`.
## Built-in model catalogs
- **Replicate** models: `alphanumericuser/kokoro-82m`, `google/gemini-3.1-flash-tts`, `minimax/speech-2.8-turbo`, `qwen/qwen3-tts`, `inworld/tts-1.5-mini` (or choose `Other` and enter any Replicate model ID, such as `owner/model` or `owner/model:version`)
- **DeepInfra** models: includes `hexgrad/Kokoro-82M` and additional hosted models (depending on API key / feature flags)
- **Speech SDK** models: `openai/gpt-4o-mini-tts`, `elevenlabs/eleven_multilingual_v2`, `cartesia/sonic-3.5`, `deepgram/aura-2`, `google/gemini-2.5-flash-preview-tts`, `inworld/inworld-tts-1.5-max` (or choose `Other` and enter any `provider/model` the SDK supports)
## Custom provider requirements
Self-hosted or custom providers only need an OpenAI-compatible speech endpoint:
- `POST /v1/audio/speech` — **required**.
- Voice listing is **optional** and auto-discovered: OpenReader probes `/v1/audio/voices`, `/v1/voices`, then `/v1/styles`. If none respond, it falls back to default voices — the Kokoro set for Kokoro models, otherwise the standard OpenAI voices (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`).
The speech endpoint may return any common audio format — `mp3`, `wav`, `ogg`, or `flac`. OpenReader detects the format and transcodes non-mp3 audio to mp3 automatically, so your server does not need to honor `response_format: mp3`. An API key is optional; keyless servers work.
:::warning TTS requests are server-side
TTS requests originate from the **Next.js server**, not the browser. `API_BASE` must be reachable from the server runtime. In Docker, use container names or `host.docker.internal` rather than `localhost`.
description: Deploy the standalone worker used for Whisper alignment and PDF layout parsing.
---
Use this guide when OpenReader runs compute as a separate service. For the default embedded/local flow (`pnpm dev` or `pnpm start` without `COMPUTE_WORKER_URL`), configure the root `.env` instead and see [Local Development](./local-development).
## What the worker does
- Runs Whisper word alignment jobs
- Runs PDF layout parsing jobs
- Stores durable job state in NATS JetStream and NATS KV
The app server submits resource-specific operations under `/v1` and listens for updates on
`GET /v1/operations/:opId/events`.
## When to use it
- Required for Vercel-style deployments where heavy compute must run outside the app server
- `PORT=8081` for local/manual runs. Platforms like Railway usually inject `PORT`.
- `LOG_FORMAT=json` and `COMPUTE_LOG_LEVEL=info`
- `COMPUTE_PREWARM_MODELS=false` by default. Set it to `true` to pre-download ONNX models during worker startup.
- `COMPUTE_JOB_CONCURRENCY=1`
- `COMPUTE_WHISPER_TIMEOUT_MS=30000`
- `COMPUTE_PDF_TIMEOUT_MS=300000`
- `COMPUTE_PDF_JOB_ATTEMPTS=1`
- `COMPUTE_JOBS_STREAM_MAX_BYTES=268435456`
- `COMPUTE_EVENTS_STREAM_MAX_BYTES=134217728`
- `COMPUTE_JOB_STATES_MAX_BYTES=67108864`
- `COMPUTE_NATS_REPLICAS=1`
- `COMPUTE_OP_STALE_MS=1800000`
- `WHISPER_MODEL_BASE_URL`
- `PDF_LAYOUT_MODEL_BASE_URL`
If you need the broader app config reference, see [Environment Variables](../reference/environment-variables).
## App server environment
Set these on the Next.js app server:
```env
COMPUTE_WORKER_URL=https://worker.example.com
COMPUTE_WORKER_TOKEN=<same-token-as-worker>
# Optional shared overrides:
# COMPUTE_WHISPER_TIMEOUT_MS=30000
# COMPUTE_PDF_TIMEOUT_MS=300000
# COMPUTE_PDF_JOB_ATTEMPTS=1
# COMPUTE_OP_STALE_MS=1800000
```
Notes:
- Model artifact overrides (`WHISPER_MODEL_BASE_URL`, `PDF_LAYOUT_MODEL_BASE_URL`) belong on the worker service, not the app server.
- There is no app-local compute fallback once `COMPUTE_WORKER_URL` is set. If the worker is unavailable, worker-backed requests fail.
## Deployment notes
- App and worker must share the same object storage.
- Embedded `weed mini` is not supported for external worker mode.
- Protect `COMPUTE_WORKER_TOKEN` and do not expose worker routes without auth.
- The worker connects to NATS lazily and disconnects after 120 seconds of full idle time. That allows platforms like Railway to sleep the service, but the first request after a cold start will be slower.
## Health endpoints
- `GET /health/live` returns `{ ok: true }`.
- `GET /health/ready` returns `{ ok: true, natsConnected }` and reflects the current NATS session without forcing a reconnect.
## Railway + Synadia example
Deploy the worker image to Railway and set worker env vars similar to:
```env
COMPUTE_WORKER_HOST=0.0.0.0
COMPUTE_WORKER_TOKEN=<shared-token>
NATS_URL=tls://connect.ngs.global:4222
NATS_CREDS="-----BEGIN NATS USER JWT-----
...
------END USER NKEY SEED------"
S3_BUCKET=<bucket>
S3_REGION=<region>
S3_ACCESS_KEY_ID=<key>
S3_SECRET_ACCESS_KEY=<secret>
# Optional:
# S3_ENDPOINT=https://...
# S3_FORCE_PATH_STYLE=true
# S3_PREFIX=openreader
```
If your platform supports mounted files, you can use `NATS_CREDS_FILE` instead of `NATS_CREDS`.
On first boot, `API_KEY` / `API_BASE` can bootstrap `default-openai`, and `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH` can seed runtime config + providers. After that, the admin UI is authoritative and editing bootstrap env vars no longer changes app behavior. See [Admin Panel](../configure/admin-panel).
:::
:::note User BYOK restriction default
If you want each user to enter personal provider credentials, set `restrictUserApiKeys=false` (from **Settings → Admin**, or by seeding `runtimeConfig.restrictUserApiKeys=false` in runtime seed JSON).
:::
:::info
For all environment variables, see [Environment Variables](../reference/environment-variables).
:::
See [Auth](../configure/auth) for app/auth behavior.
See [Admin Panel](../configure/admin-panel) for the shared-provider and feature-flag management UI.
Storage configuration details are in [Object / Blob Storage](../configure/object-blob-storage).
Refer to [Database](../configure/database) for database modes.
Learn about migration behavior and commands in [Migrations](../configure/migrations).
:::info Scheduled maintenance tasks
Local and self-hosted Node.js deployments start the scheduled-task loop in-process and check for due work once per minute. No `CRON_SECRET` is required unless you intentionally invoke the cron HTTP route yourself. Manage task intervals and inspect failures from **Settings → Admin → Scheduled tasks**.
Run manual DB migrations only for troubleshooting or explicit migration workflows:
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
```bash
pnpm migrate
```
:::info
If `POSTGRES_URL` is set, migrations target Postgres; otherwise local SQLite is used. To disable automatic startup migrations, set `RUN_DRIZZLE_MIGRATIONS=false` and/or `RUN_FS_MIGRATIONS=false`. You can run storage migration manually with `pnpm migrate-fs`.
# First-boot seed for the TTS shared provider (optional; manage in-app afterwards)
# API_KEY=your_replicate_key
# API_BASE only needed for OpenAI-compatible self-hosted providers
```
If you also run an external worker service (for example Railway), set these there too:
- `LOG_FORMAT=json`
- `COMPUTE_LOG_LEVEL=info`
:::note Env vars vs. admin panel (important for Vercel)
`API_KEY` / `API_BASE` are one-shot bootstrap seeds on first deploy. After boot, manage providers and site features in **Settings → Admin**. Changes there apply on refresh without a redeploy. See [Admin Panel](../configure/admin-panel).
2. Enable public networking on that Railway service and set:
- `COMPUTE_WORKER_URL=https://<railway-worker-domain>` (in Vercel)
3. Use the same `COMPUTE_WORKER_TOKEN` value in both Vercel and Railway worker env vars.
For complete Railway worker env vars (`NATS_*`, `S3_*`, health checks, and Synadia `.creds` guidance), see [Compute Worker (NATS JetStream)](./compute-worker).
## 2. First-run admin configuration (recommended)
After the first successful deploy and admin login, open **Settings → Admin** and configure:
- **Shared providers**: create/edit your provider key(s) here (encrypted at rest).
- **Site features**:
- `enableDocxConversion=false` on Vercel (`soffice` unavailable).
- `enableTtsProvidersTab=false` if you want shared-provider-only UX.
- `enableUserSignups=true` unless you explicitly want an invite-only deployment.
- `restrictUserApiKeys=true` to block user BYOK through the hosted server.
- `defaultTtsProvider=replicate` (or your preferred shared slug).
- `showAllProviderModels=false` if you want users locked to each provider's default model.
- `enableAudiobookExport=true`.
## 3. Runtime JSON seed (optional)
If you must pre-seed site features/providers at deploy time, use `RUNTIME_SEED_JSON` or `RUNTIME_SEED_JSON_PATH` (versioned JSON seed document). Prefer the admin panel for ongoing management.
See [Environment Variables](../reference/environment-variables#runtime-json-seed-v4) for schema and examples.
:::warning Auth recommendation
Set both `BASE_URL` and `AUTH_SECRET` — they are required in v4+ and also required for the admin panel and for encrypting admin-stored TTS credentials.
Admin-managed TTS provider keys are encrypted with a key derived from `AUTH_SECRET`. If you rotate `AUTH_SECRET` after the first deploy, you must re-enter each admin shared provider's API key from the UI.
:::
:::tip
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
:::
## 4. Database and data migrations
Vercel deployments do not run the `@openreader/bootstrap` process, so automatic startup migrations do not run there.
- Run `pnpm migrate` in a controlled environment to apply Drizzle schema migrations to your Postgres DB.
- Run `pnpm migrate-fs` only when migrating legacy local filesystem data (`docstore/documents_v1`, `docstore/audiobooks_v1`) into object storage + DB rows. Fresh Vercel deployments usually do not need this.
## 5. Scheduled maintenance tasks
The repository configures `/api/admin/tasks/tick` as a Vercel Cron route. Set `CRON_SECRET`; requests without the matching bearer token are rejected.
The checked-in Hobby-compatible schedule invokes the route once daily. The admin task panel therefore prevents selecting intervals shorter than one day on Vercel, even though self-hosted deployments can run tasks more frequently.
Each due task is claimed with a database-backed lease, due tasks start independently, and individual runs are aborted and marked failed after four minutes. Review failures and run tasks manually from **Settings → Admin → Scheduled tasks**.
## 6. FFmpeg packaging in Vercel functions
`ffmpeg-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for:
- `/api/audiobook`
- `/api/audiobook/chapter`
- `/api/tts/segments/ensure`
:::info
`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
:::
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
## 7. Function memory sizing
FFmpeg workloads benefit from more memory/CPU. This repo includes:
- `-e API_BASE=...` / optional `-e API_KEY=...`: **first-boot seed only.** On the first container start, these are auto-migrated into a `default-openai` admin shared provider stored in the DB (key encrypted at rest when provided). After that, the running app no longer reads them — manage the provider from **Settings → Admin → Shared providers**. See [Admin Panel](./configure/admin-panel).
- `-e BASE_URL=...` and `-e AUTH_SECRET=...`: required for v4+ auth/session startup.
- `-e ADMIN_EMAILS=...`: (optional, requires auth) comma-separated emails auto-promoted to admin. Admins see the **Admin** tab in Settings.
</TabItem>
<TabItemvalue="local-network"label="LAN Host">
Use this when the app should be reachable from other devices on your LAN:
Replace `YOUR_LAN_IP` with the Docker host IP address on your local network to allow access from other devices.
What this command enables:
- LAN access from phones/tablets/other computers via `http://<YOUR_LAN_IP>:3003`.
- `BASE_URL` points auth/session cookies and callbacks at your LAN URL.
- `AUTH_TRUSTED_ORIGINS` allows localhost loopback origins in addition to your primary LAN origin.
- `USE_ANONYMOUS_AUTH_SESSIONS=true` allows guest sessions while auth is enabled.
- `API_BASE` seeds the default TTS endpoint into the admin-managed `default-openai` shared provider on first boot. Edit it from **Settings → Admin → Shared providers** after that.
- `API_KEY` optionally seeds the default provider's key (encrypted at rest). Omit it for an upstream that does not require authentication.
- `ADMIN_EMAILS=...` (optional) auto-promotes the listed email(s) to admin so they can manage shared providers and site feature flags from the UI.
- `openreader_docstore` volume keeps data persistent across restarts.
</TabItem>
<TabItemvalue="minimal"label="Minimal">
Auth required, embedded storage ephemeral, no library import:
```bash
docker run --name openreader \
--restart unless-stopped \
-p 3003:3003 \
-p 8333:8333 \
-e BASE_URL=http://localhost:3003 \
-e AUTH_SECRET=$(openssl rand -base64 32) \
ghcr.io/richardr1126/openreader:latest
```
What this command enables:
- Fast startup with only the required auth env vars.
- No persistent volume (`/app/docstore` stays container-local), so data is ephemeral unless you add a mount.
- The app still requires `BASE_URL` + `AUTH_SECRET` in v4+, so include them even in minimal mode.
- No TTS provider preset by default. Configure `API_BASE` and, when required, `API_KEY` on first boot if you want a seeded shared provider, or run auth+admin mode and manage providers from the admin panel.
</TabItem>
</Tabs>
:::tip Quick Tips
- Set `API_BASE` on first boot to a TTS endpoint the container can reach (`host.docker.internal` works for host-local services). After first boot, manage providers in **Settings → Admin → Shared providers**.
- `BASE_URL` and `AUTH_SECRET` are required in v4+. The admin panel requires auth.
- Set `ADMIN_EMAILS` to your email if you want the **Admin** tab in Settings.
- `restrictUserApiKeys` controls shared-provider-only mode. For per-user BYOK, toggle it off in **Settings → Admin → Site features** or seed `runtimeConfig.restrictUserApiKeys=false` via runtime seed JSON.
- Use a `/app/docstore` mount if you want data to survive container/image replacement.
- Startup automatically runs DB/storage migrations via the shared entrypoint.
- Scheduled maintenance tasks run in-process and can be managed from **Settings → Admin → Scheduled tasks**; Docker/self-hosted deployments do not need `CRON_SECRET`.
:::
:::warning Port `8333` Exposure
Expose `8333` for direct browser presigned upload/download with embedded SeaweedFS.
If `8333` is not reachable from the browser, direct presigned access is unavailable. Uploads can still fall back to `/api/documents/blob/upload/fallback`, and document reads/downloads continue through `/api/documents/blob`.
:::
## 2. Configure settings in the app UI
Visit [http://localhost:3003](http://localhost:3003) after startup.
- If you set `ADMIN_EMAILS`, sign in with that email and open **Settings → Admin** to manage shared TTS providers and site feature flags for all users.
- Per-user: set TTS provider/model in **Settings → TTS Provider**. API key/base URL inputs are shown only when `restrictUserApiKeys=false`.
- Select the model voice from the voice dropdown.
## 3. Update Docker image
Legacy image compatibility: `ghcr.io/richardr1126/openreader-webui:latest` remains available as an alias.
For external compute mode image details, see [Compute Worker (NATS JetStream)](./deploy/compute-worker).
OpenReader is an open-source text-to-speech document reader built with Next.js. It provides a multilingual read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
> Previously named **OpenReader-WebUI**.
It supports multiple TTS providers including OpenAI, Replicate, DeepInfra, and custom OpenAI-compatible endpoints such as [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI), [KittenTTS-FastAPI](https://github.com/richardr1126/KittenTTS-FastAPI), and [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI).
## ✨ Highlights
- 🧱 **Layout-aware PDF Parsing**
- PP-DocLayoutV3 (ONNX) detects structured blocks with cross-page stitching and geometry-based highlighting for precise read-along sync and clean TTS segmentation
- ⏱️ **Word-by-word Highlighting** via ONNX Whisper alignment
- Powered by the external compute worker control plane (NATS JetStream-backed)
- ⚡ **Segment-based TTS Playback**
- Sentence-aware generation with cached audio segments, background preloading, and resumable playback across EPUB, PDF, TXT, MD, and DOCX
- 🎯 **Multi-Provider TTS Support**
- Self-hosted: [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI) (multi-voice combinations), [**KittenTTS-FastAPI**](https://github.com/richardr1126/KittenTTS-FastAPI), [**Orpheus-FastAPI**](https://github.com/Lex-au/Orpheus-FastAPI), or any custom OpenAI-compatible endpoint
- Cloud: [**OpenAI**](https://platform.openai.com/docs/pricing#transcription-and-speech) (`tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`), [**Replicate**](https://replicate.com/explore) (built-in catalog + any model ID), [**DeepInfra**](https://deepinfra.com/models/text-to-speech) (Kokoro-82M and others)
- 🌐 **Multilingual Support**
- Choose a document language for language-aware narration and highlighting
- Available languages depend on the configured provider, model, and voice
- 🎧 **Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
- 🗂️ **Flexible Backend** — embedded SeaweedFS or S3-compatible storage, SQLite or Postgres, server library import, and device sync
- 🔐 **Auth and User Isolation** — auth is required in v4+, with optional anonymous auth sessions for guest flows
- 🎨 **Customizable** — 13 built-in themes (light and dark palettes), per-user TTS settings, and document handling controls
- If the JSON seed includes `providers` (including an empty array), `API_BASE` / `API_KEY` fallback is skipped.
- If the JSON seed does not include a `providers` key, the legacy `API_BASE` / `API_KEY` bootstrap fallback can still create `default-openai` when provider rows are empty. `API_BASE` alone is sufficient for an upstream that does not require authentication.
Precedence summary:
- Runtime reads: admin DB runtime rows override built-in defaults.
- Seed input (`RUNTIME_SEED_JSON*`) only populates missing runtime rows on first boot; it does not overwrite existing/admin-edited rows.
This page covers database mode selection for OpenReader.
## Database mode
- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
- Postgres: enabled when `POSTGRES_URL` is set; recommended for production/distributed deployments.
## What the database stores
- Document and audiobook metadata/state used by server routes.
- Auth/session tables (`user`, `session`, `account`, `verification`) when auth is enabled — schema is auto-generated by Better Auth.
- TTS character usage counters (`user_tts_chars`) for daily rate limiting (when enabled).
- User settings preferences (`user_preferences`) when auth is enabled.
- User reading progress (`user_document_progress`) when auth is enabled.
- Document preview job/asset metadata (`document_previews`) for server-side PDF/EPUB thumbnails.
App-specific tables are manually maintained in Drizzle schema files, while auth tables are generated by the Better Auth CLI. Both are migrated together via Drizzle. See [Migrations](./migrations) for details.
## Related variables
- `POSTGRES_URL`
For database variable behavior, see [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
## Related docs
- [Migrations](./migrations)
- [Object / Blob Storage](./object-blob-storage)
- [Auth](./auth)
## State sync summary
- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
- With auth disabled, settings and reading progress remain local in the browser.
- Sync is currently request-based (not realtime push invalidation).
`pnpm generate` is a two-phase script for contributors and schema changes:
1. **Better Auth schema generation** — runs the Better Auth CLI twice (once for SQLite, once for Postgres) to produce auto-generated Drizzle schema files for auth tables (`user`, `session`, `account`, `verification`).
2. **Drizzle migration generation** — runs `drizzle-kit generate` for both `drizzle.config.sqlite.ts` and `drizzle.config.pg.ts`, producing SQL migration files from all schema files (app + auth).
:::note
Most users do not need to run `pnpm generate`. Use it when contributing or when you have changed Drizzle schema files and need new migration files.
:::
### Schema ownership
Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
- `src/db/schema_auth_sqlite.ts`
- `src/db/schema_auth_postgres.ts`
App-specific tables are manually maintained in the standard Drizzle schema files:
- `src/db/schema_sqlite.ts`
- `src/db/schema_postgres.ts`
Both sets of schema files are included in the Drizzle configs, so `drizzle-kit generate` and `drizzle-kit migrate` handle all tables together.
Running `drizzle-kit generate` directly skips the Better Auth CLI step. If auth schema has changed upstream (e.g. after a Better Auth version bump), run `pnpm generate` instead to regenerate the auth schema files first.
This page documents how server library import works and how to configure it.
## What it does
Server library import lets you browse files from one or more server directories and import selected files into OpenReader.
- Import is user-driven via a selection modal
- Only selected files are imported
- Imported files become normal OpenReader documents
## FS / Volume Mounts
### App data mount
- Target: `/app/docstore`
- Recommended: yes, for persistence
- Purpose: stores app runtime data, metadata DB, and embedded storage state
- Mount string: `-v openreader_docstore:/app/docstore`
### Library source mount
- Target: `/app/docstore/library`
- Recommended: yes for this feature, use read-only (`:ro`)
- Purpose: exposes host files as import candidates in Server Library Import
- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
## Import flow
1. Open **Settings -> Documents -> Server Library Import**.
2. Select files in the modal.
3. Click **Import**.
Selected files are fetched from the server library endpoint and imported into OpenReader storage.
:::warning Shared Library Roots
Library roots are configured at the server level (not per-user). Any user who can access Server Library Import can browse/import from the same configured roots.
Imported documents are still saved to the importing user's document scope.
:::
## Supported file types
- `.pdf`
- `.epub`
- `.html`, `.htm`
- `.txt`
- `.md`, `.mdown`, `.markdown`
## Optional: Configure Library Roots
You only need this when the default mounted path is not what you want.
By default, OpenReader uses `docstore/library` as the import root. You can override that with environment variables:
- `IMPORT_LIBRARY_DIRS` (takes precedence): multiple roots separated by comma, colon, or semicolon
- `IMPORT_LIBRARY_DIR`: single root
See [Environment Variables](../reference/environment-variables#library-import) for variable details.
## Notes
- Library listing is capped per request (up to 10,000 files).
- When auth is enabled, library import endpoints require a valid session.
- The mounted library is a source; removing it does not delete already imported documents.
- Stable S3 credentials via `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`
- External S3 storage by setting `USE_EMBEDDED_WEED_MINI=false` and related S3 vars
:::info
For all environment variables, see [Environment Variables](../reference/environment-variables).
:::
See [Auth](../configure/auth) for app/auth behavior.
Storage configuration details are in [Object / Blob Storage](../configure/object-blob-storage).
Refer to [Database](../configure/database) for database modes.
Learn about migration behavior and commands in [Migrations](../configure/migrations).
4. Run DB migrations.
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
- You only need manual migration commands for one-off troubleshooting or explicit migration workflows:
```bash
pnpm migrate
```
:::info
If `POSTGRES_URL` is set, migrations target Postgres; otherwise local SQLite is used. To disable automatic startup migrations, set `RUN_DRIZZLE_MIGRATIONS=false` and/or `RUN_FS_MIGRATIONS=false`. You can run storage migration manually with `pnpm migrate-fs`.
:::
5. Start the app.
<TabsgroupId="local-run-mode">
<TabItemvalue="dev"label="Dev"default>
```bash
pnpm dev
```
</TabItem>
<TabItemvalue="prod"label="Build + Start">
```bash
pnpm build
pnpm start
```
</TabItem>
</Tabs>
:::warning API Base Reachability
`API_BASE` must be reachable from the Next.js server process, not just your browser.
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
:::
:::warning Auth recommendation
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
:::
:::tip
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
:::
## 2. FFmpeg packaging in Vercel functions
`ffmpeg-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for:
- `/api/audiobook`
- `/api/audiobook/chapter`
- `/api/audiobook/status`
- `/api/whisper`
:::info
`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
:::
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
## 3. Function memory sizing
FFmpeg workloads benefit from more memory/CPU. This repo includes:
Adjust memory per route if your files are larger or your plan differs.
## 4. Runtime expectations and caveats
- Audiobook APIs require S3 configuration; otherwise they return `503`.
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
## 5. Smoke test after deploy
1. Upload and read a PDF/EPUB document.
2. Confirm sync/blob fetch works across refreshes/devices.
3. Generate at least one audiobook chapter and play/download it.
4. If using word highlighting, verify timestamps are produced and rendered.
Replace `<YOUR_LAN_IP>` with the Docker host IP address on your local network to allow access from other devices.
</TabItem>
</Tabs>
:::tip Quick Tips
- Remove `/app/docstore/library` if you do not need server library import.
- Remove either `BASE_URL` or `AUTH_SECRET` to keep auth disabled.
- Set `API_BASE` to your reachable TTS server base URL.
:::
:::warning Port `8333` Exposure
Expose `8333` for direct browser presigned upload/download with embedded SeaweedFS.
If `8333` is not reachable from the browser, direct presigned access is unavailable. Uploads can still fall back to `/api/documents/blob/upload/fallback`, and document reads/downloads continue through `/api/documents/blob`.
:::
:::info Auth and Migrations
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
- DB/storage migrations run automatically at container startup via the shared entrypoint.
OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
> Previously named **OpenReader-WebUI**.
It supports multiple TTS providers including OpenAI, DeepInfra, and custom OpenAI-compatible endpoints such as [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI) and [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI).
## ✨ Highlights
- 🎯 **Multi-Provider TTS Support**
- [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI): supports multi-voice combinations (for example `af_heart+af_bella`)
- State sync: request-based today (not realtime push updates)
- Authentication: [Better Auth](https://www.better-auth.com/) server handlers/adapters
- Metadata DB: [Drizzle ORM](https://orm.drizzle.team/) with SQLite (`better-sqlite3`) by default and optional Postgres (`pg`)
- App tables are manually maintained in Drizzle schema files
- Auth tables are auto-generated by the [Better Auth](https://www.better-auth.com/) CLI and migrated alongside app tables via Drizzle
- Blob storage: embedded [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (`weed mini`) by default, or external S3-compatible storage via AWS SDK v3
- Audio/processing pipeline: OpenAI-compatible TTS providers, [ffmpeg](https://ffmpeg.org/) for audiobook assembly, optional [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for word timestamps
This page covers database mode selection for OpenReader.
## Database mode
- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
- Postgres: enabled when `POSTGRES_URL` is set; recommended for production/distributed deployments.
## What the database stores
- Document and audiobook metadata/state used by server routes.
- Auth/session tables (`user`, `session`, `account`, `verification`) when auth is enabled — schema is auto-generated by Better Auth.
- TTS character usage counters (`user_tts_chars`) for daily rate limiting (when enabled).
- User settings preferences (`user_preferences`) when auth is enabled.
- User reading progress (`user_document_progress`) when auth is enabled.
- Document preview job/asset metadata (`document_previews`) for server-side PDF/EPUB thumbnails.
App-specific tables are manually maintained in Drizzle schema files, while auth tables are generated by the Better Auth CLI. Both are migrated together via Drizzle. See [Migrations](./migrations) for details.
## Related variables
- `POSTGRES_URL`
For database variable behavior, see [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
## Related docs
- [Migrations](./migrations)
- [Object / Blob Storage](./object-blob-storage)
- [Auth](./auth)
## State sync summary
- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
- With auth disabled, settings and reading progress remain local in the browser.
- Sync is currently request-based (not realtime push invalidation).
`pnpm generate` is a two-phase script for contributors and schema changes:
1. **Better Auth schema generation** — runs the Better Auth CLI twice (once for SQLite, once for Postgres) to produce auto-generated Drizzle schema files for auth tables (`user`, `session`, `account`, `verification`).
2. **Drizzle migration generation** — runs `drizzle-kit generate` for both `drizzle.config.sqlite.ts` and `drizzle.config.pg.ts`, producing SQL migration files from all schema files (app + auth).
:::note
Most users do not need to run `pnpm generate`. Use it when contributing or when you have changed Drizzle schema files and need new migration files.
:::
### Schema ownership
Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
- `src/db/schema_auth_sqlite.ts`
- `src/db/schema_auth_postgres.ts`
App-specific tables are manually maintained in the standard Drizzle schema files:
- `src/db/schema_sqlite.ts`
- `src/db/schema_postgres.ts`
Both sets of schema files are included in the Drizzle configs, so `drizzle-kit generate` and `drizzle-kit migrate` handle all tables together.
Running `drizzle-kit generate` directly skips the Better Auth CLI step. If auth schema has changed upstream (e.g. after a Better Auth version bump), run `pnpm generate` instead to regenerate the auth schema files first.
This page documents how server library import works and how to configure it.
## What it does
Server library import lets you browse files from one or more server directories and import selected files into OpenReader.
- Import is user-driven via a selection modal
- Only selected files are imported
- Imported files become normal OpenReader documents
## FS / Volume Mounts
### App data mount
- Target: `/app/docstore`
- Recommended: yes, for persistence
- Purpose: stores app runtime data, metadata DB, and embedded storage state
- Mount string: `-v openreader_docstore:/app/docstore`
### Library source mount
- Target: `/app/docstore/library`
- Recommended: yes for this feature, use read-only (`:ro`)
- Purpose: exposes host files as import candidates in Server Library Import
- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
## Import flow
1. Open **Settings -> Documents -> Server Library Import**.
2. Select files in the modal.
3. Click **Import**.
Selected files are fetched from the server library endpoint and imported into OpenReader storage.
:::warning Shared Library Roots
Library roots are configured at the server level (not per-user). Any user who can access Server Library Import can browse/import from the same configured roots.
Imported documents are still saved to the importing user's document scope.
:::
## Supported file types
- `.pdf`
- `.epub`
- `.html`, `.htm`
- `.txt`
- `.md`, `.mdown`, `.markdown`
## Optional: Configure Library Roots
You only need this when the default mounted path is not what you want.
By default, OpenReader uses `docstore/library` as the import root. You can override that with environment variables:
- `IMPORT_LIBRARY_DIRS` (takes precedence): multiple roots separated by comma, colon, or semicolon
- `IMPORT_LIBRARY_DIR`: single root
See [Environment Variables](../reference/environment-variables#library-import) for variable details.
## Notes
- Library listing is capped per request (up to 10,000 files).
- When auth is enabled, library import endpoints require a valid session.
- The mounted library is a source; removing it does not delete already imported documents.
- Stable S3 credentials via `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`
- External S3 storage by setting `USE_EMBEDDED_WEED_MINI=false` and related S3 vars
:::info
For all environment variables, see [Environment Variables](../reference/environment-variables).
:::
See [Auth](../configure/auth) for app/auth behavior.
Storage configuration details are in [Object / Blob Storage](../configure/object-blob-storage).
Refer to [Database](../configure/database) for database modes.
Learn about migration behavior and commands in [Migrations](../configure/migrations).
4. Run DB migrations.
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
- You only need manual migration commands for one-off troubleshooting or explicit migration workflows:
```bash
pnpm migrate
```
:::info
If `POSTGRES_URL` is set, migrations target Postgres; otherwise local SQLite is used. To disable automatic startup migrations, set `RUN_DRIZZLE_MIGRATIONS=false` and/or `RUN_FS_MIGRATIONS=false`. You can run storage migration manually with `pnpm migrate-fs`.
:::
5. Start the app.
<TabsgroupId="local-run-mode">
<TabItemvalue="dev"label="Dev"default>
```bash
pnpm dev
```
</TabItem>
<TabItemvalue="prod"label="Build + Start">
```bash
pnpm build
pnpm start
```
</TabItem>
</Tabs>
:::warning API Base Reachability
`API_BASE` must be reachable from the Next.js server process, not just your browser.
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
:::
:::warning Auth recommendation
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
:::
:::tip
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
:::
## 2. FFmpeg packaging in Vercel functions
`ffmpeg-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for:
- `/api/audiobook`
- `/api/audiobook/chapter`
- `/api/audiobook/status`
- `/api/whisper`
:::info
`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
:::
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
## 3. Function memory sizing
FFmpeg workloads benefit from more memory/CPU. This repo includes:
Adjust memory per route if your files are larger or your plan differs.
## 4. Runtime expectations and caveats
- Audiobook APIs require S3 configuration; otherwise they return `503`.
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
## 5. Smoke test after deploy
1. Upload and read a PDF/EPUB document.
2. Confirm sync/blob fetch works across refreshes/devices.
3. Generate at least one audiobook chapter and play/download it.
4. If using word highlighting, verify timestamps are produced and rendered.
Replace `<YOUR_LAN_IP>` with the Docker host IP address on your local network to allow access from other devices.
</TabItem>
</Tabs>
:::tip Quick Tips
- Remove `/app/docstore/library` if you do not need server library import.
- Remove either `BASE_URL` or `AUTH_SECRET` to keep auth disabled.
- Set `API_BASE` to your reachable TTS server base URL.
:::
:::warning Port `8333` Exposure
Expose `8333` for direct browser presigned upload/download with embedded SeaweedFS.
If `8333` is not reachable from the browser, direct presigned access is unavailable. Uploads can still fall back to `/api/documents/blob/upload/fallback`, and document reads/downloads continue through `/api/documents/blob`.
:::
:::info Auth and Migrations
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
- DB/storage migrations run automatically at container startup via the shared entrypoint.
OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
> Previously named **OpenReader-WebUI**.
It supports multiple TTS providers including OpenAI, DeepInfra, and custom OpenAI-compatible endpoints such as [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI), [KittenTTS-FastAPI](https://github.com/richardr1126/KittenTTS-FastAPI), and [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI).
## ✨ Highlights
- 🎯 **Multi-Provider TTS Support**
- [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI): supports multi-voice combinations (for example `af_heart+af_bella`)
- State sync: request-based today (not realtime push updates)
- Authentication: [Better Auth](https://www.better-auth.com/) server handlers/adapters
- Metadata DB: [Drizzle ORM](https://orm.drizzle.team/) with SQLite (`better-sqlite3`) by default and optional Postgres (`pg`)
- App tables are manually maintained in Drizzle schema files
- Auth tables are auto-generated by the [Better Auth](https://www.better-auth.com/) CLI and migrated alongside app tables via Drizzle
- Blob storage: embedded [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (`weed mini`) by default, or external S3-compatible storage via AWS SDK v3
- Audio/processing pipeline: OpenAI-compatible TTS providers, [ffmpeg](https://ffmpeg.org/) for audiobook assembly, optional [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for word timestamps