getPositionAnchor maß jeden Block vom Buchanfang bis zur Leseposition und suchte
die Blöcke bei jedem Aufruf neu – im E-Reader-Modus bei jedem Umblättern (die
Prozentanzeige), beim Speichern des Fortschritts sowieso. In Das Kapital ab
Kapitel 15: 0,8 s bei 6-fach gedrosselter CPU; auf dem tolino filtert compat61
das :has() dazu in JavaScript.
- Blockliste samt Kapitel je Block einmal je geöffnetem Buch (verworfen bei
jeder Änderung am Inhalt, MutationObserver)
- Binärsuche nur über die angezeigten Kapitel; gleiche Regel wie vorher
(letzter Block mit Höhe, der an/über der Oberkante beginnt, bei gleicher Höhe
der erste; sonst der erste darunter)
- Geprüft an 480 Stellen (Kapital, Hyperpolitik; E-Reader und Scrollen): gleiche
Anker wie vorher; 53/281 ms → 4 ms; Umblättern 21–48 ms statt bis 0,9 s
- sw v98
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- <a id="x"/> (XHTML) blieb beim Parsen als HTML offen und umschloss den
folgenden Text, in jedem weiteren Absatz neu (Suhrkamp/transpect): die
inline-Elemente werden vorher geschlossen.
- Seitenzahlen der Druckausgabe (epub:type="pagebreak") als kleine graue
Marke statt am Text klebend („3Anton Jäger“).
- Fußnoten-Popover: Absatz der Notiz auch bei id auf einem inline-Element,
ohne eigene Nummer und Seitenzahl, mehrabsätzige Noten (Gedichte).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gemessen mit Das Kapital (148 Kapitel, 26 000 Absätze) im diora Reader, CPU
6-fach gedrosselt wie auf einem E-Reader: Öffnen 2,4 s → 1,2 s.
- parseEpub entpackt alle Kapitel auf einmal statt eines nach dem anderen
(jedes wartete auf JSZips eigene Ticks) und pausiert nach ~50 ms Arbeit statt
alle 8 Kapitel einen Frame; ebenso das Einfügen der Kapitel
- Vorbereitete Bücher (entpackt und bereinigt) liegen verschlüsselt mit dem
Buchschlüssel in IndexedDB („parsed“): gültig für eine Fassung der Buchdatei
(IV) und des Vorbereitungs-Codes (Hash seines Quelltexts – eine Änderung am
Sanitizer bereitet jedes Buch neu vor); Bilder bleiben in der Buchdatei, die
Kapitel tragen „diora-img:<Pfad>“ statt Blob-URLs. Nach 4 Wochen ohne Öffnen weg.
Geprüft: Text, Absätze, Bilder, Fußnotenlinks, Inhaltsverzeichnis identisch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PDFs liefern die echte Seitenzahl, bei EPUBs wird sie aus der Zeichenzahl
geschätzt (1800 Zeichen je Seite). Gespeichert in den verschlüsselten Metadaten
beim Öffnen des Buchs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Die Buch-Synchronisation der Apps braucht vom Schnappschuss nur Bücher,
Fortschritt, Markierungen und Lesezeichen; der Podcast-Teil (jede je gespielte
Folge) ist für ein Konto aus AntennaPod 11 MB groß und wurde bei jedem Abgleich
mitgeladen – über mobile Daten Gigabytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Source.folders (Etiketten, Groß/Klein egal, max. 40 Zeichen); die bisherige
Kategorie eines Blogs wird sein erster Ordner (Migration)
- /feeds/folders/rename/ und /dissolve/; in den Blog-Einstellungen Häkchen je
Ordner plus „Neuer Ordner“; langer Druck/Rechtsklick auf einen Ordner: öffnen,
umbenennen, auflösen; Ordnerliste in „Feeds verwalten“
- Lesefortschritt je Post (Item.progress, geräteübergreifend): die Leseansicht
springt zurück an die Stelle, ab 90 % gilt ein Blogpost als gelesen (Nachrichten
weiter beim Öffnen); Fortschrittsbalken und „Weiterlesen“ oben in den Blogs
- sw v95
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Source.notify; ein neuer Post wird beim Eintreffen markiert (Item.notify),
nie beim ersten Abruf eines Blogs, nie älter als 2 Tage
- GET /feeds/notifications/?after=<id>: ungelesene markierte Posts von Blogs mit
Glocke nach der zuletzt gesehenen id; ohne after nur der Startpunkt
- Glocken-Schalter in der Blog-Leiste und in den Einstellungen des Blogs;
window.__dioraFeedsOpenItem öffnet einen Post (Antippen der Benachrichtigung)
- sw v94
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Abschnitte Heute/Gestern/Diese Woche/Diesen Monat/Älter; je Abschnitt bis zu
5 Posts je Favoriten-Blog zuerst, dann „Seltene zuerst“ (mittlerer Abstand der
letzten 10 Posts, ein Post = 30 Tage) oder chronologisch
- Blog „im Hauptfeed ausblenden“: nicht unter Alle und nicht gezählt, im Ordner da
- NEU (jünger als 24 h, ungelesen) am Post, als Zahl am Tab, Punkt am Ordner
- Chips Ungelesen/Alle, ★ Favoriten, Seltene zuerst; Schalter je Blog
- Lesezeit (Wörter/230) an jedem Post; words wird beim Speichern gezählt
- sw v93
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- In den Einstellungen einer Quelle: Zugangsdaten der Seite; der Server prüft
die Anmeldung sofort, speichert das Passwort verschlüsselt (Fernet, Schlüssel
aus SECRET_KEY) und gibt es nie wieder heraus
- Artikel werden mit der angemeldeten Sitzung geholt; hinter der Bezahlschranke
bleibt der freie Teil (paywalled) und wird nach der Anmeldung neu geholt
- Adapter je Seite (feeds/logins.py), zuerst jungewelt.de
- sw v92
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Die Kategorie eines Blogs ist sein Ordner: Ordner-Chips mit Ungelesen-Zahl in
der Blog-Ansicht, /feeds/blogs/?folder=, Ordner im OPML-Export und -Import
- Neue Feeds ohne (brauchbare) Daten: die Reihenfolge des Feeds gilt als Zeit,
nur die ersten 3 ungelesen; ein neuer Blog bringt 7 statt 14 Tage ungelesen
- sw v91
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static/js/feeds.js für Web, Desktop und beide Android-Apps:
- Nachrichten: „Seit letztem Mal“ (Top-Themen, dann nach Ressort, „Alles
gelesen“), Stories nach Tag, Ressort-Filter
- Blogs: Ungelesen-Liste, je Blog filterbar, „Alle gelesen“
- Artikel im eigenen Lesemodus (Volltext, Schriftgröße, merken, alle Berichte
einer Story), Browser nur auf Wunsch
- Feeds verwalten: Seite oder Feed suchen, als Nachrichten/Blog abonnieren,
Ressort, Volltext-Regel, OPML-Import pro Ordner, Export, Aufbewahrung
- diora Reader (ohne Tab-Leiste) bekommt eine kleine Leiste Bücher/Feeds
Server: Feed-Namen ohne Slogan, Feeds mit einem Zeitstempel für alle Einträge
(Deutschlandfunk) zählen ab dem ersten Sehen; sw v89
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Neue App feeds: Quellen sind entweder Nachrichten oder Blog.
- Abruf wie bei Podcasts (ETag, Body-Hash, Backoff), refresh_newsfeeds für den Cron
- Anrisstexte: der Artikel wird von der Seite geholt (trafilatura), HTML bereinigt,
relative Links absolut
- Nachrichten: gleiche Meldung aus mehreren Quellen wird eine Story (tf-idf, je
Artikel verglichen), Briefing „Seit letztem Mal“ (Top-Themen, dann nach Ressort),
verfallen nach news_keep_days (Standard 2)
- Blogs: Ungelesen-Liste je Blog, bleiben bis gelesen
- API unter /feeds/: Quellen suchen/abonnieren/OPML, News, Stories, Blogs,
Artikel, gelesen, merken
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- podcasts/silence.py: ffmpeg (imageio-ffmpeg) findet die Pausen einer Folge einmal
(silencedetect -35 dB, ab 0,4 s), im Hintergrund, höchstens 2 zugleich; gespeichert in
PodcastEpisode.silences (Migration 0018); GET episodes/<id>/silences/
- podcasts-web.js: skip_silence als Einstellung (Gerät, pro Podcast vom Server), springt
beim Abspielen über die Pausen (0,15 s bleiben)
- Drift: Ringpuffer für die Körner als AudioWorklet statt ScriptProcessor im Hauptthread
(gehäufte Aussetzer am Desktop, wenn die Seite kurz stockte)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auf dem S21 lagen App- und Renderer-Prozess bei je ~105 % CPU. Auf Telefonen jetzt:
höchstens 10 Stimmen zugleich, eine Säge und ein Wavetable-Zyklus pro Stimme, FM nur
deutlich, keine offline gerenderten Rückwärts-Swells (Rechenspitzen), Bild in halber
Auflösung mit ~20 Bildern/s und einfachen Partikeln.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der stündliche Abruf der 502 Feeds dauerte 114 s, fast nur Parsen: viele Server
ignorieren ETag/Last-Modified und schicken jedes Mal den ganzen Feed (Lage der Nation
8,4 MB = 5,9 s). ⟳ in der App lief damit in ihr 60-s-Zeitlimit und schlug fehl.
- Fingerabdruck des Feed-Inhalts (ohne lastBuildDate/Kommentare, http_body_hash,
Migration 0017): derselbe Inhalt zählt als unverändert und wird nicht geparst
- _refresh_feeds: ein Pool für alle Feeds (16 parallel), jeder Feed wird geschrieben,
sobald er da ist, optionales Zeitlimit
- refresh-all wartet höchstens 40 s (done/left in der Antwort); der Browser-Host
holt den Stand dann eine Minute später noch einmal, und alle 15 Minuten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zwei Achsen kurz nacheinander (3,1 m), 33 m Schiene bis zum nächsten Paar, dumpferer
Anschlag, Karosserie klingt mit zwei Moden nach. Dioras eigene Drift-Änderungen bleiben.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- app.js: __dioraAmbientInfo (spielt, zuletzt gestartet, Klangwelt), meldet Moduswechsel
an die Leiste; Radio/Folge übernimmt sie wieder
- Drift (Telefone): Musik-Bett, Delay und Hall werden vom Ausgang getrennt, wenn eine
Klangwelt ohne sie läuft (Braunes Rauschen …); Zugfahrt und Herzschlag planen 2,5 s voraus
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auf dem S21 lag die App mit Drift bei ~185 % CPU, der Ton stockte. Auf Telefonen jetzt:
Bild in einfacher Auflösung und ~30 Bilder/s, 3,5 s statt 7 s Hall, kein Oversampling,
kein ScriptProcessor (Körner) im Hauptthread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drift: AudioContext mit latencyHint 'playback' – der Niedriglatenz-Weg (AAudio/MMAP)
blieb auf dem Samsung des Nutzers stumm, obwohl Drift lief
- Drift: cmd 'volume' (0..1) von der Seite ist die Lautstärke; eingebettet ist der eigene
Regler ausgeblendet
- app.js: setVolume() und das Laden des Frames schicken die Lautstärke an Drift (nicht in
der Android-App: dort gelten die Lautstärketasten)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aus der Session "Ambient-Synthesizer-Webapp": Ambient-Musik ist jetzt ein Modus neben
weißem/pinkem/braunem/blauem/violettem/grauem Rauschen, Regen, Gewitter, Meer, Wind, Bach,
Lagerfeuer, Sommernacht, Ventilator, Flugzeugkabine, Zugfahrt, binauralen Beats,
Klangschalen und Herzschlag. Alles live synthetisiert, offline; Schnittstelle zu diora
unverändert (neu: cmd 'mode').
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- app.js: __dioraAmbientCmd, meldet Start/Stopp an die App (DioraNative.ambientState,
sie hält Drift bei ausgeschaltetem Bildschirm am Laufen) und an den Browser-Host
- podcasts-web.js: der Sleep-Timer zählt auch, solange Ambient spielt, stoppt es beim
Ablauf; Nachtautomatik auch beim Ambient-Start
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- static/ambient/index.html: Drift (aus der Session "Ambient-Synthesizer-Webapp",
eine Datei, Web Audio, offline), eingebettet per iframe (?embed=1&bg=#000)
- app.js: Tab 'ambient', iframe lädt beim ersten Öffnen und füllt den Platz bis
zur Leiste; Radio/Podcast und Ambient wechseln sich ab (postMessage
{type:'ambient', cmd|state})
- sw.js: Ambient im Offline-Cache (ignoreSearch für die Shell-Dateien)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- app.js, Templates, Stream-Fenster, Manifest: alle sichtbaren Texte deutsch
(Eingabefenster "Abbrechen", Markierungs-Export, Lesezeichen, Reader-Einstellungen …)
- LANGUAGE_CODE 'de': Formular-, Passwort- und Validierungsmeldungen von Django deutsch
- Empfehlungen: "Was du unter der Woche abends oft hörst:"
- Fehlercodes der API bleiben englisch (Vertrag für Clients und Tests); was die
Seite davon zeigt, übersetzt _germanError()
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Setzt eine Seite window.DIORA_PODCAST_HOST, arbeitet podcasts-web.js trotz
vorhandenem DioraNative als ihr Podcast-Host:
- dock: 'existing' nimmt native-radio.js' #nui-dock statt einer Leiste in der
Web-Radioleiste; die Podcast-Seite lädt erst nach DOMContentLoaded
- styles: 'ui-only' nur podcast-ui.css, ohne das Browser-Layout (html.dpw)
- radio: false die Radio-Hooks (__dioraRadioNow …) kommen von native-radio.js;
der Sender legt die Folge weiterhin beiseite, Radio-Stopp meldet der Sync weiter
- deviceName/deviceKind wie der gemeinsame Player das Gerät führt
Ohne die Variable ändert sich im Browser nichts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ab 900 px Fensterbreite starten Bücher mit 20 px, Zeilenhöhe 1.6, 90 Zeichen
Breite und Gewicht 375; Handys behalten 16 / 1.8 / 65 / normal.
- Gespeicherte Einstellungen tragen jetzt v: 2. In älteren gelten Werte, die
genau dem alten Standard entsprechen, als nie gewählt und weichen dem
heutigen Standard; bewusst geänderte Werte bleiben.
- Der Breiten-Regler geht bis 140 statt 90 Zeichen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- state/ liefert inbox_total (alle neuen Folgen) und inbox_limit neben der
gekürzten Liste; Test dazu.
- Browser: Startseite (nui_start_page) beim Öffnen, „Rückgängig“-Leiste über
der Radioleiste, helle Ansicht für die neuen Teile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Der Chip "Benachrichtigen" auf der Podcast-Seite setzt notify auch im
Browser (die Mitteilungen kommen von den Telefonen). Die Einstellung
"Im Hintergrund abgleichen" gibt es nur in der App.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PodcastFeed.notify (Standard aus) wie auto_download: in feeds/ und
state/, änderbar über feeds/<id>/settings/ (nur true/false). Die Geräte
melden damit neue Folgen dieses Podcasts. Der AntennaPod-Import übernimmt
Feeds.episode_notification (an -> notify an; aus, AntennaPods Standard,
lässt dioras Einstellung stehen; ältere Backups ohne die Spalte: aus).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bisher lud die Seite bei einer neuen Version sofort neu – auch mitten im
Radio oder Podcast. Jetzt erst, wenn nichts spielt, kein Buch offen ist
und kein Textfeld den Fokus hat. Der Browser heißt im Player-Sync kurz
„Chrome · Windows“.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>