Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec596d442e | ||
|
|
bd9ffe6faa | ||
|
|
3389ba17f4 | ||
|
|
c05cea3a6d |
No files matched your search
@@ -3,7 +3,7 @@
|
|||||||
"title": "Sendewerk",
|
"title": "Sendewerk",
|
||||||
"description": "Redaktionelle Planung und Durchführung von heinzlive-Sendungen.",
|
"description": "Redaktionelle Planung und Durchführung von heinzlive-Sendungen.",
|
||||||
"tagline": "Vom Fundstück bis zur Livesendung",
|
"tagline": "Vom Fundstück bis zur Livesendung",
|
||||||
"version": "0.5.0",
|
"version": "0.5.12",
|
||||||
"healthCheckPath": "/healthz",
|
"healthCheckPath": "/healthz",
|
||||||
"httpPort": 3000,
|
"httpPort": 3000,
|
||||||
"addons": {
|
"addons": {
|
||||||
|
|||||||
@@ -412,3 +412,44 @@
|
|||||||
- `bash -n start.sh`: erfolgreich
|
- `bash -n start.sh`: erfolgreich
|
||||||
- reale OBS-Browserquelle, echte YouTube-/TikTok-Wiedergabe und Safari auf einem
|
- reale OBS-Browserquelle, echte YouTube-/TikTok-Wiedergabe und Safari auf einem
|
||||||
iOS-Gerät nicht ausgeführt; Anbieterrequests wurden lokal abgefangen
|
iOS-Gerät nicht ausgeführt; Anbieterrequests wurden lokal abgefangen
|
||||||
|
|
||||||
|
## Verwaltungskorrektur 0.5.11
|
||||||
|
|
||||||
|
| Kriterium | Nachweis | Stand |
|
||||||
|
| ------------------------------------------ | ---------------------------------------------------------------------------------------- | --------------------- |
|
||||||
|
| Werkzeugdialog ist initial geschlossen | Chromium prüft den Dialog vor jeder Bedienaktion bei 390 und 1280 Pixeln | erfüllt am 23.10.2026 |
|
||||||
|
| Dialog öffnet nur nach bewusster Aktion | Browser legt ein Werkzeug an und öffnet ein bestehendes Werkzeug anschließend zum Ändern | erfüllt am 23.10.2026 |
|
||||||
|
| Bestehender Werkzeugablauf bleibt erhalten | Browser prüft Icon, Format, Deaktivierung und Piece-Zuordnung nach erneutem Laden | erfüllt am 23.10.2026 |
|
||||||
|
| Themen-Iconpicker bleibt im Viewport | Chromium prüft Öffnen und Auswahl bei 390, 960 und 1280 Pixeln | erfüllt am 23.10.2026 |
|
||||||
|
|
||||||
|
## Kompakte Live-Regie 0.5.12
|
||||||
|
|
||||||
|
| Kriterium | Nachweis | Stand |
|
||||||
|
| ------------------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------- |
|
||||||
|
| Globale Navigation bleibt aus der Regie | Chromium findet weder Seitenleiste noch mobilen App-Header oder Bottomnavigation | erfüllt am 24.10.2026 |
|
||||||
|
| Kleines Desktopfenster bleibt zweispaltig | Chromium prüft Moderation, Ablauf und feste Aktionen bei 900 × 450 Pixeln | erfüllt am 24.10.2026 |
|
||||||
|
| Niedrige Desktophöhe wird berücksichtigt | 900 × 450 aktiviert kompakte Abstände ohne Transform oder CSS-Zoom | erfüllt am 24.10.2026 |
|
||||||
|
| Lange Inhalte scrollen innerhalb der Fläche | Synthetischer Langtext und zusätzliche Ablaufabschnitte erzeugen zwei unabhängige Scroller | erfüllt am 24.10.2026 |
|
||||||
|
| Mobile Regie verwendet ein Ablauf-Sheet | 390 × 700 prüft Endlage, Fokus, Escape, Aktionsschließen und Wechsel auf 900 Pixel | erfüllt am 24.10.2026 |
|
||||||
|
| Leseposition bleibt am Piece | Nach tiefem Scrollen beginnt ein direkt gewähltes anderes Piece am oberen Rand | erfüllt am 24.10.2026 |
|
||||||
|
| Zustände sind ausgeschrieben | Ablauf prüft Kommend, Aktuell, Erledigt und Übersprungen | erfüllt am 24.10.2026 |
|
||||||
|
| Erstes und letztes Piece direkt wählbar | Browser wechselt per stabiler Usage-ID auf Desktop und aus dem mobilen Sheet in beide Richtungen | erfüllt am 24.10.2026 |
|
||||||
|
| Navigation veröffentlicht nicht automatisch | Öffentliche Ausgabe bleibt bei direkter Auswahl unverändert | erfüllt am 24.10.2026 |
|
||||||
|
| Timer und Livezustand überleben Reload | Aktuelles Piece und laufender Abschnittscountdown bleiben nach Neuladen erhalten | erfüllt am 24.10.2026 |
|
||||||
|
| Wiederaufnahme nur bei Unterbrechung | Während eines aktiven Pieces fehlt die Aktion; nach unterbrochenem Stand ist sie prominent | erfüllt am 24.10.2026 |
|
||||||
|
| Sendungsende ist eindeutig | Letztes Piece zeigt Endaktion ohne horizontalen Überlauf; danach fehlen Weiter-Aktionen | erfüllt am 24.10.2026 |
|
||||||
|
|
||||||
|
## Ausgeführte Prüfungen 0.5.12
|
||||||
|
|
||||||
|
- `npm run lint`: erfolgreich
|
||||||
|
- `npm run check`: 0 Fehler und 0 Warnungen
|
||||||
|
- `npm test`: 40 Tests in 7 Dateien erfolgreich
|
||||||
|
- gezielter Live-Regie-Lauf: 1 von 1 Chromium-Test erfolgreich
|
||||||
|
- `npm run test:e2e`: 16 von 16 Chromium-Browsertests erfolgreich
|
||||||
|
- Screenshots der Live-Regie bei 1440 × 900, 900 × 450 und 390 × 700 Pixeln
|
||||||
|
einschließlich mobilem Ablauf-Sheet visuell kontrolliert
|
||||||
|
- `npm run build`: erfolgreich mit `@sveltejs/adapter-node`
|
||||||
|
- Versionsgleichheit `0.5.12` in Paket, Lockdatei und Cloudron-Manifest geprüft
|
||||||
|
- `bash -n start.sh` und `git diff --check`: erfolgreich
|
||||||
|
- reale OBS-Browserquelle, echte YouTube-/TikTok-Wiedergabe und Safari auf einem
|
||||||
|
iOS-Gerät nicht ausgeführt; Anbieterrequests wurden lokal abgefangen
|
||||||
@@ -283,6 +283,19 @@ Der Live-Modus ist eine eigene, fokussierte Arbeitsansicht. Die
|
|||||||
Präsentationsausgabe verwendet eine separate, strikt reduzierte Datenansicht
|
Präsentationsausgabe verwendet eine separate, strikt reduzierte Datenansicht
|
||||||
ohne interne Anwendungsnavigation.
|
ohne interne Anwendungsnavigation.
|
||||||
|
|
||||||
|
Die private Route `/live` verwendet ebenfalls einen eigenen authentifizierten
|
||||||
|
Layoutmodus ohne allgemeine Seitenleiste, mobilen App-Header oder
|
||||||
|
Bottomnavigation. Ihre Regiefläche belegt die verfügbare Viewporthöhe als Raster
|
||||||
|
aus fester Statuszeile, unabhängig scrollender Piece- und Ablaufspalte sowie
|
||||||
|
fester Aktionszeile. Die Breite entscheidet über zwei Spalten oder das mobile
|
||||||
|
Sheet; eine geringe Desktophöhe verdichtet zusätzlich Abstände und Leisten,
|
||||||
|
ohne Schrift oder Oberfläche zu skalieren. Unterhalb des schmalen Breakpoints
|
||||||
|
wird ausschließlich die Ablaufspalte als modales Sheet mit Fokusbegrenzung
|
||||||
|
eingeblendet; beim Wechsel auf einen breiteren Viewport wird dieser Modalzustand
|
||||||
|
aufgehoben. Dieser Layoutmodus ändert weder die Live-Actions noch die getrennte
|
||||||
|
öffentliche `/heinzlive`-Projektion. Lokale Lesepositionen sind an die jeweilige
|
||||||
|
Verwendung gebunden und werden nicht zwischen Pieces übertragen.
|
||||||
|
|
||||||
Icons werden aus dem lokalen Font-Awesome-Classic-Regular-Sprite unter
|
Icons werden aus dem lokalen Font-Awesome-Classic-Regular-Sprite unter
|
||||||
`static/icons` gerendert. Die Verwaltung lädt zusätzlich die durchsuchbaren
|
`static/icons` gerendert. Die Verwaltung lädt zusätzlich die durchsuchbaren
|
||||||
Metadaten aller 3.671 Icons, zeigt je Suchergebnis aber höchstens 120 Einträge
|
Metadaten aller 3.671 Icons, zeigt je Suchergebnis aber höchstens 120 Einträge
|
||||||
|
|||||||
@@ -360,3 +360,19 @@ Validierung, während ein statischer SVG-Sprite über den gemeinsamen
|
|||||||
Pfaddaten in ihren JavaScript-Chunk. Es gibt weder einen externen Laufzeitabruf
|
Pfaddaten in ihren JavaScript-Chunk. Es gibt weder einen externen Laufzeitabruf
|
||||||
noch eine Laufzeitabhängigkeit vom benachbarten Quellverzeichnis; Herkunft und
|
noch eine Laufzeitabhängigkeit vom benachbarten Quellverzeichnis; Herkunft und
|
||||||
Lizenz stehen in `THIRD_PARTY_NOTICES.md`.
|
Lizenz stehen in `THIRD_PARTY_NOTICES.md`.
|
||||||
|
|
||||||
|
## D-024: Private Live-Regie als fokussierter Viewport
|
||||||
|
|
||||||
|
Die authentifizierte Live-Steuerung nutzt einen eigenen Root-Layoutmodus und
|
||||||
|
rendert die allgemeine App-Navigation nicht. Kopf- und Aktionszeile bleiben fest;
|
||||||
|
Moderation und Ablauf sind die einzigen unabhängig scrollenden Arbeitsflächen.
|
||||||
|
Die Umschaltung zum mobilen Sheet hängt von der Fensterbreite ab, damit ein
|
||||||
|
breites, niedriges Desktopfenster weiterhin zwei Spalten verwendet. Die Höhe
|
||||||
|
steuert dort unabhängig die Dichte von Abständen und Bedienleisten. Textgröße
|
||||||
|
und Gesamtoberfläche werden nicht skaliert.
|
||||||
|
|
||||||
|
Diese Darstellung ist keine neue Live-Zustandsgrenze. Start, direkte Auswahl,
|
||||||
|
Erledigen, Überspringen, Reserveeinfügung und Präsentation bleiben unveränderte
|
||||||
|
serverseitige Actions mit ihren bisherigen Revisions- und Idempotenzregeln. Ein
|
||||||
|
Piece-Wechsel veröffentlicht daher weiterhin nichts; nur die ausdrückliche
|
||||||
|
Präsentationsaction verändert die getrennte öffentliche Ausgabe.
|
||||||
+135
-9
@@ -1,12 +1,126 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## 0.5.12 - Kompakte Live-Regie
|
||||||
|
|
||||||
|
Veröffentlicht am 24. Oktober 2026
|
||||||
|
|
||||||
|
Sendewerk 0.5.12 macht aus der privaten Live-Steuerung eine kompakte Regieansicht
|
||||||
|
für Vollbild, Smartphone und kleine Desktopfenster neben OBS oder Firebot. Das
|
||||||
|
aktuelle Piece, die Position im Ablauf, der Abschnittscountdown und die
|
||||||
|
wichtigsten Aktionen bleiben gleichzeitig erreichbar. Die öffentliche
|
||||||
|
heinzlive-Ausgabe und die gespeicherten Livezustände bleiben technisch getrennt.
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
- Die Live-Regie verwendet die gesamte Fensterfläche ohne normale
|
||||||
|
App-Seitenleiste, mobilen App-Header oder Bottomnavigation.
|
||||||
|
- Eine kompakte Kopfzeile zeigt aktuellen Abschnitt, Abschnittscountdown und den
|
||||||
|
tatsächlich präsentierten Ausgabezustand.
|
||||||
|
- Moderation und Ablauf bleiben auch bei einem nur 900 × 450 Pixel großen
|
||||||
|
Desktopfenster nebeneinander. Lange Inhalte scrollen innerhalb ihrer
|
||||||
|
Arbeitsfläche; Kopf- und Aktionszeile bleiben sichtbar.
|
||||||
|
- Niedrige Desktopfenster verdichten Abstände und Bedienleisten anhand der
|
||||||
|
verfügbaren Höhe, ohne Text oder die gesamte Oberfläche zu skalieren.
|
||||||
|
- Die feste Fußleiste bündelt nächstes Piece, „Präsentieren“, „Überspringen“ und
|
||||||
|
„Erledigen & weiter“. Beim letzten offenen Piece wird das Sendungsende
|
||||||
|
eindeutig benannt.
|
||||||
|
- Auf dem Smartphone öffnet „Ablauf · Piece X/Y“ den Ablauf als separates Sheet,
|
||||||
|
während das aktuelle Piece die Hauptfläche bleibt. Fokusführung, Escape und
|
||||||
|
der Wechsel zurück auf eine breite Ansicht schließen den Modalzustand sauber.
|
||||||
|
|
||||||
|
### Moderation und Materialien
|
||||||
|
|
||||||
|
- Interner Titel, Einleitung, Moderationsstichpunkte und Überleitung erscheinen
|
||||||
|
in klarer Lesereihenfolge. Leere Bereiche werden weggelassen.
|
||||||
|
- Recherche, Quellen, Werkzeuge und eigene Notizen sind kompakt beschriftet und
|
||||||
|
werden nur bei Bedarf geöffnet.
|
||||||
|
- Die Prompter-Schriftgröße bleibt einstellbar und wird weiterhin lokal
|
||||||
|
gespeichert. Texte werden nicht automatisch verkleinert.
|
||||||
|
|
||||||
|
### Ablauf und Liveverhalten
|
||||||
|
|
||||||
|
- Abschnitte sind kompakt aufklappbar; der aktuelle Abschnitt ist geöffnet und
|
||||||
|
das aktive Piece bleibt in der Ablaufspalte sichtbar.
|
||||||
|
- Pieces kennzeichnen „Aktuell“, „Erledigt“, „Übersprungen“ und „Kommend“ auch
|
||||||
|
textlich und lassen sich direkt auswählen, einschließlich des ersten Eintrags.
|
||||||
|
- Die Reserve bleibt zusammengeklappt erreichbar. Dauerhafte Sortierpfeile und
|
||||||
|
die separate große „Als Nächstes“-Karte entfallen; Umsortieren bleibt über
|
||||||
|
eine kompakte Aktion am jeweiligen Eintrag erreichbar.
|
||||||
|
- Die lokal gespeicherte Leseposition gehört zum jeweiligen Piece. Eine direkte
|
||||||
|
Auswahl beginnt daher oben, ohne die Position des vorherigen Textes zu erben.
|
||||||
|
- Direkte Auswahl verändert keinen Erledigungsstatus und veröffentlicht nichts.
|
||||||
|
Nur „Präsentieren“ oder „Präsentation leeren“ verändert die öffentliche
|
||||||
|
Ausgabe.
|
||||||
|
- Abschnittstimer, tatsächliche Laufzeiten, Livefortschritt und Wiederaufnahme
|
||||||
|
verwenden unverändert die vorhandenen persistenten Zustände.
|
||||||
|
- „Live-Stand wiederaufnehmen“ erscheint nur bei einem tatsächlich
|
||||||
|
unterbrochenen Ablauf, nicht während eines aktiven Pieces.
|
||||||
|
|
||||||
|
### Einstellungen
|
||||||
|
|
||||||
|
- Ausgabekanal verbinden, Schlüssel erneuern, Kanal trennen, Theme wechseln,
|
||||||
|
Zeiten ein-/ausblenden und „Zur Planung“ bleiben im Regiemenü erreichbar.
|
||||||
|
|
||||||
|
### Update und Kompatibilität
|
||||||
|
|
||||||
|
- Dieses Release benötigt keine neue Datenbankmigration und keine Änderung der
|
||||||
|
Konfiguration.
|
||||||
|
- Bestehende Sendungen, Livezustände, Timer, Ausgabekanäle und
|
||||||
|
Präsentationssnapshots bleiben erhalten.
|
||||||
|
- Paket, Lockdatei und Cloudron-Manifest tragen gemeinsam Version `0.5.12`.
|
||||||
|
|
||||||
|
### Prüfung
|
||||||
|
|
||||||
|
- 40 Unit- und Verhaltenstests sowie 16 Chromium-E2E-Abläufe sind erfolgreich.
|
||||||
|
- Die Live-Regie wurde bei 1440 × 900, 900 × 450 und 390 × 700 Pixeln mit
|
||||||
|
langen Inhalten, unabhängigen Scrollflächen und mobilem Ablauf-Sheet geprüft;
|
||||||
|
die erzeugten Screenshots wurden visuell kontrolliert.
|
||||||
|
- Reales OBS, echte YouTube-/TikTok-Wiedergabe und Safari auf einem iOS-Gerät
|
||||||
|
standen nicht zur Verfügung.
|
||||||
|
|
||||||
|
## 0.5.11 - Verwaltungskorrektur
|
||||||
|
|
||||||
|
Veröffentlicht am 23. Oktober 2026
|
||||||
|
|
||||||
|
- Der Dialog „Werkzeug hinzufügen“ bleibt beim Aufruf der Verwaltungsseite
|
||||||
|
wieder zuverlässig geschlossen.
|
||||||
|
- Der Dialog erscheint erst, wenn ein neues Werkzeug angelegt oder ein
|
||||||
|
bestehendes Werkzeug bearbeitet wird.
|
||||||
|
- Die Iconauswahl unter „Inhaltliche Zuordnung“ bleibt auf Desktop und
|
||||||
|
Smartphone vollständig im sichtbaren Browserbereich.
|
||||||
|
- Die Korrektur gilt gleichermaßen für Desktop- und Smartphone-Ansichten und
|
||||||
|
verändert keine vorhandenen Werkzeuge oder Zuordnungen.
|
||||||
|
|
||||||
## 0.5.10 - Editor-Erlebnis
|
## 0.5.10 - Editor-Erlebnis
|
||||||
|
|
||||||
|
Veröffentlicht am 23. Oktober 2026
|
||||||
|
|
||||||
|
Sendewerk 0.5.10 beschleunigt die tägliche Redaktionsarbeit und schärft die
|
||||||
|
Grenze zwischen privaten Medien und der öffentlichen Präsentation. Titel,
|
||||||
|
Themen, Werkzeuge und Präsentationsbilder lassen sich direkter bearbeiten; die
|
||||||
|
wichtigsten Listen und Dialoge benötigen auf Desktop und Smartphone weniger
|
||||||
|
Platz. Bestehende Sendungsfassungen bleiben bei späteren Änderungen am Fundus
|
||||||
|
stabil.
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
- Titel von Fundus- und Sendungsfassungen lassen sich direkt in der jeweiligen
|
||||||
|
Ansicht ändern. Die Bedienung funktioniert auch vollständig per Tastatur.
|
||||||
|
- Themen und Werkzeuge verwenden kompakte, durchsuchbare Auswahlflächen. Der
|
||||||
|
Werkzeugkatalog folgt nun demselben Bedienmuster wie die Themenverwaltung.
|
||||||
|
- Für Themen, Werkzeuge, Navigation und Aktionen stehen 3.671 lokal
|
||||||
|
bereitgestellte Classic-Regular-Icons zur Verfügung. Zur Laufzeit werden
|
||||||
|
keine Icons von externen Diensten geladen.
|
||||||
|
- Präsentationsbilder werden bewusst aus den aktiven Piece-Medien gewählt und
|
||||||
|
beim Einplanen in die Sendungsfassung übernommen. Private Uploads werden nicht
|
||||||
|
allein durch das Hochladen öffentlich.
|
||||||
|
- Pieces lassen sich wiederherstellbar löschen. Bereits vorbereitete
|
||||||
|
Sendungsfassungen und ihre Medien-Snapshots bleiben dabei erhalten.
|
||||||
|
- Die heinzlive-Ausgabe kann ausdrücklich geleert werden. Im leeren Zustand ist
|
||||||
|
sie vollständig transparent und zeigt auch keinen Verbindungsstatus.
|
||||||
|
|
||||||
### Redaktion und Verwaltung
|
### Redaktion und Verwaltung
|
||||||
|
|
||||||
- Titel von Fundus- und Sendungsfassungen lassen sich direkt und per Tastatur
|
|
||||||
bearbeiten.
|
|
||||||
- Themen und Werkzeuge verwenden gemeinsame kompakte Auswahlflächen.
|
|
||||||
- Der Werkzeugkatalog entspricht der Themenverwaltung: kompakte Glasfläche,
|
- Der Werkzeugkatalog entspricht der Themenverwaltung: kompakte Glasfläche,
|
||||||
Icon, Name, Status und Bearbeitungsaktion statt dauerhaft offener Formulare.
|
Icon, Name, Status und Bearbeitungsaktion statt dauerhaft offener Formulare.
|
||||||
- Anlegen und Bearbeiten eines Werkzeugs verwenden denselben responsiven Dialog
|
- Anlegen und Bearbeiten eines Werkzeugs verwenden denselben responsiven Dialog
|
||||||
@@ -14,9 +128,8 @@
|
|||||||
- Die Formatzuordnung nutzt dieselbe durchsuchbare Mehrfachauswahl wie andere
|
- Die Formatzuordnung nutzt dieselbe durchsuchbare Mehrfachauswahl wie andere
|
||||||
Zuordnungen. Deaktivierte Werkzeuge und bestehende Piece-Zuordnungen bleiben
|
Zuordnungen. Deaktivierte Werkzeuge und bestehende Piece-Zuordnungen bleiben
|
||||||
erhalten.
|
erhalten.
|
||||||
- Alle 3.671 Icons aus Font Awesome Pro Plus 7.0.0 Classic Regular stehen lokal
|
- Fundus, Eingang, Archiv, Vorlagen und Sendungsansichten sind kompakter und
|
||||||
und durchsuchbar zur Verfügung. Ein lokaler SVG-Sprite hält den allgemeinen
|
bleiben per Klick, Touch und Tastatur bedienbar.
|
||||||
Anwendungs-Chunk klein; es gibt keine externe Laufzeitabhängigkeit.
|
|
||||||
|
|
||||||
### Pieces und Präsentation
|
### Pieces und Präsentation
|
||||||
|
|
||||||
@@ -27,8 +140,10 @@
|
|||||||
- Pieces können wiederherstellbar gelöscht werden. Offene, nie live gelaufene
|
- Pieces können wiederherstellbar gelöscht werden. Offene, nie live gelaufene
|
||||||
Verwendungen und Abschnitte lassen sich unter geschützten Bedingungen
|
Verwendungen und Abschnitte lassen sich unter geschützten Bedingungen
|
||||||
entfernen.
|
entfernen.
|
||||||
- Fundus, Eingang, Archiv, Vorlagen und Sendungsansichten sind kompakter und
|
- Themenicons werden zusammen mit der jeweiligen Sendungsfassung gespeichert.
|
||||||
bleiben per Klick, Touch und Tastatur bedienbar.
|
Spätere Änderungen an Thema, Werkzeug oder Original-Piece verändern eine
|
||||||
|
vorbereitete Sendung weiterhin nur nach ausdrücklicher Aktualisierung aus dem
|
||||||
|
Fundus.
|
||||||
|
|
||||||
### Live-Ausgabe
|
### Live-Ausgabe
|
||||||
|
|
||||||
@@ -37,13 +152,18 @@
|
|||||||
- Eine aktive Präsentation lässt sich unabhängig vom aktuell laufenden Piece
|
- Eine aktive Präsentation lässt sich unabhängig vom aktuell laufenden Piece
|
||||||
leeren.
|
leeren.
|
||||||
|
|
||||||
### Datenbank und Kompatibilität
|
### Update und Kompatibilität
|
||||||
|
|
||||||
- `0010_editor_experience.sql` ergänzt Themenicons, Medien-Snapshots und Soft
|
- `0010_editor_experience.sql` ergänzt Themenicons, Medien-Snapshots und Soft
|
||||||
Delete.
|
Delete.
|
||||||
- `0011_tool_icons.sql` ergänzt persistente Werkzeugicons mit einem sicheren
|
- `0011_tool_icons.sql` ergänzt persistente Werkzeugicons mit einem sicheren
|
||||||
Standardwert. Werkzeug-IDs, Formatbeziehungen, Piece-Zuordnungen und
|
Standardwert. Werkzeug-IDs, Formatbeziehungen, Piece-Zuordnungen und
|
||||||
Deaktivierungszustände bleiben erhalten.
|
Deaktivierungszustände bleiben erhalten.
|
||||||
|
- Die Migrationen laufen beim nächsten Serverstart automatisch. Vor dem Update
|
||||||
|
sollte wie üblich eine konsistente Sicherung der SQLite-Datenbank und des
|
||||||
|
Upload-Verzeichnisses angelegt werden.
|
||||||
|
- Für dieses Release sind keine manuellen Datenkonvertierungen und keine
|
||||||
|
Änderungen an der Konfiguration erforderlich.
|
||||||
|
|
||||||
### Prüfung
|
### Prüfung
|
||||||
|
|
||||||
@@ -53,3 +173,9 @@
|
|||||||
verglichen.
|
verglichen.
|
||||||
- Reales OBS, echte YouTube-/TikTok-Wiedergabe und Safari auf einem iOS-Gerät
|
- Reales OBS, echte YouTube-/TikTok-Wiedergabe und Safari auf einem iOS-Gerät
|
||||||
standen nicht zur Verfügung.
|
standen nicht zur Verfügung.
|
||||||
|
|
||||||
|
### Noch nicht enthalten
|
||||||
|
|
||||||
|
Der Abschluss und die Archivierung vollständiger Sendungen, externe
|
||||||
|
Lesefreigaben, RSS-Import und API gehören zu späteren Etappen und sind nicht Teil
|
||||||
|
von 0.5.10.
|
||||||
@@ -7,6 +7,47 @@ Anmeldung, Rollen, persistente Inhaltsbearbeitung, Sendungsplanung,
|
|||||||
Live-Steuerung, Präsentationsausgabe und lokaler Betriebsweg sind dokumentiert.
|
Live-Steuerung, Präsentationsausgabe und lokaler Betriebsweg sind dokumentiert.
|
||||||
Etappe 6 wurde nicht begonnen.
|
Etappe 6 wurde nicht begonnen.
|
||||||
|
|
||||||
|
## Kompakte Live-Regie 0.5.12
|
||||||
|
|
||||||
|
- fokussierte private Liveansicht ohne normale App-Seitenleiste oder mobile
|
||||||
|
Bottomnavigation; Planung, Theme, Schriftgröße, Zeitdarstellung und
|
||||||
|
Ausgabekanal bleiben über das kompakte Regiemenü erreichbar
|
||||||
|
- feste Kopfzeile mit aktuellem Abschnitt, persistentem Abschnittscountdown und
|
||||||
|
öffentlichem Ausgabezustand sowie feste Fußleiste mit nächstem Piece,
|
||||||
|
Präsentation, Überspringen und kontextgerechtem Erledigen
|
||||||
|
- aktuelle Moderation und schmale Ablaufspalte bleiben auch bei 900 × 450 Pixeln
|
||||||
|
nebeneinander; beide Arbeitsflächen scrollen unabhängig, ohne die ganze Seite
|
||||||
|
oder einzelne Textblöcke zu verschachtelten Scrollcontainern zu machen
|
||||||
|
- bei niedrigen Desktopfenstern werden Abstände und Bedienleisten anhand der
|
||||||
|
verfügbaren Höhe verdichtet, ohne Text oder Gesamtoberfläche zu skalieren
|
||||||
|
- auf schmalen Ansichten bleibt das Piece die Hauptfläche und der Ablauf öffnet
|
||||||
|
als separates, safe-area-fähiges Sheet mit Fokusführung und Escape-Schließen
|
||||||
|
- kompakter aufklappbarer Ablauf mit ausgeschriebenen Zuständen, direkter
|
||||||
|
Auswahl jedes Pieces, automatisch sichtbarem aktuellen Eintrag und
|
||||||
|
zusammengeklappter Reserve; Sortierung bleibt in einer kompakten Aktion
|
||||||
|
erreichbar, dauerhafte Sortierpfeile entfallen
|
||||||
|
- die Leseposition wird je Piece lokal erhalten; ein erstmals gewähltes Piece
|
||||||
|
beginnt oben statt an der Position des vorherigen Textes
|
||||||
|
- Recherche, Quellen, Werkzeuge und eigene Notizen erscheinen nur bei Bedarf;
|
||||||
|
leere Moderationsbereiche werden nicht dargestellt
|
||||||
|
- Livefortschritt, direkte Auswahl, Abschnittstimer und öffentliche Präsentation
|
||||||
|
verwenden unverändert ihre getrennten persistenten Zustände
|
||||||
|
- die Wiederaufnahmeaktion erscheint nur bei tatsächlich unterbrochenem
|
||||||
|
Live-Stand statt während eines laufenden Pieces
|
||||||
|
|
||||||
|
Etappe 6 bleibt unverändert unbegonnen.
|
||||||
|
|
||||||
|
Die lokale Prüfmatrix umfasst 40 Verhaltenstests und 16 Chromium-Abläufe.
|
||||||
|
Reales OBS, echte YouTube-/TikTok-Wiedergabe sowie Safari auf einem iOS-Gerät
|
||||||
|
bleiben mangels verfügbarer Umgebung offen.
|
||||||
|
|
||||||
|
## Verwaltungskorrektur 0.5.11
|
||||||
|
|
||||||
|
- der Werkzeugdialog bleibt beim Laden geschlossen und öffnet erst nach einer
|
||||||
|
bewussten Aktion
|
||||||
|
- die Themen-Iconauswahl bleibt bei mobilen und breiten Ansichten vollständig
|
||||||
|
innerhalb des Viewports
|
||||||
|
|
||||||
## Editor-Erlebnis 0.5.10
|
## Editor-Erlebnis 0.5.10
|
||||||
|
|
||||||
- direkte, tastaturbedienbare Titelbearbeitung in Fundus- und Sendungsfassungen
|
- direkte, tastaturbedienbare Titelbearbeitung in Fundus- und Sendungsfassungen
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sendewerk",
|
"name": "sendewerk",
|
||||||
"version": "0.5.10",
|
"version": "0.5.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sendewerk",
|
"name": "sendewerk",
|
||||||
"version": "0.5.10",
|
"version": "0.5.12",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-rs/argon2": "^2.2.2",
|
"@node-rs/argon2": "^2.2.2",
|
||||||
"better-sqlite3": "^13.0.3",
|
"better-sqlite3": "^13.0.3",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sendewerk",
|
"name": "sendewerk",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.10",
|
"version": "0.5.12",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.17 <25"
|
"node": ">=22.17 <25"
|
||||||
|
|||||||
+823
-402
File diff suppressed because it is too large.
Load diff
@@ -2,5 +2,6 @@ import type { LayoutServerLoad } from './$types';
|
|||||||
|
|
||||||
export const load: LayoutServerLoad = ({ locals, url }) => ({
|
export const load: LayoutServerLoad = ({ locals, url }) => ({
|
||||||
user: url.pathname.startsWith('/heinzlive/') ? null : locals.user,
|
user: url.pathname.startsWith('/heinzlive/') ? null : locals.user,
|
||||||
presentationMode: url.pathname.startsWith('/heinzlive/')
|
presentationMode: url.pathname.startsWith('/heinzlive/'),
|
||||||
|
liveControllerMode: url.pathname === '/live'
|
||||||
});
|
});
|
||||||
@@ -34,6 +34,8 @@
|
|||||||
|
|
||||||
{#if data.presentationMode}
|
{#if data.presentationMode}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
{:else if data.user && data.liveControllerMode}
|
||||||
|
<main id="main-content" class="live-controller-shell">{@render children()}</main>
|
||||||
{:else if data.user}
|
{:else if data.user}
|
||||||
<div class="app-shell">
|
<div class="app-shell">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
|
|||||||
+632
-351
File diff suppressed because it is too large.
Load diff
+226
-13
@@ -891,10 +891,33 @@ test('inbox, archive and administration stay concise', async ({ page }) => {
|
|||||||
]);
|
]);
|
||||||
expect(boxes[0]?.y).toBe(boxes[1]?.y);
|
expect(boxes[0]?.y).toBe(boxes[1]?.y);
|
||||||
expect(boxes[1]?.y).toBe(boxes[2]?.y);
|
expect(boxes[1]?.y).toBe(boxes[2]?.y);
|
||||||
|
|
||||||
|
const topicIconPicker = page.locator('.topic-management-panel .taxonomy-form .icon-picker');
|
||||||
|
for (const width of [1280, 960, 390]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
await topicIconPicker.getByText('Icon auswählen', { exact: true }).click();
|
||||||
|
const popover = topicIconPicker.locator('.icon-picker-popover');
|
||||||
|
await expect(popover).toBeVisible();
|
||||||
|
const popoverBox = await popover.boundingBox();
|
||||||
|
expect(popoverBox?.x ?? -1).toBeGreaterThanOrEqual(0);
|
||||||
|
expect((popoverBox?.x ?? width) + (popoverBox?.width ?? 1)).toBeLessThanOrEqual(width);
|
||||||
|
await topicIconPicker.getByText('Icon auswählen', { exact: true }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1280, height: 900 });
|
||||||
|
await topicIconPicker.getByText('Icon auswählen', { exact: true }).click();
|
||||||
|
await topicIconPicker.getByPlaceholder('Icon suchen').fill('Satellite Dish');
|
||||||
|
await topicIconPicker.getByRole('button', { name: 'Satellite Dish', exact: true }).click();
|
||||||
|
await expect(topicIconPicker.locator('input[type="hidden"]')).toHaveValue('satellite-dish');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('tools stay editable in pieces after catalog deactivation', async ({ page }, testInfo) => {
|
test('tools stay editable in pieces after catalog deactivation', async ({ page }, testInfo) => {
|
||||||
await page.goto('/verwaltung');
|
await page.goto('/verwaltung');
|
||||||
|
for (const width of [390, 1280]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
await expect(page.locator('.tool-editor-dialog')).toBeHidden();
|
||||||
|
await expectNoHorizontalOverflow(page);
|
||||||
|
}
|
||||||
await page.getByRole('button', { name: 'Werkzeug hinzufügen' }).click();
|
await page.getByRole('button', { name: 'Werkzeug hinzufügen' }).click();
|
||||||
const create = page.getByRole('dialog', { name: 'Neues Werkzeug' });
|
const create = page.getByRole('dialog', { name: 'Neues Werkzeug' });
|
||||||
await create.getByLabel('Name').fill('E2E Tierlist');
|
await create.getByLabel('Name').fill('E2E Tierlist');
|
||||||
@@ -980,6 +1003,7 @@ test('validated YouTube and TikTok players switch without exposing private field
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
await page.goto('/live?show=e2e-presentation');
|
await page.goto('/live?show=e2e-presentation');
|
||||||
|
await page.getByText('Menü', { exact: true }).click();
|
||||||
const popupPromise = page.waitForEvent('popup');
|
const popupPromise = page.waitForEvent('popup');
|
||||||
await page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click();
|
await page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click();
|
||||||
const output = await popupPromise;
|
const output = await popupPromise;
|
||||||
@@ -1053,10 +1077,17 @@ test('live control persists state and exposes only explicit presentation data',
|
|||||||
await plannedConsole.getByRole('button', { name: 'Aus Fundus aktualisieren' }).click();
|
await plannedConsole.getByRole('button', { name: 'Aus Fundus aktualisieren' }).click();
|
||||||
|
|
||||||
await page.goto('/live?show=show-october');
|
await page.goto('/live?show=show-october');
|
||||||
await expect(page.getByText('Noch nicht gestartet.')).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Bereit für die Sendung' })).toBeVisible();
|
||||||
|
await expect(page.locator('.sidebar')).toHaveCount(0);
|
||||||
|
await expect(page.locator('.mobile-header')).toHaveCount(0);
|
||||||
|
await expect(page.locator('.mobile-navigation')).toHaveCount(0);
|
||||||
await page.getByRole('button', { name: 'Sendung starten' }).click();
|
await page.getByRole('button', { name: 'Sendung starten' }).click();
|
||||||
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||||
await expect(page.locator('.section-countdown')).toContainText(/Noch 0[45]:\d{2}/);
|
await expect(page.locator('.section-countdown')).toContainText(/0[45]:\d{2}/);
|
||||||
|
await expect(
|
||||||
|
page.locator('.live-menu button', { hasText: 'Live-Stand wiederaufnehmen' })
|
||||||
|
).toHaveCount(0);
|
||||||
|
await page.getByText('Quellen · 1', { exact: true }).click();
|
||||||
await expect(page.locator('.live-materials')).toContainText('Herstellerseite');
|
await expect(page.locator('.live-materials')).toContainText('Herstellerseite');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText('Herstellerangaben gegen unabhängige Tests prüfen')
|
page.getByText('Herstellerangaben gegen unabhängige Tests prüfen')
|
||||||
@@ -1065,15 +1096,17 @@ test('live control persists state and exposes only explicit presentation data',
|
|||||||
const before = await page
|
const before = await page
|
||||||
.locator('.live-current')
|
.locator('.live-current')
|
||||||
.evaluate((element) => getComputedStyle(element).getPropertyValue('--prompt-size'));
|
.evaluate((element) => getComputedStyle(element).getPropertyValue('--prompt-size'));
|
||||||
|
await page.getByText('Menü', { exact: true }).click();
|
||||||
await page.getByRole('button', { name: 'Schrift vergrößern' }).click();
|
await page.getByRole('button', { name: 'Schrift vergrößern' }).click();
|
||||||
await page.reload();
|
await page.reload();
|
||||||
const restored = await page
|
const restored = await page
|
||||||
.locator('.live-current')
|
.locator('.live-current')
|
||||||
.evaluate((element) => getComputedStyle(element).getPropertyValue('--prompt-size'));
|
.evaluate((element) => getComputedStyle(element).getPropertyValue('--prompt-size'));
|
||||||
expect(restored).not.toBe(before);
|
expect(restored).not.toBe(before);
|
||||||
await page.getByText('Manuelle Recherche öffnen').click();
|
await page.locator('.research-details > summary').click();
|
||||||
await expect(page.locator('.research-details')).toHaveAttribute('open', '');
|
await expect(page.locator('.research-details')).toHaveAttribute('open', '');
|
||||||
|
|
||||||
|
await page.getByText('Menü', { exact: true }).click();
|
||||||
const [popup] = await Promise.all([
|
const [popup] = await Promise.all([
|
||||||
page.waitForEvent('popup'),
|
page.waitForEvent('popup'),
|
||||||
page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click()
|
page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click()
|
||||||
@@ -1117,28 +1150,208 @@ test('live control persists state and exposes only explicit presentation data',
|
|||||||
await expect(output.locator('.heinzlive-output')).toHaveClass(/empty/);
|
await expect(output.locator('.heinzlive-output')).toHaveClass(/empty/);
|
||||||
await page.getByRole('button', { name: 'Präsentieren', exact: true }).click();
|
await page.getByRole('button', { name: 'Präsentieren', exact: true }).click();
|
||||||
|
|
||||||
|
await page.getByText('Reserve', { exact: true }).click();
|
||||||
await page.getByRole('button', { name: 'Als Nächstes' }).click();
|
await page.getByRole('button', { name: 'Als Nächstes' }).click();
|
||||||
await expect(page.locator('.next-card')).toContainText('Pixelart');
|
await expect(page.locator('.live-next-summary')).toContainText('Pixelart');
|
||||||
await page.getByRole('button', { name: 'Erledigen und weiter' }).click();
|
await page.getByRole('button', { name: 'Erledigen & weiter' }).click();
|
||||||
await expect(page.locator('.live-current')).toContainText('Pixelart');
|
await expect(page.locator('.live-current')).toContainText('Pixelart');
|
||||||
await page.getByRole('button', { name: 'Überspringen' }).click();
|
await page.getByRole('button', { name: 'Überspringen' }).click();
|
||||||
await page.getByRole('button', { name: 'Zurück' }).click();
|
await expect(page.locator('#current-title')).toHaveText(
|
||||||
await expect(page.locator('.live-current')).toContainText('Pixelart');
|
'Warum praktische Effekte wieder auffallen'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
page.locator('.live-run-item').filter({ hasText: 'Die kleine Konsole mit dem großen Dreh' })
|
||||||
|
).toContainText('Erledigt');
|
||||||
|
await expect(
|
||||||
|
page.locator('.live-run-item').filter({ hasText: 'Pixelart, die nicht nostalgisch sein will' })
|
||||||
|
).toContainText('Übersprungen');
|
||||||
|
await expect(
|
||||||
|
page.locator('.live-run-item').filter({ hasText: 'Warum praktische Effekte wieder auffallen' })
|
||||||
|
).toContainText('Aktuell');
|
||||||
|
await page.locator('.live-current-scroll').evaluate((element) => {
|
||||||
|
for (let index = 0; index < 12; index += 1) {
|
||||||
|
const paragraph = document.createElement('p');
|
||||||
|
paragraph.textContent = 'Temporärer Langtext für die Lesepositionsprüfung.';
|
||||||
|
element.append(paragraph);
|
||||||
|
}
|
||||||
|
element.scrollTop = element.scrollHeight;
|
||||||
|
element.dispatchEvent(new Event('scroll'));
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
await page.locator('.live-current-scroll').evaluate((element) => element.scrollTop)
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Die kleine Konsole mit dem großen Dreh als aktuelles Piece auswählen'
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||||
|
await expect
|
||||||
|
.poll(() => page.locator('.live-current-scroll').evaluate((element) => element.scrollTop))
|
||||||
|
.toBe(0);
|
||||||
|
await expect(
|
||||||
|
page.locator('.live-run-item').filter({ hasText: 'Warum praktische Effekte wieder auffallen' })
|
||||||
|
).toContainText('Kommend');
|
||||||
|
await expect(
|
||||||
|
output.getByRole('heading', { name: 'Die kleine Konsole mit dem großen Dreh' })
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Warum praktische Effekte wieder auffallen als aktuelles Piece auswählen'
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(page.locator('#current-title')).toHaveText(
|
||||||
|
'Warum praktische Effekte wieder auffallen'
|
||||||
|
);
|
||||||
|
await expect(output.getByText('Wenn das Licht wirklich auf ein Modell fällt.')).toHaveCount(0);
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 });
|
||||||
|
await setTheme(page, 'dark');
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||||||
|
await expectNoHorizontalOverflow(page);
|
||||||
|
await page.screenshot({ path: testInfo.outputPath('live-controller-desktop-dark.png') });
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 900, height: 450 });
|
||||||
|
await setTheme(page, 'light');
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const title = document.querySelector('#current-title');
|
||||||
|
if (title)
|
||||||
|
title.textContent =
|
||||||
|
'Warum praktische Effekte in einer außergewöhnlich langen Live-Moderation wieder auffallen';
|
||||||
|
const copy = document.querySelector('.prompter-copy');
|
||||||
|
if (copy) {
|
||||||
|
for (let index = 0; index < 8; index += 1) {
|
||||||
|
const paragraph = document.createElement('p');
|
||||||
|
paragraph.textContent =
|
||||||
|
'Zusätzlicher langer Moderationstext prüft die unabhängige Scrollfläche ohne die feste Bedienleiste zu verdrängen.';
|
||||||
|
copy.append(paragraph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rail = document.querySelector('.live-run-scroll');
|
||||||
|
const section = rail?.querySelector('.rail-section');
|
||||||
|
if (rail && section) {
|
||||||
|
for (let index = 0; index < 8; index += 1) rail.append(section.cloneNode(true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const compactLayout = await page.evaluate(() => {
|
||||||
|
const current = document.querySelector('.live-current')?.getBoundingClientRect();
|
||||||
|
const rail = document.querySelector('.live-rail')?.getBoundingClientRect();
|
||||||
|
const footer = document.querySelector('.live-action-bar')?.getBoundingClientRect();
|
||||||
|
const pageStyle = getComputedStyle(document.querySelector('.live-page') as HTMLElement);
|
||||||
|
const pieceScroll = document.querySelector('.live-current-scroll');
|
||||||
|
const railScroll = document.querySelector('.live-run-scroll');
|
||||||
|
return {
|
||||||
|
pageFits: document.documentElement.scrollHeight <= innerHeight,
|
||||||
|
twoColumns: Boolean(current && rail && rail.x > current.x + current.width - 1),
|
||||||
|
footerVisible: Boolean(footer && footer.bottom <= innerHeight && footer.top >= 0),
|
||||||
|
shortViewportDensity: parseFloat(pageStyle.gap) < 8,
|
||||||
|
unscaled: pageStyle.transform === 'none',
|
||||||
|
unzoomed: ['', '1', 'normal'].includes(pageStyle.getPropertyValue('zoom')),
|
||||||
|
pieceScrolls: Boolean(pieceScroll && pieceScroll.scrollHeight > pieceScroll.clientHeight),
|
||||||
|
railScrolls: Boolean(railScroll && railScroll.scrollHeight > railScroll.clientHeight)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(compactLayout).toEqual({
|
||||||
|
pageFits: true,
|
||||||
|
twoColumns: true,
|
||||||
|
footerVisible: true,
|
||||||
|
shortViewportDensity: true,
|
||||||
|
unscaled: true,
|
||||||
|
unzoomed: true,
|
||||||
|
pieceScrolls: true,
|
||||||
|
railScrolls: true
|
||||||
|
});
|
||||||
|
await page.screenshot({ path: testInfo.outputPath('live-controller-compact-desktop.png') });
|
||||||
|
|
||||||
|
const countdownBeforeReload = await page.locator('.section-countdown strong').textContent();
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.locator('#current-title')).toHaveText(
|
||||||
|
'Warum praktische Effekte wieder auffallen'
|
||||||
|
);
|
||||||
|
const countdownAfterReload = await page.locator('.section-countdown strong').textContent();
|
||||||
|
expect(countdownAfterReload).not.toBe('05:00');
|
||||||
|
expect(countdownAfterReload).toMatch(/0[34]:\d{2}/);
|
||||||
|
expect(countdownBeforeReload).toMatch(/0[34]:\d{2}/);
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 390, height: 700 });
|
||||||
|
const liveRail = page.locator('.live-rail');
|
||||||
|
const railClose = liveRail.getByRole('button', { name: 'Ablauf schließen' });
|
||||||
|
await expect(liveRail).toBeHidden();
|
||||||
|
await expect(page.locator('.live-action-bar')).toBeVisible();
|
||||||
|
await expectNoHorizontalOverflow(page);
|
||||||
|
expect(
|
||||||
|
await page.evaluate(() => document.documentElement.scrollHeight <= window.innerHeight)
|
||||||
|
).toBe(true);
|
||||||
|
await page.screenshot({ path: testInfo.outputPath('live-controller-mobile.png') });
|
||||||
|
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||||
|
await expect(liveRail).toHaveClass(/open/);
|
||||||
|
await expect(railClose).toBeFocused();
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const box = await liveRail.boundingBox();
|
||||||
|
return box ? Math.ceil(box.x + box.width) : Number.POSITIVE_INFINITY;
|
||||||
|
})
|
||||||
|
.toBeLessThanOrEqual(390);
|
||||||
|
const mobileRailBox = await liveRail.boundingBox();
|
||||||
|
expect(mobileRailBox?.width ?? 0).toBeGreaterThan(340);
|
||||||
|
await expect(page.locator('.live-run-item.current')).toBeInViewport();
|
||||||
|
await page.screenshot({ path: testInfo.outputPath('live-controller-mobile-run.png') });
|
||||||
|
await page.keyboard.press('Escape');
|
||||||
|
await expect(liveRail).toBeHidden();
|
||||||
|
await expect(page.getByRole('button', { name: /Ablauf · Piece/ })).toBeFocused();
|
||||||
|
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||||
|
await page.setViewportSize({ width: 900, height: 450 });
|
||||||
|
await expect(liveRail).not.toHaveAttribute('role', 'dialog');
|
||||||
|
await expect(page.locator('.live-command-header')).not.toHaveAttribute('inert', '');
|
||||||
|
await page.setViewportSize({ width: 390, height: 700 });
|
||||||
|
await expect(liveRail).toBeHidden();
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||||
|
await liveRail
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Die kleine Konsole mit dem großen Dreh als aktuelles Piece auswählen'
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(liveRail).toBeHidden();
|
||||||
|
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||||
|
await page.getByRole('button', { name: /Ablauf · Piece/ }).click();
|
||||||
|
await liveRail
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'Warum praktische Effekte wieder auffallen als aktuelles Piece auswählen'
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await expect(liveRail).toBeHidden();
|
||||||
|
await expect(page.locator('#current-title')).toHaveText(
|
||||||
|
'Warum praktische Effekte wieder auffallen'
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Erledigen' }).click();
|
||||||
|
await expect(page.getByRole('button', { name: 'Live-Stand wiederaufnehmen' })).toBeVisible();
|
||||||
|
await page.getByRole('button', { name: 'Live-Stand wiederaufnehmen' }).click();
|
||||||
|
await expect(page.locator('#current-title')).toHaveText('Die kleine Konsole mit dem großen Dreh');
|
||||||
|
await expectNoHorizontalOverflow(page);
|
||||||
|
await page.getByRole('button', { name: 'Erledigen & Sendungsende' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Alle Pieces bearbeitet' })).toBeVisible();
|
||||||
|
await expect(page.locator('.live-finished-label')).toHaveText('Ablauf abgeschlossen');
|
||||||
|
await expect(page.getByRole('button', { name: 'Erledigen & weiter' })).toHaveCount(0);
|
||||||
|
|
||||||
await output.route('**/state', (route) => route.abort());
|
await output.route('**/state', (route) => route.abort());
|
||||||
await expect(output.getByText('Verbindung unterbrochen')).toBeVisible({ timeout: 5_000 });
|
await expect(output.getByText('Verbindung unterbrochen')).toBeVisible({ timeout: 5_000 });
|
||||||
await output.unroute('**/state');
|
await output.unroute('**/state');
|
||||||
await expect(output.getByText('Verbunden')).toBeVisible({ timeout: 5_000 });
|
await expect(output.getByText('Verbunden')).toBeVisible({ timeout: 5_000 });
|
||||||
await page.setViewportSize({ width: 390, height: 844 });
|
await page.getByRole('button', { name: 'Präsentation leeren' }).click();
|
||||||
await expectNoHorizontalOverflow(page);
|
await expect(output.locator('.output-content')).toHaveCount(0);
|
||||||
await page.screenshot({
|
await expect(output.locator('.heinzlive-output')).toHaveClass(/empty/);
|
||||||
path: testInfo.outputPath('live-controller-mobile.png'),
|
|
||||||
fullPage: true
|
|
||||||
});
|
|
||||||
await page.setViewportSize({ width: 1280, height: 900 });
|
await page.setViewportSize({ width: 1280, height: 900 });
|
||||||
|
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
window.open = () => null;
|
window.open = () => null;
|
||||||
});
|
});
|
||||||
|
await page.getByText('Menü', { exact: true }).click();
|
||||||
await page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click();
|
await page.getByRole('button', { name: 'Ausgabe verbinden und öffnen' }).click();
|
||||||
await expect(
|
await expect(
|
||||||
page
|
page
|
||||||
|
|||||||
Reference in new issue
Block a user