Turnleaf is an offline-first, mobile EPUB reader for Kavita. It is a local app only: there is no companion server, no proxy, and no hidden backend.
The goal is simple: connect to Kavita, download books, read them as paginated pages, and keep progress synchronized across devices without making reading feel like a web app.
Latest release: GitHub Releases
Tracked release notes: RELEASE_NOTES.md
If a Kavita refresh fails, open Settings → Diagnostics after reproducing it. Copy the log or use View diagnostics to select the text on Android, then paste it into your GitHub issue. The log includes endpoint statuses and unexpected field types, but excludes your server address, API key, book titles, and response bodies. It stays in memory until the app closes or you clear it.
GitHub release bodies are generated from the matching versioned section in
RELEASE_NOTES.md; ## Unreleased records changes that are still in
development.
- Improve mobile library filtering, view controls, and large-library performance.
- Add reader appearance panels, bookmarks, highlights, notes, and in-book search.
- Make native metadata refreshes transactional and cover loading cancellable.
- Generate clean GitHub release notes without npm command output.
- Guard library startup teardown and limit concurrent Kavita detail requests for large libraries.
- Read with a monochrome e-ink theme and an optional keep-awake setting.
- Discover EPUBs in numbered series volumes and Light Novel libraries.
- Keep reader text clear of Android navigation bars and refresh progress when returning to the library.
- Develop against a local Kavita Docker Compose instance.
- Include patched production dependencies and updated Android tooling.
I built Turnleaf because the existing options I tried did not fit the use case I wanted:
- Kavita's PWA scrolls books instead of rendering proper pages, and it does not provide offline access.
- Inkita also scrolls books.
- Inkavi is missing an ePub dependency.
- KOReader was difficult to get working reliably for cross-device sync.
- Kover renders books as one continuous block of text without the indentation and paragraph spacing I wanted.
Turnleaf is the small, direct answer to those problems.
- Connects directly to a Kavita server with the Kavita REST API.
- Browses only book-oriented library content.
- Downloads EPUBs into app-private storage for offline reading.
- Opens downloaded EPUBs instantly and reads them as paginated pages.
- Turns pages with left and right taps.
- Shows reader controls with a center tap.
- Synchronizes reading progress back to Kavita.
- Restores the correct semantic EPUB location after reopening.
- Lets you tune the reading experience with font, size, margin, line-height, and theme controls.
- Supports light and dark modes plus a theme selector.
- Keeps credentials in native secure storage instead of localStorage.
- Stores durable app data in SQLite.
Turnleaf is intentionally narrow in scope.
Supported:
- EPUB books in Kavita Book and Light Novel libraries, including books with illustrations
- Text-first novels and long-form prose
Not supported:
- Manga
- Comics
- PDFs
- Audiobooks
- Magazines
- Image-based readers
That limitation is deliberate. Turnleaf is optimized for conventional book reading, not for every media type Kavita can host.
Turnleaf is built with:
- Svelte 5
- TypeScript with strict type checking
- Vite
- Capacitor
- Skeleton UI
- Tailwind CSS
- EPUB.js for EPUB rendering
- SQLite for local structured data
@capacitor-community/sqlitefor durable app storage@aparajita/capacitor-secure-storagefor Keychain / Keystore-backed credentials- Capacitor Filesystem and File Transfer for native EPUB downloads
- DOMPurify for sanitizing Kavita-provided HTML
The app stays intentionally boring inside:
- Svelte components own the UI.
- Small domain modules handle Kavita, storage, downloads, sync, and reader state.
- SQLite stores server configuration, book metadata, reading state, and sync queue entries.
- Secure storage stores the credential reference and the actual secret.
- Downloaded EPUBs live in app-private native storage.
- Reader rendering is isolated inside a sandboxed EPUB.js iframe.
There is no repository layer, service locator, event bus, or backend abstraction for its own sake.
- Only EPUB is supported.
- I do not have a Mac, so iOS has only been scaffolded and synchronized as far as Linux allows.
- Android is the only platform I have personally tested end-to-end.
- The browser dev server is useful for UI work, but native secure storage and native EPUB file handling are still Capacitor features.
Because the app is a Capacitor wrapper around a Svelte application, it should build on macOS for iOS once Xcode is available, but that has not been verified from this machine.
- Node.js 22 (see
.nvmrc) - npm
- For Android: Java 21, Android Studio, Android SDK, and an emulator or device
- For iOS on macOS: Xcode and CocoaPods
- Docker, if you want to run the local Kavita fixture
npm ci
npm run verifynpm ci installs the dependencies and enables the repository's Git hooks. The
single contributor check is npm run verify; it runs the production dependency
audit, formatting check, lint, type check, unit tests, and web build.
npm run devUse this for fast UI iteration and browser-based testing of the Svelte app. Native storage, downloads, and secure credential storage require Capacitor builds.
When running the browser app against Kavita, the development proxy accepts
loopback http:// and https:// targets such as http://localhost:5000. It
rejects remote targets by default. To use a remote server, explicitly allow its
HTTPS origin before starting Vite:
TURNLEAF_DEV_PROXY_ORIGINS="https://books.example.com:5000" npm run devThe proxy accepts only HTTP(S), rejects URL credentials, forwards only the Kavita API headers it needs, limits request bodies to 1 MiB, and stops upstream requests after 12 seconds. It does not follow redirects or forward cookies, authorization, or forwarding headers. Plain HTTP is intentionally limited to loopback development servers.
Docker is optional, but it provides a repeatable server for onboarding, download, offline, and reader testing:
npm run dev:kavita
npm run devComplete the one-time Kavita setup in dev/README.md. Stop the
fixture with npm run dev:kavita:down, or inspect it with
npm run dev:kavita:logs.
npm run verifyRun npm run format separately when you intend to rewrite formatting.
Turnleaf has been tested on Android. The repo includes the Capacitor Android project and release/debug build scripts.
export ANDROID_HOME="$HOME/Android/Sdk"
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
npm run android:doctor
npm run android:debugandroid:doctor checks the SDK path, API 36, Android Build Tools, and Java 17+
before a Gradle build. android:debug and android:release repeat that check,
then pass the selected SDK path to both Capacitor sync and Gradle. If the SDK is
installed somewhere else, set ANDROID_HOME or create the ignored
android/local.properties file with its absolute path.
Useful follow-up commands:
npm run android:open
npm run android:smoke
npm run android:releaseandroid:smoke installs the debug APK as app.turnleaf.reader.debug and
verifies that the app process starts on a connected emulator or USB device. The
.debug application ID lets it run alongside a signed production install.
android:open and ios:open read an optional .env file when present, so a
fresh checkout does not need one. Copy .env.example to .env only when you
need to set an Android Studio path or a remote browser-proxy origin.
If you want to run from Android Studio, open the generated Android project after cap sync and use the usual emulator or device workflow.
The iOS Capacitor project exists and can be synchronized on Linux, but I have not built or tested it because I do not have a Mac.
On macOS:
xcode-select --install
brew install node cocoapods
npm ci
npm run build
npm run ios:sync
npm run ios:openThen in Xcode:
- Open the generated app project.
- Select the
Apptarget. - Set the development team and signing identity.
- Confirm the bundle identifier is correct.
- Build and run on a simulator or device.
If you are trying iOS, please report back what works and what does not.
Contributions and forks are welcome. The complete setup, testing, Android validation, pull-request, and release-note workflow is in CONTRIBUTING.md.
Please keep changes small and direct:
- Prefer the simplest implementation that solves the problem.
- Avoid adding abstractions unless they earn their keep.
- Keep EPUB reading, Kavita integration, and local storage behavior explicit.
- Preserve offline-first behavior.
- Do not add new dependencies unless they are clearly necessary.
GitHub Actions runs npm run verify, an Android debug build, an emulator startup smoke test, dependency review, and CodeQL scanning. PRs that touch native behavior should also be verified locally on the relevant platform when possible.
Use DEVICE_VALIDATION.md for the Android accessibility, network-isolation, and offline-reopen acceptance pass. CI also runs an emulator startup smoke test; that smoke test does not replace TalkBack or network-trace validation.
Before opening a pull request, run:
npm run verifyIf your change touches native behavior, also run the relevant Capacitor or Android build command.
Screenshots for UI changes are appreciated, especially for reader, settings, and library work.
Turnleaf is released under the MIT License.




