No description
Find a file
Bastien Quelen 4390a60fb4
All checks were successful
build / build (push) Successful in 11m12s
fix(lecteur): progression réelle envoyée à edda (v1.35)
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.
2026-10-17 22:43:43 +02:00
.claude Bootstrap eink-cli: Kotlin/Native OPDS client + OpenSpec change 2026-06-19 18:46:44 +02:00
.forgejo/workflows ci: build et publication uniquement sur tag v* 2026-10-13 21:33:01 +02:00
app fix(lecteur): progression réelle envoyée à edda (v1.35) 2026-10-17 22:43:43 +02:00
design-system Libraire : chat avec le libraire d'edda (SSE, relecture e-ink toutes les 2 s) 2026-10-09 20:08:04 +02:00
gradle feat(lecture): statut, note et abandon (DNF) envoyés à edda, libraire depuis le lecteur (v1.32) 2026-10-10 14:27:42 +02:00
licenses Bundle editorial fonts (task 2.4) 2026-06-19 20:02:22 +02:00
openspec feat(lecture): téléchargements visibles, sync rapportée, tome suivant (v1.29) 2026-08-13 21:39:30 +02:00
.gitignore Pivot to boox-opds: Android/Compose e-ink OPDS app + design system 2026-06-19 19:08:06 +02:00
build.gradle.kts Pivot to boox-opds: Android/Compose e-ink OPDS app + design system 2026-06-19 19:08:06 +02:00
CLAUDE.md docs: utiliser l'agent mobile-developer pour ce projet 2026-06-28 09:58:36 +02:00
gradle.properties ci(muninn): gradle plus économe en mémoire pour le runner (parallel off, Kotlin in-process, workers=2) 2026-06-28 12:30:36 +02:00
gradlew Bootstrap eink-cli: Kotlin/Native OPDS client + OpenSpec change 2026-06-19 18:46:44 +02:00
gradlew.bat Bootstrap eink-cli: Kotlin/Native OPDS client + OpenSpec change 2026-06-19 18:46:44 +02:00
LICENSE Licence MIT — © 2026 Bastien Quelen 2026-10-05 20:25:07 +02:00
README.md feat(libraire): questions à choix du libraire (v1.33) 2026-10-11 21:39:23 +02:00
settings.gradle.kts refactor: renomme boox-opds → muninn-eink, package net.helheim.muninn 2026-06-27 16:24:31 +02:00

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/).