Commit graph

72 commits

Author SHA1 Message Date
dbd4fb6e3a Podcast-Artikel auf dem Gerät: podcast-article.js schreibt den Artikel mit dem eigenen Key und holt offene Wünsche ab; DioraAI.ask mit onProgress und signal
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 09:21:24 +01:00
67f983dd1c Nach dem Authelia-Login sind die Bücher gleich offen, ohne Code und Passwort (sw CACHE v162)
Some checks failed
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 11m27s
Test / e2e (push) Failing after 1m30s
Authelia gibt dem Gerät (nicht dem diora-Server) ein geheimes Attribut
heraus. Damit legt das Gerät beim Verknüpfen den Bücher-Schlüssel
verschlüsselt bei diora ab und holt ihn auf neuen Geräten nach dem
Authelia-Login unsichtbar zurück. Code und Passwort bleiben als Notweg, wenn
Authelia nichts liefert. Aktiv, sobald DIORA_OIDC_APP_CLIENT_ID gesetzt ist.

Details und Grenzen: docs/accounts.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:35:21 +01:00
077e6d4543 Anmelden mit Authelia als zweiter Weg, Bücher auf neuen Geräten per Code entsperren (sw CACHE v161)
Some checks failed
Build and push Docker image / build (push) Successful in 17s
Test / test (push) Successful in 11m22s
Test / e2e (push) Failing after 1m29s
Auf der Login-Seite gibt es „Mit Authelia anmelden“, wenn der Server dafür
eingerichtet ist. Das eigene Konto wird einmal in den Einstellungen verknüpft,
die Anmeldung mit Passwort bleibt. Fremde Authelia-Konten kommen nicht hinein,
neue Konten entstehen darüber nicht.

Fehlt einem Gerät der Bücher-Schlüssel (nach der Authelia-Anmeldung oder nach
gelöschtem Speicher), erzeugt diora keinen neuen mehr, der die Bücher
unlesbar machen würde. Stattdessen fragt es: Ein schon entsperrtes Gerät zeigt
denselben Code und gibt den Schlüssel verschlüsselt frei, oder man gibt einmal
das diora-Passwort ein. Der Server sieht den Schlüssel dabei nie.

Details: docs/accounts.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 15:01:25 +01:00
e8b525a3de Nachrichten und Blogs im Buch-Reader, Doppelklick für Vollbild, Hörbücher wie die Bücher-Bibliothek
Some checks failed
Build and push Docker image / build (push) Successful in 18s
Test / test (push) Successful in 11m2s
Test / e2e (push) Failing after 1m36s
65-article.js (DioraArticle): Artikel öffnen im selben Reader wie Bücher – Schrift, Farben,
Seiten/Doppelseite mit Fußnoten am Seitenfuß, Suche, Vorlesen, Vollbild. Lesezeichen,
Markierungen und Notizspalte bleiben Büchern vorbehalten. Merken, Projekt und Original
stehen in der Kopfzeile (Apps: im Lesemenü). feeds.js gibt sein eigenes Overlay auf;
Fortschritt wie bisher (bis zum unteren Bildschirmrand gelesen, ab 90 % gelesen).
Einstellungen der Artikel gelten für alle Artikel zusammen.

62-reader.js: Doppelklick (Maus) auf den Text schaltet Vollbild an/aus, die Leisten gehen mit.

audiobooks.js: die Hörbücher sind gebaut wie die Bücher – Kopf mit Suche, Sortierung und +,
Chips Angefangen/Ungehört/Gehört und Ordner, „Weiterhören“ wie „Weiterlesen“, Kacheln aus
denselben Bausteinen (53-library.js gibt seine Styles auch an #audiobook-shelf), Cover quadratisch.

63-pages.js: bei nur einem Kapitel zeigt die Fußzeile nur die Seiten.

sw CACHE v158

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:15:00 +01:00
6bb774adb9 Lesen in Seiten auch im Browser und auf dem Desktop, mit Fußnoten unten auf der Seite und Doppelseite
Some checks failed
Build and push Docker image / build (push) Successful in 17s
Test / test (push) Successful in 11m2s
Test / e2e (push) Failing after 1m29s
63-pages.js (DioraPages): die Seitenansicht des E-Reader-Modus der Android-Apps, jetzt für alle –
Seitenumbrüche vor angeschnittenen Zeilen, ein Kapitel auf einmal, Fußnoten am Seitenfuß wie im
gedruckten Buch, Blättern per Tippen/Klick am Rand, Wischen, Pfeil-/Bild-/Leertaste und Mausrad.
Neu: Doppelseite (zwei Seiten nebeneinander ab 900 px Breite). Einstellung „Scrollen | Seiten“
(+ „Fußnoten unten“, „Doppelseite“) im Schrift-&-Layout-Panel, je Gerät.

Fußnoten: Erkennung und Auflösung aus diora-android native-ui.js übernommen (40-epub.js,
78-footnotes.js) – die Notiz wird im richtigen Kapitel gesucht (IDs wiederholen sich je Datei),
auch das Popover zeigt jetzt genau die Notiz. Vorbereitete Bücher werden einmal neu aufbereitet.

Behoben gegenüber der App-Fassung: ein Verweis in der ersten Zeile einer Seite (hochgestellt)
zählte zur Vorseite, und ein Verweis, der samt Notiz nicht mehr passte, blieb ohne Notiz stehen –
jetzt wandert er mit ihr auf die nächste Seite. Schnelles Blättern löst „Weit gescrollt“ nicht mehr aus.

sw CACHE v157

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:41:27 +01:00
0ba16a40ff KI-Assistent (eigener Anthropic-Key, Ende-zu-Ende verschlüsselt, Aufrufe direkt vom Gerät) mit „Mit KI ordnen“ für Bücher; Bibliothek als XML exportieren; sw v148
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 10m46s
Test / e2e (push) Failing after 1m27s
- /accounts/vault/<name>/: kleine verschlüsselte Geräte-Einstellungen (UserVault)
- static/js/ai.js: DioraAI (SDK 0.133.0 gebündelt in static/vendor), Vorschläge zum Abhaken
- static/js/app/55-library-tools.js: ✦ Ordnen (Ordner, Serien, Titel/Autor, Zeitschrift) und ⇩ XML-Export
- docs/ai.md, docs/library.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:34:49 +02:00
cec07dd208 Bücher: „Eingang“ – eine Cloud-Quelle bringt ihre neuen Bücher von selbst in die Bibliothek (Gerät holt, verschlüsselt, lädt hoch; Server merkt sich, was übernommen ist); sw v147
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:12:39 +02:00
e5a170838b Projekte mit Preset Forschung: Quellen (Paper per DOI, Bücher, Folgen mit Zeitmarke, Artikel), Zitierschlüssel, BibTeX/CSL-Export, Markdown-Notizen mit [@key]; Ende-zu-Ende-verschlüsselt. Navigation: Projekte als Tab am Handy, Suche als Lupe; sw v141
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:14:17 +02:00
8df8ae2be3 Suche über ganz diora: eigener Abschnitt (Handy neben Home · Lesen · Hören, Seitenleiste unten über Abos), Bücher lokal, Rest über /search/; sw v139
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:09:07 +02:00
a399374216 YouTube-Kanäle: zufällige 404 abfangen; „Automatisch“ in der Android-App
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 9m7s
Test / e2e (push) Successful in 2m10s
- YouTube antwortet auf Kanal-Feeds derzeit bei etwa zwei von drei Abrufen
  grundlos mit 404 (Kanäle zeigten „lädt nicht: HTTP 404“): ein YouTube-Feed
  wird bis zu 12-mal im Abstand von 0,25 s gefragt. Tests dazu.
- Erscheinungsbild „Automatisch“: in der Android-App folgt die WebView nicht
  dem System, sondern dem (dunklen) App-Theme – die Seite fragt dort
  DioraNative.systemScheme() und hört __dioraSystemScheme bei einem Wechsel.
sw.js: diora-v129.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 08:16:09 +02:00
ea7d62789f Zeitschriften: das Heft und seine Artikel
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 9m1s
Test / e2e (push) Successful in 2m1s
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
  Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
  Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
  Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
  abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
  (gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
  beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
6d7fb4a849 Neue Navigations-Icons (Stil B), mehrere Bücher auf einmal, generiertes Cover pro Buch wählbar
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 9m6s
Test / e2e (push) Successful in 2m45s
- Icons: eigene SVGs, kräftig und geometrisch (2,4 px, eckige Enden), in
  nav.js für alle Leisten (DioraNav.icon, sections()/groups() .svg); der
  aktive Bereich gefüllt.
- Hochladen: mehrere Dateien auf einmal (Auswahl oder auf die Bibliothek
  ziehen) und aus der Cloud per Häkchen („Alle auswählen“, „n importieren“);
  nacheinander mit Fortschritt, ein schon vorhandenes Buch (Titel und Autor)
  wird übersprungen.
- Pro Buch: „Generiertes Cover verwenden“ statt des eigenen (im verschlüsselten
  Meta, cover: 'generated').

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 21:21:10 +02:00
8f5be379fe Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
  Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
  (umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
  verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
  (/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
  übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
  (/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
  Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
  Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
  E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
  verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
  auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
  Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
  Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
  Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
  und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
143950df94 Abos & Quellen: alle Abos an einem Ort (Navigation Phase 2)
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m58s
Test / e2e (push) Successful in 1m58s
Ein neuer Abschnitt (docs/sources.md, static/js/sources.js) für Podcasts,
YouTube-Kanäle, Hörbücher, Nachrichten und Blogs:
- ein Feld zum Hinzufügen: Adresse (Feed, Seite, YouTube) oder Name eines
  Podcasts; die Art ist wählbar (Podcast, Hörbuch, Nachrichten, Blog);
  /feeds/discover/ erkennt dafür Podcasts (Folgen mit Ton oder Video);
- eine Liste aller Abos mit Filtern (auch „Probleme“) und Suche;
- je Abo: Inhalt öffnen, Einstellungen (die vorhandenen Editoren), Art ändern,
  abbestellen; OPML-Import und -Export.
In der Seitenleiste unten, auf dem Telefon im App-Menü; ⚙ in den Feeds führt
hierher. Abschnitte, die beim Laden schon gezeigt werden, füllen sich jetzt
auch (nav.js zeigt sie nach dem Laden aller Skripte noch einmal).
Podcast-UI aus diora-android 50c6f9c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:12 +02:00
838457f6f9 Navigation: Lesen und Hören (Phase 1, noch nicht veröffentlicht)
Die Bereiche nach dem, was man tut (docs/navigation.md): Home; Lesen mit
Bücher, Zeitschriften, Nachrichten, Blogs; Hören mit Hörbücher, Podcasts,
Radio, Ambient. static/js/nav.js ist die eine Definition für alle Plattformen
(DioraNav: Abschnitte, Gruppen, go, Hash, zuletzt gewählter Abschnitt je
Gruppe). Die Webseite zeichnet ihre Leiste daraus: breit alle Abschnitte unter
ihrer Gruppe, schmal Home · Lesen · Hören mit einer Abschnittszeile oben.

- Hörbücher haben ein eigenes Panel (#tab-audiobooks), nicht mehr über den E-Books.
- Zeitschriften: die Bücher im Ordner „Zeitschriften“ (oder „Magazine“).
- Nachrichten/Blogs wählt die Navigation; der eigene Umschalter verschwindet dann.
- Ein Link auf einen Abschnitt (#news …) schlägt die Startseite.

Die Apps zeichnen ihre Leiste (native-radio.js, diora-android) bald ebenfalls
daraus; bis dahin bleibt das hier unveröffentlicht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:31:58 +02:00
5be1b3fbaf Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m52s
Test / e2e (push) Successful in 1m59s
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.

Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
656520fe8e Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
Some checks failed
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 8m23s
Test / e2e (push) Failing after 1m54s
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.

Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
884a18e8ef Hell und Dunkel (docs/theme.md), Vorlese-Schnittstelle für die Playerleiste; sw v102
Some checks failed
Build and push Docker image / build (push) Successful in 19s
Test / test (push) Has been cancelled
- Farbvariablen für beide Schemata in app.css (--bg --fg --muted --card --panel
  --hover --raised --border --border-strong --accent --on-accent --scrim
  --shadow), Hell als html[data-theme=light]. Umschalter im Kopf jeder Seite
  (base.html, vor dem ersten Zeichnen): Dunkel / Hell / Automatisch, ☾☀◐ in der
  Navigation; __dioraTheme / __dioraSetTheme / Ereignis diora-theme.
- Reader: neues Standard-Theme "Wie die App" (Dunkel/Hell folgt der App).
- ◑ (Kontrast zum Hintergrundbild) nur noch mit Hintergrundbild.
- home.js auf die Variablen.
- window.__dioraTts (state/toggle/stop/next/prev) und Ereignis diora-tts für
  die Playerleiste; mit __dioraTtsDock keine eigene Vorlese-Leiste mehr.
- Reader-Hinweise über der Playerleiste, wenn sie im Reader steht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:02:33 +02:00
fda6c01943 Einstellungen: Cloud-Verbindung bearbeiten (Name, URL, Benutzer, Unterordner, Passwort); sw v99
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 7m52s
Bisher ließ sich eine Verbindung nur anlegen und löschen, der Startordner
also nicht nachträglich setzen. Das Passwort wird nicht angezeigt; leer
lassen behält das gespeicherte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:22:04 +02:00
35701260e0 Feed-Reader: Tab „Feeds“ mit Nachrichten (Briefing, Stories) und Blogs
Some checks failed
Build and push Docker image / build (push) Successful in 1m2s
Test / test (push) Has been cancelled
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>
2026-10-04 20:16:24 +02:00
8b9c28cda1 Home: neue Startseite mit Weiterlesen, Weiterhören, Radio-Favoriten und Ambient; sw v87
Some checks failed
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Has been cancelled
- 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>
2026-10-04 15:34:02 +02:00
b7a81f3e26 Neuer Tab „Ambient“: Drift, ein generativer Ambient-Synthesizer; sw v73
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m55s
- 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>
2026-10-03 23:08:57 +02:00
c522b22cd9 Oberfläche eingedeutscht: Radio, Bücher/Reader, Fokus, Konto, Anmeldung; sw v71
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m56s
- 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>
2026-10-03 13:53:01 +02:00
76a164709d Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.

static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
  Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
  Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
  wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
  alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
  automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
  Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
  Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
  legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
  Bildschirmen eine mittige Spalte (760 px)

In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.

Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
3717640a4e Radio: Stream-Adresse ins Suchfeld einfügen; Favoriten-Stern reparieren (SW v50)
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 1m35s
- Eine http(s)-Adresse im Suchfeld wird nicht gesucht, sondern als Treffer
  mit Play und Save (Name wählbar) angeboten. Der Query-String (oft ein
  Zugangstoken) wird nicht angezeigt, aber mitgespeichert.
- toggleFav gab es zweimal; die zweite Fassung verdeckte die erste und
  brauchte den Button als Argument, den die serverseitig gerenderten Zeilen
  nicht übergeben. Folge: Favorit auf dem Server gesetzt, Stern blieb leer,
  der nächste Klick nahm ihn wieder weg. Jetzt eine Fassung für beide Fälle,
  inklusive Umsortieren (Favoriten zuerst, wie der Server).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:33:34 +02:00
marwin
26f983bbaa Default-Lautstärke auf 40% senken
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 1m31s
Betrifft alle Sender, insbesondere creamfresh radio.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Drq8o4HfFmwkXz2AgvaQ4p
2026-09-04 12:30:58 +02:00
marwin
171560c71e Add thumbs up/down for creamfresh radio, hardcoded to that station
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 1m35s
Relays to the DJ's own /dj/feedback via the same server-side-auth
proxy pattern as the stream itself, so the browser never needs the
station's credentials. Buttons only show when the currently playing
station is creamfresh radio (matched by URL against a hardcoded
constant) -- no other station's backend does anything with a vote,
so no other station gets the buttons yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Drq8o4HfFmwkXz2AgvaQ4p
2026-09-04 11:38:34 +02:00
marwin
0fd5461daa Vorlesen: Sprachauswahl DE/EN mit englischer Piper-Stimme (SW v43)
All checks were successful
Build and push Docker image / build (push) Successful in 1m7s
Test / test (push) Successful in 1m32s
Reader-Header bekommt ein #reader-tts-lang-Dropdown (Auswahl, keine
Sprach-Erkennung — persistiert in localStorage); /tts/synthesize/ nimmt jetzt
{"lang": "de"|"en"} entgegen. tts/piper_engine.py lädt pro Sprache eine eigene
PiperVoice lazy (settings.TTS_VOICES), Docker-Image lädt zusätzlich
en_US-lessac-medium beim Build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb6yX2S9aTGepYA9JFba9x
2026-09-01 07:04:27 +02:00
marwin
05216a8613 Vorlesen-Funktion im Reader: Piper/Thorsten-TTS (SW v42)
All checks were successful
Build and push Docker image / build (push) Successful in 1m3s
Test / test (push) Successful in 1m32s
Serverseitige Sprachsynthese (deutsche Thorsten-Stimme, Piper) für den
EPUB-Reader: /tts/synthesize/ nimmt einen Satz (max. 500 Zeichen) entgegen,
synthetisiert und streamt WAV zurück, ohne zu persistieren, zu loggen oder zu
cachen — eine bewusste, eng begrenzte Ausnahme vom "Server sieht nie
Klartext"-Prinzip (siehe CLAUDE.md). Der neue ▶-Button im Reader-Header liest
ab der aktuellen Position satzweise vor, hebt den gerade gesprochenen Satz
hervor und scrollt mit; eine kleine Leiste bietet Pause/Stop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xb6yX2S9aTGepYA9JFba9x
2026-08-31 22:29:13 +02:00
marwin
b205625e21 Bücher: Cloud-Import aus WebDAV/Nextcloud (SW v41)
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 52s
Verbindungen werden pro Nutzer in den Einstellungen angelegt (Nextcloud,
ownCloud, Synology oder generisches WebDAV) — der Server ist bewusst nicht
auf eine feste Instanz verdrahtet. Im Bücher-Tab lässt sich der entfernte
Ordner durchblättern und eine .epub/.pdf direkt in die Bibliothek ziehen.

Der Download läuft über den Server, weil der WebDAV-Host cross-origin ist
und keine CORS-Header schickt. Verschlüsselt wird trotzdem erst im Browser:
uploadEbook ist in _importEbookBuffer aufgeteilt, das sich lokaler Upload
und Cloud-Import teilen. Gespeichert wird wie bisher nur Geheimtext.

Weil jeder registrierte Nutzer die Ziel-URL bestimmt und diora im Docker-Netz
neben anderen Diensten läuft, ist der Import eine SSRF-Fläche. Dagegen:

  - assert_safe_url weist Hosts ab, die auf nicht-öffentliche Adressen
    auflösen (inkl. NAT64 und IPv4-kompatibler v6-Adressen, die is_global
    durchlässt)
  - _assert_peer_is_safe prüft die tatsächliche Peer-Adresse nach dem
    Verbinden — requests löst den Namen ein zweites Mal auf, sonst wäre der
    Guard per DNS-Rebinding umgehbar
  - Redirects werden abgelehnt statt verfolgt
  - identische Fehlermeldung für "nicht auflösbar" und "privat", ohne die
    IP zu nennen, damit der Endpunkt kein Scanner für interne Dienste wird

Antwort-Bodies laufen durch _read_capped, und DTDs werden vor dem Parsen
abgewiesen: ElementTree expandiert interne Entities, und seit Python 3.12
gibt es XMLParser.parser nicht mehr, um einen Handler zu setzen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 08:26:22 +00:00
marwin
30a6d29ca8 Sync-API für lokale Clients: Personal-Access-Token + GET /api/sync/
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 23s
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>
2026-08-15 16:20:54 +02:00
marwin
d3c435dd59 Reader: Notizen auf Mobile schnell setzbar statt über Randspalte (SW v32)
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 15s
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>
2026-08-04 20:13:55 +02:00
marwin
b805d62b11 Books: Notizen-Sidebar auto-öffnen, Notizen-Download + Gelesen-Status in Übersicht (SW v29)
All checks were successful
Build and push Docker image / build (push) Successful in 17s
Test / test (push) Successful in 16s
- 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>
2026-08-04 18:10:33 +02:00
marwin
9648730be6 Reader: Prozentanzeige mit Nachkommastelle bei langen Büchern (SW v27)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 15s
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>
2026-08-04 13:51:05 +02:00
marwin
3f40a4078a Reader: Notiz-Eingabe direkt im Rand statt Sprung zur Sidebar (SW v25)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 14s
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>
2026-08-04 11:23:16 +02:00
marwin
a2f4be8e1e Reader: Positions-Koordinate im Export + Verzeichnis für Markierungen/Notizen (SW v24)
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>
2026-08-04 11:10:56 +02:00
marwin
bc180daab0 Reader: Marker-Modus statt impliziter Auswahl, Notiz-Anker rein positional (SW v23)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 14s
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>
2026-08-04 10:49:30 +02:00
marwin
090aec9c60 Reader: Annotationen — Marker, freie Notizen, Margin-Sidebar, Export (SW v22)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 15s
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>
2026-08-04 10:17:01 +02:00
marwin
17e205b6f0 Reader/UI: mehrere Fixes + Playwright-Setup (SW v21)
All checks were successful
Build and push Docker image / build (push) Successful in 55s
Test / test (push) Successful in 15s
- 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>
2026-08-04 09:01:52 +02:00
marwin
29f6cc4413 Books: Fortschritt-Konflikt via max() lösen — weiter = gewinnt (SW v14)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 15s
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>
2026-05-31 15:41:35 +02:00
marwin
4b47f6e67a Books: broken-book repair flow + simplify password change
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 16s
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>
2026-05-25 17:03:59 +02:00
marwin
817323ad19 Accounts: fix password change key migration
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 15s
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>
2026-05-25 16:46:03 +02:00
marwin
1426c69a73 Accounts: re-encrypt all books on password change
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 15s
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>
2026-05-25 16:37:41 +02:00
marwin
e3a9c61c05 Accounts: add password change form to settings
All checks were successful
Build and push Docker image / build (push) Successful in 57s
Test / test (push) Successful in 15s
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>
2026-05-25 16:26:07 +02:00
marwin
e5dc58d84f Replace focus station sidebar with compact radio player sidebar
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 15s
The 📻 button now opens a sidebar with the currently playing station/track,
play/stop + volume control, and the saved stations list. The old focus
station configuration UI (presets, custom URL input, save-to-server) and
the auto-play-on-book-open behavior are removed.

Closes #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 14:22:07 +02:00
marwin
e9c5b8058b Centralize remaining magic numbers in settings.py
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 15s
- HIGHLIGHTS_MAX_BYTES, BOOKMARKS_MAX_BYTES: books size limits now in settings
- PODCAST_INBOX_PAGE_SIZE: shared between podcasts/views.py and app.js via DIORA_CONFIG
- VOLUME_DEFAULT: radio stream player default volume
- ITUNES_TIMEOUT: unified iTunes API timeout (was 5s vs 6s inconsistency)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:10:14 +02:00
marwin
0a6ba6feac Inject upload size limits from settings into frontend via DIORA_CONFIG
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 15s
Backend settings are now the single source of truth for EBOOK_MAX_BYTES
and BG_MAX_BYTES. A new context processor exposes them to all templates,
and base.html injects them as window.DIORA_CONFIG so app.js reads from
there instead of hardcoded values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:05:51 +02:00
marwin
f040a45325 Open HTTP streams in raw new tab instead of custom HTTPS player
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 14s
The custom /radio/stream-player/ page is served over HTTPS, so the browser
still applies mixed-content restrictions and upgrades http:// audio to https://,
which fails for streams without TLS support.

Fix: window.open(url, '_blank') navigates the tab directly to the HTTP URL.
The tab itself is then HTTP, bypassing mixed-content restrictions entirely.
Browser plays the stream natively with its built-in audio player.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 10:48:28 +01:00
marwin
9e08079dec Fix stream player autoplay: handle blocked play() promise
All checks were successful
Build and push Docker image / build (push) Successful in 11s
Test / test (push) Successful in 17s
window.open() doesn't transfer the user gesture to the new tab, so autoplay
is blocked. Previously play().catch(()=>{}) swallowed the error silently while
setting playing=true, leaving the UI in a broken state.

Now: if play() rejects, reset state and show "Click Play to start". The user's
click on the Play button IS a user gesture, so it succeeds on the second attempt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 10:39:43 +01:00
marwin
85776390f6 Open HTTP streams in minimal standalone player tab
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 16s
Instead of trying a HTTPS upgrade (which fails for IP-based streams):
- playStation() detects http:// URL on https:// page, opens /radio/stream-player/
  with url, name, vol as query params, then returns — main stream is already
  stopped by the stopPlayback(false) call at the top of playStation()
- New view stream_player renders a standalone minimal player page
- Template: auto-plays on load, correct volume from URL param, volume changes
  synced back to localStorage so main window picks it up next time,
  live track metadata via SSE, tab title updates on track change,
  close-tab button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:50:12 +01:00