Phase 4: Terminal im Gerätehaus mit Offline-Betrieb #5

Merged
florian.wilde merged 2 commits from feat/phase-4-kiosk into main 2026-09-03 18:25:36 +00:00
Owner

Ein Wandtablet neben der Fahrzeughalle. Es muss zwei Dinge können, die eine
Oberfläche am Schreibtisch nicht können muss: mit Handschuhen bedienbar
sein und weiterarbeiten, wenn das Netz weg ist.

Version 0.5.0.

Was drin ist

Terminaloberfläche unter /kiosk — Kachelschirm (sieben Kacheln, je nach
Rechten und Zustand), Anmeldung per Ausweis, PIN oder QR-Code vom Handy, sechs
Wizards (Mangel, Kommen/Gehen, Fahrtenbuch, Wäsche, Inventur), Abmeldeuhr,
Hallenmonitor im Vollbild.

Offline — Service Worker (von Hand, 80 Zeilen, keine Bibliothek), eine
Warteschlange in der IndexedDB, durch die jede schreibende Aktion geht,
auch online. Fotos warten als Blob mit. Der Zustand steht als Leiste über
allem, in Klartext.

Idempotenz — die Kennung eines Vorgangs erzeugt der Browser beim
Einreihen, nicht der Server beim Empfangen. Kommt derselbe Vorgang nach
abgebrochener Übertragung erneut an, erkennt ihn der Server an derselben
Kennung wieder. kiosk_vorgang hat sie als Primärschlüssel, und die Datenbank
verweigert dem Anwendungszugang UPDATE und DELETE darauf.

Konflikte — was sich beim Nachtragen nicht anwenden lässt (Gerät
ausgesondert, Prüfung festgeschrieben), wird weder erzwungen noch verworfen,
sondern landet unter /terminal und wartet auf eine Entscheidung.

Anmeldemarke — QR-Code fürs Handy, mit AUTH_SECRET signiert, nirgends
gespeichert, 60 Sekunden gültig.

Wetterwarnungen des DWD — optional und standardmäßig aus. Es ist die
einzige Verbindung, die FLORIAN von sich aus nach außen aufbaut.

Datenmodell

Fahrt, Anwesenheit, Waescheabgabe, Inventurgang, Inventurposten,
KioskVorgang, SyncKonflikt — alle mit organisationId und RLS. Vier neue
Prüfungen setzt die Datenbank selbst durch: Kilometerstände laufen vorwärts,
eine Rückkehr liegt nicht vor der Abfahrt, ein Gehen nicht vor dem Kommen,
eine Wäscheabgabe umfasst mindestens ein Stück.

Zwei Fehler, die die Tests gefunden haben

  1. Die Warteschlange hätte einen liegen gebliebenen Vorgang nie ein zweites
    Mal geschickt.
    jetztSenden hatte das Mutationsobjekt von React Query in
    den Abhängigkeiten — das ist bei jedem Rendern ein neues, also wurde der
    20-Sekunden-Zeitgeber ständig neu aufgezogen und lief nie ab. Alle
    Unit-Tests waren grün; gefunden hat es nur der Playwright-Test mit
    context.setOffline(true).
  2. Der Anwesenheits-Wizard tauschte die Oberfläche unter dem Finger: erst
    „Kommen", nach dem Laden des eigenen Stands „Gehen". Wer zügig tippt,
    drückte ins Leere. Jetzt steht bis dahin ein Platzhalter.

Zum Testen

git checkout feat/phase-4-kiosk
docker compose up -d
npm install
npm run db:migrate
npm run db:seed
npm run dev

Terminal einrichten (einmal, der Link enthält das Gerätegeheimnis aus der
.env):

http://localhost:3100/kiosk/einrichten?wehr=ff-musterhausen&schluessel=bitte_aendern_kiosk_geheimnis

Danach anmelden mit Mitgliedsnummer 0001 und PIN 471203.

Was sich lohnt anzusehen:

  • Mangel melden — /kiosk/mangel: Inventarnummer INV-0001 eintippen,
    einen Satz dazu, Dringlichkeit antippen. Unter dreißig Sekunden.
  • Der Offline-Fall — in den Entwicklerwerkzeugen des Browsers das Netz auf
    „Offline" stellen und weiter erfassen. Die Leiste oben wird rot und zählt.
    Netz wieder an: die Warteschlange läuft von selbst los.
  • Hallenmonitor — /kiosk/monitor, für den Fernseher in der Halle.
  • Terminalübersicht — /terminal: Konflikte, wer im Haus ist, Wäsche,
    Fahrtenbuch.
  • Anmeldemarke — /anmeldemarke, der QR-Code fürs Handy.

Nachweise

  • npx tsc --noEmit ohne Ausgabe
  • npm run test — 238 Tests grün, davon 19 gegen eine echte Datenbank
  • npx playwright test tests/e2e/kiosk.spec.ts — 18 Tests grün (Desktop und Tablet)
  • npm run build — 34 Routen

Drei Punkte, die mir aufgefallen sind

Sie gehören nicht in diesen PR, sondern ins Dashboard:

  1. npm run lint läuft nicht — es gibt keine ESLint-Konfiguration im
    Repo, next lint fragt interaktiv nach einer. Älter als diese Phase.
  2. Die E2E-Tests schreiben in die Demowehr. Inzwischen liegen dort fünf
    „THL 1 Testeinsatz …" als laufende Einsätze — sie stehen sogar auf dem
    Hallenmonitor. Nötig wäre ein eigener Testmandant, den die Suite abräumt.
  3. Zuordnung offline erfasster Vorgänge. Der Server schreibt sie der
    Person zu, die beim Empfang angemeldet ist. Das Fenster ist geschlossen
    (vor jedem Abmelden wird noch einmal übertragen), sauber wäre aber, die
    erfassende Person im Vorgang mitzuführen.

🤖 Generated with Claude Code

Ein Wandtablet neben der Fahrzeughalle. Es muss zwei Dinge können, die eine Oberfläche am Schreibtisch nicht können muss: **mit Handschuhen bedienbar** sein und **weiterarbeiten, wenn das Netz weg ist**. Version 0.5.0. ## Was drin ist **Terminaloberfläche unter `/kiosk`** — Kachelschirm (sieben Kacheln, je nach Rechten und Zustand), Anmeldung per Ausweis, PIN oder QR-Code vom Handy, sechs Wizards (Mangel, Kommen/Gehen, Fahrtenbuch, Wäsche, Inventur), Abmeldeuhr, Hallenmonitor im Vollbild. **Offline** — Service Worker (von Hand, 80 Zeilen, keine Bibliothek), eine Warteschlange in der IndexedDB, durch die **jede** schreibende Aktion geht, auch online. Fotos warten als Blob mit. Der Zustand steht als Leiste über allem, in Klartext. **Idempotenz** — die Kennung eines Vorgangs erzeugt der Browser beim Einreihen, nicht der Server beim Empfangen. Kommt derselbe Vorgang nach abgebrochener Übertragung erneut an, erkennt ihn der Server an derselben Kennung wieder. `kiosk_vorgang` hat sie als Primärschlüssel, und die Datenbank verweigert dem Anwendungszugang `UPDATE` und `DELETE` darauf. **Konflikte** — was sich beim Nachtragen nicht anwenden lässt (Gerät ausgesondert, Prüfung festgeschrieben), wird weder erzwungen noch verworfen, sondern landet unter `/terminal` und wartet auf eine Entscheidung. **Anmeldemarke** — QR-Code fürs Handy, mit `AUTH_SECRET` signiert, nirgends gespeichert, 60 Sekunden gültig. **Wetterwarnungen des DWD** — optional und standardmäßig **aus**. Es ist die einzige Verbindung, die FLORIAN von sich aus nach außen aufbaut. ## Datenmodell `Fahrt`, `Anwesenheit`, `Waescheabgabe`, `Inventurgang`, `Inventurposten`, `KioskVorgang`, `SyncKonflikt` — alle mit `organisationId` und RLS. Vier neue Prüfungen setzt die Datenbank selbst durch: Kilometerstände laufen vorwärts, eine Rückkehr liegt nicht vor der Abfahrt, ein Gehen nicht vor dem Kommen, eine Wäscheabgabe umfasst mindestens ein Stück. ## Zwei Fehler, die die Tests gefunden haben 1. **Die Warteschlange hätte einen liegen gebliebenen Vorgang nie ein zweites Mal geschickt.** `jetztSenden` hatte das Mutationsobjekt von React Query in den Abhängigkeiten — das ist bei jedem Rendern ein neues, also wurde der 20-Sekunden-Zeitgeber ständig neu aufgezogen und lief nie ab. Alle Unit-Tests waren grün; gefunden hat es nur der Playwright-Test mit `context.setOffline(true)`. 2. **Der Anwesenheits-Wizard tauschte die Oberfläche unter dem Finger**: erst „Kommen", nach dem Laden des eigenen Stands „Gehen". Wer zügig tippt, drückte ins Leere. Jetzt steht bis dahin ein Platzhalter. ## Zum Testen ```bash git checkout feat/phase-4-kiosk docker compose up -d npm install npm run db:migrate npm run db:seed npm run dev ``` **Terminal einrichten** (einmal, der Link enthält das Gerätegeheimnis aus der `.env`): ``` http://localhost:3100/kiosk/einrichten?wehr=ff-musterhausen&schluessel=bitte_aendern_kiosk_geheimnis ``` Danach anmelden mit **Mitgliedsnummer `0001`** und **PIN `471203`**. Was sich lohnt anzusehen: - **Mangel melden** — `/kiosk/mangel`: Inventarnummer `INV-0001` eintippen, einen Satz dazu, Dringlichkeit antippen. Unter dreißig Sekunden. - **Der Offline-Fall** — in den Entwicklerwerkzeugen des Browsers das Netz auf „Offline" stellen und weiter erfassen. Die Leiste oben wird rot und zählt. Netz wieder an: die Warteschlange läuft von selbst los. - **Hallenmonitor** — `/kiosk/monitor`, für den Fernseher in der Halle. - **Terminalübersicht** — `/terminal`: Konflikte, wer im Haus ist, Wäsche, Fahrtenbuch. - **Anmeldemarke** — `/anmeldemarke`, der QR-Code fürs Handy. ## Nachweise - `npx tsc --noEmit` ohne Ausgabe - `npm run test` — 238 Tests grün, davon 19 gegen eine echte Datenbank - `npx playwright test tests/e2e/kiosk.spec.ts` — 18 Tests grün (Desktop und Tablet) - `npm run build` — 34 Routen ## Drei Punkte, die mir aufgefallen sind Sie gehören nicht in diesen PR, sondern ins Dashboard: 1. **`npm run lint` läuft nicht** — es gibt keine ESLint-Konfiguration im Repo, `next lint` fragt interaktiv nach einer. Älter als diese Phase. 2. **Die E2E-Tests schreiben in die Demowehr.** Inzwischen liegen dort fünf „THL 1 Testeinsatz …" als laufende Einsätze — sie stehen sogar auf dem Hallenmonitor. Nötig wäre ein eigener Testmandant, den die Suite abräumt. 3. **Zuordnung offline erfasster Vorgänge.** Der Server schreibt sie der Person zu, die beim Empfang angemeldet ist. Das Fenster ist geschlossen (vor jedem Abmelden wird noch einmal übertragen), sauber wäre aber, die erfassende Person im Vorgang mitzuführen. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
florian.wilde added 1 commit 2026-09-03 16:37:00 +00:00
Phase 4: eine eigene Oberflaeche unter /kiosk fuer das Wandtablet in der
Fahrzeughalle — mit Handschuhen bedienbar und arbeitsfaehig, wenn das Netz
weg ist.

Datenmodell: Fahrt, Anwesenheit, Waescheabgabe, Inventurgang,
Inventurposten, KioskVorgang und SyncKonflikt. Die Kennung eines Vorgangs
erzeugt der Browser beim Einreihen und ist damit der Idempotenzschluessel;
kommt derselbe Vorgang nach abgebrochener Uebertragung erneut an, erkennt
ihn der Server wieder. Die Datenbank setzt vier Regeln selbst durch und
verweigert UPDATE und DELETE auf kiosk_vorgang.

Offline: von Hand geschriebener Service Worker, Warteschlange in der
IndexedDB (Dexie), Fotos als Blob daneben, sichtbarer Zustand als Leiste
ueber allem. Was sich beim Nachtragen nicht anwenden laesst, landet als
Konflikt unter /terminal statt verworfen zu werden.

Dazu: Anmeldung per Ausweis, PIN oder signierter QR-Marke vom Handy,
Hallenmonitor mit optionalen DWD-Warnungen, Installation als App.

Behoben: die Warteschlange zog ihren Wiederholungstakt bei jedem Rendern
neu auf und haette einen liegen gebliebenen Vorgang nie ein zweites Mal
geschickt. Gefunden vom Offline-E2E-Test.

31 neue Tests (20 Unit, 11 gegen eine echte Datenbank, 9 E2E je Browser).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
florian.wilde added 1 commit 2026-09-03 17:24:27 +00:00
Drei Nachbesserungen aus dem Gesamtlauf der E2E-Tests:

Die Offline-Seite blieb stehen, wenn nur eine einzelne Anfrage unterwegs
abgebrochen war. Fuer ein Wandtablet ist das falsch herum gedacht: dort
tippt niemand auf "erneut versuchen". Sie versucht es jetzt selbst — sofort,
wenn der Browser wieder Netz meldet, sonst alle zehn Sekunden.

Dazu ein Wiederholungsversuch auch fuer den lokalen Testlauf. Vier
gleichzeitige Arbeiter gegen "next dev" plus bcrypt mit zwoelf Runden lassen
gelegentlich einen Test ins Zeitlimit laufen, der einzeln zuverlaessig gruen
ist — verstreut ueber alle Dateien, nicht an einer Stelle. Ein echter Fehler
faellt zweimal durch und bleibt sichtbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

Nachtrag e500ad6 — drei Dinge, die erst der Gesamtlauf der E2E-Suite gezeigt hat:

  • Die Offline-Seite blieb stehen, wenn nur eine einzelne Anfrage unterwegs abgebrochen war. Für ein Wandtablet ist das falsch herum gedacht — dort tippt niemand auf „erneut versuchen". Sie versucht es jetzt selbst: sofort, wenn der Browser wieder Netz meldet, sonst alle zehn Sekunden.
  • Der Hallenmonitor stapelte alle laufenden Einsätze übereinander und schob Anwesenheit, Fahrzeuge und Prüfungen aus dem Bild. Zwei stehen jetzt groß, der Rest als Zeile darunter.
  • Ein Wiederholungsversuch auch lokal (retries: 1). Vier gleichzeitige Arbeiter gegen next dev plus bcrypt mit zwölf Runden lassen gelegentlich einen Test ins Zeitlimit laufen, der einzeln zuverlässig grün ist — verstreut über alle Dateien, auch in Tests aus Phase 1 bis 3. Ein echter Fehler fällt zweimal durch und bleibt sichtbar.

Stand danach:

  • npx tsc --noEmit ohne Ausgabe
  • npm run test — 238 grün
  • npx playwright test tests/e2e/kiosk.spec.ts — 18 grün (Desktop und Tablet)
  • npm run build — 34 Routen
Nachtrag `e500ad6` — drei Dinge, die erst der Gesamtlauf der E2E-Suite gezeigt hat: - **Die Offline-Seite blieb stehen**, wenn nur eine einzelne Anfrage unterwegs abgebrochen war. Für ein Wandtablet ist das falsch herum gedacht — dort tippt niemand auf „erneut versuchen". Sie versucht es jetzt selbst: sofort, wenn der Browser wieder Netz meldet, sonst alle zehn Sekunden. - **Der Hallenmonitor** stapelte alle laufenden Einsätze übereinander und schob Anwesenheit, Fahrzeuge und Prüfungen aus dem Bild. Zwei stehen jetzt groß, der Rest als Zeile darunter. - **Ein Wiederholungsversuch auch lokal** (`retries: 1`). Vier gleichzeitige Arbeiter gegen `next dev` plus bcrypt mit zwölf Runden lassen gelegentlich einen Test ins Zeitlimit laufen, der einzeln zuverlässig grün ist — verstreut über alle Dateien, auch in Tests aus Phase 1 bis 3. Ein echter Fehler fällt zweimal durch und bleibt sichtbar. Stand danach: - `npx tsc --noEmit` ohne Ausgabe - `npm run test` — 238 grün - `npx playwright test tests/e2e/kiosk.spec.ts` — 18 grün (Desktop und Tablet) - `npm run build` — 34 Routen
florian.wilde merged commit b2b31ed00a into main 2026-09-03 18:25:36 +00:00
florian.wilde deleted branch feat/phase-4-kiosk 2026-09-03 18:25:36 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: florian.wilde/FireAdministration#5