2026-08-15 16:04:18 +02:00
|
|
|
|
# diora-tui
|
|
|
|
|
|
|
TUI: durchgehende Leseansicht mit web-kompatiblen Anchors, Fußnoten, bidirektionalem Progress-Sync
Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel
statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py,
reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR
gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive
linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte
sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen
den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung
(Terminal hat keine Pixel-Geometrie), was funktioniert, weil der
Furthest-Wins-Vergleich primär nach blockIndex sortiert.
Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt
eines einzelnen riesigen Static — bei großen Büchern (mehrere reale
heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den
Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached
diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte
für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und
zentriert.
Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js'
_looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen
Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele
häufig auf einem inneren <a> statt dem umschließenden <p> sitzen.
Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da
beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus
dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei
offenen server-verknüpften Büchern Updates zurück (force: false, im
Hintergrund-Worker).
Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback
localStorage.getItem(...) falls die Clipboard-API in der Konsole
verweigert wird.
Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a.
3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik
per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll,
Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere
Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s,
warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine
Race-Condition beim ersten Post-Load-Scroll, und Progress-Push
End-to-End gegen einen echten Dev-Server verifiziert.
2026-08-15 17:59:46 +02:00
|
|
|
|
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).
|
|
|
|
|
|
|
TUI: Lesestand-Vertrag (zuletzt gelesen gewinnt), Login wie diora_client, Bücher beim Öffnen laden
progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:08:59 +01:00
|
|
|
|
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.
|
2026-08-15 16:04:18 +02:00
|
|
|
|
|
|
|
|
|
|
## Setup
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
cd tui
|
|
|
|
|
|
pip install -e .
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Nutzung
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
diora-tui --library ~/Books # Standard: ~/Books
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Tastenkürzel:
|
|
|
|
|
|
|
|
|
|
|
|
- `↑`/`k`, `↓`/`j` — zeilenweise scrollen
|
TUI: durchgehende Leseansicht mit web-kompatiblen Anchors, Fußnoten, bidirektionalem Progress-Sync
Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel
statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py,
reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR
gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive
linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte
sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen
den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung
(Terminal hat keine Pixel-Geometrie), was funktioniert, weil der
Furthest-Wins-Vergleich primär nach blockIndex sortiert.
Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt
eines einzelnen riesigen Static — bei großen Büchern (mehrere reale
heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den
Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached
diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte
für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und
zentriert.
Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js'
_looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen
Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele
häufig auf einem inneren <a> statt dem umschließenden <p> sitzen.
Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da
beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus
dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei
offenen server-verknüpften Büchern Updates zurück (force: false, im
Hintergrund-Worker).
Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback
localStorage.getItem(...) falls die Clipboard-API in der Konsole
verweigert wird.
Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a.
3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik
per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll,
Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere
Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s,
warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine
Race-Condition beim ersten Post-Load-Scroll, und Progress-Push
End-to-End gegen einen echten Dev-Server verifiziert.
2026-08-15 17:59:46 +02:00
|
|
|
|
- `n` — nächstes Kapitel, `p` — vorheriges Kapitel
|
TUI: Lesestand-Vertrag (zuletzt gelesen gewinnt), Login wie diora_client, Bücher beim Öffnen laden
progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:08:59 +01:00
|
|
|
|
- `w` — zur weitesten Stelle des Buchs springen (auf Wunsch; beim Öffnen weist ein Hinweis darauf hin)
|
TUI: durchgehende Leseansicht mit web-kompatiblen Anchors, Fußnoten, bidirektionalem Progress-Sync
Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel
statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py,
reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR
gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive
linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte
sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen
den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung
(Terminal hat keine Pixel-Geometrie), was funktioniert, weil der
Furthest-Wins-Vergleich primär nach blockIndex sortiert.
Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt
eines einzelnen riesigen Static — bei großen Büchern (mehrere reale
heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den
Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached
diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte
für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und
zentriert.
Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js'
_looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen
Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele
häufig auf einem inneren <a> statt dem umschließenden <p> sitzen.
Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da
beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus
dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei
offenen server-verknüpften Büchern Updates zurück (force: false, im
Hintergrund-Worker).
Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback
localStorage.getItem(...) falls die Clipboard-API in der Konsole
verweigert wird.
Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a.
3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik
per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll,
Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere
Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s,
warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine
Race-Condition beim ersten Post-Load-Scroll, und Progress-Push
End-to-End gegen einen echten Dev-Server verifiziert.
2026-08-15 17:59:46 +02:00
|
|
|
|
- `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"`)
|
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
|
|
|
|
- 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.
|
2026-08-15 16:04:18 +02:00
|
|
|
|
- `Escape` / `q` — zurück zur Bibliothek (im Reader) bzw. beenden (in der Bibliothek)
|
|
|
|
|
|
|
TUI: durchgehende Leseansicht mit web-kompatiblen Anchors, Fußnoten, bidirektionalem Progress-Sync
Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel
statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py,
reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR
gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive
linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte
sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen
den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung
(Terminal hat keine Pixel-Geometrie), was funktioniert, weil der
Furthest-Wins-Vergleich primär nach blockIndex sortiert.
Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt
eines einzelnen riesigen Static — bei großen Büchern (mehrere reale
heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den
Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached
diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte
für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und
zentriert.
Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js'
_looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen
Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele
häufig auf einem inneren <a> statt dem umschließenden <p> sitzen.
Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da
beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus
dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei
offenen server-verknüpften Büchern Updates zurück (force: false, im
Hintergrund-Worker).
Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback
localStorage.getItem(...) falls die Clipboard-API in der Konsole
verweigert wird.
Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a.
3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik
per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll,
Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere
Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s,
warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine
Race-Condition beim ersten Post-Load-Scroll, und Progress-Push
End-to-End gegen einen echten Dev-Server verifiziert.
2026-08-15 17:59:46 +02:00
|
|
|
|
Der Fließtext ist auf 120 Zeichen Breite begrenzt und horizontal zentriert (lesbarer als
|
|
|
|
|
|
volle Terminalbreite bei breiten Fenstern).
|
|
|
|
|
|
|
2026-08-15 20:36:54 +02:00
|
|
|
|
Unten rechts zeigt eine Statusleiste Akkustand (falls vorhanden, via `psutil`) und Uhrzeit,
|
|
|
|
|
|
sekündlich aktualisiert, auf Bibliotheks- und Reader-Ansicht.
|
|
|
|
|
|
|
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
|
|
|
|
## 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.
|
2026-08-15 20:30:09 +02:00
|
|
|
|
|
TUI: Lesestand-Vertrag (zuletzt gelesen gewinnt), Login wie diora_client, Bücher beim Öffnen laden
progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:08:59 +01:00
|
|
|
|
## Anmelden und synchronisieren
|
2026-08-15 20:30:09 +02:00
|
|
|
|
|
TUI: Bücher per `diora-tui sync` vom diora-Server holen und entschlüsseln
Neuer `sync`-Subcommand: authentifiziert über den Personal-Access-Token
gegen GET /api/sync/ + GET /books/<id>/data/, entschlüsselt Metadaten und
Buchbytes lokal mit AES-256-GCM (diora_tui/crypto.py, kompatibel zu
static/js/app.js' encryptBytes/decryptBytes) und legt EPUBs als normale
Dateien in der Library ab — PDFs werden übersprungen. Zugangsdaten
(Server-URL, Token, Base64-Key) landen auf Wunsch in
~/.config/diora-tui/config.json (0600), sonst interaktive Abfrage pro Lauf.
Lesefortschritt wird bewusst noch nicht synced: der Server verankert
Position als "blockIndex:innerFraction" in der Absatz-Nummerierung des
Web-Readers, die nicht 1:1 auf das Kapitel-basierte scroll_fraction dieser
TUI abbildet — ein naiver Abgleich würde falsche Positionen liefern.
Getestet: AES-GCM-Rundreise + Falsch-Schlüssel-Ablehnung, vollständiger
sync-Lauf gegen einen echten Dev-Server (Token/Key-Abfrage, Download,
Entschlüsselung, Dedup bei erneutem Lauf, Fehlerfälle bei falschem
Token/Key, --save inkl. 0600-Datei und Wiederverwendung ohne Prompt).
2026-08-15 16:41:50 +02:00
|
|
|
|
```bash
|
TUI: Lesestand-Vertrag (zuletzt gelesen gewinnt), Login wie diora_client, Bücher beim Öffnen laden
progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:08:59 +01:00
|
|
|
|
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
|
TUI: Bücher per `diora-tui sync` vom diora-Server holen und entschlüsseln
Neuer `sync`-Subcommand: authentifiziert über den Personal-Access-Token
gegen GET /api/sync/ + GET /books/<id>/data/, entschlüsselt Metadaten und
Buchbytes lokal mit AES-256-GCM (diora_tui/crypto.py, kompatibel zu
static/js/app.js' encryptBytes/decryptBytes) und legt EPUBs als normale
Dateien in der Library ab — PDFs werden übersprungen. Zugangsdaten
(Server-URL, Token, Base64-Key) landen auf Wunsch in
~/.config/diora-tui/config.json (0600), sonst interaktive Abfrage pro Lauf.
Lesefortschritt wird bewusst noch nicht synced: der Server verankert
Position als "blockIndex:innerFraction" in der Absatz-Nummerierung des
Web-Readers, die nicht 1:1 auf das Kapitel-basierte scroll_fraction dieser
TUI abbildet — ein naiver Abgleich würde falsche Positionen liefern.
Getestet: AES-GCM-Rundreise + Falsch-Schlüssel-Ablehnung, vollständiger
sync-Lauf gegen einen echten Dev-Server (Token/Key-Abfrage, Download,
Entschlüsselung, Dedup bei erneutem Lauf, Fehlerfälle bei falschem
Token/Key, --save inkl. 0600-Datei und Wiederverwendung ohne Prompt).
2026-08-15 16:41:50 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
TUI: Lesestand-Vertrag (zuletzt gelesen gewinnt), Login wie diora_client, Bücher beim Öffnen laden
progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:08:59 +01:00
|
|
|
|
`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.
|
TUI: Bücher per `diora-tui sync` vom diora-Server holen und entschlüsseln
Neuer `sync`-Subcommand: authentifiziert über den Personal-Access-Token
gegen GET /api/sync/ + GET /books/<id>/data/, entschlüsselt Metadaten und
Buchbytes lokal mit AES-256-GCM (diora_tui/crypto.py, kompatibel zu
static/js/app.js' encryptBytes/decryptBytes) und legt EPUBs als normale
Dateien in der Library ab — PDFs werden übersprungen. Zugangsdaten
(Server-URL, Token, Base64-Key) landen auf Wunsch in
~/.config/diora-tui/config.json (0600), sonst interaktive Abfrage pro Lauf.
Lesefortschritt wird bewusst noch nicht synced: der Server verankert
Position als "blockIndex:innerFraction" in der Absatz-Nummerierung des
Web-Readers, die nicht 1:1 auf das Kapitel-basierte scroll_fraction dieser
TUI abbildet — ein naiver Abgleich würde falsche Positionen liefern.
Getestet: AES-GCM-Rundreise + Falsch-Schlüssel-Ablehnung, vollständiger
sync-Lauf gegen einen echten Dev-Server (Token/Key-Abfrage, Download,
Entschlüsselung, Dedup bei erneutem Lauf, Fehlerfälle bei falschem
Token/Key, --save inkl. 0600-Datei und Wiederverwendung ohne Prompt).
2026-08-15 16:41:50 +02:00
|
|
|
|
|
TUI: durchgehende Leseansicht mit web-kompatiblen Anchors, Fußnoten, bidirektionalem Progress-Sync
Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel
statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py,
reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR
gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive
linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte
sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen
den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung
(Terminal hat keine Pixel-Geometrie), was funktioniert, weil der
Furthest-Wins-Vergleich primär nach blockIndex sortiert.
Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt
eines einzelnen riesigen Static — bei großen Büchern (mehrere reale
heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den
Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached
diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte
für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und
zentriert.
Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js'
_looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen
Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele
häufig auf einem inneren <a> statt dem umschließenden <p> sitzen.
Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da
beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus
dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei
offenen server-verknüpften Büchern Updates zurück (force: false, im
Hintergrund-Worker).
Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback
localStorage.getItem(...) falls die Clipboard-API in der Konsole
verweigert wird.
Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a.
3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik
per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll,
Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere
Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s,
warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine
Race-Condition beim ersten Post-Load-Scroll, und Progress-Push
End-to-End gegen einen echten Dev-Server verifiziert.
2026-08-15 17:59:46 +02:00
|
|
|
|
## 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.
|
|
|
|
|
|
|
2026-10-26 12:40:20 +01:00
|
|
|
|
## 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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-10-26 12:59:03 +01:00
|
|
|
|
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).
|
2026-10-26 12:40:20 +01:00
|
|
|
|
|
2026-08-15 16:04:18 +02:00
|
|
|
|
## Grenzen der aktuellen Version
|
|
|
|
|
|
|
TUI: Lesestand-Vertrag (zuletzt gelesen gewinnt), Login wie diora_client, Bücher beim Öffnen laden
progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:08:59 +01:00
|
|
|
|
- Nur EPUB, kein PDF (der Abgleich überspringt PDFs und meldet sie).
|
TUI: durchgehende Leseansicht mit web-kompatiblen Anchors, Fußnoten, bidirektionalem Progress-Sync
Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel
statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py,
reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR
gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive
linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte
sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen
den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung
(Terminal hat keine Pixel-Geometrie), was funktioniert, weil der
Furthest-Wins-Vergleich primär nach blockIndex sortiert.
Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt
eines einzelnen riesigen Static — bei großen Büchern (mehrere reale
heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den
Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached
diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte
für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und
zentriert.
Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js'
_looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen
Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele
häufig auf einem inneren <a> statt dem umschließenden <p> sitzen.
Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da
beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus
dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei
offenen server-verknüpften Büchern Updates zurück (force: false, im
Hintergrund-Worker).
Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback
localStorage.getItem(...) falls die Clipboard-API in der Konsole
verweigert wird.
Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a.
3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik
per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll,
Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere
Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s,
warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine
Race-Condition beim ersten Post-Load-Scroll, und Progress-Push
End-to-End gegen einen echten Dev-Server verifiziert.
2026-08-15 17:59:46 +02:00
|
|
|
|
- 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.
|