- Kotlin 100%
|
All checks were successful
build / build (push) Successful in 11m12s
Le locator de l'élément visible n'a pas de progression : Muninn l'estimait en comptant les chapitres (début du chapitre 9 sur 34 = 23,5 %, alors que c'est 11 % du texte), et edda partageait ce chiffre avec Saga et KOReader, qui faisaient reculer le livre. La progression vient maintenant du locator du navigateur Readium quand il est dans la même ressource, sinon d'une estimation pondérée par la taille des ressources. |
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| app | ||
| design-system | ||
| gradle | ||
| licenses | ||
| openspec | ||
| .gitignore | ||
| build.gradle.kts | ||
| CLAUDE.md | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| LICENSE | ||
| README.md | ||
| settings.gradle.kts | ||
Muninn
An Android app — built for Boox e-ink readers — that browses your edda-library library over OPDS 2.0, downloads EPUBs, and opens them in NeoReader (the native Boox reader). It runs on the device.
Built with Kotlin + Jetpack Compose, tuned for e-ink: pure black-on-white, no animations, large touch targets.
A built-in EPUB reader may come later; v0.1 hands reading off to NeoReader.
Why
Reading the library on a Boox today means browsing the web UI in a sluggish e-ink browser, downloading, then finding the file. Muninn is a native, e-ink-first catalog client: search, tap, download, read.
Features (v0.1 scope)
- Connect to an edda-library server with a per-user or shared OPDS token.
- Browse the OPDS 2.0 navigation feed (authors, tags, publishers, series, unread…).
- Search the catalog.
- Download EPUBs and open them in NeoReader via an Android intent.
- E-ink-optimised Compose UI (no motion, high contrast).
- Ask the paired librarian (edda's AI chat relay) from the library bar or a book page — see Librarian.
Build
Requires the Android SDK and JDK 17.
./gradlew :app:assembleDebug # build debug APK
./gradlew :app:installDebug # install on a connected Boox over adb
The debug APK lands at app/build/outputs/apk/debug/app-debug.apk. Release APKs
are attached to each GitHub release (published on a v* tag).
Installing on the Boox
Enable developer options / USB debugging on the Boox, connect over USB (or
adb connect <ip>), then ./gradlew :app:installDebug — or sideload the APK
directly.
Configuration
In-app: enter your server URL and OPDS token (stored via DataStore). The token
is sent as the ?token= query parameter that edda-library accepts on /opds/*.
Librarian
When edda has a librarian paired (GET /opds/chat → {"enabled":true}), a
« Libraire » entry appears in the library bar and every book page gets
« Demander au libraire », which opens the chat with « Que peux-tu me dire sur
« titre » de auteur ? » already sent. Answers stream over SSE from
POST /opds/chat?token= (single-JSON replies from older librarians are
accepted too); the last six turns are sent back as context. E-ink rules: no
spinner (a static « Le libraire réfléchit… » line), the streamed text is
repainted at most every 2 s, and markdown is reduced to bold, bullets and
headings. Cancel stops the run server-side; errors offer a retry.
The librarian can ask a question with predefined answers: Muninn announces
"capabilities":["question"] in every request, and a
{"kind":"question","question":…,"options":[…],"multiple":bool} frame shows
the question as the librarian's message with its options as full-width
bordered rows under it (no ripple, no animation). A single choice is sent on
tap; a multiple one ticks boxes and « Valider » sends the labels joined with
« ; » in the options' order. Typing in the input box answers freely instead.
Once answered, or for any older question, the options are a plain numbered
list. The question goes back in history as its text rendering (the question,
a blank line, 1. option… and (plusieurs réponses possibles) when several
answers are allowed).
From the built-in reader, the control bar's librarian icon opens « Demander au
libraire »: a summary of what was read, who a character is, or a free
question. The message starts with the reading position (« Je lis « titre »
de auteur, chapitre « chapitre », à 42 %. ») and the request also carries
it as context (book_id, title, author, chapter, progression 0–1)
so the librarian answers without spoilers. The chat opens over the book; back
returns to the page. Books handed to NeoReader have no librarian entry.
Reading status
Reaching the end of a book marks it finished on edda and opens an end-of-book page: « Terminer » (then an optional rating, « Sans note » or 1–5), « Abandonner (DNF) » after a confirmation, and the next volume when the series continues. The library sheet and the catalog book page offer « Noter » and « Abandonner (DNF) » for any edda book.
Changes go to POST /opds/books/{id}/read?token= as {"status":…} and/or
{"rating":…} (edda v1.188+). They show on the shelf at once and are queued
on the device: one made offline is re-sent with the next progress sync (app
opening, every 5 min in the foreground, every 15 min in the background), and a
later change to the same book supersedes the queued one field by field. The
status and rating set on another device arrive through the edda:status /
edda:rating fields of the catalog feed.
Fonts
The editorial type system bundles three OFL (SIL Open Font License) variable
fonts in app/src/main/res/font/ — Playfair Display (titles), Source
Serif 4 (body), JetBrains Mono (labels). License texts are in licenses/.
Development
Spec-driven via OpenSpec — see
openspec/changes/bootstrap-opds-client/ for the proposal, design, capability
specs, and tasks. Conventions live in CLAUDE.md.
License
MIT — © 2026 Bastien Quelen. The bundled fonts keep their SIL Open
Font License (texts in licenses/).