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.
diora_tui/crypto.py:derive_key_b64 reproduziert app.js' deriveAndStoreKey()
(PBKDF2-HMAC-SHA256, 200000 Iterationen, Salt "diora:"+username) — der
`sync`-Prompt bietet das jetzt als Standardweg zum Schlüssel an, als
Alternative zum bisherigen Weg über die Browser-Konsole (await
exportEncKey()). Funktioniert nur für Accounts, die den Key je über
diora's "Unlock with password"-Formular abgeleitet haben; ein falsches
Passwort/Account führt zu einem abgefangenen DecryptError ("falscher
Key?"), nie zu stillem Fehlverhalten.
Getestet: zwei unabhängige PBKDF2-Implementierungen (hashlib,
cryptography) stimmen für die verwendeten Parameter byte-genau überein;
vollständiger sync-Lauf gegen einen echten Dev-Server mit einem Buch, das
unter einem exakt so abgeleiteten Key verschlüsselt wurde, entschlüsselt
korrekt; falsches Passwort wird sauber als Fehler gemeldet statt
abzustürzen.
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).
Ermöglicht Apps außerhalb des Browsers (z.B. den TUI-Client), sich per
Authorization: Bearer <token> zu authentifizieren statt per Session-Cookie.
Die neue ApiTokenAuthMiddleware setzt request.user genau wie ein Login,
wodurch alle bestehenden books/podcasts/radio-Endpunkte ohne Änderungen
token-fähig werden. GET /api/sync/ liefert zusätzlich den kompletten
Nutzerzustand (Bücher-Metadaten, Lesefortschritt, Notizen, Podcasts,
Sender) in einem Request; Schreiben läuft weiter über die bestehenden
Endpunkte, um deren Merge-Semantik (furthest-wins Progress, Notes-Upsert)
wiederzuverwenden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Python + Textual, unter tui/. Liest EPUBs aus einem Bibliotheksordner,
zeigt Kapitel als Fließtext, j/k/Pfeiltasten zum Scrollen, n/p für
Kapitelwechsel. Fortschritt wird lokal (progress.json via platformdirs)
nach demselben "furthest wins"-Prinzip wie diora's Web-Reader gespeichert,
damit ein Sync-Layer gegen die künftige diora-API später ohne Rewrite
andocken kann. Vorerst rein lokal, kein Server-Kontakt, keine
Verschlüsselung.
.reader-toast-btn hatte keine font-family gesetzt; Buttons erben das
anders als normaler Text nicht vom body, dadurch fiel "Zurückspringen"
auf die System-Schrift statt Courier New zurück.
.reader-toast/.reader-toast-action erbten außerdem den globalen
Halo-Textschatten (--outline), der eigentlich Text gegen wechselnde
Hintergründe absetzen soll. Die Toasts tauschen Vorder-/Hintergrund
aber bewusst (weiße Pille, schwarzer Text) und sitzen bereits auf
eigenem, blickdichtem Hintergrund — der geerbte Schatten hatte
dadurch exakt die Textfarbe und verdickte die Schrift nur unnötig,
statt einen Kontrast-Effekt zu erzielen. Jetzt wie die übrigen
Elemente mit eigenem farbigen Hintergrund (.btn-play etc.) explizit
mit text-shadow:none von diesem Effekt ausgenommen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Die requestAnimationFrame-Schleife aus dem letzten Fix lief mit der tatsächlichen
Bildwiederholrate des Displays statt einer festen Rate — auf Laptops mit
120/144/165Hz-Panels bedeutete das 2-3x mehr scrollBy-Aufrufe (und damit
Scroll-Listener-Durchläufe) pro Sekunde als beabsichtigt, was sich als Lag
statt als Beschleunigung äußerte. Zurück zu setInterval, aber mit fester
20ms-Kadenz unabhängig vom Display und dt-kompensierter, konstanter
Geschwindigkeit (~1200px/s, kein Ramp-up mehr).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
j/k nutzte ein starres 8px/16ms-setInterval (~500px/s, unsauber getaktet
gegenüber dem Frame-Takt) — ersetzt durch eine requestAnimationFrame-Schleife
mit sanftem Ramp-up (700 -> 1800px/s über 300ms), das behebt sowohl die zu
niedrige Geschwindigkeit als auch das Ruckeln kurz nach Scrollbeginn.
Der Service Worker war unter /static/js/sw.js registriert, wodurch sein
Scope (mangels Service-Worker-Allowed-Header) automatisch auf /static/js/
beschränkt war statt die ganze App abzudecken — Navigationen zu z.B. /
liefen nie durch den fetch-Handler, weshalb offline nur die Browser-eigene
Fehlerseite statt der App-Shell erschien. Jetzt wird sw.js zusätzlich unter
der Root /sw.js ausgeliefert (diora/urls.py) und dort mit explizitem
scope:'/' registriert; alte Registrierungen mit dem falschen Scope werden
beim Laden automatisch entfernt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Auf schmalen Screens saß der Marker-Modus-Button bisher nur in der 150px-
Randspalte — man musste sie erst öffnen (und damit den ohnehin knappen
Lesebereich verkleinern), um überhaupt markieren zu können. Jetzt zusätzlich
ein Marker-Button direkt im Haupt-Header (nur <=600px sichtbar, Desktop
bleibt bei der bisherigen Anordnung in der Notes-Leiste).
Im Marker-Modus legt ein einfacher Tap auf reinen Lesetext (ohne Auswahl)
jetzt direkt eine freie Notiz an der Wortposition an — man muss nicht mehr
den exakten Klick-Punkt in der Randspalte treffen. Das Bearbeiten der
Notiz läuft auf schmalen Viewports über ein neues Bottom-Sheet (volle
Breite) statt über das Inline-Textfeld in der 150px-Spalte, das auf dem
Handy-Keyboard unangenehm zu bedienen ist.
Notiz-Objekte werden jetzt erst bei tatsächlich eingegebenem Text angelegt
und gespeichert (vorher: sofort beim Setzen des Ankers) — verhindert, dass
ein abgebrochener/leerer Tap eine Ghost-Notiz in currentHighlights
hinterlässt. Gilt für alle Eingabewege (Bottom-Sheet UND die bestehende
Inline-Randspalten-Bearbeitung), per Playwright verifiziert inkl. Escape-
Cancel auf Desktop.
Priorität laut Nutzer: Notizen-Erfassung muss auf Mobile gut funktionieren;
dass bestehende Notizen dort weniger prominent sichtbar sind, ist explizit
in Ordnung — die vertiefte Auseinandersetzung mit Notizen bleibt eine
Desktop-Aufgabe.
Verifiziert per Playwright (390×844 mobil + 1280×900 Desktop-Regression):
Marker-Button-Sichtbarkeit, Bottom-Sheet-Flow (Erstellen/Abbrechen/Speichern,
kein Ghost-Eintrag), reale Text-Selektion + Highlight+Notiz weiterhin
funktionsfähig auf Mobile, Desktop-Workflow (Randspalte, Inline-Editor,
Escape-Cancel, Blur-Commit) unverändert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Die Erkennung von Fußnoten-Links war auf class="footnote" + gleiche-Datei-
Anker (href startsWith '#') hartkodiert. Andere Editionen markieren Fußnoten
anders — z.B. die e-artnow-Ausgabe von Marx' "Das Kapital" (zeno.org-Konvertierung)
nutzt gar keine Klasse und verlinkt mit einem <sup> als Kind (statt Vorfahre)
des <a> auf eine ANDERE Kapiteldatei ("../Text/Karl0045.html#F537") — dadurch
griff weder die Klassen- noch die Hash-Prüfung, das Popover blieb tot.
_looksLikeFootnoteLink() erkennt jetzt zusätzlich epub:type="noteref" (EPUB3-
Standard), jede Klasse mit "note"/"footnote"/"fn" im Namen, und als Fallback
jeden Link mit Fragment-Anker, der irgendwo mit einem <sup> verschachtelt ist
(in beide Richtungen) — der in EPUBs praktisch universelle Marker für
Fußnotenziffern. Die Fragment-Extraktion nimmt jetzt alles nach dem letzten
'#' statt nur bei href.startsWith('#'), da das Buch als eine zusammenhängende
DOM gerendert wird und getElementById dateiübergreifend funktioniert.
Verifiziert per Playwright: Marx-Edition zeigt jetzt beim Hover 1326 erkannte
Fußnoten-Links mit korrektem Popover-Inhalt; Žižeks "Weniger als nichts"
(vorher funktionierend, class="footnote"-basiert) weiterhin unverändert
funktionsfähig — keine Regression.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mark-Elemente haben browserseitig einen UA-Default (color:black), der bislang
nicht überschrieben wurde — beim Markieren von Text im dunklen Standard-Theme
(weißer Fließtext) sprang die Markierung dadurch abrupt auf schwarzen Text.
Jetzt color:inherit, damit markierter Text immer die Farbe des umgebenden
Texts behält; die vier Hintergrundfarben sind dafür kräftiger/dunkler gewählt,
sodass sie in allen drei Themes (dunkel/weißer Text, Sepia, Hell) ausreichend
Kontrast zum inherited Text bieten (WCAG-Kontrastwerte 4.8:1–11.7:1, per
Playwright-Screenshot in allen drei Themes gegengeprüft).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- EBook.is_read (Migration) + neuer /books/<id>/read/-Endpoint zum Togglen
- book_list liefert is_read und has_highlights (Existenz einer Highlights-Zeile)
- Reader öffnet die Notizen-Sidebar automatisch, wenn das Buch bereits
Markierungen/Notizen hat, statt sie hinter dem manuellen Toggle zu verstecken
- Buch-Übersicht: Download-Button für Notizen/Markierungen direkt aus der Liste
(ohne den Reader zu öffnen), "Als gelesen markieren" mit Bestätigungsdialog,
Filter "Gelesene Bücher anzeigen" (standardmäßig aus)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_repositionMarginMarkers() filterte Einträge anhand eines festen ±60px-
Fensters um den reinen Ankerpunkt, bevor die tatsächliche Kartenhöhe bekannt
war. Für Notizen mit mehr Text als eine "Standard"-Karte reichte das nicht:
die Karte hätte teilweise schon sichtbar sein müssen, obwohl ihr Anker noch
weiter außerhalb lag — sie blieb beim Scrollen also länger verschwunden als
erwartet.
Vorfilter jetzt großzügig (mind. 400px bzw. die Höhe des sichtbaren
Bereichs), danach ein zweiter Cull-Durchgang mit den tatsächlich gemessenen
Kartenhöhen nach dem Stapeln — dadurch werden nur Einträge entfernt, die
wirklich komplett außerhalb liegen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bei sehr langen Büchern (ab ca. 1 Mio. Zeichen, entspricht etwa 600-700
Druckseiten) bewegt sich die ganzzahlige Prozentanzeige beim Lesen kaum noch
— eine Nachkommastelle macht sie wieder aussagekräftig. Betrifft die
Fortschrittsanzeige im Reader-Header sowie die "≈X% im Buch"-Koordinate bei
Annotationen-Export und -Verzeichnis.
Formatierung über toLocaleString() (Komma bei de, Punkt bei en, etc.). Das
Fortschrittsfeld ist dafür von <input type="number"> auf type="text" mit
inputmode="decimal" umgestellt — number-Inputs erzwingen intern immer einen
Punkt, unabhängig von der Locale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vier vom Nutzer gemeldete Bugs behoben:
- Markierter Text war kaum lesbar: der globale weiße Text-Outline (fürs
Hintergrundbild-Feature gedacht) kollidierte mit der Highlight-Farbe.
Reader sitzt immer auf eigenem, opakem Hintergrund — Outline dort komplett
deaktiviert.
- "Notiz zu Markierung hinzufügen" öffnete weiterhin die rechte Sidebar statt
den Rand. Jeder Notiz-Eintragspunkt (Highlight+Notiz aus der Textauswahl,
Edit-Note-Klick auf eine Markierung im Fließtext) läuft jetzt über
_openMarginAndEditNote() — öffnet bei Bedarf den Rand und bearbeitet dort
inline. openNoteEditor()/rechte Sidebar für Notizen komplett entfernt.
- Kernursache für "Notiz verschwindet, Markierung heftet sich an falsche
Notiz": Anker basierten auf XPath mit Sibling-Index unter den Textknoten
eines Blocks — jede weitere Markierung im selben Block (surroundContents
spaltet Textknoten) verschob diese Indizes und korrumpierte lautlos ALLE
anderen Anker im selben Block. Anker adressieren jetzt stattdessen
Block-Index + Zeichen-Offset innerhalb des Blocks (wie das bestehende
Lesefortschritt-Ankersystem) — Wrapping/Unwrapping durch andere
Markierungen ändert weder Blockreihenfolge noch Zeichenanzahl, ist also
erschütterungsfrei für andere Anker im selben Block.
- Löschen einer Markierung riss zuvor aus demselben Grund weiter unten
liegende Notizen optisch mit raus (Daten blieben in der DB) — mit dem
neuen Ankersystem behoben.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Notizen setzen/bearbeiten passiert jetzt inline an der Stelle im Notizen-Rand,
wo die Notiz auch angezeigt wird — kein Wechsel mehr zur rechten Sidebar.
Textfeld wird beim Öffnen automatisch fokussiert und ausgewählt. Enter
speichert, Escape verwirft die Änderung.
Stift-Button (Marker-Modus) zieht in die Titelzeile des Notizen-Rands um,
neben "Notes" — vorher musste man dafür ständig zur Hauptleiste zurück, die
im Immersive-Modus eingeklappt ist.
Nebenbei zwei Bugs beim Testen gefunden und behoben: ein Klick auf leere
Randfläche während ein Editor bereits offen war, legte durchs Blur-Event
gleichzeitig eine zweite, ungewollte Notiz an; und Escape im Notizfeld
bubbled zum globalen Escape-Handler durch, der dann den ganzen Reader
schloss (und damit die gerade bearbeiteten Highlights aus dem Speicher warf).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Echte Buchseiten sind fürs Erste zurückgestellt (kein Konzept dafür bei
reflowable EPUB-Text) — stattdessen bekommt jeder Export-Eintrag jetzt eine
grobe Prozent-Koordinate ("≈37% im Buch"), zusätzlich zur Kapitelgruppierung.
Neu: ein Verzeichnis für Markierungen/Notizen im selben Sidebar-Stil wie die
Lesezeichen (Button in der Margin-Kopfzeile), das alle Einträge in
Lesereihenfolge mit Vorschau und Koordinate auflistet und per Klick an die
jeweilige Textstelle springt — anders als die Margin-Leiste, die nur zeigt,
was gerade sichtbar ist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drei Nachbesserungen am Annotationen-Feature nach Live-Test:
- Neuer Stift-Button (Marker-Modus) in der Kopfleiste: Textauswahl löst das
Highlight-Popup nur noch aus, wenn der Modus aktiv ist — vorher war die
Funktion unauffindbar (impliziter "einfach Text markieren"-Gestus ohne
sichtbaren Einstiegspunkt) und hätte jede Textauswahl (auch zum Kopieren)
gekapert.
- Freiform-Notizen bekommen keine Textdekoration mehr im Lesefluss — der
Anker dient nur der Positionierung im Rand, nicht der optischen Markierung.
- Die Margin-Sidebar zeigt Notiztext jetzt direkt lesbar als kleine
Post-it-Karte an der zugehörigen Stelle (mit Stapel-Logik gegen
Überlappung), statt nur ein abstraktes Symbol zum Anklicken. Reine
Highlights ohne Notiz bleiben ein kleiner Farbpunkt.
- Highlight-Popover/Tooltip klammern ihre Position jetzt hart an den
Viewport, damit eine Auswahl nahe Seitenrand nicht unsichtbar außerhalb
des sichtbaren Bereichs landet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Textauswahl→Highlight-Popover war implementiert, aber nie verdrahtet (kein
mouseup/touchend-Listener) — als Voraussetzung mit behoben. Neu: freiformige
Notizen ohne Textmarkierung, verankert per Klick in einer neuen linken
Margin-Sidebar (Notiz am ersten Wort der Zeile auf Klickhöhe); Sidebar zeigt
Marker für alle Highlights/Notizen im sichtbaren Bereich und schiebt den
Lesetext nach rechts. Export sammelt alle Markierungen/Notizen eines Buchs in
Lesereihenfolge, nach Kapitel gruppiert, als Textdatei. EPUB-only — PDFs haben
noch keinen Text-Layer (siehe Issue #10).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Books: Reader sprang beim Scroll+sofortigem Tap auf die alte Position
zurück (Anker-Tracking lief nur alle 2s über den Save-Debounce, jetzt
eigener 150ms-Tracker unabhängig davon)
- Books: +/- Buttons neben den Reader-Slidern (Font/Zeilenhöhe/Breite)
- Books: Sicherheitsnetz-Toast bei plötzlichen großen Scroll-Sprüngen
(Fling, aus Versehen 'G' gedrückt) mit Zurückspringen-Option; bewusste
Sprünge (TOC, Lesezeichen, Fortschritts-Eingabe, Suche) lösen ihn nicht aus
- Radio: Donation-Hinweis kam bei vielen Lieblingssendern gefühlt ständig —
jetzt zusätzlich auf ~1-von-10 Plays gedrosselt
- UI: farbige Emoji-Icons (🔍📻⏪⏩💡 etc.) durch monochrome Unicode-Symbole
ersetzt, die nicht mehr je nach OS/Browser-Font unterschiedlich aussehen;
Lesezeichen-Icon war zudem fast identisch zum TOC-Icon
- UI: native alert()/confirm()/prompt() durch eigenes Modal-System ersetzt
- UI: Hash-basiertes URL-Routing für Tabs (#radio/saved, #podcasts/feeds, …)
— Browser-Zurück/Vor und Deep-Links funktionieren jetzt
- Tests: Playwright e2e-Setup gegen isolierte Test-DB (DIORA_DB_NAME),
Smoke- und Auth-Specs
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Die max()-Semantik aus v14 verglich nur die grobe scroll_fraction. Da
scroll_fraction und position_anchor gemeinsam, aber entkoppelt gespeichert
werden, konnte ein einzelner Save mit transient zu hoher fraction (z.B.
instabiles Layout beim Laden) die Position dauerhaft einfrieren: jeder
spätere echte Fortschritt hatte eine kleinere fraction und wurde inklusive
Anker verworfen — der Leser landete immer wieder an derselben Stelle.
Fix: Vergleich läuft jetzt über den Blockindex im Anker (die Größe, die
auch die Sprungposition bestimmt), mit innerFraction als Tiebreaker.
scroll_fraction dient nur noch als Fallback für PDFs ohne Anker. Server
(_progress_is_further) und Client (_cmpProgress) nutzen identische Logik;
loadBookList-Push und lokaler Cache ebenfalls. Bestehende eingefrorene
Einträge entfrieren sich beim ersten Weiterlesen selbst.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der am weitesten gelesene Wert hat immer Vorrang:
- Server: scroll_fraction = max(neu, aktuell) — ein afk-Fenster bei 10%
kann nicht mehr 80% überschreiben
- Client loadBookList: wenn lokaler Fortschritt > Server, wird gepusht
und für die Anzeige verwendet (deckt Offline-Lesen ab)
- Pending-Flag-Logik entfernt — nicht mehr nötig dank max-Semantik
- saveReaderProgress(force=true): neuer force-Parameter schreibt
Position unabhängig vom gespeicherten Maximum; Button ⚑ im
Reader-Header für den Ausnahmefall "zurück-zum-Anfang"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Timestamp-Vergleich in _saveBookMeta entfernt: client-seitige
ISO-Strings (new Date().toISOString) lagen fast immer leicht vor dem
Serverwert und haben dadurch regelmässig korrekten Online-Fortschritt
überschrieben.
Neu: explizites Pending-Flag (localStorage diora_pending_progress) —
gesetzt wenn saveReaderProgress offline scheitert, gelöscht nach
erfolgreicher Synchronisierung.
- _saveBookMeta nutzt das Flag statt Zeitstempel: Server-Daten
überschreiben lokalen Fortschritt nur wenn kein Pending-Flag gesetzt
- _syncPendingProgress: pusht ausstehenden Fortschritt beim
online-Event bevor loadBookList läuft
- saveReaderProgress setzt das Flag nur wenn !navigator.onLine
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _saveBookMeta vergleicht local vs. Server last_read — neuere lokale
Daten (offline gelesen) überleben den nächsten Server-Abruf
- loadBookList pusht beim Online-Werden automatisch lokal neueren
Fortschritt an den Server bevor die Liste gerendert wird
- saveBookmarks/saveHighlights markieren fehlgeschlagene Saves als
pending sync (localStorage); beim nächsten online-Event werden alle
pending Annotationen zuerst gepusht, dann erst die Buchliste geladen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Statt padding-top gegen die Navbar zu kämpfen ist #tabs jetzt
position:fixed direkt unterhalb der Navbar (top:var(--nav-h),
z-index:150). Damit ist die Leiste garantiert immer sichtbar,
unabhängig vom Scroll-Zustand oder Offline-Status.
main-content padding-top auf calc(var(--nav-h)+3rem) erhöht um
Platz für Navbar+Tabs zu schaffen. Offline-Banner vereinfacht zu
einer body-Klasse (kein fixed DOM-Element mehr).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- main-content padding-top auf calc(var(--nav-h) + 0.75rem) erhöht
damit die Tab-Leiste (Radio/Focus/Podcasts/Books) sicher unterhalb
der fixed Navbar liegt — auf Desktop und Mobile
- SW v10: sendet SW_ACTIVATED an alle offenen Tabs wenn ein neuer SW
übernimmt; app.js lädt daraufhin neu (außer wenn ein Buch geöffnet
ist) — verhindert dass der Browser eine uralte HTTP-gecachte Version
ausliefert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bei Offline-Start oder Wechsel in Flugzeugmodus: roter Banner
unterhalb der Navbar, automatischer Wechsel zum Books-Tab (einziger
sinnvoller Tab offline), Upload-Zone wird ausgeblendet
- Bei Rückkehr online: Banner verschwindet, Upload-Zone erscheint wieder,
Buchliste wird neu synchronisiert
- openBook unterscheidet jetzt zwischen "nicht gecacht" (Toast-Hinweis
zum einmaligen Online-Öffnen) und echtem Defekt (markBookBroken wie
bisher) — kein stilles Scheitern mehr
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ohne diesen Fix schlägt das Öffnen der App im Flugzeug fehl, weil der
Service Worker die HTML-Seite (/) nicht cached — der Browser zeigt
dann eine Netzwerkfehler-Seite bevor JS überhaupt startet.
Strategie: Network-first für Navigationsanfragen, bei Netzwerkfehler
die zuletzt gecachte Version ausliefern. Nach dem ersten Online-Besuch
funktioniert die App vollständig offline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ohne navigator.storage.persist() ist der IndexedDB-Speicher "best-effort"
und kann vom Browser bei Speicherknappheit still gelöscht werden. Der
Aufruf erfolgt beim ersten Schreiben eines Buches in den Cache — Chrome
gewährt das für installierte PWAs automatisch, Firefox kann einen Dialog
zeigen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chrome 98+ hat Probleme mit kombinierten purpose-Werten ("any maskable")
auf einem einzelnen Icon-Eintrag. Separate Einträge für "any" und
"maskable" sind zuverlässiger und entsprechen der Spec-Empfehlung.
SW-Version auf v8 erhöht, um nach den JS-Änderungen einen sauberen
Cache-Neustart zu erzwingen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Popover had transparent background because --bg-card is explicitly
transparent; now uses hardcoded colors with theme-aware overrides.
Also strips title attributes during EPUB sanitization to prevent
native browser tooltips from appearing while reading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IndexedDB v2 mit zwei neuen Stores: book_meta (Buchliste +
Lesefortschritt) und book_annotations (Lesezeichen & Highlights)
- loadBookList fällt bei Netzwerkfehler auf lokalen Meta-Cache zurück,
blendet Upload-Zone aus und zeigt Offline-Hinweis
- Leseposition (scroll_fraction + position_anchor) wird bei jedem
Auto-Save lokal gecacht, sodass offline-Öffnen am richtigen Ort
landet
- Lesezeichen und Highlights werden nach Server-Abruf lokal gecacht
und bei Netzwerkfehler von dort geladen; Änderungen offline bleiben
bis zur nächsten Synchronisierung erhalten
- Mobile Reader-Header: kompaktere Abstände, Fortschritts-Input auf
kleinen Screens ausgeblendet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hovering over a footnote reference shows the footnote content in a
floating popover; moving the mouse away dismisses it. On mobile, a
tap toggles the popover with an ✕ close button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
replace-data endpoint now optionally accepts meta_ct/meta_iv.
The client extracts title/author from the uploaded file (same as
normal upload), re-encrypts metadata with the current key, and sends
both data and meta together — so broken books are fully restored
including their displayed title and author.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a book fails to open, a '!' button appears next to it in the list.
Clicking it prompts for the original file; the file is re-encrypted with
the current key and replaces the broken ciphertext on the server while
keeping all metadata, progress, highlights and bookmarks intact.
- Add POST /books/<pk>/replace-data/ endpoint (updates data only)
- Add _evictCachedBook() to clear IndexedDB cache for a single book
- Replace complex client-side re-encryption on password change with a
simple confirm() warning that books will need to be re-uploaded
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old key comparison was wrong — the localStorage key may have been
randomly generated rather than PBKDF2-derived. Fix:
- Add /accounts/check-password/ to validate the old password server-side
before touching any keys
- Use the localStorage key directly as the old decryption key (it is
always the correct source of truth, regardless of how it was generated)
- Derive the new key from the new password via PBKDF2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The encryption key is PBKDF2-derived from the login password. Changing
the password without migrating the key would make all books undecryptable
on the next login.
- Add POST /books/<pk>/rekey/ endpoint to replace a book's ciphertexts
- Password change form is now JS-driven: before submitting to the server,
derives the old key, verifies it matches localStorage, derives the new
key, re-encrypts all books (data + meta) and their highlights/bookmarks,
updates localStorage, then submits the Django form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses Django's built-in PasswordChangeForm and update_session_auth_hash
so the session stays valid after the change. Form is hidden in a
<details> element and opens automatically on validation errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Publisher EPUBs often set font-weight via <b>/<strong> tags or inline
style attributes. A new toggle in the settings panel adds the
.reader-no-bold class which overrides all font weights to normal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add font family selector (Serif/Sans/Verdana/Mono) to reader settings panel,
saved per book in localStorage
- Strip <style>/<script> blocks before regex and DOMParser processing to avoid
working over large base64-embedded font CSS in publisher EPUBs
- Parallelize image blob URL creation with Promise.all instead of sequential await
- Inject chapter HTML progressively in batches with requestAnimationFrame yields
to keep the UI responsive when loading many-chapter books
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Current page renders almost instantly, no overlay needed.
Overlay stays for initial book open only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
renderPdf now takes an optional pivotPage. Pass 1 builds the full DOM
and sizes all canvases (instant). Then scrolls to the pivot page
immediately so the user stays in place. Pass 2 renders pixels from
pivot→end, then pivot-1→start.
reRenderPdf detects the current visible page before re-rendering and
passes it as pivot, so zoom no longer resets scroll position.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scroll position was restored before browser finished layout, so
scrollHeight was still wrong. Now waits two animation frames after
reRenderPdf completes before restoring position.
Also shows loading overlay during zoom re-renders, not just on initial load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses a 16ms interval (~60fps) instead of per-event smooth scroll,
so holding j/k scrolls fluidly. d/u/f/b remain one-shot with smooth.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moved overlay out of contentEl (which renderPdf clears immediately)
into the reader-overlay element. It now stays visible for the entire
render duration and is removed only after renderPdf resolves.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Displays a dark background with a spinning indicator in the reader
content area before renderPdf starts. renderPdf clears innerHTML
itself when it begins, so no explicit cleanup needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>