diora-web/tui
marwin e9683fbdbd TUI: Zeichen am rechten Rand bei schmalen Terminals nicht mehr abgeschnitten
ContinuousBookViews vertikale Scrollbar reserviert 2 Spalten, die vorher
nicht aus der Wrap-Breite herausgerechnet wurden — der Text wurde also
2 Zeichen breiter gewrappt, als tatsächlich sichtbar war, wodurch die
Scrollbar die letzten 1-2 Buchstaben jeder Zeile überdeckt hat. Bei
schmalen Terminals (großer Font, wenig Spalten) war das besonders
auffällig, betraf strukturell aber jede Breite.

Fix: overflow-x: hidden (eine ungewollte horizontale Scrollbar hat
zusätzlich eine Zeile unten geklaut) + overflow-y: scroll (hält die
Scrollbar-Breite von Anfang an konstant, kein Rätselraten je nach
Inhaltsgröße) in book_view.py. reader_screen.py misst die Wrap-Breite
jetzt am Container statt an book_view selbst (vermeidet einen
Miss-nach-Einschränken-Zirkelbezug bei wiederholten Resizes) und rechnet
die feste Scrollbar-Breite (SCROLLBAR_GUTTER=2) heraus; beim Anwenden
des Layouts wird sie wieder daraufgerechnet, damit book_view.styles.width
weiterhin fürs Zentrieren passt.

Getestet: content_region-Breite stimmt jetzt exakt mit der Wrap-Breite
über sechs verschiedene Terminalbreiten (60-200 Spalten, inkl. des
120-Zeichen-Cap-Bereichs) überein — vorher lag sie durchgehend 2 Spalten
darunter. Voller Regressionstest (Scroll, Fußnote, Resize) läuft weiter
fehlerfrei.
2026-08-15 21:07:41 +02:00
..
diora_tui TUI: Zeichen am rechten Rand bei schmalen Terminals nicht mehr abgeschnitten 2026-08-15 21:07:41 +02:00
pyproject.toml TUI: Statusleiste mit Akkustand und Uhrzeit 2026-08-15 20:36:54 +02:00
README.md TUI: Statusleiste mit Akkustand und Uhrzeit 2026-08-15 20:36:54 +02:00

diora-tui

EPUB-Reader im Terminal — die erste Stufe einer TUI-Version von diora.

Zeigt ein Buch als eine durchgehende Ansicht über alle Kapitel hinweg (wie diora's Web-Reader), nicht Kapitel für Kapitel. Lesefortschritt wird als dieselbe "blockIndex:innerFraction"-Positionsangabe geführt wie der Web-Reader (books/models.py, EBookProgress) — blockIndex zählt dabei exakt wie static/js/app.jss EPUB_BLOCK_SELECTOR (p, h1-h6, li, blockquote, dt, dd, figcaption + textbasierte divs ohne Element-Kinder), fortlaufend über das ganze Buch. Dadurch ist die Position zwischen TUI und Web-Reader direkt vergleichbar, und diora-tui sync kann Fortschritt in beide Richtungen synchronisieren (siehe unten).

Fortschritt wird — genau wie im Web-Reader (save_progress) — nur vorwärts überschrieben ("furthest wins"), lokal in progress.json (~/.local/share/diora-tui/, via platformdirs), damit ein älterer/gestaffelter Lauf nie eine bereits weiter gelesene Position zurücksetzt.

Setup

cd tui
pip install -e .

Nutzung

diora-tui --library ~/Books   # Standard: ~/Books

Tastenkürzel:

  • /k, /j — zeilenweise scrollen
  • n — nächstes Kapitel, p — vorheriges Kapitel
  • f — Fußnote in der Nähe der aktuellen Position anzeigen (Peek-Overlay, Escape/f/q zum Schließen); Erkennung folgt derselben Heuristik wie app.jss _looksLikeFootnoteLink (Link in/um <sup>, Klassenname mit note/footnote/fn, oder epub:type="noteref")
  • Enter — markiertes Buch aus der Bibliothek öffnen
  • r (in der Bibliothek) — gelesene Bücher ein-/ausblenden (siehe unten)
  • Escape / q — zurück zur Bibliothek (im Reader) bzw. beenden (in der Bibliothek)

Der Fließtext ist auf 120 Zeichen Breite begrenzt und horizontal zentriert (lesbarer als volle Terminalbreite bei breiten Fenstern).

Unten rechts zeigt eine Statusleiste Akkustand (falls vorhanden, via psutil) und Uhrzeit, sekündlich aktualisiert, auf Bibliotheks- und Reader-Ansicht.

Die Bibliotheksansicht ist nach zuletzt geöffnetem Buch sortiert (neueste zuerst; anhand des Zeitstempels der zuletzt gespeicherten Position), und blendet gelesene Bücher standardmäßig aus (EBook.is_read aus dem Sync-Snapshot, lokal in library.json gespiegelt) — r zeigt sie wieder an, für diese Sitzung.

Bücher + Fortschritt vom Server holen (diora-tui sync)

Sobald einmal Zugangsdaten gespeichert sind (~/.config/diora-tui/config.json, siehe unten), synct diora-tui automatisch — einmal leise im Hintergrund beim Start (neue Bücher + Fortschritt werden nachgeladen, die Bibliotheksliste aktualisiert sich von selbst) und einmal beim Beenden über q (kurzer Moment Verzögerung, bevor die App tatsächlich schließt). Der explizite Befehl ist für's Ersteinrichten und für Nicht-interaktive Nutzung (Cron o.ä.):

diora-tui sync                              # nutzt gespeicherte Zugangsdaten, sonst interaktive Abfrage
diora-tui sync --server https://diora.creamfresh.xyz --save   # einmalig einrichten + speichern

Lädt alle EPUBs des Accounts über GET /api/sync/ + GET /books/<id>/data/ herunter, entschlüsselt sie lokal (AES-256-GCM, kompatibel zu static/js/app.js) und legt sie als normale .epub-Dateien in --library ab (Dateiname <id> - <Titel>.epub) — von da an funktionieren sie wie jedes andere lokale Buch. Bereits heruntergeladene Bücher werden beim nächsten Lauf übersprungen (kein erneuter Download). Der Fortschritt aus dem Snapshot wird dabei ebenfalls übernommen (nur vorwärts, wie lokal auch).

Während ein so heruntergeladenes Buch geöffnet ist (erkennbar am <id> - -Dateinamens- Präfix), schickt der Reader Fortschritts-Updates zusätzlich zurück an den Server (POST /books/<id>/progress/, force: false — überschreibt also nie eine weiter gelesene Position, egal ob die vom Web-Reader oder einem anderen Gerät stammt). Rein lokale Bücher (ohne dieses Präfix) bleiben unangetastet, kein Netzwerkzugriff.

Dafür nötig, beim ersten Lauf abgefragt (danach optional lokal gespeichert unter ~/.config/diora-tui/config.json, chmod 600):

  • Server-URL — z.B. https://diora.creamfresh.xyz.
  • API-Token — diora → Einstellungen (/accounts/settings/) → "Personal Access Token".
  • Verschlüsselungs-Key — der AES-256-Schlüssel, mit dem deine Bücher clientseitig verschlüsselt wurden. Der sync-Prompt bietet zwei Wege:
    1. Aus Benutzername + Passwort ableiten (Standard) — reproduziert exakt, was diora's "Unlock with password"-Formular im Browser tut (PBKDF2-HMAC-SHA256, 200.000 Iterationen, Salt "diora:" + username; siehe static/js/app.js:deriveAndStoreKey). Funktioniert nur, wenn der Account diesen Weg im Browser mindestens einmal benutzt hat — sonst wurde der Key ursprünglich zufällig im Browser erzeugt, und diese Ableitung trifft ihn nicht. sync meldet einen falschen Key als Entschlüsselungsfehler (harmlos, kein Datenverlust), nie als falsches Ergebnis.
    2. Base64-Key direkt einfügen — für den Fall, dass Weg 1 nicht passt. Der Export-Button dafür ist im Browser-UI aktuell nicht verdrahtet (exportEncKey() in app.js existiert, hat aber keinen sichtbaren Button); bis das nachgezogen ist, in der Browser-Devtools-Konsole auf der diora-Seite (nicht /accounts/settings/ — die lädt app.js nicht) ausführen: await exportEncKey() — kopiert den Key ins Clipboard, von dort ins sync-Prompt einfügen. Schlägt das mit einem NotAllowedError/InvalidAccessError fehl, stattdessen direkt aus localStorage lesen: localStorage.getItem('diora_enc_key_' + window.USER_ID).

Der Key/Token wird genauso vertrauensvoll behandelt wie im Web-Client (dort liegt der Schlüssel unverschlüsselt in localStorage): lokal als Klartext in einer 0600-Datei.

Performance bei großen Büchern

Sehr große Bücher (mehrstellige Tausend Absätze — z.B. Sammelbände) können beim ersten Öffnen mehrere Sekunden bis niedrige zweistellige Sekunden brauchen (Parsing + Zeilenumbruch-Berechnung für die durchgehende Ansicht). Ein zweites Öffnen desselben Buchs bei gleicher Terminalbreite ist dank Cache (~/.cache/diora-tui/layout_cache/) deutlich schneller. Das Rendering selbst skaliert nicht mit der Buchgröße — nur die tatsächlich sichtbaren Zeilen werden gezeichnet (Textual Line API), nicht das ganze Buch auf einmal.

Grenzen der aktuellen Version

  • Nur EPUB, kein PDF — sync lädt PDFs im Account gar nicht erst herunter (übersprungen, wird gemeldet), da der Reader sie ohnehin nicht darstellen kann.
  • Text wird als Fließtext ohne Bild-/Layout-Rendering dargestellt.
  • innerFraction (die Position innerhalb eines Blocks) ist eine Terminal-Näherung (zeilenbasiert statt pixelbasiert wie im Browser) — für die Fortschritts-Sortierung zählt primär blockIndex, der exakt mit dem Web-Reader übereinstimmt; innerFraction ist nur ein Tiebreaker innerhalb desselben Blocks.