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.
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
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).
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):
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:
npm run lint läuft nicht — es gibt keine ESLint-Konfiguration im
Repo, next lint fragt interaktiv nach einer. Älter als diese Phase.
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.
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.
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)
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>
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>
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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 nachRechten 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_vorganghat sie als Primärschlüssel, und die Datenbankverweigert dem Anwendungszugang
UPDATEundDELETEdarauf.Konflikte — was sich beim Nachtragen nicht anwenden lässt (Gerät
ausgesondert, Prüfung festgeschrieben), wird weder erzwungen noch verworfen,
sondern landet unter
/terminalund wartet auf eine Entscheidung.Anmeldemarke — QR-Code fürs Handy, mit
AUTH_SECRETsigniert, nirgendsgespeichert, 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 mitorganisationIdund RLS. Vier neuePrü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
Mal geschickt.
jetztSendenhatte das Mutationsobjekt von React Query inden 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).„Kommen", nach dem Laden des eigenen Stands „Gehen". Wer zügig tippt,
drückte ins Leere. Jetzt steht bis dahin ein Platzhalter.
Zum Testen
Terminal einrichten (einmal, der Link enthält das Gerätegeheimnis aus der
.env):Danach anmelden mit Mitgliedsnummer
0001und PIN471203.Was sich lohnt anzusehen:
/kiosk/mangel: InventarnummerINV-0001eintippen,einen Satz dazu, Dringlichkeit antippen. Unter dreißig Sekunden.
„Offline" stellen und weiter erfassen. Die Leiste oben wird rot und zählt.
Netz wieder an: die Warteschlange läuft von selbst los.
/kiosk/monitor, für den Fernseher in der Halle./terminal: Konflikte, wer im Haus ist, Wäsche,Fahrtenbuch.
/anmeldemarke, der QR-Code fürs Handy.Nachweise
npx tsc --noEmitohne Ausgabenpm run test— 238 Tests grün, davon 19 gegen eine echte Datenbanknpx playwright test tests/e2e/kiosk.spec.ts— 18 Tests grün (Desktop und Tablet)npm run build— 34 RoutenDrei Punkte, die mir aufgefallen sind
Sie gehören nicht in diesen PR, sondern ins Dashboard:
npm run lintläuft nicht — es gibt keine ESLint-Konfiguration imRepo,
next lintfragt interaktiv nach einer. Älter als diese Phase.„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.
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
Nachtrag
e500ad6— drei Dinge, die erst der Gesamtlauf der E2E-Suite gezeigt hat:retries: 1). Vier gleichzeitige Arbeiter gegennext devplus 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 --noEmitohne Ausgabenpm run test— 238 grünnpx playwright test tests/e2e/kiosk.spec.ts— 18 grün (Desktop und Tablet)npm run build— 34 Routen