diora-web/tui/README.md
mrwnslz 695037fb9c TUI: Bibliothek wie im Web (Weiterlesen-Karte, Status- und Ordner-Chips, Suche, Sortierung, Länge)
library.py hält die Regeln der Web-Bibliothek (53-library.js) als reine Funktionen:
Status Angefangen/Ungelesen/Gelesen, Ordner (mehrere je Buch, Reihenfolge aus den
verschlüsselten Bibliothekseinstellungen, "Ohne Ordner"), Suche ohne Akzente, sechs
Sortierungen mit natürlicher Zahlenordnung, Weiterlesen-Karte aus den zuletzt gelesenen,
Bücher/Zeitschriften. library_screen.py zeigt sie an (Tasten 1-4, o, /, s, m, c; Chips und
Karte klickbar). Metadaten (Ordner, Seitenzahl, Zeitschrift) und die Ordnerreihenfolge
kommen beim Abgleich; fehlt die Länge, rechnet die TUI sie für geladene Bücher aus dem Text
(1800 Zeichen je Seite). crypto.encrypt für Stufe 3. 54 Tests, Smoke prüft Ordner/Länge/Prefs
gegen den lokalen Server.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:18:49 +01:00

160 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.js`s
`EPUB_BLOCK_SELECTOR` (`p, h1-h6, li, blockquote, dt, dd, figcaption` + textbasierte
`div`s 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).
Die Lesestelle folgt dem Vertrag aus `docs/progress-sync.md`: **die zuletzt gelesene Stelle
gewinnt** (auf welchem Gerät auch immer), die weiteste bleibt als Hinweis erhalten. Die
Regeln stehen in `progress.py` und sind dieselben wie auf dem Server und in den Apps.
## Setup
```bash
cd tui
pip install -e .
```
## Nutzung
```bash
diora-tui --library ~/Books # Standard: ~/Books
```
Tastenkürzel:
- `↑`/`k`, `↓`/`j` — zeilenweise scrollen
- `n` — nächstes Kapitel, `p` — vorheriges Kapitel
- `w` — zur weitesten Stelle des Buchs springen (auf Wunsch; beim Öffnen weist ein Hinweis darauf hin)
- `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.js`s
`_looksLikeFootnoteLink` (Link in/um `<sup>`, Klassenname mit note/footnote/fn, oder
`epub:type="noteref"`)
- Bibliothek: `Enter` öffnet das markierte Buch, `c` das Buch der „Weiterlesen“-Karte, `1`–`4`
Status (Alle · Angefangen · Ungelesen · Gelesen), `o` Ordner, `/` Suche, `s` Sortierung,
`m` Bücher ⇄ Zeitschriften, `Escape` setzt Suche und Filter zurück. Chips und Karte lassen
sich auch anklicken.
- `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 Bibliothek
Wie die Bibliothek im Web (`docs/library.md`), nur im Terminal:
- **Weiterlesen-Karte** oben (Titel, Autor, Balken, „Seite 132 von 186“, wann und auf welchem
Gerät zuletzt gelesen) und darunter die Bücher, die davor gelesen wurden. Das Buch der Karte
steht nicht noch einmal in der Liste. Bei Suche und Filtern entfällt die Karte.
- **Chips:** Alle · Angefangen · Ungelesen · Gelesen (mit Anzahl), dann die Ordner in der
Reihenfolge, die im Web eingestellt ist (verschlüsselte Bibliothekseinstellungen), und
„Ohne Ordner“. Ein Buch kann in mehreren Ordnern stehen.
- **Suche** in Titel und Autor, ohne Beachtung von Groß-/Kleinschreibung und Akzenten.
- **Sortierung:** Zuletzt · Titel · Autor · Neu hinzugefügt · Kürzeste/Längste zuerst (Zahlen
nach Wert: „Band 2“ vor „Band 10“). Sie bleibt gespeichert.
- **Länge:** die Seitenzahl aus den Metadaten (`pages`); fehlt sie, rechnet die TUI sie für
schon geladene Bücher einmal aus dem Text aus (1800 Zeichen je Seite, wie der Web-Reader), wenn
nach Länge sortiert wird („Länge wird ermittelt …“). Nicht geladene Bücher ohne Länge stehen am
Ende. Zurück auf den Server schreibt die TUI die Länge nicht; das macht der Web-Reader.
- **Bücher ⇄ Zeitschriften** (`m`): Zeitschriften sind, was in den Metadaten so markiert ist
oder im Ordner „Zeitschriften“/„Magazine“ liegt. Die Heft-Ansicht mit Artikeln gibt es in der
TUI nicht; ein Heft ist ein Buch.
- ☁ vor dem Titel: auf dem Server, noch nicht geladen. ✓: gelesen.
Nicht in der TUI: Serien-Stapel, Ausgaben (mehrere Fassungen eines Werks stehen einzeln),
Cover, Ordner anlegen/zuweisen, Eingang aus der Cloud.
## Anmelden und synchronisieren
```bash
diora-tui login # einmal; fragt Benutzername und Passwort
diora-tui # die Oberfläche, synct beim Start und beim Beenden
diora-tui sync # nur abgleichen, ohne Oberfläche
diora-tui logout # gespeicherte Zugangsdaten löschen
```
`login` meldet sich wie die Apps über das Web-Formular an, liest den vorhandenen API-Token
von der Einstellungsseite (es wird nie ein neuer erzeugt: das würde die Apps abmelden) und
leitet den Buchschlüssel aus dem Passwort ab (PBKDF2, wie „Mit Passwort entsperren“ im Web).
Das Passwort wird nicht gespeichert. Passt der abgeleitete Schlüssel nicht (Passwort seit dem
Hochladen geändert, Schlüssel zufällig erzeugt), fragt `login` nach dem Schlüssel
(diora: Einstellungen → Schlüssel kopieren; `--paste-key` fragt gleich danach).
Token und Schlüssel liegen geschützt in `config.json`: unter Windows per DPAPI (nur dieser
Windows-Benutzer auf diesem Rechner kann sie lesen), sonst als Base64 in einer 0600-Datei.
Eine ältere Konfiguration mit Klartextfeldern wird beim ersten Lesen geschützt neu geschrieben.
Alle Dateien liegen unter `platformdirs`' Verzeichnissen; `DIORA_TUI_HOME=<Ordner>` legt sie
stattdessen gemeinsam in einen Ordner (zweites Konto, Tests).
**Was synchronisiert wird:** die Bücherliste (`GET /books/`, ohne die Blobs der Markierungen)
samt Lesestand, Gelesen-Status und der Uhr des Servers. Die Bücher selbst werden erst beim
Öffnen geladen und entschlüsselt (`<id> - <Titel>.epub` im Bibliotheksordner). PDFs werden
übersprungen.
**Lesestand** (`progress.py`, `reader_screen.py`):
- Gespeichert wird nur, was der Leser selbst bewegt hat (Tasten, Mausrad), mit dem Zeitpunkt
des Ankommens (`read_at`). Wiederherstellen, Umbruch bei Größenänderung und Sprünge auf
Angebote ändern nichts. Ein offenes, ruhendes Buch kann deshalb nie eine neuere Stelle von
woanders überschreiben.
- Zeiten sind Serverzeit: die TUI lernt aus jeder Antwort den Versatz ihrer Uhr, und der
Server korrigiert mit `sent_at` nach. Ein falsch gehender Rechner ordnet trotzdem richtig.
- Jede Stelle wird lokal angewendet und vorgemerkt (`pending`, je Buch die später gelesene),
bis der Server sie hat, auch offline; nach einem Abgleich wird sie nach derselben Regel auf
den Stand des Servers gelegt.
- Alle zwei Minuten fragt ein offenes Buch den Server. Liegt dort eine neuere Stelle von einem
anderen Gerät, fragt die TUI „Auf *X* weitergelesen — bei n %. Springen?“ (`y`/`n`), und
bewegt sich nie von allein.
- Der Fortschritt in % ist wie im Web der Anteil am Buchtext in Zeichen
(`blocks.fraction_for_anchor`), nicht mehr 0.
## 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.
## Tests und Anker-Parität mit dem Web-Reader
```bash
pip install -e . pytest
pytest tests # Blockliste, Anker, App-Start (Textual-Pilot)
```
Die Position ist ein Block-Index, den alle Clients gleich zählen müssen. Ob die TUI das tut,
prüft `tools/parity/` gegen den echten Web-Code (`parseEpub` aus `static/js/app/40-epub.js`
in headless Chromium):
```bash
NODE_PATH=<diora-web>/node_modules PLAYWRIGHT_BROWSERS_PATH=... node tools/parity/web_blocks.cjs <diora-web> web.json buch1.epub buch2.epub
python tools/parity/compare_blocks.py web.json
```
Stand 2026-10-26 (6 Bücher, bis 25.798 Blöcke): Anzahl, Tags, Text, Zeichenposition und
-länge aller Blöcke sowie die Gesamtlänge sind identisch. Das setzt voraus, dass die TUI den
EPUB-Inhalt wie der Browser liest: UTF-8-dekodiert, mit dem HTML5-Parser (`html5lib`, nicht
libxml2) und aus den Rohdaten (`item.content`; `get_content()` von ebooklib formatiert um und
fügt Leerraum ein).
## Grenzen der aktuellen Version
- Nur EPUB, kein PDF (der Abgleich überspringt PDFs und meldet sie).
- 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.