Commit graph

209 commits

Author SHA1 Message Date
c8e03f343e Feeds: Ordner für Blogs, Blogs ohne Datum in Feed-Reihenfolge
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 7m30s
- 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>
2026-10-04 20:54:29 +02:00
1a17c75c1b Home: Karte „Nachrichten“ mit den Top-Themen seit dem letzten Mal, neue Meldungen und Blogartikel; sw v90
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 7m26s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 20:22:56 +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
4256bf6b29 Home: oben der rote diora-Schriftzug statt der Begrüßung; sw v88
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 7m2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:45:15 +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
c1bbaccccf Stille überspringen im Browser/Desktop (Server findet die Pausen); Ambient-Rekorder im Audio-Thread; sw v86
Some checks failed
Build and push Docker image / build (push) Successful in 52s
Test / test (push) Has been cancelled
- 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>
2026-10-04 12:38:06 +02:00
6cc62fd4d1 Podcasts: Stille-Schalter im Tempo-Menü, Leiste folgt dem, was spielt, podcastPause; sw v85
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 6m58s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:25:27 +02:00
45e498eb87 Ambient-Musik auf Telefonen leichter gegen Aussetzer; sw v84
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 6m57s
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>
2026-10-04 10:42:45 +02:00
14720b3669 Feeds abrufen: unveränderte Feeds nicht mehr parsen, ⟳ antwortet nach höchstens 40 s; sw v83
All checks were successful
Build and push Docker image / build (push) Successful in 17s
Test / test (push) Successful in 6m59s
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>
2026-10-04 10:22:01 +02:00
986ccad5c9 Ambient: Zugfahrt – zweiter Klack jedes Paares 2 Halbtöne höher; sw v82
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 6m50s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:28:44 +02:00
356a5299da Ambient: Zugfahrt „klack-klack … klack-klack“ (Drift v8, Diff eingespielt); sw v81
Some checks failed
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Has been cancelled
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>
2026-10-04 00:24:43 +02:00
6e28312947 Ambient in der Player-Leiste; weniger Aussetzer bei den Klangwelten auf Telefonen; sw v80
Some checks failed
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Has been cancelled
- 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>
2026-10-04 00:18:06 +02:00
2c64342c53 Ambient: leichter Modus für Telefone gegen Ruckeln; sw v79
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 6m54s
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>
2026-10-04 00:09:28 +02:00
bd9193c869 Ambient: Frame misst sich neu, wenn die Leiste unten wächst oder schrumpft; Bedienfeld 960 px; sw v78
Some checks failed
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-04 00:07:07 +02:00
16a1e23fcc Ambient: Ton auf Android über den normalen Medienweg; dioras Lautstärke gilt für Drift; sw v77
Some checks failed
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Has been cancelled
- 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>
2026-10-04 00:01:03 +02:00
3ff998f9cf Ambient: Drift v4 – Bach klingt nach Bach (glucksende Bläschengruppen), Bedienfeld auf breiten Fenstern zentriert; sw v76
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m52s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:41:48 +02:00
0fbb948c1a Ambient: Drift v3 – 23 Klangwelten (Rauschfarben, Natur, Technik, Fokus & Schlaf); sw v75
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
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>
2026-10-03 23:37:09 +02:00
794dbadbf5 Ambient: Sleep-Timer zählt und stoppt Drift (Browser und App), ☾ im Ambient-Tab; sw v74
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m55s
- 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>
2026-10-03 23:22:00 +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
64e9d70647 Podcasts: mehrere Folgen nacheinander wegwischen, während „Rückgängig“ steht; sw v72
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m51s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:40:39 +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
f12451f30e Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m54s
- 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>
2026-10-03 12:59:16 +02:00
985e635f9a Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 6m52s
- 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>
2026-10-02 20:27:24 +02:00
461b42a5bf Podcasts: diora desktop kann den Browser-Host mitbenutzen (DIORA_PODCAST_HOST), sw v68
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m39s
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>
2026-10-02 19:39:54 +02:00
3e67d324c7 sw v67: Podcast-Oberfläche und Reader-Standards kamen beide als v66
Some checks failed
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-02 19:35:31 +02:00
4053334f7f Reader: größere Standards auf breiten Bildschirmen, Breite bis 140 Zeichen (SW v66)
Some checks failed
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Has been cancelled
- 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>
2026-10-02 19:32:32 +02:00
e097e13a11 Podcast-Oberfläche aus diora-android 339de8f (Podcast-Seite mit ⚙, Wischgesten, Schrift, Farben, Startseite); „Neu“ zählt alle neuen Folgen, sw v66
- 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>
2026-10-02 19:30:03 +02:00
54fbd58293 Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m39s
- 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>
2026-10-02 18:46:55 +02:00
3577fca9ba Merge branch 'wt-notify'
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m28s
# Conflicts:
#	static/js/sw.js
#	static/podcasts/native-podcast.js
2026-10-02 18:03:07 +02:00
da60198bbb Podcast-Oberfläche aus diora-android (Benachrichtigen), sw v61
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>
2026-10-02 16:36:00 +02:00
d3e432e165 Podcast-Oberfläche aus diora-android 3d1281c (Listen + Abonnieren-Vorschau), sw v63
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m21s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:29:15 +02:00
69b22cf2e3 Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- 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>
2026-10-02 16:23:00 +02:00
779b54623e Web: neue Version lädt erst, wenn nichts spielt; kurze Gerätenamen, sw v61
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 5m57s
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>
2026-10-02 15:37:38 +02:00
262dd0d207 Player-Sync im Web: nur angenommene eigene Berichte zählen als letzter Stand, sw v60
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 5m53s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:30:06 +02:00
21c314dbd7 Podcast-Oberfläche aus diora-android (Player-Sync + Abstände), sw v59
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:24:07 +02:00
3eb6a27ec3 Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.

Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
713b25f36a Web: der Lautstärkeregler gilt auch für Podcasts, sw v57
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 5m18s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:39:15 +02:00
cd0547c2f0 Web: Player und Radio-Leiste sind eine Leiste unten
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 5m23s
Die Player-Leiste sitzt in der Radio-Leiste an der Stelle von „läuft
gerade“: am Desktop eine Zeile (Player, Lautstärke, Fokusmodus, Timer),
am Handy zwei; die Fortschritts- bzw. Live-Linie läuft über die ganze
Leiste. Der Fokusmodus zeigt den Sender weiter groß. sw v56.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 14:18:09 +02:00
1533497409 Web: der Sleep-Timer zählt und stoppt auch beim Radio
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 5m17s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:16:24 +02:00
7c87343e66 Web: ein Player für Radio und Podcasts
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
Die Podcast-Leiste und der Vollbild-Player zeigen auch den Sender (Logo,
„● Live“, Stopp behält den Sender, speichern, Sleep-Timer, Sender
wechseln); die Radio-Leiste behält nur Lautstärke, Votes, Fokusmodus und
Timer. Podcast-Oberfläche aus diora-android e6761d0, sw v55.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:12:59 +02:00
649cf0f340 AntennaPod-Import: --replace für einen Neuanfang; Podcast-Oberfläche aus diora-android 6c2b071
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 5m19s
--replace bestellt Podcasts ab, die das Backup nicht hat, behält nur
AntennaPods Warteschlange und lässt AntennaPod auch über spätere
Änderungen in diora gewinnen. Die Oberfläche bringt den gemeinsamen
Player und die Download-Übersicht (nur in der App sichtbar). sw v54.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:44:49 +02:00
35ea42b6c2 Podcast-Oberfläche aus diora-android f94c501 (Downloads einreihen), sw v53
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 4m59s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:46:37 +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
176771a407 Podcasts: Position in der Warteschlange einstellbar; Feeds nur noch auf dem Server holen (SW v51)
All checks were successful
Build and push Docker image / build (push) Successful in 19s
Test / test (push) Successful in 2m0s
Warteschlange
- PodcastSettings: wohin neue Folgen kommen, getrennt für Hinzufügen von
  Hand (auch beim Abspielen einer Folge außerhalb der Warteschlange) und
  für automatisch eingereihte neue Folgen: ans Ende, an den Anfang oder
  nach die laufende Folge. Voreinstellung wie bisher (Hand: Ende,
  automatisch: Anfang). GET/POST /podcasts/settings/.
- queue/add nimmt auch mehrere Folgen (episode_ids), eine Position
  (where) und die laufende Folge (current_episode_id); ohne Angabe gilt
  die zuletzt angehörte, nicht fertige Folge als laufende.
- Positionen werden lückenlos neu durchnummeriert.

Feeds
- POST /podcasts/feeds/refresh-all/ aktualisiert alle Feeds des Nutzers
  auf dem Server: Downloads parallel, jede Feed-Adresse nur einmal (auch
  über Nutzer hinweg), mit Timeout; kürzlich aktualisierte werden
  übersprungen. Der stündliche Cron-Job nutzt dasselbe.
- GET /podcasts/state/ liefert Abos, Warteschlange, Inbox und
  Einstellungen in einer Antwort (für die Apps).
- Web: „↻ All“ ist eine Anfrage statt einer pro Feed; die stündliche
  Schleife im Browser lädt nur noch die Listen neu, statt jeden Feed
  selbst anzustoßen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:26:16 +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
bca9d6b5e9 Radio: http-Streams in nativen Hüllen abspielen lassen; Play-Session nicht mehr verlieren (SW v49)
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 1m34s
playStation() öffnet http-Sender auf einer https-Seite in einem neuen
Fenster. Native Hüllen (diora Android, diora desktop), die den Stream
selbst abspielen, setzen window.DIORA_PLAYS_HTTP_STREAMS und überspringen
das.

stopPlaySession() wurde von playStation() ohne await aufgerufen und
setzte currentPlayId erst nach dem fetch auf null – beim Senderwechsel
also die frisch gesetzte ID der neuen Session. Die ID wird jetzt vor dem
await übernommen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 20:00:42 +02:00
086ce29020 Reader: Buch nie doppelt öffnen; ungültige Anker selbst reparieren (SW v48)
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 1m34s
openBook leert #reader-content und hängt die Kapitel über mehrere awaits
an. Ein zweiter Aufruf dazwischen (zweites Tippen, während ein langsamer
E-Reader noch entschlüsselt) hing das Buch doppelt an. Daraus gespeicherte
Anker lagen hinter dem echten Buchende (tolino: Block 1491 bei 1206
Blöcken) und schlugen beim Server-Vergleich "weitester Stand gewinnt"
jede echte Position: Fortschritt wurde nicht mehr gespeichert.

- openBook läuft nur einmal gleichzeitig (gleiches Buch: auf den laufenden
  Aufruf warten; anderes Buch: danach öffnen).
- Aus einem doppelt gerenderten Dokument wird kein Fortschritt gespeichert.
- Ein gespeicherter Anker, der nicht zum Buch passt, führt zum Textanteil
  (scroll_fraction) statt an den Anfang, und die nächste Speicherung
  überschreibt ihn (force), damit der Fortschritt wieder läuft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 21:19:05 +02:00
7ee44622ee Reader: Schriftgewicht einstellbar (SW v47)
All checks were successful
Build and push Docker image / build (push) Successful in 17s
Test / test (push) Successful in 1m36s
Neuer Regler im Einstellungs-Panel, 200-800: bei Source Serif 4 (variabel) in
25er-Schritten, bei Systemschriften in 100ern. null behält das Gewicht aus dem
Stylesheet. --reader-weight erlaubt alten WebViews ohne variable Schriften,
einen statischen Schnitt zu wählen. "Kein Fett" erbt jetzt das eingestellte
Gewicht statt fest normal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:25:59 +02:00
09055b9874 Reader: Lesefortschritt nach Textanteil statt Pixeln (SW v46)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 1m33s
Der Fortschritt (Speichern/Sync, Prozentanzeige im Reader-Kopf, Sprung
über das Prozentfeld, "≈ x % im Buch" bei Markierungen) wird jetzt aus
dem Positions-Anker als Anteil des Buchtexts in Zeichen berechnet statt
aus scrollTop/scrollHeight. Das ist unabhängig vom Layout (Schriftgröße,
Breite, welche Kapitel gerendert sind) und damit auf allen Clients gleich
— die Android-App rendert im E-Reader-Modus nur ein Kapitel und kennt die
Pixelhöhe des Buchs nicht. Pixel zählten außerdem Ränder, Überschriften
und Bilder mit. Anker und Server-Logik bleiben unverändert; PDFs weiter
nach Seiten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 19:57:10 +02:00
marwin
9d097f5fba Reader: Notiz-Randspalte auf dem Handy nicht automatisch öffnen (SW v45)
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 1m33s
Beim Öffnen eines Buchs mit Markierungen klappte die Randspalte auch auf
schmalen Bildschirmen auf und verdeckte den Text. Auf Mobil (_isMobileReader)
bleibt sie jetzt zu und ist weiter über ✎ erreichbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 10:49:09 +00:00