No description
  • JavaScript 62.7%
  • HTML 25.8%
  • CSS 10.7%
  • Python 0.5%
  • Shell 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Maurice Schreiter d30f6a44c9
Some checks are pending
Tests / test (push) Waiting to run
Record that a full checkout sends the table's whole ticket history
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:46:23 +00:00
.agents Delete rollback archives older than 14 days with the morning DEV timer 2026-10-09 09:30:54 +00:00
.ai_brain Cleanup debug files and pack AI brain for GitHub sync 2026-07-01 13:00:51 +02:00
.forgejo/workflows Repository auf Forgejo umstellen und Doku angleichen 2026-08-19 11:26:05 +02:00
assets Ask Ja or Nein for the coaster row of the supplier order 2026-09-22 16:41:32 +02:00
deploy Refresh the DEV database every morning from the Synology 2026-10-09 08:39:12 +00:00
docs Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
scripts Add a probe slip that shows which character table the slip printers know 2026-10-13 11:48:18 +00:00
tests Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
.gitattributes Replace scanned supplier order template with clean vector PDF 2026-09-14 13:08:16 +00:00
.gitignore Ignore local copies of production pub data 2026-10-02 16:03:22 +00:00
APP_REVIEW_2026-07-17.html feat: finish touch oven and archive workflows 2026-07-17 22:57:51 +00:00
auth-client.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
auth.css Add branded startup and self-contained offline launch screen 2026-09-07 15:57:42 +00:00
bar.html Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
bestellungen.pdf Add mobile supplier orders with original PDF export and shared dialog design 2026-09-14 12:58:09 +00:00
CHANGELOG.md Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
checkout-operation.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
CHEF_UEBERSICHT.html Add concise pub system overview for owner 2026-07-11 13:11:46 +00:00
coaster-rounds.js Fix atomic ordering, transaction isolation and persistent table rounds 2026-09-21 15:10:28 +00:00
coaster-transfer.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
diagnostic-shift-log.js Extend diagnostics logging window to 10:00–03:00 Berlin time 2026-10-01 16:16:01 +00:00
favicon.ico Initial backup commit 2026-06-28 22:20:05 +02:00
favicon.svg Initial backup commit 2026-06-28 22:20:05 +02:00
finish-diagnostics.js Log GC pauses, heap and uncoded rejection reasons in shift diagnostics 2026-10-03 08:55:16 +00:00
index.html Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
nachbon.js Show newly added coaster items and combined rows first 2026-09-08 09:19:12 +00:00
offline-outbox.js Fix atomic ordering, transaction isolation and persistent table rounds 2026-09-21 15:10:28 +00:00
offline-start.js Add branded startup and self-contained offline launch screen 2026-09-07 15:57:42 +00:00
offline.html Make touch controls reliable for long presses and prevent text selection 2026-09-08 10:55:32 +00:00
order-insights.js Count nachbons and oven food in the evening's orders 2026-10-05 09:00:05 +00:00
order-operation.js Count nachbons and oven food in the evening's orders 2026-10-05 09:00:05 +00:00
oven-store.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
package-lock.json Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
package.json Add a probe slip that shows which character table the slip printers know 2026-10-13 11:48:18 +00:00
pickup-store.js Reduce pickup backlog read round trips under concurrent load 2026-09-28 12:49:29 +00:00
pickups.js Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
printer-queue.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
printer-ticket-layout.js Produktionsbons lesbar und strukturiert drucken 2026-08-28 12:55:51 +00:00
product-catalog.js Remove duplicate Baileys card and preserve sent orders 2026-09-23 17:56:55 +00:00
products.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
pub-tables.js Unify reservation UI, add full-day rentals and standard gin tonic 2026-09-12 13:29:09 +00:00
quick-sales.js Service tisch-zuerst und mobil gestalten 2026-08-28 11:15:11 +00:00
README.md Extend diagnostics logging window to 10:00–03:00 Berlin time 2026-10-01 16:16:01 +00:00
receipt-archive.js Answer the archive evaluation questions with charts instead of lists 2026-10-03 14:56:43 +00:00
receipt-request.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
receipt-tax.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
reservations-store.js Unify reservation UI, add full-day rentals and standard gin tonic 2026-09-12 13:29:09 +00:00
reservations.js Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
ROADMAP.md Record that a full checkout sends the table's whole ticket history 2026-10-15 12:46:23 +00:00
schedule.css Unify reservation UI, add full-day rentals and standard gin tonic 2026-09-12 13:29:09 +00:00
schedule.js Dienstplan: stabile Chef-Hoehe (kein Sprung nach PIN, konsistent mit Arbeitszeit) 2026-07-15 17:06:32 +02:00
server.js Print accents and the middle dot as plain letters on slips 2026-10-13 11:42:44 +00:00
sqlite-isolated-transaction.js Fix atomic ordering, transaction isolation and persistent table rounds 2026-09-21 15:10:28 +00:00
sqlite-transaction-queue.js Fix atomic ordering, transaction isolation and persistent table rounds 2026-09-21 15:10:28 +00:00
start-screen.css Make touch controls reliable for long presses and prevent text selection 2026-09-08 10:55:32 +00:00
station-admin.js ui: compact combined station header and move print toggle into admin menu 2026-09-20 14:49:37 +00:00
station-audio.js Remove oven feature and background work from Bar station 2026-09-21 12:29:34 +00:00
station-diagnostics.js Harden cross-module transactions and recovery; correct receipt VAT 2026-09-27 15:07:00 +00:00
station-routing.js fix: route Cremant to Theke 2026-09-21 12:23:02 +00:00
station-workspace.js Move food queue above workspace and remove standalone meal cards 2026-09-21 13:43:29 +00:00
style.css Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
supplier-order-store.js Ask Ja or Nein for the coaster row of the supplier order 2026-09-22 16:41:32 +02:00
supplier-orders.js Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
sw.js Make touch controls reliable for long presses and prevent text selection 2026-09-08 10:55:32 +00:00
theke.html Switch on the gold scroll grip for the agreed popups 2026-10-13 14:52:53 +00:00
theme-shell.js Refresh iPad status tint on theme changes and align native background 2026-09-13 10:56:08 +00:00
ticket-validation.js Raise coaster line limit to 200 and warn before collective coasters fill up 2026-10-03 09:29:19 +00:00
timesheet.css Unify reservation UI, add full-day rentals and standard gin tonic 2026-09-12 13:29:09 +00:00
timesheet.js UI-Konsistenz: geteilte .modal-eyebrow, einheitliche h2-Modaltitel, SVG-Zuruecklinks 2026-07-15 15:02:27 +02:00
touch-controls.js Let the oven waiting list fade at its edges instead of showing a scrollbar 2026-10-13 14:23:58 +00:00

Willy's Pub – Kassen- & Bar-System

Vier nachgewiesene Entwurfsfehler behoben (2026-09-28)

Neue Deckeländerungen werden vor Versand gesichert; abgelaufene Entwürfe bleiben ohne automatische Übertragung zur Prüfung erhalten. Fehlgeschlagene Konfliktsicherung meldet keinen falschen Erfolg und bewahrt vorhandene Kopien. Erfolgreich übertragene Entwürfe erscheinen sofort auf dem Gerät. Ausschließlich Service-Entwurfspfad in index.html geändert; keine Server-, Datenbank-, Bestell-, Zahlungs- oder Ofenregeländerung. 736 Tests, sechs Entwurfs- und sechs bestehende Offline-Browserfälle sowie der bestehende Bestell-/UI-Browserlauf bestanden. Nachweis, Grenzen und Übernahme. Noch nicht auf dem Pi installiert. Geräteabnahme in ROADMAP.

Verbleibende Entwurfsverluste isoliert reproduziert (2026-09-28)

Auf Nutzerwunsch ausschließlich Diagnose, keine Appänderung: drei Verlustfälle bei noch ungesendeten Deckeländerungen reproduziert, drei Vergleichsfälle erhalten die Daten. Zusätzlich falsche leere Anzeige nach erfolgreich gespeichertem Offline-Entwurf; erneutes Laden zeigt die Position. Zwei vollständige Chromium-Läufe auf synthetischer DB. Befunde und genaue Grenzen. Der damalige Diagnoseauftrag änderte die App nicht. Die anschließend beauftragte Reparatur ist oben dokumentiert; auf dem Pi noch nicht installiert.

Freigegebene Altbons abgeschlossen (2026-09-28)

Für genau die drei geprüften Bons aus DdaUgl ist die Statuskorrektur vom Betreiber freigegeben. Separates Werkzeug scripts/close-reviewed-tickets.js: standardmäßig nur lesende Vorprüfung, mit --apply geprüfter vollständiger SQLite-Snapshot und atomare Änderung ausschließlich von tickets.status. Keine App-Laufzeitänderung. 15 gezielte Tests einschließlich echter Server-/Socketprüfung bestanden. Auf dem Pi ausgeführt: genau drei Bons korrigiert, geprüfte Sicherung erstellt, PM2 neu gestartet. Nachbericht kwC33Y: null offene Bons, Integrität/FK/Zuordnungen OK, sämtliche Tabellenzeilenzahlen unverändert. Keine erneute Korrektur nötig. Nachweis, Geräteprüfung weiterhin in ROADMAP.

Produktionsbestand ausgewertet (2026-09-28)

Die Pi-Berichte PeeKpM und DdaUgl bestätigen die geprüfte SQLite-Integrität und Zuordnungen ohne Befund. Keine ungesendeten Serverpositionen oder Ofenbelegungen. Die damals drei offenen Altbons stammen vom 11., 13. und 19.09.2026: jeweils Zahlungsnachweis und Barabschluss vorhanden, Gesamtstatus trotzdem open. Nach heutigem Routing benötigen die exportierten Artikel ausschließlich die Bar. Dieser Statuswiderspruch wird von den Strukturprüfungen nicht erfasst; seine historische Ursache ist nicht belegt. Die freigegebene Statuskorrektur ist inzwischen ausgeführt und nachgeprüft (siehe oben). Details im Pi-Bericht.

Für die gezielte Sichtung: node scripts/check-db-readonly.js ./pub.db --open-tickets. Nur lesend; zusätzlich höchstens 20 offene Bons mit Tisch-/Gastbezeichnung, Artikeln, Mengen und Stationsstatus im Bericht. Keine Notizen oder Mitarbeiterdaten. Keine produktive App-Änderung und kein Neustart nötig.

Produktive Datenbank lesend prüfen (2026-09-28)

In einer Betriebspause bei weiterlaufendem PM2: node scripts/check-db-readonly.js ./pub.db. Expliziter Pfad, OPEN_READONLY, query_only und gemeinsame Lesetransaktion; keine Appinitialisierung, Migration, Bereinigung oder neue Abhängigkeit. Bericht unter /tmp/pub-db-pruefung-*/bericht.json mit Integrität/Fremdschlüsseln, JSON-Formaten, ausgewählten Zuordnungen und Bestandszahlen. Geräteentwürfe sind separat zu prüfen. Pi-Befehle als Text, Details in Backup/Betrieb.

Offline-Versand im Browser geprüft (2026-09-28)

Sechs echte Chromium-Ausfallfälle mit isolierter SQLite-Datenbank bestanden: Verbindung vor Versand, verzögerte Serverantwort, verlorene Bestätigung mit Serverneustart, 15-Minuten-Grenze, Artikelerfassung bei gesperrtem Browsernetz und neue Offline-Tischrunde. Zusätzlich 51 gezielte Regressionen bestanden. Nur Testwerkzeug und Dokumentation ergänzt; keine App- oder Datenbankänderung, kein produktiver Neustart für diesen Prüfschritt nötig. Nachweis und Grenzen, Geräteablauf als Text; offene Abnahme ausschließlich ROADMAP.

Kombinierter Pi-Lastlauf bestanden (2026-09-28)

Der vierte kombinierte Lauf (819cc5e, Archiv ODvXdx) ist auf dem Pub-Pi vollständig bestanden und ausgewertet: 140 Runden in 910 s, 2.380 Getränke, 2.240 Ofenportionen, 700 Zahlungen, parallele Fertigmeldungs-/Archivleser und Neustart. Keine unerwarteten Server- oder Readfehler; Mengen und Umsatz stimmen. Bestellung p95 840 ms/max. 1.992 ms. Der erste Altbestandsabgleich braucht weiterhin 6,04 s, beantwortet das erste Gerät aber nach 3,01 s mit der vorgesehenen Warteantwort; danach vollständige Daten. Messwerte und Grenzen im Pi-Messbericht.

Der Lasttest selbst hat die produktive App nicht neu gestartet. Der produktive Neustart nach Pull ist inzwischen im Rahmen der Altbonkorrektur bestätigt (PID 75669). Service/Bar/Theke vollständig neu laden und echte Grundabläufe prüfen; diese Geräteabnahme steht weiterhin ausschließlich in ROADMAP. Kein weiterer Neustart für diese Dokumentation.

Lange Erstabfragen rechtzeitig beantworten (2026-09-28)

Der dritte kombinierte Pi-Lauf benötigt beim ersten Fertigmeldungsabgleich noch 6,07 s. Die bisherigen Optimierungen verkürzen die Arbeit, garantieren aber keine Antwortreserve für jeden Altbestand. Deshalb erhält jetzt auch das zuerst anfragende Gerät nach drei Sekunden die bereits unterstützte Rückmeldung PICKUPS_BUSY. Der eine laufende Abgleich behält seine Sperre bis zum Ende; Wiederholungen starten keinen zweiten. Nach Erfolg fordert pickups_changed zum frischen Laden auf. Keine Teilantworten oder doppelten ACKs; späte Fehler werden gezählt und protokolliert.

Das begrenzt die Wartezeit auf eine Rückmeldung, nicht die Dauer des vollständigen Erstabgleichs. Die Anzeige kann dabei weiterhin einige Sekunden zum Abgleich brauchen. Bestell-/Abholregeln und die 6-Sekunden-Clientfrist bleiben gleich. Der erneute Pi-Lauf ist inzwischen bestanden (siehe oben). Produktiver Server inzwischen neu gestartet (siehe oben).

Fertigmeldungsabgleich mit Altbestand beschleunigt (2026-09-28)

Die ersten beiden kombinierten Pi-Läufe überschritten beim Laden der Fertigmeldungen die 6-Sekunden-Frist. Der zweite Lauf misst 8,69 s trotz Cursorindex und gebündelter Ablaufwrites. Deshalb werden jetzt auch die Lesepakete der Fertigmeldungsansicht auf höchstens 1.024 Kandidaten erweitert: 12 statt 94 Reads bei 12.000 Altmeldungen. Gezielte Abhol-/Übergabeaktionen behalten ihre kleinen Lesezugriffe. Anzeigezeit, Übergaben, Abholung und Schutz vor veralteten Änderungen bleiben gleich. Keine Meldungen werden gelöscht oder als abgeholt markiert.

Der bestandene Lauf ODvXdx prüft die neue Version in seiner separaten Datenbank; PM2 lief dabei weiter. Der spätere produktive Neustart ist inzwischen bestätigt. Der Index wird beim Start automatisch additiv angelegt; keine manuelle Indexanlage und kein npm ci für diese Änderung erforderlich. Messbefund und Grenzen: Pi-Messbericht.

Geführte Prüfung auf dem Pub-Pi (2026-09-28)

Erste Hardware-/Speichermessungen und genaue Testgrenzen stehen im Pi-Messbericht. In einer Betriebspause startet node scripts/check-pi-soak.js einen isolierten 15-Minuten-Konsistenztest mit automatischem Diagnosearchiv. Produktive App und Datenbank bleiben unverändert; es entsteht zusätzliche CPU-/Speicherlast auf dem Pi. Kein npm ci oder Serverneustart zum Einspielen dieses reinen Testwerkzeugs erforderlich. Der Zusatz mixed startet den Mischtest mit Ofenbelegung, Warteschlange, Zahlungen und konkurrierenden Ofenaktionen. Neue Testdatenbanken liegen in einem eigenen Ordner unter ~/pub-testdaten-*, um auf dem Pi die SD-Karte statt /tmp-RAM zu prüfen. Beide 15-Minuten-Läufe sind inzwischen auf dem Pi bestanden und anhand der Archive ausgewertet. Der Abdeckungsstand der gesamten App trennt Entwicklungsprüfungen, Pi-Lastnachweise und Gerätebeobachtungen; nächste Abnahmen in ROADMAP.

node scripts/check-pi-soak.js combined erweitert den Mischlauf um gleichzeitig laufende Fertigmeldungs- und Archivabfragen mit Altbestand, Mango-Übergaben, Undo, Deckelkonflikte, Rückruf und Sofortverkauf/Nachbon. Umfang, Mengen, Antwortfristen und erwartete Fehler stehen im kombinierten Lastprofil. Auch hierfür bleiben PM2 und die produktive Datenbank unverändert.

Gesicherte Entwürfe sichtbar prüfen und erledigen (2026-09-27)

„Deckel prüfen“ öffnet eine eigene Ansicht mit lokal gesichertem Entwurf, Sicherungszeit und aktuellem Deckel. Auch geschlossene Tische und leere Entwürfe werden ausdrücklich angezeigt. Das Fenster folgt der Gestaltung von Abrechnung und Umbuchen, mit Tischname über dem Titel, einheitlichen Positionszeilen und großen Aktionsschaltflächen. Nach dem Abgleich kann „Entwurf erledigt“ → „Entwurf entfernen“ genau diese lokale Sicherung entfernen. Andere Entwürfe, offene Übertragungen und Serverbestellungen bleiben erhalten; bei ausstehenden Übertragungen für denselben Deckel ist das Entfernen gesperrt. „Später prüfen“ und „Schließen“ behalten den Entwurf. Mehrere Sicherungen werden nacheinander angeboten; auf unterschiedlichen Geräten müssen deren lokale Sicherungen einzeln geprüft werden.

Nur Clientänderung: git pull, anschließend Service, Bar und Theke vollständig neu laden. Keine Datenbankbereinigung, Migration oder neue Abhängigkeit für diese Korrektur.

Technischer Audit und Betriebsabsicherung (2026-09-27)

Der Auditbericht beschreibt Befunde, Ursachen, Reparaturen, Tests und die Grenzen der Prüfung für Service, Bar und Theke gemeinsam. Deckelbereinigung, Rückruf, Ofenzuordnung bei Umbuchungen und Stationsabschluss bleiben bei Schreibfehlern konsistent. Rückrufe werden dauerhaft nachgewiesen. Späte Ofenaktionen können keine neu eingelegte Speise abschließen. Schema-/Restorefehler verhindern einen unsicheren Start; beschädigte Ofendaten werden zur Wiederherstellung erhalten.

Bewirtungsbelege trennen Speisen (7 %) und Getränke (19 %) positionsbezogen; gemischte Belege weisen beide Steuergruppen aus. Checkout-/Archivbeträge werden in Cent berechnet, Steuersätze mit den bezahlten Positionen gespeichert. Alte Archive bleiben unverändert. „Diverses Getränk“ ist weiter nur für Getränke gedacht. Quellen und Beispiele im Auditbericht.

Einspielen in einer Betriebspause: geprüfte DB-Sicherung, npm ci, Serverneustart und vollständiger Reload aller drei Module (auth-client.js/station-diagnostics.js 20260927-2). Das alte numerische Ofenabschlussprotokoll wird abgelehnt. Additive Schemaänderungen: ticket_recalls und vier Indizes; keine neue Laufzeitabhängigkeit. In Produktion sind abgeschaltete Authentifizierung und fehlendes TLS jetzt Startfehler.

Druckqueues sind begrenzt; deren Annahme ist weiterhin keine Papierbestätigung. Offene Betriebsabnahmen und verbleibende Risiken stehen ausschließlich in ROADMAP.md.

Weitere Absicherung ohne geänderte Fachabläufe (2026-09-27)

Ofen-Fertigmeldung und dauerhafte Platzfreigabe werden gemeinsam gespeichert. Bei einem Schreibfehler bleibt die Belegung erhalten und die Aktion meldet einen Fehler; sie kann erneut ausgeführt werden. Ein alter, nur halb gespeicherter Abschluss kann seinen Platz freigeben, ohne erneut eine Fertigmeldung oder einen Druckauftrag zu erzeugen. Einlegen, Abschließen und Tischumbuchungen überschreiben keine gleichzeitig laufende Ofenaktion.

Eine Übergabe prüft nur die betroffenen Positionen samt Zulieferungen. Die Archivübersicht verarbeitet höchstens 256 Belege je Leseblock und behält nur die sichtbaren Belege plus Auswertungszähler. Summen, Filter, Tagesgrenzen, Stationsanteile und Prognoseregeln bleiben gleich. Während der Auswertung neu gespeicherte Abschlüsse erscheinen beim nächsten Abgleich.

Der Server lässt eine Archivauswertung gleichzeitig zu. Geräte warten bei Auslastung mit wachsenden Abständen; schnelle Filterwechsel werden zum neuesten gewünschten Stand zusammengefasst. Eine Antwort darf bis zu 30 Sekunden benötigen. Schließen oder Sperren verwirft alte Antworten und Wiederholungen. Der Diagnose-Heartbeat enthält dazu archives mit denselben Zählern wie pickups. Keine neue Migration oder Laufzeitabhängigkeit; Server neu starten und alle Geräte vollständig neu laden. Vor-Ort-Abnahme ausschließlich in ROADMAP.md.

Stabilität bei Fertigmeldungen und verzögertem Versand (2026-09-27)

Alte Fertigmeldungen werden auch bei unterschiedlichen Fertigzeiten auf demselben Bon zuverlässig ausgeblendet. Das bestätigt weiterhin keine Abholung; offene Übergaben und wartende Zulieferungen bleiben erhalten. Der Server verarbeitet die Liste portionsweise und lässt nur eine Listenabfrage gleichzeitig zu. Weitere Geräte versuchen den Abgleich mit wachsenden, leicht versetzten Abständen erneut. Es entsteht keine Warteschlange vollständiger Listen im Arbeitsspeicher.

Bei einer bereits bekannten Tischrunde darf eine fehlende Bestätigung des Entwurfsspeicherns den Bestellversand nicht still abbrechen. Die Bestellung wird über die vorhandene Outbox mit unveränderter ID übertragen und bis zur Bestätigung wiederholt, höchstens 15 Minuten. Echte Ablehnungen und Rundenwechsel bleiben geschützt; der Entwurf bleibt zur Prüfung erhalten. Ist die erste Tischrunde noch nicht bestätigt, weist die Oberfläche ausdrücklich auf den noch ausstehenden Versand hin.

Einspielen in einer Betriebspause: normale DB-Sicherung prüfen, Server aktualisieren und neu starten, anschließend Service, Bar und Theke vollständig neu laden (pickups.js?v=20260927-1). Keine neue Datenbankmigration oder Laufzeitabhängigkeit. Die vorhandenen Altmeldungen werden beim nächsten Listenabgleich verarbeitet; keine Tabelle manuell leeren. Vor-Ort-Abnahme und Pi-Messung stehen ausschließlich in ROADMAP.md.

Die bestehenden 30-Sekunden-Diagnosezeilen enthalten jetzt pickups: active (0/1), completed, busy, failed sowie last_ms und max_ms. Zähler und Maximum gelten seit Prozessstart. Diagnosepakete und core.<PID> sind vom statischen HTTP-Zugriff ausgeschlossen. Reproduzierbarer Speichertest ausschließlich mit künstlichen Daten im RAM: node --expose-gc --max-old-space-size=64 scripts/check-pickup-load.js.

Baileys bereinigt (2026-09-23)

Baileys gibt es einmal mit 2 cl zu 2,50 € und 4 cl zu 4,50 € sowie separat als „Baileys on Ice“ (4 cl zu 4,50 €). Der doppelte 2-cl-Artikel wurde entfernt. Bereits gesendete Positionen des entfernten Artikels bleiben gültig. Nach Einspielen Server in einer Betriebspause neu starten und Geräte vollständig neu laden. Ungesendete Positionen des entfernten Artikels entfernen und neu hinzufügen.

Bierdeckel mit Ja oder Nein statt Menge (2026-09-22)

Die Zeile „Bierdeckel“ der Getränkebestellung fragt jetzt Ja oder Nein ab statt einer Menge. Ein Tipp auf ihr Feld öffnet an derselben Stelle wie der Zahlenblock zwei große Antworten; ein erneuter Tipp auf die aktive Antwort hebt die Wahl wieder auf. Ohne Antwort steht dort wie bei allen Zeilen ein Strich – in der Liste und im PDF. Die anderen 61 Zeilen sind unverändert Mengenzeilen; physische Zifferntasten wirken dort weiter, bei Bierdeckel nicht.

Im Händler-PDF steht „Ja“ beziehungsweise „Nein“ mittig im vorhandenen Mengenfeld der Bierdeckel-Zeile, in derselben Schrift wie die Mengen. Vorlage, Spalten und Reihenfolge sind unverändert, die erzeugte Datei bleibt genauso lang wie bisher. Eine Antwort ist keine Einheit: Kontrolle, Übergabe und Historie nennen Einheiten nur, wenn wirklich Verpackungen bestellt sind, zählen die Zeile aber als Position.

Der Pub-Server prüft jede Zeile nach ihrem eigenen Vertrag – Mengenzeilen nur 1–9999, die Bierdeckel-Zeile nur „Ja“ oder „Nein“. Der Artikelkatalog steht damit auf Version 4; ein noch nicht neu geladenes Gerät erhält beim Bestätigen den Hinweis, die App neu zu laden. Ein vor dem Update gespeicherter Entwurf behält seine übrigen Mengen und weist einmal darauf hin, dass Bierdeckel jetzt eine Antwort braucht. Gespeicherte Bestellungen der Historie bleiben unverändert.

Nach Einspielen Server in einer Betriebspause neu starten, damit er denselben Katalog liest, und alle Servicegeräte vollständig neu laden (CSS 20260922-5, supplier-orders.js 20260922-1, Katalog und Vorlage ?v=4). Keine Migration.

Ruhige Wechsel in Abschlüssen und Reservierungen (2026-09-22)

Ein Wechsel von Heute/Woche/Monat, ein Tipp auf die Blätterpfeile, Gesamt/Theke/Bar, der Produktfilter, die Suche und die automatische Aktualisierung nach einer Kassierung lassen die Auswertung jetzt stehen, wo sie ist. Vorher wurde der ganze Block für die Dauer der Abfrage durch eine 44 px hohe Ladezeile ersetzt und danach wieder aufgebaut – das war das Auf- und Zuklappen. Jetzt bleiben die alten Zahlen sichtbar und werden dezent abgedunkelt, bis die neuen da sind; ein Ladeplatzhalter erscheint nur noch beim ersten Laden nach der PIN-Freigabe, nach einem Fehler und beim Rückweg aus einem Abschlussdetail.

Die drei Bereichsköpfe „Artikel & Größen“, „Tage & Uhrzeiten“ und „Hinweise & Ausblick“ sind über Abgleiche hinweg dieselben Elemente. Ein offener Bereich bleibt offen, behält den Tastaturfokus und wird nur neu aufgebaut, wenn sich sein Inhalt wirklich geändert hat. Die seitliche Bewegung der Abschlussliste gehört wieder allein dem Wechsel zwischen Liste und Detailansicht.

Im Reservierungsbuch klappen die Bereichslisten mit derselben ruhigen Bewegung wie in den Abschlüssen: Deckkraft und Verschiebung statt Höhe. Eine Änderung von einem anderen Gerät aktualisiert einen offenen Bereich still, ohne ihn nachzucken zu lassen. Auch „Weitere Angaben“ im Formular folgt dieser Bewegung.

Nach Einspielen alle Geräte vollständig neu laden (CSS 20260922-4, Reservierungen 20260922-1). Kein Serverneustart nötig, es betrifft nur die Darstellung.

Abschlüsse: drei Bereiche statt einer langen Bahn (2026-09-22)

„Details & Trends“ ist in drei benannte Bereiche aufgeteilt, die einzeln aufklappen; es ist immer nur einer offen, und alle starten geschlossen:

  • Artikel & Größen – Top und Flop 10 nach Menge und Umsatz, die vollständige Artikeltabelle je Größe, darunter „Ohne Verkauf im Zeitraum“.
  • Tage & Uhrzeiten – Tagestabelle des Zeitraums und der Stundenverlauf mit Tabelle.
  • Hinweise & Ausblick – Betriebstagsgrenze, Bereichshinweise, genauer Vergleichszeitraum und die Wochenprognose.

Die Erklärtexte stehen damit nicht mehr zwischen den Zahlen. Die Hauptansicht ist unverändert: drei Kennzahlen, Vergleichszeile, Blätterleiste und Tagesbalken.

Die Artikeltabelle zeigt zunächst 40 Zeilen; „Alle … Größen zeigen“ baut den Rest auf ausdrücklichen Tipp auf. Weil die meistverkauften Größen oben stehen, sind die relevanten Entscheidungen ohne diesen Schritt sichtbar.

Damit ist auch das Ruckeln beim Aufklappen erledigt. Ein Bereich entsteht erst beim Öffnen und gibt seinen Inhalt beim Schließen wieder frei; ein Monat mit 320 verkauften Größen bringt geschlossen 108 statt 2.514 Elemente ins Dokument. Ein Neuladen während des Dienstes – nach jeder Kassierung – baut nur noch den offenen Bereich neu auf. Die Bewegung verwendet weiterhin 220 ms, animiert aber Deckkraft und Verschiebung statt der Höhe: Höhe erzwingt in jedem Frame ein neues Layout des gesamten Inhalts. node scripts/profile-archive-insights.js hält diese Kosten reproduzierbar fest (ohne Server und ohne Betriebsdaten); Frame-Zeiten auf den Tablets sind damit nicht gemessen.

Nach Einspielen alle Geräte vollständig neu laden (CSS 20260922-3). Für diese Änderung ist kein Serverneustart nötig, sie betrifft nur die Darstellung.

Abschlüsse: Größen getrennt, Top/Flop 10 und Artikel ohne Verkauf (2026-09-22)

Unter „Artikel & Größen“ trennen Ranglisten und die neue Artikeltabelle jetzt die Größen: „Licher Pilsner 0,5 l“ und „Licher Pilsner 0,3 l“ sind eigene Zeilen, weil sie unterschiedliche Entscheidungen sind. Die Größe steht als ruhiger Goldmarker hinter dem Namen. Der Produktfilter oben bleibt bewusst auf Artikelebene und fasst alle Größen eines Artikels zusammen.

Top und Flop zeigen je zehn statt fünf Einträge, nach Menge und nach Umsatz.

Neu ist „Alle verkauften Artikel“: eine Zeile je Artikel und Größe mit Menge, Umsatz und Anteil am Umsatz des gewählten Bereichs, meistverkaufte zuerst. Gezeigt werden bis zu 400 Größen; mehr wird ausdrücklich als gekürzt gekennzeichnet.

Darunter steht „Ohne Verkauf im Zeitraum“: die Katalogartikel, die im gewählten Zeitraum kein einziges Mal verkauft wurden, gruppiert nach Warengruppe. Diese Liste kommt aus dem Katalog, nicht aus dem Archiv – nicht verkaufte Artikel sind keine Archivpositionen. Im Service verborgene Artikel (die Tonic-Aufpreise) stehen nicht darin, Saison- und Eventartikel dagegen schon. Bei „Theke“ oder „Bar“ enthält die Liste nur Artikel der jeweiligen Station. Mit Produktfilter oder Suche entfällt sie, weil sie dann nichts Sinnvolles aussagt.

Einkaufspreise, Margen und Litermengen sind bewusst nicht enthalten.

Nach Einspielen Server in einer Betriebspause neu starten und alle Geräte vollständig neu laden (CSS 20260922-2). Keine Migration, keine Änderung an gespeicherten Abschlüssen.

Abschlüsse: Zeiträume blättern und eindeutige Vergleiche (2026-09-22)

Unter „Heute / Woche / Monat“ steht eine Blätterleiste: linker Pfeil einen Betriebstag, eine Woche oder einen Monat zurück, rechter Pfeil wieder vorwärts. Die Mitte nennt den angezeigten Zeitraum („Dienstag, 22.09.2026“, „21.09.–27.09.2026“, „September 2026“) und führt mit einem Tipp zurück zum aktuellen Zeitraum. Der Rückwärtspfeil ist gesperrt, sobald davor keine archivierten Abschlüsse liegen; der Vorwärtspfeil ist im aktuellen Zeitraum gesperrt. Bereich (Gesamt/Theke/Bar), Produktfilter und Suche bleiben beim Blättern erhalten. Ein Wechsel der Zeitraumart und jede neue PIN-Freigabe starten wieder im aktuellen Zeitraum.

Alle Grenzen rechnet der Server aus Betriebstagen ab 06:00 Uhr Berlin; Geräte senden nur Zeitraumart und Schrittzahl, keine Zeitstempel. Zurück reichen 520 Schritte (Tage, Wochen oder Monate).

Die Vergleiche sind jetzt ausdrücklich benannt:

  • Ein abgeschlossener Zeitraum wird vollständig gegen den vollständigen Vorzeitraum gestellt („Vollständiger Zeitraum gegen vollständigen Zeitraum“).
  • Ein laufender Zeitraum vergleicht bis zum gleichen Stand („Tag 22 von 30“), gemessen in Betriebstagen plus Uhrzeit. Die Zeitumstellung verschiebt das Fenster damit nicht mehr.
  • Ist der Vorzeitraum kürzer als der bisherige Fortschritt – etwa Februar gegen den 31. März –, wird er vollständig gewertet und genau so beschriftet („28 statt 31 Tage“).
  • Monat vergleicht gegen den Vormonat, Woche gegen die Vorwoche. Ein Betriebstag vergleicht gegen denselben Wochentag der Vorwoche, weil der feste Ruhetag den Vortag als Referenz unbrauchbar macht; der Vortag steht zusätzlich als ruhige Zahl darunter.

Die Monatsansicht hat jetzt dieselbe Tagesstruktur wie die Woche: ein Balken je Betriebstag mit Umsatzspitze, Tag mit den meisten Abschlüssen und einer vollständigen Tagestabelle unter „Tage & Uhrzeiten“. Zukünftige Tage bleiben ohne Wert. Der Ausblick bleibt der laufenden Woche vorbehalten.

Nach Einspielen Server in einer Betriebspause neu starten und Bar/Theke sowie Servicegeräte vollständig neu laden (CSS 20260922-1). Keine Migration, keine Änderung an gespeicherten Abschlüssen; die Auswertung liest weiterhin nur receipt_archives.

Getränkebestell-PDF und Archivzeiträume (2026-09-21)

Leere Mengenfelder im neu exportierten Getränkebestell-PDF zeigen einen zentrierten Strich. Nach git pull --ff-only Service beziehungsweise Stationsseite vollständig neu laden und das PDF neu erzeugen; vorhandene PDF-Dateien ändern sich nicht. Das Abschlussarchiv nennt den ausgewerteten Zeitraum oberhalb der Kennzahlen und kennzeichnet den Vormonat ausdrücklich als Vergleich. „1 verkauft“ in der Flop-Liste ist eine Verkaufsmenge; mehrere Artikel können dieselbe Menge haben. Nicht verkaufte Artikel sind nicht Teil der Ranglisten; sie stehen seit dem 22.09.2026 in einer eigenen Liste (siehe oben). Für diese Anzeigeänderung ist kein zusätzlicher Serverneustart nötig.

Atomare Bestellungen und Tischrunden (2026-09-21)

Bestellungen speichern Deckelpositionen, Produktionsbon und Wiederholungsnachweis gemeinsam. new_ticket benötigt jetzt round_id, base_revision und eine stabile order_line_id je Position. Der Server bestätigt und verteilt erst nach dem Commit. Wiederholung derselben Bestell-ID liefert das gespeicherte Ergebnis; andere Nutzdaten unter derselben ID werden abgelehnt. Unabhängige Ergänzungen derselben Runde dürfen parallel eintreffen, bereits bestellte oder zwischenzeitlich veränderte Positionen werden nicht nochmals produziert. Nachbons werden im gleichen Vorgang abrechenbar, bleiben aber ohne Produktionsauftrag.

coaster_rounds hält unveränderliche Runden-IDs und abgeschlossene Revisionsstände fest. Eine neue Bewirtung unter demselben Deckelnamen erhält eine andere ID und einen höheren Revisionsstand. Auch ältere Änderungsanforderungen ohne Rundenfeld können so keinen alten Revisionsstand wiederverwenden. Leergekassierte Sofortverkaufsplätze beginnen eine neue Runde. Bestehende offene Deckel werden beim Start additiv zugeordnet; Belege und Produktionsdaten werden nicht umgeschrieben. order_operations und order_lines sichern Bestellwiederholungen.

Verwaltungstransaktionen und die Sofortverkaufs-Migration laufen auf einer exklusiven, seriell genutzten Verbindung (sqlite-isolated-transaction.js). Normale Deckelwrites auf der Hauptverbindung können damit nicht in den Rollback einer Verwaltungstransaktion geraten. Bestellung, Verwaltung und Checkout teilen dieselbe Transaktionswarteschlange auf checkoutDb; Umbuchungen behalten ihre eigene Verbindung/Warteschlange. Es wird keine zusätzliche dauerhafte SQLite-Verbindung geöffnet. Archivlesezugriffe verwenden die dauerhaft serialisierte Hauptverbindung, nicht die Checkout-Transaktionsverbindung. Damit belegen wartende Reads nicht den Worker-Pool, den der schreibende Vorgang für seinen Commit benötigt.

Update: In einer Betriebspause mit leerem Ofen und geprüfter DB-Sicherung einspielen, Server neu starten und alle Service-/Stationsseiten vollständig neu laden. Alte Clients können keine neuen Produktionsbons mehr senden; ihre Entwürfe müssen vor dem Update geklärt sein. Die neue Oberfläche wartet vor Bestellversand/Offline-Replay auf die Rundenfreigabe vom Server. Kein automatisches Live-Deployment durch die Prüfskripte.

Bei Konflikten bleiben Entwürfe lokal erhalten. „Deckel prüfen“ zeigt den gesicherten Snapshot zum Abgleich; er überschreibt den Serverstand nicht automatisch. Abgelehnte Bestellungen landen zur Prüfung bei den gespeicherten Offline-Entwürfen. Ein Entwurf einer geschlossenen Runde wird nicht auf eine neue Bewirtung desselben Tisches übertragen. Unbestätigte Bestellungen bleiben bis zum ACK beziehungsweise zur bisherigen 15-Minuten-Grenze in der Outbox. Eine erstmals offline angelegte Runde benötigt vor dem ersten Senden eine Serververbindung; ihr lokaler Entwurf bleibt erhalten.

Reproduzierbare Prüfungen ohne produktive Daten:

node --test tests/consistency-socket.test.js
node scripts/check-consistency-soak.js # 15 Minuten, eigene DB und eigener Server
PLAYWRIGHT_MODULE=/pfad/zu/playwright CHROMIUM_BIN=/pfad/zu/chromium node scripts/check-order-browser.js

Automatische Betriebsdiagnose auf dem Pi (2026-09-21)

Die kleine Diagnose für langsame Vorgänge aller Geräte läuft automatisch. Einmalig in einer Betriebspause aktualisieren:

git pull --ff-only
pm2 restart willyspub

Danach Bar/Theke vollständig neu laden. Die Diagnose läuft während jedes Dienstes 10:00 bis 03:00 Uhr in Europe/Berlin, inklusive Sommer-/Winterzeit. Außerhalb werden keine Diagnosevorgänge aufgezeichnet. Automatisch entsteht im Projekt:

runtime-diagnostics/dienst-2026-09-21.jsonl

Diese Datei umfasst den 21.09. ab 10 Uhr bis zum 22.09. vor 03 Uhr. Einträge nach Mitternacht gehören weiterhin zum Startdatum. Neustarts hängen an die bestehende Datei an; nichts wird überschrieben oder automatisch gelöscht. Die Datei kann direkt zur Auswertung weitergegeben werden, ein Export ist nicht nötig. Auf dem Pi liegt sie normalerweise unter ~/willyspub/runtime-diagnostics/.

Optional eine Zusammenfassung anzeigen (Datum = Dienstbeginn):

python3 scripts/export-finish-diagnostics.py --date 2026-09-21

Der Befehl liest die vorhandene Dienstdatei und zeigt Anzahl, p50/p95/max, Fehler, fehlende Berichte sowie die langsamsten Vorgänge. --output <Datei> erstellt auf Wunsch eine Kopie ohne Überschreiben vorhandener Dateien. --logs <Muster> unterstützt ältere PM2-Logs einschließlich gzip; auch diese werden nach dem Berliner Dienstfenster gefiltert.

Erfasst werden Servereingang und ACK von finish_ticket_station_batch/finish_oven_slot, kumulative Teilzeiten einschließlich DB-Wartezeit, Client-ACK und beobachtete Entfernung des Abschlussbuttons/Ofenartikels, Timerverzögerung und Touch/Hintergrundstatus. DOM-Abfrage alle 100 ms nur während eines Vorgangs; zwei Animationsframes schätzen eine Darstellungsmöglichkeit, nicht den tatsächlichen Bildschirm-Paint. Messstart ist der Aktionsversand, nicht die physische Fingerberührung. Server und Client messen jeweils monoton; Geräteuhren werden nicht voneinander subtrahiert.

Zusätzlich werden Bestellen (new_ticket), Deckeländerung/-löschung, Sofortverkauf, Kassieren, Umbuchen, Bonrückruf, Einlegen in den Ofen und einzelne Stationsaktionen serverseitig für alle Geräte gemessen. Pro schnellem Vorgang eine Ergebniszeile mit Startzeit, Dauer, Aktion, Socketkennung/Rolle und Ergebnis; ab einer Sekunde zusätzlich activity_pending, nach 30 Sekunden ohne Antwort activity_unconfirmed. Das ist nur ein Diagnoseende, kein Abbruch der Geschäftsaktion; eine spätere Antwort wird unverändert zugestellt. Maximal 256 solche Messungen gleichzeitig. Keine Payloads und keine zusätzlichen Geräteanfragen. Auswertung gruppiert diese Laufzeiten nach Aktion. Client-/DOM-Messung bleibt auf Stations-/Ofenabschlüsse beschränkt; Gerätekennung gilt pro Socketverbindung.

Gemeinsame dbGet/dbRun/dbAll melden Fehler und Aufrufe ab 250 ms ohne SQL/Parameter; direkte DB-Aufrufe in weiteren Modulen sind dadurch nicht vollständig instrumentiert. Alle 30 Sekunden: Node-CPU, RSS, maximale Event-Loop-Verzögerung und Socketanzahl (eingebetteter Service zählt als eigene Verbindung). Verbindungen/Abbrüche tragen Zeitstempel. Keine Gäste-, Tisch-, Artikel-, Passwort- oder Tokeninhalte in Diagnosezeilen. Bestehende andere Logs werden nicht verändert.

Dateigröße: maximal 4 MiB (4.194.304 Bytes) pro Dienst, einschließlich Neustarts. Ein normaler Dienst benötigt voraussichtlich weniger; die tatsächliche Größe hängt von der Aktivität ab. 30 Dateien belegen höchstens 120 MiB, 365 höchstens etwa 1,43 GiB. Keine automatische Löschung alter Dateien. Zusätzlich maximal 12.000 Diagnosezeilen pro Dienst und Prozess. Beim Dateilimit bleibt eine file_limit-Zeile erhalten; danach entfallen weitere Einträge für diesen Dienst. Fehlende Berichte allein beweisen keinen Verbindungsabbruch. Einträge werden asynchron etwa einmal pro Sekunde gesammelt angehängt, ohne Datenbankwrites. Maximal 256 KiB warten im Puffer plus ein laufender Schreibblock gleicher Größe; Überlauf/Dateifehler unterbrechen keine Geschäftsaktion. Bei abruptem Stromverlust können noch ungeflushte Einträge fehlen. Geordneter Neustart flusht den Puffer innerhalb des bestehenden Shutdown-Zeitlimits.

Die Dateien sind von Git und HTTP-Auslieferung ausgeschlossen. Bestehende PM2-Logs bleiben unverändert erhalten; neue Diagnosedaten werden nicht zusätzlich darin dupliziert. STATION_DIAGNOSTICS=0 bleibt nur als technischer Not-Aus, nicht als Bedienungsschritt.

Thekentisch direkt bedienen (2026-09-21)

Jeder Klick auf „Kombi“ oder „Bedienen“ wählt an der Bar „Bar - Theke“, an der Theke „Pub - Theke“. Auch beim Laden einer gespeicherten Ansicht ist die eigene Theke gewählt. Andere Tische bleiben manuell wählbar. Laufende oder ungeklärte Umbuchungen werden nicht unterbrochen. Stationsseiten vollständig neu laden; kein Serverneustart erforderlich (station-workspace.js 20260921-1).

Bar-Login nach Ofenentfernung (2026-09-21)

Die fehlende Socket.IO-Einbindung der Bar ist wiederhergestellt. Sie hatte nach korrekter Passworteingabe den Start verhindert und später den WLAN-Hinweis ausgelöst. Nach git pull die Bar-Seite vollständig neu laden; für diese HTML-Korrektur ist kein zusätzlicher Serverneustart erforderlich. Der Ofen bleibt aus der Bar entfernt. Der neue Browsercheck scripts/check-station-login.js prüft den echten Login und Socket-Verbindungsaufbau mit isolierter Serverkopie und Testdatenbank.

Bar ohne Ofen (2026-09-21)

Die Bar zeigt in Fokus, Kombi und Bedienen keinen Ofen mehr. Ofendock, Dialoge, Sekundentimer und Ofenton entfallen dort vollständig. Der Server sendet Ofenstände auch nicht an den eingebetteten Service einer Bar-Station. Die Theke führt weiterhin Warteliste, Belegung, Timer, Alarm und Abschluss. Speisen lassen sich im Service der Bar weiterhin bestellen und werden regulär an die Theke geroutet. Nach Einspielen Server neu starten und alle Stationsansichten neu laden (station-audio.js 20260921-1).

Mehr Luft über der mobilen Tischwahl (2026-09-20)

Die Kopfzeile der Tisch-/Deckelansicht hat bis 700 px Breite oben 24 statt 12 px Innenabstand. Im eigenständigen Service kommt die Geräte-Safe-Area hinzu; im eingebetteten Service bleibt es bei 24 px. Nach Einspielen Clients neu laden (CSS 20260920-13); für diese CSS-Korrektur ist kein Serverneustart nötig.

Gin mit eindeutiger Tonic-Auswahl (2026-09-20)

Gin-Karten trennen „2 cl pur“ von „Mit Tonic“ (4 cl). Der Tonic-Button öffnet den bestehenden Auswahldialog mit Schweppes (inklusive), Fever-Tree und Thomas Henry (je +2,50 €), jeweils mit dem vollständigen Getränkepreis. Auch „Gin Tonic Standard“ nutzt diese Auswahl und bleibt ohne Pur-Variante. Gin und Tonic bilden genau eine Position auf Deckel, Barbon, Gästebeleg und im Archiv. Bestehende Preise bleiben gleich. Die bisherigen separaten Aufpreisartikel sind im Service einschließlich Suche, Favoriten und Eventkarte verborgen; alte/offline gespeicherte Buchungen bleiben gültig.

Technisch erzeugt products.js aus dem bisherigen Grundpreis drei reguläre Varianten; tonicChoices steuert nur die Auswahl. Die vorhandene serverseitige Katalogprüfung validiert Sorte und Gesamtpreis. Die gespeicherte Variante bleibt unverändert; getProductionItemDisplay trennt auf Stations-/Arbeitsbons die Größe „4 cl“ von der Tonic-Sorte. Diese steht darunter in den vorhandenen „+“-Zusatzboxen beziehungsweise im Sonderwunschbereich des Papierbons. Weitere Notizen bleiben erhalten. Die bisherige Variante „4 cl + Tonic“ bleibt für Altbestellungen erhalten. Neue Buchungen nennen das gewählte Tonic ausdrücklich im Variantenlabel. Nach Einspielen Server neu starten und alle Clients neu laden (Produkte 20260920-2, CSS 20260920-12), damit Server und Geräte denselben Katalog verwenden.

Kompakter Stationskopf im Fokusmodus (2026-09-20)

Bar und Theke nutzen im Fokusmodus 12 statt 24 px Innenabstand oben und unten. Der einzeilige Tablet-Kopf ist damit 73 statt 97 px hoch; die Bons gewinnen 24 px. Schriften, Symbole, Touchflächen und Anordnung bleiben gleich. Schmale Ansichten behalten ihre bestehenden Umbrüche. Die Höhe hängt nicht von wartendem Essen ab. Kombi und Bedienen bleiben unverändert. Nach Einspielen Clients neu laden (CSS 20260920-11); diese CSS-Anpassung benötigt keinen Serverneustart.

Automatische Ofenwarteliste (2026-09-20)

Ofengerichte erscheinen beim Bestelleingang direkt in der gemeinsamen Ofenwarteliste. Reine Essensaufträge erzeugen keine Karte im Bonbereich. Gemischte Bons behalten ihren gewohnten Kopf mit Raumzeichen, Tisch, Nummer und Zeitsiegel. Rechts neben „Fertig“ steht ein Button mit Goldakzent, Ofensymbol und „2 Essen“, der den passenden Auftrag in der Warteliste öffnet. Der grüne Fertigbutton bleibt die größere Hauptaktion; reine Getränkebons nutzen weiter die volle Buttonbreite. Keine zusätzliche Zeile. Nach Getränkeabschluss verschwindet der Bon; wartendes Essen bleibt in der gemeinsamen Leiste enthalten. Getränke lassen sich bei vollem Ofen mit „Fertig“ abschließen. Ihr Arbeitsbon und ihre Fertigmeldung enthalten kein wartendes Essen. Bestellung, Deckel und Zahlung bleiben zusammen.

Der Essensbutton verwendet dieselbe Buttonkomponente wie „Fertig“: gleiche Schrift, Symbolgröße, Rundung, Konturstärke und Höhe. Goldtext/-kontur markieren Essen, Grün die Fertigaktion. Der abgestimmte Hintergrund bleibt im Light Mode neutral (--bg-header), im Dark Mode dunkel (--gold-tint-solid). Unterkante und Zusatzschatten entfallen am Button. Die obere Leiste behält Goldunterkante und Schatten. Bei schmalen Bons stehen die Symbole bei beiden Buttons über ihrem Text, innerhalb derselben Buttonzeile und Höhe.

Die sichtbare 44-px-Leiste sitzt in Fokus/Kombi direkt unter dem Stationskopf und außerhalb der scrollenden Bonliste. In Bedienen verschiebt sich derselbe Button an den oberen Rand der Servicefläche; die Ansichtssteuerung berücksichtigt die 56-px-Touchhöhe. Die Ofenetagen mit ihren Timern bleiben unten. Ob eine oder acht Portionen warten, der Platzbedarf der gemeinsamen Leiste bleibt gleich. Sie zeigt das Ofensymbol, „8 Portionen warten“ und rechts die älteste Wartezeit. Sehr schmal steht „8 warten“. Die Vorschau zeigt die nächsten vier Artikelpositionen in Wartelistenreihenfolge, älteste zuerst, jeweils mit Tisch, vollständiger Menge und Gericht. Auch bei neuen Eingängen bleibt diese Reihenfolge sichtbar. Gleiche Gerichte verschiedener Aufträge bleiben getrennt. Bei Platzmangel wechselt die Vorschau alle sechs Sekunden zwischen vollständigen Wortabschnitten, ohne Laufanimation. Berührung und offener Dialog pausieren. Unter 700 px stehen Tisch/Gericht kompakt untereinander; die Altersangabe ist dort nur im zugänglichen Namen enthalten, damit die Vorschau Platz hat. Die Leiste bleibt 44 px hoch.

Neue Essensaufträge setzen „Neu“ auf Goldfläche rechts vor den Chevron, bis die Warteliste geöffnet wird. Der Marker ersetzt rechts die sichtbare Altersangabe, niemals die Auftragsvorschau. Mehrere Eingänge behalten denselben Marker und die älteste Vorschau. Jeder echte neue Eingang startet den 500-ms-Goldimpuls erneut. Reload und Duplikate erzeugen keinen neuen Hinweis; Reduced Motion behält den Text ohne Bewegung. Entfernte/eingelegte Aufträge verschwinden aus der Vorschau und werden aus der ungesehenen Gruppe entfernt. Während der Etagenwahl eintreffendes Essen bleibt bis zur Rückkehr in die Liste markiert. Ab 15 Minuten gilt Warning auf Kontur und Zeit. 6 px echtes Buttonpadding schützen die 44-px-Fläche; gehaltene Stationsaktionen frieren den Einstieg ein. Ohne wartendes Essen entfällt die Zeile. Antippen öffnet den bestehenden Ofendialog mit Tisch, Menge, Gericht, Sonderwunsch und Wartezeit; der Essensbutton neben „Fertig“ springt zum passenden Auftrag. Älteste Bestellungen stehen zuerst. „In den Ofen“ wechselt im selben Dialog zur bekannten Etagenwahl; „Zur Warteliste“ führt mit dem gemeinsamen umrandeten Sekundärbutton samt Linkspfeil zurück (48 px Touchhöhe). Je Auswahl wird eine Portion belegt. Weitere Portionen bleiben stehen. Timer beginnen erst nach bestätigtem Einlegen. Nach erfolgreichem Einlegen bleibt die Liste für weitere Gerichte offen; ist sie leer, schließt der Dialog. Ofenabschluss und Druckschalter gelten wie bisher. Die Warteliste verwendet die dezente warme Gold-Tönung und Kontur der Etagenwahl. Sie folgt den vorhandenen Auswahlkarten: Tisch als Überschrift, darunter Menge und Gericht, Wartezeit als ruhiger Text, Sonderwünsche in Goldtext. Die älteste Bestellung steht oben; Rangnummern, Zeit-Badges und Sonderflächen entfallen. Im Wartelistenmodus bleiben Titel und Schließen stehen; nur die Liste scrollt mit eigener, in Hell/Dunkel gestalteter Scrollspur und mindestens 12 px Inhaltsabstand. Ein Tipp auf den Bonhinweis öffnet die Liste beim passenden Auftrag. Teilmengen aktualisieren den Hinweis; nach dem letzten bestätigten Einlegen entfällt er. Bezahlte Runden bleiben separat, Bonzähler und Bonnummern zählen weiterhin nur gewöhnliche Stationsaufträge. Die sichtbaren Etagenkarten bleiben unverändert. Die Etagenbuttons haben einen unsichtbaren 4-px-Trefferrand einschließlich der runden Ecken; in der 12-px-Lücke bleiben 4 px neutral. So verwirft der zusätzliche Touch-Treffertest keine Randtaps, die der Browser bereits dem Button zugeordnet hat. Halten löst erst beim Loslassen aus; Abrutschen, Scrollen und Durchklicken bleiben gesperrt.

Die Warteliste verwendet die vorhandenen persistenten tickets; kein zusätzlicher Bon und keine neue Datenbanktabelle. completed_stations beschreibt den Getränke- beziehungsweise normalen Stationsauftrag. Solange Ofengerichte warten, bleibt das Ticket offen, auch nach Kassieren oder Neustart. Das letzte Einlegen schließt einen bereits an allen Stationen erledigten Restbon atomar ab. Fertigmeldungen für Speisen entstehen ausschließlich beim Ofenabschluss. Dessen Bon erhält auch Sonderwünsche. Der zentrale Katalogresolver getOvenLane trennt Ofengerichte von normalen Artikeln; Namen-Fragmente im Thekenrendering sind dafür nicht mehr maßgeblich.

Update der Anzeige: nach Pull alle Stationsseiten vollständig neu laden (CSS 20260921-3, Workspace 20260921-2). Dafür ist kein Serverneustart nötig. Die ursprüngliche Ofenwarteliste benötigt beim erstmaligen Einspielen einen Serverneustart. Warteliste und laufende Belegung sind Bestandteil der normalen DB-Sicherung. Geräteabnahme ausschließlich in ROADMAP.md; Tests verwenden isolierte Datenbanken.

Teesortenauswahl und Wodka Sour (2026-09-20)

Tee (2,50 €) öffnet beim Buchen die vorhandene Auswahl: Pfefferminz, Kamille, Schwarz, Grün, Früchte, Rooibos, Ingwer-Zitrone und Kräuter. Die Sorte wird als Artikelnotiz auf Deckel und Bon mitgegeben; Abbrechen bucht nichts. Wodka Sour steht unter „Cocktails · Sours“: regulär 8,50 €, Happy Hour 7,50 €. Beide Getränke laufen an die Bar. Nach Einspielen Server in einer Betriebspause neu starten (Serverkatalog) und alle Service-/Stationsansichten neu laden.

Datenbankzugriffe bei gleichzeitiger Bedienung (2026-09-19)

Die gemeinsam genutzte SQLite-Verbindung bleibt mit db.serialize() dauerhaft seriell. Zuvor galt die Serialisierung nur innerhalb des Schema-Initialisierungsblocks. Parallele Deckeländerungen konnten während einer Kassiertransaktion mehrere libuv-Worker mit SQLite-Sperrwartezeiten belegen und dadurch auch COMMIT und das Laden statischer Dateien verzögern. WAL und der bestehende Busy-Timeout bleiben erhalten.

Ein isolierter Socket-Test mit einmal Kassieren und acht gleichzeitigen Änderungen anderer Deckel reproduzierte vor der Korrektur acht SQLITE_BUSY-Fehler und eine HTTP-Antwortzeit von rund 40 Sekunden. Mit Serialisierung wurden alle Vorgänge im gleichen lokalen Vergleich innerhalb von 44 ms bestätigt. Dies ist keine zugesicherte Pi-Laufzeit und kein rückwirkender Beweis für die gemeldeten Handy-Ausfälle. tests/sqlite-contention.test.js prüft Bestätigungen, HTTP-Erreichbarkeit und den anschließend gespeicherten Zustand ausschließlich mit einer temporären Datenbank. Die Änderung benötigt einen Serverneustart in einer Betriebspause; keine Migration oder Änderung von Zugangsdaten ist erforderlich. Geräteabnahme siehe ROADMAP.md.

Zusätzlich werden vollständige Kassier- und Umbuchungstransaktionen pro verwendeter Verbindung über sqlite-transaction-queue.js eingereiht. Der bestehende Schutz pro Deckel genügte für verschiedene Tische nicht: zwei gleichzeitige Kassierungen oder zwei unabhängige Umbuchungen konnten auf derselben Verbindung ein zweites BEGIN ausführen und mit cannot start a transaction within a transaction scheitern. Die Warteschlange hält bis COMMIT beziehungsweise ROLLBACK; ein Fehler wird an den Aufrufer zurückgegeben und blockiert nachfolgende Vorgänge nicht. Tests prüfen parallele Vorgänge, Wiederholschutz, gespeicherte Mengen/Belege und Weiterarbeit nach Rollback. Auch diese Korrektur wird erst nach einem Serverneustart wirksam.

Unbestätigte Bons automatisch erneut senden (2026-09-16)

Neue Bons bleiben vor dem ersten Versand lokal in der Outbox gespeichert, bis der Server sie bestätigt. Fehlt nach vier Sekunden die Bestätigung, folgt bei bestehender Verbindung nach einer Sekunde ein weiterer Versuch; ein Reconnect ist dafür nicht mehr erforderlich. Pro Ticket-ID läuft höchstens ein Versuch gleichzeitig. Wiederholungen behalten die ursprüngliche 15-Minuten-Frist, auch nach einem Browserneustart. Abgelaufene Bons bleiben in der bestehenden Quarantäne. Der Server prüft den dauerhaften Zustellnachweis atomar beim Einfügen, sodass auch zurückgeholte oder gelöschte Bons nicht erneut entstehen. Diese Änderung behebt eine konkrete Wiederholungslücke; die Ursache der beobachteten Verzögerungen bis zehn Sekunden ist damit nicht rückwirkend nachgewiesen.

iPhone-Service: Übergang zur Statusleiste (2026-09-16)

Die eigenständige iOS-Serviceansicht verwendet für den nativen HTML-/Body-Hintergrund und den oberen Rand dieselbe deckende Kopffarbe (--bg-header), ohne Root-Farbtransition. Eine echte, nicht interaktive service-status-surface deckt die obere Safe Area ab (mindestens 12 px auch bei gemeldetem Inset 0). service-canvas bewahrt die bisherige Katalogfarbe unter Glow und Vignette. Start-/Anmeldefläche folgt der Kopffarbe. Die Regeln gelten nur für html.service-app[data-platform="ios"]:not(.embedded). CSS 20260916-1; Service-PWA nach dem Update vollständig schließen und neu öffnen. Die native Geräteabnahme ist in ROADMAP.md erfasst.

Ofenbelegung übersteht einen Serverneustart (2026-09-16)

Offene Ofenbelegungen werden in oven_slots gespeichert und beim Start vollständig wiederhergestellt, auch aus früheren Betriebstagen. Nur unlesbare oder ungültige Einträge werden entfernt. Die Originalzeit bleibt erhalten; abgelaufene Timer erscheinen als FERTIG und erfordern den ausdrücklichen Abschluss an der Theke. Der Start druckt nichts automatisch.

Das Einlegen überträgt die Position atomar vom Bon in den Ofen. Schlägt das Speichern fehl, bleibt der ursprüngliche Bon vollständig erhalten. Gleichzeitiges Einlegen wird serialisiert; Clients bekommen die Änderung erst nach erfolgreichem Speichern. Umbuchungen schreiben weiterhin den neuen Deckelnamen mit. Der Erstzustand wartet auf die Wiederherstellung.

Die Tabelle wird automatisch angelegt; pub.db bleibt am bisherigen Ort. Update auf dem Pub-Server einspielen und Server neu starten. Bereits vor Installation dieser Persistenz verlorene Einträge können dadurch nicht rekonstruiert werden. Tests in tests/oven-persistence.test.js prüfen Neustarts am selben und über einen Betriebstag hinweg sowie Rollback bei Schreibfehlern und belegten Plätzen.

Automatische Stationsbons (2026-09-14)

Bar und Theke haben im Stationskopf neben „Ansicht“ den Schiebeschalter „Automatisch / Bondruck“ (rechts = an mit satter Pubgrün-Fläche und hellem Knopf, links = aus mit transparenter Spur). In Kombi sitzt derselbe Schalter im Admin-Menü. Die Wahl gilt stationsweit, synchronisiert alle verbundenen Tablets und bleibt nach Neuladen sowie Serverneustart gespeichert. Standard ist aus: Ohne bewusst gespeicherte Wahl druckt keine Station automatisch. Theke umfasst auch Ofenbons. Fertigstellen, Fertigmeldungen und Abrechnung funktionieren unabhängig vom Ausdruck; ausdrücklich angeforderte Gästebelege und andere manuelle Ausdrucke bleiben möglich. Beim Einschalten werden ausgelassene Bons nicht nachgedruckt. Bereits in der Druckqueue angenommene Aufträge werden durch Ausschalten nicht zurückgenommen. Ohne Verbindung oder während des Speicherns ist der Schalter gesperrt. Eine früher an einer Station ausdrücklich eingeschaltete Wahl bleibt gespeichert und muss dort einmal ausgeschaltet werden; der neue Standard gilt für Stationen ohne gespeicherte Wahl. Für dieses Update Server neu starten und Bar/Theke neu laden (CSS 20260914-21, Stationsmodul station-admin.js 20260914-2, Ansichtsmodul station-workspace.js 20260914-1). Die Tabelle station_print_settings wird automatisch angelegt; die vorhandene Datenbank bleibt am bisherigen Ort.

Getränkebestellung am Handy (2026-09-15)

Nur auf dem eigenständigen Service-Gerät öffnet „Getränke bestellen“ im ⋯-Menü eine mobile Vollbildliste. In der eingebetteten Serviceansicht von Bar/Theke (Kombi und Bedienen) ist die Getränkebestellung nicht verfügbar. Die App zeigt die 62 Artikel als Lagerrundgang mit Warengruppen: Lager oben, Vorderer Keller, Kühlhaus und zuletzt Artikel ohne Lagerortangabe. Innerhalb eines Lagerorts bleiben die Warengruppen des Händlerformulars als Unterabschnitte erhalten – Fässer, Flaschenbier, Apfelwein und so weiter –, damit Fass und Flasche nicht durchmischen. Erst innerhalb einer Warengruppe stehen die Artikel alphabetisch (deutsche Sortierung, Umlaute unter ihrem Grundbuchstaben, deshalb steht Köstritzer bei K). Jede Zeile nennt Verpackung und Artikelnummer. Die Händler-PDF selbst bleibt unangetastet und wird weiterhin exakt in der Reihenfolge des Papierformulars gefüllt. Es gibt keine Sprungleiste und keine Filterung; Rundgang, Warengruppe und Sortierung sind die Reihenfolge der Liste selbst. Der Lagerortkopf bleibt beim Scrollen sichtbar und zeigt „erfasst von gesamt“ für seinen Lagerort; die Warengruppen sind ruhige Versalien-Zwischenzeilen und scrollen mit. Die Liste hat unterhalb von „Aktuelle Bestellung / Bestellübersicht“ eine eigene Kante mit Abstand, damit gescrollte Zeilen nicht an den beiden Beschriftungen kleben. Eine Menge zählt ganze Verpackungen aus dem Händlerformular – Kiste, Fass, Gebinde oder Flasche, also etwa 24 × 0,33 l, 50 l oder 10 kg. In der App heißen sie durchgehend „Einheiten“. Mengen sind ganze Einheiten (1–9999), leere Mengen und Null bestellen nichts. Große Mengenbuttons öffnen den appinternen Zahlenblock (0–9, Leeren und letzte Ziffer löschen), ohne Handytastatur. Einzige Ausnahme ist die Zeile „Bierdeckel“: Sie fragt Ja oder Nein (siehe oben). Die erste Ziffer ersetzt beim erneuten Öffnen eine vorhandene Menge; weitere Ziffern werden angehängt. Jede Eingabe speichert sofort. „Weiter“ geht in Listenreihenfolge – Lagerort, Warengruppe, darin alphabetisch – zum nächsten Artikel, ohne Eingabe wird dessen Menge unverändert übersprungen. „Fertig“ kehrt zur Liste zurück; beim letzten Artikel führt „Zur Übersicht“ zur Kontrolle. Escape schließt zuerst den Zahlenblock, danach den Dialog. Physische Zifferntasten sowie Rücktaste/Entfernen bleiben nutzbar. Der Entwurf wird unter willys_supplier_order_v1 ausschließlich auf diesem Gerät im Browser gespeichert; keine Synchronisierung zwischen Geräten. Dieser Dauerhinweis steht als ruhige Fußnote, nicht als Meldung. Speicherfehler, Warnungen und Verbindungsfehler erscheinen dagegen farblich als Fehler beziehungsweise Warnung. „Leeren“ verlangt eine Bestätigung und ist als Löschaktion gekennzeichnet; die Bestätigung nennt die Zahl der betroffenen Positionen. Die Kontrollansicht zeigt nur gewählte Artikel, nach Lagerort und Warengruppe gegliedert, mit Positions-, Einheiten- und Lagerortsumme.

„PDF erstellen“ füllt eine sauber nachgezeichnete, zweitseitige PDF-Vorlage und führt in einen eigenen Übergabeschritt mit Dateiname, Umfang und drei beschrifteten Aktionszeilen: „PDF an Mail-App geben“ übergibt die Datei an die Systemfreigabe (nur wenn das Gerät Dateifreigabe unterstützt) und legt Betreff sowie Text bei, sofern die Plattform beides zusammen annimmt; „E-Mail vorbereiten“ öffnet die Mail-App mit fertigem Betreff und Text (Empfänger vorbelegt, sobald MERCHANT_MAIL in supplier-orders.js gesetzt ist), die PDF muss dort noch angehängt werden; „PDF herunterladen“ legt die Datei ab. Ein Browser kann keine Datei automatisch an eine Mail hängen und beim Teilen keinen Empfänger setzen – deshalb die zwei Wege. Die App verschickt nichts selbst, es gibt keinen SMTP-Versand und keine Versandbestätigung. Export löscht den Entwurf nicht.

„Bestellübersicht“ zeigt die auf dem Pub-Server gespeicherte Historie, neueste zuerst. Nach Übermittlung an den Händler „Als bestellt markieren“ wählen; die anschließende Frage bestätigt die Übermittlung ausdrücklich. Erst die bestätigte Speicherung leert den aktuellen Entwurf. Gespeichert werden der Zeitpunkt der manuellen Bestätigung, Artikelsnapshots und die exakte ausgefüllte PDF. Details und PDF-Download stehen auf allen angemeldeten Geräten zur Verfügung, auch nach späteren Vorlagenänderungen. Wiederholungen derselben Bestätigung sind idempotent. Die Historie beginnt mit dieser Funktion; frühere PDF-Downloads zählen nicht nachträglich als Bestellungen. Dies ist kein automatischer E-Mail- oder Liefernachweis.

supplier-orders.js lädt assets/orders/catalog.json und template.pdf beim ersten Öffnen. Rundgang, Warengruppen und alphabetische Sortierung werden daraus einmalig berechnet; Katalog und Vorlage bleiben unverändert. Die laufende Ansicht funktioniert danach ohne weitere Netzaufrufe; ein Offline-Neustart ist nicht zugesichert. PDF-Erzeugung findet lokal ohne neue Runtime-Abhängigkeit statt. Für dieses Update alle Servicegeräte neu laden (CSS 20260915-5, supplier-orders.js 20260915-6); ein Serverneustart ist nicht erforderlich. scripts/build-supplier-template.py regeneriert mit PyMuPDF (nur Entwicklungswerkzeug) die geprüfte Vorlage direkt aus den gepflegten Artikeln und dem Tabellenlayout. bestellungen.pdf bleibt nur die ursprüngliche Scan-Referenz. Die Exportvorlage enthält ausschließlich echte Schrift und Vektorlinien; Spalten, Reihenfolge, Leerzeilen und Seitenumbruch folgen dem Papierformular. Ein- bis vierstellige Mengen werden mittig in der Mengenspalte gesetzt; die Antwort „Ja“/„Nein“ der Bierdeckel-Zeile nutzt dasselbe vierstellige Feld. Bei Vorlagenänderungen Artikel/Koordinaten visuell prüfen, beide Assets gemeinsam erzeugen und deren URL-Version im Client erhöhen.

Sambuca Molinari läuft an die Theke (Standardzone Vorne). Beim Buchen öffnet sich der vorhandene Auswahldialog mit „Mit Kaffeebohne“ und „Ohne Kaffeebohne“. Die Wahl wird als Artikelnotiz in Deckel und Bon übernommen; Abbrechen bucht nichts.

Architektur, Setup & Entwickler-Handbuch

Echtzeit-Gastro-System für Service, Bar und Theke. Diese Datei erklärt Aufbau und Betrieb. Ergänzende Dokumente:

  • Feature-Historie: CHANGELOG.md (was wann umgesetzt wurde)
  • Geplante Features / Ideen: ROADMAP.md
  • Aktueller technischer Arbeitsstand & No-Gos: docs/AI_HANDOFF.md
  • Design-System / Farb-Token-Karte: docs/DESIGN_SYSTEM.md
  • Regeln für Agents/KI: .agents/AGENTS.md
  • Pub-Layout / Raumaufteilung: docs/PUB_LAYOUT.md
  • Backups / Wiederherstellung: docs/BACKUP_RESTORE.md
  • Funktions-, Nutzen- und Preisanalyse (Verkaufsunterlage): docs/sales/index.html als fertige Präsentationsseite (im Browser öffnen, enthält alle Screenshots, einen Knopf „Termin vorführen“ mit sieben geführten Schritten und einen Knopf „Als PDF drucken“). Inhaltliche Quelle ist docs/sales/WILLYS_PUB_SYSTEMANALYSE.md mit 69 echten Screenshots unter docs/sales/screenshots/; die Seite wird mit npm run build:sales neu erzeugt, die Screenshots mit den Entwicklungswerkzeugen in scripts/sales-demo/

Fertigmeldungen und Servicerufe (2026-09-14)

Das Fertigmeldungsfenster verwendet den vollständigen Bereichsnamen „Biergarten“. Ein neuer Tap auf den Hintergrund schließt es; die Geste muss außerhalb der Dialogkarte beginnen und enden. Herausziehen, abgebrochene Gesten und der Folge-Klick beim Öffnen schließen nicht. X, Escape und Fokusrückgabe bleiben erhalten.

Servicerufe und Fertigmeldungen lösen auf keinem Gerät Vibration aus. Hinweise erscheinen visuell und bei eingeschaltetem Abholton zusätzlich akustisch.

Die Übersicht bündelt alle aktuellen Fertigmeldungen pro Tisch in einem kompakten Eintrag, auch über mehrere Bons und Fertigzeiten hinweg. Kopf: Tischname und Alter der ältesten noch sichtbaren Fertigmeldung; darunter eine Artikelzeile je Ausgabe (Bar/Theke). Gleiche Artikel mit identischer Variante und Notiz werden innerhalb derselben Ausgabe mengenmäßig addiert. Unterschiedliche Notizen bleiben getrennt. Einzelne Meldungen verfallen weiterhin unabhängig; Produktionsübergaben behalten ihre eigenen, unveränderten Bestätigungsaktionen. Der Schließen-Button verwendet dieselbe close-coaster-Komponente wie Reservierungen und Deckel, einschließlich Fläche, Kontur, Farbe und Hover. Keine eigene Farbüberschreibung. Beim Öffnen erhält der Titel den Fokus; Tastaturfokus bleibt am X sichtbar, Schließen stellt den vorherigen Fokus wieder her.

Fertigmeldungen aller Tische sind immer sichtbar, unabhängig vom Abholton. Neue Fertigmeldungen werden über 2,5 Sekunden gebündelt: sechs Sekunden „Theke fertig“ / „Bar fertig“, bei beiden „Abholbereit“, im bestehenden Haupt- und Tischkopf. Tischname und Artikel stehen ausschließlich in der Übersicht; das Badge zählt unterschiedliche fertige Tische. Zwei nicht interaktive goldene Seitenlichter pulsen einmal für 1,4 Sekunden, weich nach innen sowie oben/unten auslaufend. Keine obere/untere Linie und keine Rahmenecken; die Systemstatusleiste wird nicht verändert. Weitere Fertigmeldungen während der Pille aktualisieren deren Inhalt ohne neuen Impuls oder Verlängerung. Ein ausdrücklicher Serviceruf hat während seiner sechssekündigen Pille Vorrang: „Theke ruft“ / „Bar ruft“ bzw. „Bar + Theke“ mit zwei Seitenlichtimpulsen in 2,8 Sekunden. Reduced Motion zeigt die Seitenlichter statisch. Die Lichtflächen reichen bei Fertigmeldungen 32 px, bei ausdrücklichem Ruf 40 px nach innen. Ein breiterer goldener Kern geht bei 22 % Breite in 60 % Gold, bei 58 % in Gold-Tint und am inneren Ende vollständig in Transparenz über; oben/unten bleibt der weiche Auslauf. Erstabgleich, Reconnect und Hintergrundrückkehr erzeugen keinen Ankunftseffekt; Trennung und Hintergrundwechsel räumen laufende Effekte auf. CSS 20260914-6, Abholmodul 20260914-5.

Auf jedem eigenständigen Servicegerät sind Meldungen für Pub, Bar, Biergarten und freie Tischnamen sichtbar. Im Detaildialog schaltet „Abholton an/aus“ ausschließlich die zusätzliche Ansage (assets/audio/abholen.mp3). Einschalten spielt die Ansage zur Probe; nach Neuladen kann „Ton aktivieren“ erforderlich sein. Die gerätebezogene Wahl wird unter willys_pickup_sound gespeichert; eine vorhandene Biergartenmodus-Wahl wird als Tonpräferenz übernommen. Ohne vorhandene Wahl ist Ton aus. Auch bei blockiertem Ton bleiben alle visuellen Hinweise aktiv.

Die Rufleuchte bleibt mit Anzahl und farbig hinterlegter Fläche sichtbar; auf der Karte gibt es keine zusätzliche Leiste oder Einblendung über den Artikeln. Die Tisch-/Deckelübersicht verwendet dieselbe Rufleuchte im vorhandenen Kopf. Systemstatus und Hell/Dunkel liegen im ⋯-Menü; Störungen bleiben als „!“ am Menü sichtbar. An Bar/Theke stehen Bon-Ton, Hell/Dunkel und das bisherige Admin-Menü rechts im Header. Die Stationsüberschrift steht im Fokusmodus wieder mittig. „Service rufen“ sitzt als runder Symbolbutton direkt neben Bon-Ton und Hell/Dunkel und verwendet dieselbe Button-Komponente. Das Admin-Menü enthält ausschließlich Reservierungen und Abschlüsse. Dienstplan und Arbeitszeit sind seit 2026-09-16 nicht mehr im Menü, weil sie derzeit nicht im Einsatz sind; beide Module bleiben geladen und lassen sich mit je einer Menüzeile wieder sichtbar machen. Produktionsübergaben bleiben über die Rufleuchte im Tisch-/Deckelkopf der eingebetteten Serviceansicht erreichbar. Ein Tipp öffnet die Details mit Tisch, Abholort, Artikeln und „vor … Min. fertiggestellt“. Orange ab einer Minute und Rot ab zwei Minuten nutzen die vorhandenen App-Farben. Nach drei Minuten verschwindet die Meldung automatisch, ohne Abholbestätigung und ohne die Behauptung, dass noch etwas steht oder bereits abgeholt wurde.

Im gesamten Service teilen die Kopfaktionen die gleiche runde Grundform und Badge-Geometrie, auch die Rufleuchte im Tisch-/Deckelkopf. Aktive Hinweise erhalten einen ruhig pulsierenden Lichtkranz; ausdrückliche Rufe zusätzlich eine kräftige Goldfläche. Orange/Rot folgen dem ältesten aktuellen Hinweis oder Ruf; ein neuer Ruf verdeckt keine ältere Warnung. Bei reduzierter Bewegung bleibt der Kranz statisch. Das Detailfenster verwendet die gemeinsamen Dialogfarben, Schließen-Aktion, Hintergrundunschärfe und 250-ms-Bewegung. Das Service-Menü teilt die Menüfläche und Aktionszeilen mit dem Tablet-Admin-Menü: goldene SVGs, Titel und zweite Zeile; „System“ zeigt einen Server-SVG und den aktuellen Verbindungsstatus.

Fertigmeldungen innerhalb von 2,5 Sekunden werden zu einer Ansage gebündelt; automatische Ansagen liegen mindestens 20 Sekunden auseinander. Es gibt keine dauernde Wiederholung. Erstabgleich, Rückkehr aus dem Hintergrund und Reconnect bleiben stumm. Der Ton benötigt die laufende, sichtbare Web-App; dies ist keine Push-Zustellung an gesperrte Geräte. Eingebetteter Service öffnet den Elternstationsdialog und erzeugt keine zweite Ansage.

Bar und Theke haben den runden Symbolbutton „Service rufen“ direkt im vorhandenen Header. Ein Tipp sendet einen neuen, allgemeinen Ruf an alle Servicegeräte, auch ohne aktuelle Fertigmeldung. Der Ruf nennt die Station und bleibt drei Minuten sichtbar. Serverbestätigung, Wiederholschutz und eine stationsweite Pause von 30 Sekunden verhindern Doppelrufe. Danach wechselt der Tablet-Button von „Gerufen“ wieder zu „Service rufen“; das dreiminütige Signal im Service läuft unabhängig davon weiter. „Ruf gesendet“ bestätigt die Serverannahme, nicht das Hören durch eine bestimmte Person. Manuelle Rufe bleiben bewusst flüchtig und werden nach Serverneustart nicht erneut zugestellt.

Stations-/Ofen-Fertig speichern weiterhin atomare Artikelsnapshots in pickup_entries. list_pickups mit {mode:'notices'} liefert aktuelle Hinweise und offene Produktionsübergaben. notice_expired_at begrenzt den aktiven Datenbestand, ohne picked_at zu setzen; Abrechnung und Produktions-Undo bleiben davon unabhängig. Alte Abholaktionen und ihre Daten bleiben zur Client-/DB-Kompatibilität vorhanden; neue Clients senden ausschließlich notwendige handoff- Aktionen. Alle Geräte nach dem Update neu laden.

Bei Mango-, Maracuja- und Pink-Grapefruitschorle bestätigt die Bar nach der Übergabe noch „Fertig“; bei der Sprudel-Karaffe die Theke. Erst dann erscheint die Fertigmeldung am endgültigen Abholort. Diese Produktionsübergaben verfallen nicht nach drei Minuten. Reine Zulieferungen (Baileys/Espresso) erscheinen nicht als Gästeabholung; der Hauptartikel wartet auf die Zulieferstation. Routing, Papierdruck und Ofenablauf bleiben erhalten.

Für diese Clientänderung alle Geräte neu laden (CSS 20260914-6, pickups.js 20260914-5); ein Serverneustart ist nicht erforderlich.

1. Systemarchitektur

Drei Stationen teilen sich ein Backend:

  1. Service/Kellner (index.html): Bestellungen aufnehmen, Deckel führen, kassieren.
  2. Bar-Station (bar.html): Cocktails, Whisky und alle weiteren dort gelagerten Produkte.
  3. Theken-Station (theke.html): Zapf-/Kühlprodukte, ausgewählte Shots sowie Essen/Ofen.

Technischer Stack:

  • Frontend: reines HTML/CSS/Vanilla-JS. Die Speise- und Getränkekarte (G und DATA) liegt separat in products.js und ist sowohl im Browser als auch in Node.js nutzbar. Ein seltenes nicht gelistetes Getränk wird dagegen kontrolliert als manuelle Position am Deckel gespeichert.
  • Backend (server.js): Node.js mit express (Ausliefern der Dateien) und socket.io (Echtzeit-Kommunikation über WebSockets).
  • Datenbank (pub.db, SQLite): speichert Deckel (coasters), Bons (tickets), Belege (receipts), bestätigte/idempotente Abrechnungen (checkout_operations), bestätigte/idempotente Deckelumbuchungen (coaster_transfer_operations), unveränderliche Positionsstände erfolgreicher Abrechnungen (receipt_archives), die laufende Ofenbelegung (oven_slots), Arbeitsstunden (work_hours), Monatsabschlüsse (work_hours_month_closures), die Personenauswahl (work_hour_employees) und das globale Event-Menü (global_favorites). Dienstplanwochen, Einsätze, Einteilungen und Verfügbarkeiten liegen in den schedule_*-Tabellen. Das interne Reservierungsbuch wird in reservations gespeichert und ist unabhängig von Dienstplan und Arbeitszeiterfassung. Kataloggetränke und ihre Preise stehen nicht in der DB, sondern in products.js; manuelle Positionen liegen mit Name, Einzelpreis und expliziter Station im jeweiligen Deckel, Bon und Belegarchiv. Häufig bestellte alkoholfreie Biermischgetränke sind als eigene Katalogartikel erfasst: Radler, saurer Radler und Colabier auf Basis von Licher Isotonisch sowie Russ und Colaweizen auf Basis von Benediktiner Weizen alkoholfrei. Sie folgen Größen und Preisen der jeweiligen normalen Varianten und laufen an Theke Hinten.
  • Stations-Routing (station-routing.js): zentrale Logik, die entscheidet, ob ein Artikel zur Bar oder zur Theke gehört. Wird von server.js, bar.html und theke.html gemeinsam genutzt. Kategorien, namentliche Ausnahmen und der Theke-Fallback bilden die reale Aufstellung ab. Kurstadt Kombo, Irish Car Bomb und Turbo Guinness werden anhand deklarativer parts tatsächlich auf beteiligte Stationen verteilt; die Bar sieht beim Irish Car Bomb nur den kompakten Zusatzauftrag „Baileys“, beim Turbo nur „Espresso“. In Deckel und Checkout zeigt der Service ebenfalls nur beschriftete Bestandteile; leere Routingteile erzeugen kein alleinstehendes +. Die drei Saftschorlen bleiben reine Thekenbons, während beide Karaffen reine Barbons sind; ihre weiteren Handgriffe folgen dem Papierbon über das Übergaberegal. Pink-Apple läuft vollständig an die Theke; Wild Times und Willy's Special bleiben normale Barbons ohne Systemhinweis. Details stehen in docs/PUB_LAYOUT.md (Abschnitte 5–6).

Servicekopf auf iOS: In der eigenständigen und eingebetteten iOS-Serviceansicht ist der Kopf fest am oberen Fensterrand verankert. Ein automatisch an die tatsächliche Kopfhöhe angepasster Platzhalter hält den Kartenbeginn frei; Safe Area, Suche und Kategorien bleiben enthalten. Nachfedern am Dokumentrand ist dort unterdrückt. Android behält seinen Sticky-Kopf. Bar und Theke verwenden ausschließlich auf iOS/iPadOS eine fixierte Stationsfläche mit 100dvh und Geräte-Safe-Areas; das Ofendock wird nicht zusammengestaucht. So bleiben Ofen und Service in Fokus, Kombi und Bedienen innerhalb der sichtbaren Fläche. Eine echte, fest verankerte station-status-surface stellt im äußeren iOS-Stationsfenster die Kopffarbe für WebKits Statusleisten-Erkennung bereit. Sie bleibt auch bei Safe Area 0 sichtbar und wird beim tatsächlichen Themewechsel durch ein identisches neues Element ersetzt, um die alte native Farbreferenz abzulösen. HTML und Body nutzen beide die Kopffarbe; eine separate station-canvas bewahrt die bisherige Inhaltsfarbe. Der äußere Hintergrund wechselt ohne Farbtransition gleichzeitig mit dem Kopf; auch die Start-/Anmeldefläche verwendet dort bereits die Kopffarbe. --theme-frames im Stations-Browsercheck prüft den Start und jeden Renderframe während der Themewechsel. Die Android-Layoutregeln bleiben erhalten. CSS-Version: 20260913-9, Theme-Helfer 20260913-1.

Produktsuche: Namen, Kategorien und explizite Suchaliase werden wortweise durchsucht; Zutatenbeschreibungen erzeugen keine Treffer. Groß-/Kleinschreibung und Akzente bleiben tolerant. Wortanfänge verwenden die normale Schreibweise; fehlende oder zusätzliche Doppelbuchstaben werden nur beim Vergleich ganzer Wörter toleriert (Guines findet Guinness). Eistee findet die beiden Iced-Tea-Produkte, ohne Jägermeister einzubeziehen. Eindeutige Synonymanfänge werden nur erweitert, wenn sie kein vorhandenes Katalogwort sind (Black). Bewusst unterstützte Wortendungen erhalten zusammengesetzte Namen wie Hefeweizen, Colaweizen, Weißweinschorle und Doppelkorn; Spritz umfasst auch Süß-/Sauergespritzter.

Touch-Bedienung: Die Bedienoberfläche unterdrückt Textmarkierung, Bildziehen und Langdruck-Kontextmenüs auf Karten, Preisen, Mengen, Bons und übrigen Bedienelementen. Eingabefelder behalten Auswahl, Einfügen und native Bearbeitung. Die Regeln stehen gemeinsam in start-screen.css und gelten bereits beim App-Start sowie auf der Offline-Seite. touch-controls.js steuert Fertig, Ofenaktionen und Suchergebnisse: Auslösen einmal beim Loslassen, ohne Zeitlimit fürs Halten. Mehr als 12 px Fingerbewegung, Scrollen, ein zweiter Finger, Abbruch, entfernte/gesperrte Ziele oder über 500 ms verspätet verarbeitete Ereignisse verwerfen die Aktion. Native Folge-Klicks werden dort nicht erneut ausgeführt. Preisbuttons behalten ihre bisherige Klick-/Pointer-Prüfung und ihre bisherige Darstellung; der appweite Schutz vor Textmarkierung gilt auch auf der Produktkarte. Suchtreffer sind ganzflächige Buttons; Text, Kategorie und Pfeil haben denselben Weg. Der Suchfokus bleibt beim Aufsetzen erhalten, damit sich die Tastatur erst nach Auswahl schließt. „Fertig“ besitzt mindestens 56 px Höhe und eine zusätzliche Trefferfläche von 8 px oben/unten und seitlich (bei benachbarten Aktionen 6 px seitlich). Seine Rückmeldung verschiebt die Trefferfläche nicht. Während des Haltens pausiert die Kartenanimation, und Bon-Updates ersetzen oder verschieben die sichtbaren Karten erst nach Ende der Geste. Der Auftrag verwendet die sichtbaren Ticket-IDs; später eingetroffene Runden bleiben offen. Bis zur Serverbestätigung steht „Wird abgeschlossen …“ im gesperrten Button; Fehler geben ihn wieder frei.

Die Bar hat keine Ofenoberfläche und erhält keine Ofenstände. Ofengerichte einlegen, fertigstellen und Ofenbons drucken kann nur die Theke (sowie ein eigenständiger Adminzugang). Der Server sperrt Ofenänderungen auch für Bar-Stationen im Betrieb ohne Anmeldung. Normale Getränkebons der Bar behalten ihren bisherigen Abschluss und Druck.

An der Theke gilt: „In den Ofen“ verwendet denselben geschützten Touch-Pfad und hält die Zeile der Ofenwarteliste während des Drückens stabil. Die Etagenwahl erscheint immer, auch bei nur einer passenden freien Etage. Erst eine eigene Betätigung der Etage sendet den Ofenauftrag; ein nativer Folge-Klick des öffnenden Tipps darf keine Etage auswählen. Der Dialog erhält zunächst selbst den Fokus. Etagenbuttons, X, Abbrechen und „Andere Auswahl“ verwenden denselben Touch-Schutz einschließlich Scrollabbruch und Tastaturbedienung. Ein neuer Tipp auf den abgedunkelten Hintergrund schließt den Ofendialog weiterhin; Aufsetzen und Loslassen müssen beide außerhalb der Dialogkarte liegen. Der Folge-Klick des öffnenden Tipps schließt nicht. X/Abbrechen und Escape bleiben verfügbar. Die Ofenübersicht und Artikelliste behalten ihre berührten Flächen bis zum Loslassen; Timerberechnung und Thekenalarm laufen weiter. Ein währenddessen abgelaufener Timer macht aus „Etage öffnen“ keinen Druckauftrag. Vor der Auswahl oder dem Abschluss wird der aktuell belegte Ofenartikel mit der angezeigten Belegung verglichen. Entfernte, geänderte oder inzwischen neu belegte Plätze erfordern eine neue Auswahl. Die zweite Bestätigung im Etagenmodal bleibt bestehen; nach Ende der Geste erscheint wieder der aktuelle Ofenstand.

Produktionsabschluss: finish_ticket_station_batch meldet die beim Klick sichtbaren Ticket-IDs einer Tisch-/Zahlungsgruppe gemeinsam an der betreffenden Station fertig. Der Server sperrt alle IDs über dieselbe Queue wie Einzelabschluss/Rückruf und speichert die Statuswechsel mit einem atomaren SQL-Update. Pro Klick entsteht ein gemeinsamer Stationsdruck; identische Varianten werden addiert, andere Größen, Preise und Sonderwünsche getrennt gehalten. Persistierte ready_stations verhindern einen erneuten Ausdruck bei Doppelklick, Retry oder Undo. Später eintreffende Runden bleiben offen. Alte Clients können die einzelnen Events weiter verwenden; für Sammeldruck müssen die Stationsseiten neu geladen sein. Kassieren lässt offene Produktionsbons bestehen; ticket_payments speichert vollständig bezahlte Ticketreferenzen unabhängig von der Produktion. Bezahlte Gruppen bleiben bei Wiederverwendung desselben Tischnamens getrennt sichtbar.

Gebündelte Bonanzeige: Bar und Theke verarbeiten eingehende Ticketereignisse sofort, bereiten ihre Bonliste aber höchstens einmal pro angefordertem Bildschirmframe vor. Der Bonzähler und akustische Signale bleiben auch während einer Berührung unmittelbar aktuell. Während einer Bonberührung entfällt diese Vorbereitung; Loslassen oder Abbruch zeichnet den neuesten Stand nach. Anfangsbestand und lokale Bestätigungen bleiben unmittelbar und ersetzen gegebenenfalls einen schon vorgemerkten Render-Aufruf. Markup, CSS, Animationen, Sounds, Gruppierung, Pending-/Offline-Sperren und die sichtbaren Abschluss-IDs bleiben erhalten. Messwerte und Aussagegrenzen stehen in docs/STATION_PERFORMANCE.md.

Verdeckte Service-Anzeige: Im Fokusmodus von Bar/Theke stellt der eingebettete Service Live-Updates der Tischübersicht und des aktiven Deckels zurück. Beim Wechsel zu Kombi/Bedienen holt er den aktuellen Stand synchron vor der Bildausgabe nach, ohne zusätzliche Kartenanimation. Die Service-App bleibt geladen; Socket, unmittelbare lokale Speicherung, Bestätigungen, Offline-Abläufe und Stationsdialoge arbeiten weiter. Eigenständiger Service und sichtbare Service-Ansichten behalten ihre Live-Aktualisierung. Messung und Speicheranalyse: docs/SERVICE_BACKGROUND_PERFORMANCE.md.

Kommunikation: Sendet der Kellner in index.html einen Bon, geht das Event per Socket.io an den Server. Dieser speichert den Bon in pub.db und sendet sofort ticket_added an die passenden Stationsgeräte (Bar und/oder Theke, je nach Artikeln). Der Server filtert zusätzlich den initialen Bonbestand beim Verbinden nach der physischen Station; damit kann auch ein veralteter Browserstand keinen reinen Barbon an der Theke anzeigen. Ergibt die Produktsuche keinen Treffer, kann der Service über Als diverses Getränk buchen einen Namen, einen positiven Einzelpreis, die Menge und zwingend Bar oder Theke erfassen. Die Preiseingabe akzeptiert Komma oder Punkt und den schnellen Ziffernweg: 9 wird 9,00 €, 98 wird 98,00 € und die dritte Ziffer formatiert direkt im Feld, etwa 999 zu 9,99; eine Live-Anzeige bestätigt zusätzlich den Buchungswert. Der Server normalisiert die Position getrennt vom Katalog, lehnt Katalognamen mit frei gesetztem Preis ab und routet ausschließlich an die gewählte Station. Solche Positionen bleiben bei Mengenänderung und Umbuchung eindeutig erhalten und laufen normal durch Bon, Kassieren und Beleg. Der Service arbeitet tisch-zuerst: Nach Anmeldung, Bonversand und vollstaendiger Abrechnung oeffnet sich verbindlich Tisch waehlen; ohne eindeutige Tisch-, Gast-/Platz- oder Sofortverkaufswahl nimmt die Produktkarte keine Buchung an. Eine Auswahl fuehrt direkt in den Bedienbereich mit Suche, Kategorien und Produkten. Ein Socket-Reconnect synchronisiert nur den Serverstand und veraendert eine laufende Bedienansicht nicht; die Pflichtauswahl wird ausschliesslich beim ersten App-Start oder durch einen fachlichen Abschluss ausgeloest. Auf Smartphones bis 700 px ist die Auswahl eine vollhohe 100dvh-Arbeitsflaeche. Sie kann per X geschlossen werden, damit sich die Karte etwa fuer eine schnelle Preisauskunft ansehen laesst; ohne Tisch bleibt jede Buchung gesperrt. Beim normalen Einstieg ueber Tische schliesst nur das X zur Karte; der Zurueck-Pfeil erscheint ausschliesslich beim Tischwechsel aus einem geoeffneten Deckel und fuehrt genau dorthin zurueck. Die Tischübersicht gleitet mit einer transform-Animation von unten herein. Beim direkten Öffnen ist die innere Ansicht bereits positioniert; beim Wechsel aus einem offenen Deckel wechseln Deckel und Tischwahl mit einer dezenten Überblendung (180 ms). Die gemeinsame theme-shell.js setzt die Plattformkennung für Android sowie iPhone/iPad (einschließlich iPad-Desktopkennung mit mehreren Touchpunkten) vor dem ersten Layout. Unter iOS sperrt eine offene Tisch-/Deckelansicht das Scrollen der verdeckten Produktseite. Nur die aktive innere Ansicht ist per Geste scrollbar; ihre Listen begrenzen Scrollweitergabe am Rand. Scrollpositionen und Überblendung bleiben erhalten. Ein nur unter iOS installierter Klassen-Observer fixiert bei .coaster-sidebar.open den Seitenhintergrund im Viewport. Er überträgt die Kartenposition vorübergehend auf den nicht per Geste scrollbaren Body und stellt sie beim Schließen ohne Scrollanimation wieder her. Das erhält die Kopfposition und vermeidet die unzureichende reine Root-Overflow-Sperre von iOS-PWAs. Keine Touch-Handler, Polling oder Eingriffe in die Buchungslogik. Prüfaufbau und Grenzen: docs/SERVICE_SCROLL_ISOLATION.md. Gestaltung und Dauer der äußeren Öffnungsbewegung bleiben unverändert. Das gilt auch für den Direkteinstieg in einen einzelnen Deckel: Ein unveränderter Deckel verwendet seine vorhandenen Artikelzeilen. Mengen werden gezielt aktualisiert; Änderungen an anderen Tischen bauen den aktiven Detaildeckel nicht neu auf. Übersicht und lokale Speicherung bleiben dabei aktuell. Die Tischübersicht behält ihre Raumstruktur und vorhandene Deckelkarten bei Live-Updates. Neue sichtbare Deckel und Sofortverkäufe erscheinen mit dem bestehenden Karten-Eintritt; Nachbarkarten rücken weich nach. Beträge und Tischbelegung werden direkt aktualisiert, ohne geöffnete Räume, Fokus oder Scrollposition zurückzusetzen. Verdeckte/neue Offscreen-Karten und reduzierte Bewegung lösen keine Live-Animation aus. Remote-Änderungen der Übersicht werden pro Bildschirmframe zusammengefasst; reine Wertänderungen aktualisieren nur betroffene Karten und Tischbadges. Eine geschlossene Übersicht zeichnet erst beim nächsten Öffnen aus dem aktuellen Datenstand. Daten und Revisionen werden weiterhin sofort verarbeitet und lokal gesichert. Aktive Deckeldetails werden sofort gezeichnet, wenn sie sichtbar sind. Hinter der Übersicht oder bei geschlossener Arbeitsfläche wird nur der Kontextanker aktualisiert; beim Öffnen erscheinen die neuesten Artikelwerte. Eine vollständige Mengenaktualisierung verwendet die bereits berechneten Artikelgruppen wieder. Messaufbau und Aussagegrenzen zur Scrollleistung stehen in docs/SCROLL_PERFORMANCE.md. Die Theke aktualisiert während eines Ofen-Countdowns nur Timertexte und zugängliche Labels. Etagen und offene Detail-/Bestätigungszeilen bleiben erhalten, bis sich Belegung, Fertigzustand oder Bedienbarkeit ändern. Der Ofenalarm klingt ausschließlich an der Theke; neue Bestellungen klingeln weiterhin an beiden Stationen. Ofenaktionen bleiben serverbestätigt.

Der feste Service-Dock ist eine zweiteilige Arbeitsleiste: Tische oeffnet die Uebersicht mit einem Tipp; der breite gruene Kontextanker nennt den aktiven Deckel, Betrag und Positionszahl und oeffnet selbst dessen Detail. Nach der Auswahl bleibt der Kontext sichtbar, waehrend Tischwechsel und Deckel jeweils direkt erreichbar sind. Auf Smartphones bis 520 px stehen beide Anker 8 px ueber dem Safe-Area-Rand; in der installierten Android-Service-App gilt mindestens 24 px Abstand zum Viewportrand (Fullscreen/Standalone, ohne Stationseinbettung). Der eigenständige iPhone-Service bis 520 px setzt beide Anker 2 px über die Safe Area (6 px tiefer als bisher); Android und eingebettete Ansichten behalten ihre Abstände. Beide Buttons sind mindestens 60 px hoch und nutzen 16-px-Rundungen sowie eine dezente rundum sichtbare Elevation. Beide Anker besitzen getrennte unsichtbare Hit-Areas: 20 px nach oben und 12 px nach aussen/unten schuetzen die darunterliegenden Produktpreise vor Fehltipps; in der Mitte teilen sie sich die Luecke ohne Ueberlappung. Normale Produktpreise sind semantisch native Buttons, behalten aber unverändert ihre bisherige Gestalt. Eine Buchung akzeptiert nur eine aktuelle Pointer-Geste ohne relevante Finger- oder Seitenscrollbewegung; nach einem blockierten Browser nachgelieferte beziehungsweise beim Scrollen verschobene Eingaben werden verworfen. Der schnelle Buchungspfad aktualisiert nur den sichtbaren Dockkontext und synchronisiert den vollständigen Deckel weiter revisionsgeschützt im Hintergrund. Mengenänderungen im geöffneten Deckel ersetzen weder Liste noch gedrückten Button, sondern ziehen bei ungesendeten Positionen Zeilenwert, Summe und Status direkt nach. Zusätzliche Mengen einer bereits gesendeten Position bleiben intern korrekt für den nächsten Bon vorgemerkt, erzeugen während der Bedienung weder eine zweite sichtbare Zeile noch eine Fluganimation. Identische Varianten bleiben auch nach dem Senden über mehrere Bestellrunden in einer Zeile zusammengefasst; Größe, Preis, Auswahl, Sonderwunsch und manuelle Station bleiben unterscheidbar. Die einzelnen gesendeten Positionen samt Ticketreferenzen bleiben intern erhalten. Jede neue Buchung setzt die sichtbare Zeile nach oben; auch bei einer zusammengefassten Zeile steigt die Menge dort direkt. Ein zahlenbezogenes Badge wie 10 Neu trennt die noch nicht gesendete Menge eindeutig vom gesendeten Teil. Die Reihenfolge folgt dem gespeicherten last_added_at der jüngsten Ergänzung, unabhängig von den für Bedienung und Abrechnung verwendeten Array-Indizes. Sie bleibt nach Senden, Neuladen und Gerätewechsel erhalten. Manuelle Getränke und auf einen Zieldeckel umgebuchte Mengen folgen derselben Regel. Minus verändert die Reihenfolge nicht aktiv; entfällt die letzte neue Teilmenge, fällt die bisher zusammengefasste Zeile auf den Zeitpunkt ihrer verbleibenden Buchung zurück. Alte Einträge ohne Sortierinformation behalten untereinander die vorhandene Reihenfolge. Solange dieser neue Anteil besteht, erscheint an derselben Zeile der bekannte Notizstift und bearbeitet ausschließlich die noch ungesendete Menge dieser Variante. Bei mehreren Stück zeigt der Zusatzdialog eine Mengenauswahl, standardmäßig für alle noch ungesendeten Stück. Die Schnellwahl ist in vier unabhängig aufklappbare Bereiche gegliedert. Beim Öffnen des Stift-Dialogs sind sie geschlossen; gewählte Zusätze und direkte Gläseranzahlen bleiben in den Bereichsköpfen sichtbar. Freitext und Speichern bleiben direkt erreichbar. Die Schnellwahl enthält auch Mit Gurke unter Beilagen & Früchte und Mit Löffel unter Zubehör; beide sind unabhängig von Zitrone beziehungsweise Strohhalm wählbar. Mehrere Zusätze können gleichzeitig gelten; eine bewusst gewählte Teilmenge erhält eigene Wünsche. Bereits gesendete Mengen bleiben unverändert. Der Zusatzdialog öffnet am Anfang mit Fokus auf dem Dialog, sodass die Tastatur erst nach Antippen des Freitextfelds erscheint. Auf Android folgt ausschließlich dieser Dialog der sichtbaren Fläche über der Tastatur; beim Schließen werden die temporären Größen und Listener entfernt. Erst ein abweichender Zusatz wie Mit Eis macht sie danach als eigene Neu-Zeile sichtbar, damit der Zusatz eindeutig zugeordnet bleibt. Das vorhandene Neu-Badge ist zugleich die kompakte Statusauswahl der Position. Ein Fingertipp öffnet nur Normal senden und Nachbon; bei einer Menge groesser eins lässt sich dort zusätzlich festlegen, wie viele bereits ausgegeben wurden. Der Stift bleibt dadurch ausschließlich für Artikelzusätze zuständig. Vor dem Senden ersetzt Nachbon das Neu-Badge, ohne eine weitere Aktion in der schmalen Smartphone-Zeile einzuführen. Nachbon kennzeichnet eine bereits direkt ausgegebene Position, die nur noch auf dem Deckel verbucht werden muss. Beim Senden wird die interne Kennzeichnung verbraucht; die Position gilt normal als gesendet und bleibt vollständig kassierbar, erhält aber keine ticket_id und erzeugt weder Stationskarte noch Produktionsdruck oder Ofeneintrag. In einer gemischten Runde gelangen ausschließlich die normalen Positionen an Bar und Theke. Auch nach dem Senden, im Kassierdialog und auf dem Kundenbeleg bleibt kein Nachbon-Hinweis sichtbar. Der Server filtert die exakte interne Kennzeichnung zusätzlich defensiv aus eingehenden Produktionsaufträgen, damit auch ein noch nicht aktualisiertes Servicegerät keinen bereits ausgegebenen Artikel erneut in die Produktion schickt. Die Tischübersicht beginnt mit Bereiche: Pub, Bar und Biergarten zeigen ihr Symbol, den Bereichsnamen und die Anzahl offener Tischdeckel sowie Gäste-/Platzdeckel. Bereichssummen und zusammengefasste Hinweise auf ungesendete Artikel entfallen. Die Zahlen beschreiben Deckel, keine physische Belegung oder Reservierungen. Leere benannte Gäste-/Platzdeckel zählen als verfügbare Deckel mit; leere Bereiche zeigen Keine offenen Deckel. Feste Tische erscheinen ausschließlich im jeweiligen Bereich in der Reihenfolge aus pub-tables.js; benannte Gäste und freie Plätze folgen im Abschnitt Gäste / Plätze, auch solange sie leer sind. Alte zugeordnete Sonderplätze bleiben dort erreichbar, Deckel ohne Bereich unter Sonstige. Eine zweite Liste aller offenen Tischdeckel entfällt. Sofortverkäufe bleiben separat sichtbar. Der aufgeklappte Bereich und die Scrollposition bleiben beim erneuten Öffnen erhalten. Der Bereich wird zusätzlich pro Browser-Tab in sessionStorage gemerkt; bewusstes Zuklappen bleibt ebenfalls erhalten. Ohne verfügbaren Browserspeicher funktioniert die Auswahl im laufenden Fenster. Geschlossene Bereiche bauen ihre Tischlisten erst beim ersten Aufklappen auf und aktualisieren vorhandene Inhalte erst beim Wiederöffnen. Ihre Kennzahlen bleiben live. Remote-Updates bündeln sich weiterhin pro Frame und berechnen nur die geänderten Deckelsummen neu; unveränderte Deckelkarten und Tischbuttons bleiben bestehen. Daten, Revisionen und lokale Sicherung bleiben sofort aktuell. Namen dürfen umbrechen, Beträge bleiben einzeilig; ungesendete Mengen erscheinen als … neu auch direkt am festen Tisch. Tisch- und Gastbuttons im Bereich sind mindestens 48 px hoch. Die bestehende untere Aktionsleiste enthält Sofortverkauf starten und Gast / Platz eröffnen. Bei Sofortverkaeufen sind ausschliesslich belegte, nummerierte Plaetze sichtbar. In der unteren Aktionsleiste gibt es genau eine Aktion Sofortverkauf starten, solange im weiterhin auf zwoelf begrenzten Pool ein Platz frei ist. Die serverseitig serialisierte Vergabe verhindert doppelte Reservierungen auf parallelen Servicegeraeten; leere DB-Zeilen bleiben unsichtbar und wiederverwendbar. Der fruehere Schnell-Deckel wird weiterhin verlustfrei nach Sofortverkauf 1 migriert. Über Umbuchen kann der Service wahlweise den ganzen Deckel oder einzelne Artikel und Mengen auf einen anderen Tisch übertragen. Ein bereits belegter Zieltisch wird dabei ergänzt; seine vorhandenen Positionen bleiben erhalten. Quelle und Ziel werden gemeinsam revisionsgeschützt und atomar in SQLite geändert, die Operation ist über ihre ID wiederholbar und wird nicht offline vorgemerkt. Offene Bar-/Thekenbons werden weder zurückgerufen noch neu erzeugt. Liegen danach alle Positionen eines laufenden Bons eindeutig auf dem Zieldeckel, wechseln dessen Stationskarte und ein eventuell laufender Ofeneintrag auf den neuen Tischnamen. Das gilt auch dann, wenn die letzte Bonposition bereits vollständig im Ofen liegt und deshalb kein offener Ticket-Datensatz mehr existiert; der spätere Ofenbon druckt den neuen Tisch. Bei einer Teilung desselben Bons über zwei Deckel bleibt die Produktionskarte als eine Einheit unter ihrem bisherigen Tischnamen bestehen. Bar und Theke gleichen sichtbare Bon-Karten inkrementell ab: Ein Ereignis ersetzt nur den betroffenen Deckel, während unveränderte Karten und laufende Touch-Interaktionen stabil bleiben. Die gemeinsame Arbeitsfläche station-workspace.js startet Bar und Theke im Bon-fokussierten „Willy Fokus“. Der mittige Stationsname bildet dort den ruhigen Anker; der einheitliche, offene Modusgriff aus kleinem Ansicht und dem aktuellen Modus steht getrennt links und öffnet Fokus, Kombi und Bedienen als materielles Dreier-Triptychon. Das aktive Feld verwendet die vorhandene dunkelgrüne Akzentfläche mit ruhiger grüner Kante; Gold bleibt den kleinen Orientierungszeichen vorbehalten. Light und Dark bleiben im gemeinsamen Markensystem. Der Lautsprecher ist ausschließlich eine Bar-/Theke-Aktion; die eigenständige Service-App zeigt nur Light/Dark. Im Fokus sitzt der Stationslautsprecher direkt beim Theme-Schalter. In Kombi und Bedienen erscheint dieselbe Stationssteuerung innerhalb des eingebetteten Service-Kopfes und ruft weiterhin Bar beziehungsweise Theke auf. Lautsprecher und Light/Dark bilden mit nur 6 px Abstand ein klares Paar; der Tonstatus wird zwischen Station und eingebettetem Service synchron gehalten. station-audio.js prüft beim bewussten Aktivieren den Bonton, an der Theke zusätzlich den Ofenton tatsächlich: Erst nach erfolgreichen Wiedergabestarts gilt Ton als bereit. Scheitert eine spätere Wiedergabe, wechselt der Schalter sichtbar auf den orangefarbenen Zustand Ton reaktivieren. Nach einem echten Socket-Reconnect signalisiert die Station genau einmal neu hinzugekommene relevante Bons; der normale Erstbestand bleibt lautlos. Ein zweiter Klick auf denselben Ansichtsgriff schließt das Menü deterministisch, unabhängig vom nativen <details>-Umschalten. Im Fokus stehen darunter die verständlichen Darstellungen Groß, Balance und Überblick. Ein gemeinsamer Faktor skaliert den gesamten Bon einschließlich Geometrie und Innenabständen proportional; eine oder zwei Spalten teilen die gesamte verfügbare Bonfläche ebenso vollständig wie drei Spalten. Groß nutzt die breite Karte als zweispaltiges Artikelmanifest, statt Inhalt in eine künstliche schmale Lesespur zu zwingen; Balance bleibt einspaltig je Bon, aber deutlich größer. Die Reihenfolge steht links im Kopf: die erste Nummer dunkelgrün, weitere goldfarben. „Jetzt“ entfällt; vollständig bezahlte Produktionsbons zeigen daneben „✓ Bezahlt“ mit denselben Success-Tokens wie „Fertig“. Das Zeitsiegel zeigt die Wartezeit ab „1 Min.“ in der ursprünglichen Schriftgröße, mit fetter Zahl; ab 15 Minuten wechseln Schrift und Kontur zur Warning-Farbe. Kopf- und Bonhöhe bleiben gleich. Lange Bereichsnamen werden vor der Tischnummer gekürzt. Gelesen wird links nach rechts, dann in die nächste Zeile; die älteste offene Runde steht zuerst. Jede Position folgt als Produktionszeile der festen Folge Menge, Produkt und Größe. Die Menge ist eine große tabellarische Zahl an einer schmalen grünen Leitkante; die Größe schließt die Zeile als kräftiger Goldmarker auf ruhiger Gold-Tint-Fläche ab. Eine feine, nach rechts dichter werdende Gold-Tint-Linie verbindet Produkt und Größe, ohne eine zusätzliche Box zu bilden. Identische Varianten aus mehreren Runden werden nur visuell addiert; unterschiedliche Größen, Notizen und Ofengerichte stehen separat in der Warteliste am Ofen. Unter 1080 px beziehungsweise 720 px begrenzt die Ansicht die wirksame Darstellung sicher auf Balance oder Groß. Modus und gewünschte Darstellung bleiben getrennt pro Bar-/Thekegerät im Browser gespeichert. Kombi zeigt Bons in einer schmaleren clamp(420px, 32vw, 480px)-Spalte und Service parallel. Ihr Stationskopf verdichtet Admin zum bekannten Schildsymbol, hält aber Station, Modus und roten Bon-Zähler vollständig sichtbar. Bedienen gibt der geladenen Service-App die volle Stationsbreite. In der vorhandenen Kopfzeile bilden der identische Ansichtsgriff und Offene Bons / Zahl zwischen Wordmark und Statusaktionen eine offene, gemeinsame Informationsgruppe; nur das Bon-Symbol hebt einen Zuwachs kurz hervor. Erst unter 760 px weicht die Gruppe mangels horizontalem Platz in die reservierte Zeile aus. Solange der eingebettete Deckel-/Gastdialog offen ist, wird nur diese Navigation ausgeblendet. Der eigenständige Service-Kopf bietet Reservierungen mit dem bekannten Kalender-Symbol und der Zahl der heute noch erwarteten Gruppen. Im eingebetteten Service entfällt dieser doppelte Einstieg; dort gilt Admin → Reservierungen. Happy Hour läuft ausschließlich automatisch von 17:00 bis 20:00 Uhr in Europe/Berlin. Der manuelle Schalter und der frühere „HH aktiv“-Hinweis unter dem Logo entfallen beide; erkennbar ist die Happy Hour ausschließlich an den umgeschalteten Preisen. An der Theke bleibt die Ofenbelegung in allen drei Arbeitsflächen operativ sichtbar. Jede der vier deutlich nummerierten Etagen hat vier Plätze: Pizza/Flammkuchen belegt drei, ein Baguette einen. Eine feste Überschrift und Legende oberhalb der Karten entfallen; stattdessen sitzt ein Ofensymbol direkt im grünen Etagenrücken über der Nummer. Das sichtbare Wort Plätze ordnet die vier Gold-/Grün-Segmente weiterhin als Kapazität ein – nicht als Zeitbalken. Damit passen exakt Pizza plus ein Baguette oder bis zu vier Baguettes. Eine freie Etage sagt nur einmal Frei; ihre leere Segmentleiste zeigt die verfügbare Kapazität ohne zweite Textwiederholung. Freie Etagen sind auf 66 px eingeklappt; belegte Etagen bleiben unabhängig von einem oder vier Artikeln fest 142 px hoch. Pro Etage steht der fertige beziehungsweise zeitlich nächste Artikel mit Gast, Artikel und Timer im Vordergrund. Weitere Artikel bleiben als kleine Typ-/Timer-Chips sichtbar. Ein Tipp öffnet alle vollständigen Artikelzeilen in einem Etagenblatt über der Arbeitsfläche, ohne Bons oder Ofen nach oben zu schieben. FERTIG bleibt dort und im priorisierten Artikel als klare Aktion antippbar. Ein noch laufender Artikel kann im Etagenblatt bewusst über die bereits vorhandene große Ofen-Artikelzeile vorzeitig abgeschlossen werden; jede Detailzeile nennt eindeutig Für Bon auswählen. Vor der Auswahl bleibt die vorhandene .modal-actions-Leiste vollständig ausgeblendet. Der erste Tipp wählt den Artikel nur aus. Dasselbe Etagenmodal wechselt dann zu Auswahl prüfen, zeigt ausschließlich den gewählten Artikel und fragt mit den vorhandenen Aktionen Andere Auswahl und Bon drucken nach. Eine vorhandene Trennlinie sowie zusätzlicher Abstand lösen die Aktionen klar von der ausgewählten Artikelzeile. Das gilt auch für bereits abgelaufene FERTIG-Zeilen im Etagenblatt. Erst dann beendet der serverbestätigte Ofenabschluss den Timer, räumt den Platz und druckt den Ofenbon mit dem kanonischen Einzelpreis des fertigen Artikels. Nach dem bestätigten Abschluss erscheint keine zusätzliche Erfolgsmeldung; der herauskommende Bon ist die Rückmeldung. Nur ein tatsächlicher Druckfehler öffnet die vorhandene Karte Druck nicht bestätigt mit Druckerstatus prüfen und Schließen. Baguettes laufen 12 Minuten, alle Flammkuchen und Pizza jeweils 15 Minuten. Die Etagenwahl nennt den Bedarf des Artikels, hält auch Sperrgründe lesbar und sperrt unpassende Kombinationen. Ab 961 px stehen alle vier Etagen in einer Reihe; bis 960 px stehen zwei Etagen je Reihe, mobil eine. Dort bleibt der Ofen auf 36dvh begrenzt und intern scrollbar, damit die Bonfläche den größeren Arbeitsanteil behält. Der zentrale Systemstatus in der eingebetteten Serviceansicht zeigt Verbindung, Infrastruktur und die Zahl verbundener Service-, Bar- und Thekegeräte. Ohne Serververbindung bleiben Bons, der Dauer-Glow des ältesten Bons und der Ofenstatus sichtbar; nur serverändernde Aktionen wie Fertig, In den Ofen, Ofenabschluss und Rückgängig sind nativ gesperrt. Nach Wiederverbindung werden sie automatisch wieder freigegeben. Ofenaktionen und Bonabschlüsse warten auf einen Server-Ack und zeigen statt eines rein optimistischen Abschlusses eine sichtbare Fehlermeldung. Wird ein Bon offline oder ohne Ack vorgemerkt, versucht der Service die Übertragung höchstens 15 Minuten lang. Danach wird er niemals automatisch an Bar oder Theke ausgespielt. Beim nächsten eindeutigen Serverkontakt prüft der Client die Ticket-ID gegen einen dauerhaften Zustellnachweis: Bereits empfangene Bons gelten als erledigt, tatsächlich fehlende erscheinen persistent als Bon nicht gesendet. Erst Auf Deckel prüfen stellt deren Artikel bewusst als Neu wieder her; Verwerfen entfernt den lokalen Altbon. Einträge ohne verlässlichen Zeitstempel gelten ebenfalls als abgelaufen. Ein versehentliches „Fertig“ lässt sich an Bar und Theke fünf Sekunden lang serverbestätigt rückgängig machen. Bar und Theke zeigen diese Frist als kompakte einzeilige Leiste im normalen Layout unter der Bonliste. Sie reserviert 62 px, statt Karten zu überlagern; neue Bestellungen bleiben vollständig sicht- und bedienbar. Im Service kann ein gerade gesendeter Bon fünf Sekunden lang zurückgeholt werden, solange Bar oder Theke seine Bearbeitung noch nicht begonnen hat. Der Service-Toast nutzt dafür eine kompakte einzeilige Leiste mit der vorhandenen 44-px-Schließen-Aktion. Das neutrale X blendet ausschließlich die Leiste aus und beendet die lokale Rücknahmemöglichkeit; es sendet niemals einen Rückholbefehl. Ausschließlich der räumlich getrennte Danger-Button Rückgängig fordert die serverbestätigte Rückholung an. Die Hinweisfläche hat auf allen Plattformen wieder ihre ursprüngliche Trefferfläche; Rückgängig, X und Countdown arbeiten unabhängig von der Hintergrund-Scrollsperre. Rückholen und Stationsstart werden pro Bon serverseitig serialisiert, sodass genau eine Aktion gewinnt. Ein vollständiger Checkout beendet außerdem einen noch sichtbaren Stations-Countdown sofort – auch wenn der fertige Bon als unveränderliche Historie in der Datenbank erhalten bleibt. Kurzmeldungen wie „Bestellung wird bereits bearbeitet“, ein lokal vorgemerkter Offline-Bon oder „WLAN wieder da“ erscheinen in einem eigenen ruhigen Statusfenster. Meldungen ersetzen sich atomar; ein ablaufender älterer Timer kann keine neuere Meldung ausblenden. Die Abrechnung zeigt standardmäßig die Komplettzahlung; Artikelmengen erscheinen erst im bewusst gewählten Modus „Getrennt zahlen“. Die Aktion erscheint nur ab zwei gesendeten Stück auf dem Deckel und bleibt zwischen aufeinanderfolgenden Teilzahlungen aktiv, solange noch mindestens zwei gesendete Stück offen sind. In der Split-Auswahl werden identische gesendete Positionen aus mehreren Bestellrunden zu einer Mengenzeile zusammengefasst. Variante, Preis, Kombo-Auswahl und Zusätze müssen dafür übereinstimmen; Normal- und Happy-Hour-Preis bleiben damit getrennt. Die Auswahl wird intern weiterhin auf die ursprünglichen Deckelzeilen verteilt. Kassierbar sind in beiden Modi ausschließlich bereits gesendete Positionen. Noch ungesendete Neu-Mengen werden im Dialog nicht als Zahlbetrag angeboten, erscheinen aber transparent im Hinweis und in der verbleibenden Deckelsumme. „Alles kassieren“ heißt bei einem gemischten Deckel deshalb sichtbar „Gesendetes kassieren“; die ungesendeten Mengen bleiben anschließend auf dem offenen Deckel. Besteht ein Deckel nur aus ungesendeten Positionen, ist Kassieren nativ deaktiviert. Beim Kassieren sendet der Service eine einmalige Checkout-ID, den Deckelnamen, die Belegwahl und bei einer Teilzahlung nur Zeilenindex und Menge – niemals Produkt- oder Preisdaten.

Die Bewirtungsbeleg-Auswahl ist eine eigene markenkonforme Checkbox: Aktiv zeigt sie auf dem dunkelgrünen Pub-Hintergrund einen goldenen Haken statt des nativen schwarzen Browser-Hakens. Auf dem gedruckten Bewirtungsbeleg werden identische Positionen aus mehreren Bestellrunden zu einer Mengenzeile zusammengefasst. Artikel, Größe, Preis, Kombo-Auswahl und Zusatz müssen dafür übereinstimmen; unterschiedliche Varianten und Preise bleiben getrennt.

Der Server lädt und kanonisiert den gespeicherten Deckel und bestätigt erst nach einer gemeinsamen SQLite-Transaktion für Deckel, offene Bons, Beleg und Idempotenz-Historie. Erst diese Bestätigung ändert die Service-Ansicht. Währenddessen zeigt die gesperrte Hauptaktion eindeutig „Wird bestätigt …“; nur bei einem wirklich unklaren Ergebnis bietet sie „Erneut bestätigen“ an. Bei einer unklaren Verbindung wird dieselbe Checkout-ID wiederholt; dadurch kann dieselbe Zahlung weder doppelt abgezogen noch doppelt gedruckt werden. Der Server wendet die Gesendet-Sperre selbst an und lehnt auch manipulierte Teilzahlungen auf ungesendete Zeilen ohne Deckeländerung ab. Nach erfolgreicher Zahlung endet außerdem die Rückgängigfrist der tatsächlich bezahlten, serverseitig diesem Deckel zugeordneten Bons sofort. Jeder neue bestätigte Voll- oder Teilcheckout speichert außerdem einen kanonischen Positions-Snapshot im Abschlussarchiv – unabhängig davon, ob ein Papierbeleg gewählt wurde. Über Admin → Abschlüsse zeigen Bar und Theke standardmäßig denselben gesamten Pub: alle archivierten Kassierungen, Artikel und Umsätze aus beiden Stationen. „Gesamt · Theke · Bar“ ist auf jedem Tablet frei wählbar. Der Bereich filtert Artikel, Umsatzanteile, Produktwahl und sämtliche Auswertungen; gemischte Bons erscheinen in beiden Einzelbereichen, ihre Beträge zählen jeweils nur anteilig. Komboumsätze zählen einmal beim Hauptartikel beziehungsweise der gewählten Biersorte. Details nennen den ursprünglichen Gesamtbetrag zusätzlich. Beim erneuten Öffnen ist immer „Gesamt“ gewählt. Die Chef-PIN wird bei jedem Öffnen neu geprüft. Eine eigene Archivfreigabe gilt nur für dieses geöffnete Fenster und höchstens 15 Minuten; Schließen widerruft sie auch serverseitig. Arbeitsstunden-/Dienstplanfreigaben öffnen das Archiv nicht. Nach Serverupdate müssen die Appseiten neu geladen werden. Die 30 neuesten passenden Vorgänge sind durchsuchbar; Kennzahlen zählen sämtliche Treffer im gewählten Zeitraum. Gleiche Artikel mit identischer Größe, Preis und Auswahl werden für die Detailansicht konsolidiert, gespeicherte Snapshots und getrennte Teilzahlungen bleiben erhalten. Der Produktfilter zeigt den tatsächlichen Produktumsatz zusätzlich zum Gesamtbetrag der betreffenden Bons beziehungsweise deren Bereichsanteil. Ohne Produktfilter steht der Umsatz des gewählten Bereichs im Mittelpunkt. Top/Flop 10 nach Menge und Umsatz, eine vollständige Artikeltabelle je Größe, die Liste nicht verkaufter Katalogartikel und Kassierungen je Uhrzeit stehen in einer aufklappbaren Auswertung. Ranglisten und Artikeltabelle trennen die Größen, der Produktfilter fasst sie zusammen. Die Hauptansicht konzentriert sich auf drei Kennzahlen: Umsatz, Abschlüsse und Durchschnitt je Abschluss. Artikelmengen, Erklärungen und genaue Vergleichszeiten liegen in den drei aufklappbaren Bereichen „Artikel & Größen“, „Tage & Uhrzeiten“ und „Hinweise & Ausblick“. Die Wochenansicht zeigt sieben Betriebstage mit Umsatzspitze und dem Tag mit den meisten Abschlüssen; laufende Tage sind markiert, zukünftige Tage bleiben ohne Ist-Wert. Abschlüsse sind Zahlungsvorgänge und erlauben keine Aussage über Gästezahl oder tatsächliche Belegung. Unter den Wochendetails berechnet die App lokal einen Umsatzrichtwert für die nächsten sieben Betriebstage: Median vergleichbarer Wochentage der letzten acht Wochen, mindestens vier erfasste Verkaufstage je Wochentag. Die Spanne zeigt historische Min-/Max-Werte, kein Prognoseintervall. Erster Archivtag, laufender Tag und Tage ohne Pub-Archivdaten liefern keine Vergleichswerte; bei nachgewiesenen Pub-Verkäufen zählt ein fehlender Stationsumsatz dagegen als echte Null. Bei zu wenig Historie erscheint ein Hinweis. Suche/Produktfilter deaktivieren den Ausblick, Gesamt/Theke/Bar bleiben wählbar. Es werden keine externen KI-Dienste oder Wetterdaten verwendet. Die Auswertung öffnet und schließt mit dem bestehenden 220-ms-Größenwechsel; reduzierte Bewegung wird respektiert. Nummerierte SVG-Rangbalken und ein Stundenverlauf ergänzen die lesbaren Zahlen und die genaue Tabelle. Zeitraumwahl und Blätterleiste sind getrennt: Heute/Woche/Monat legt die Einheit fest, die Pfeile darunter gehen Betriebstag, Woche beziehungsweise Monat zurück. Der Betriebstag beginnt um 06:00 Uhr in Berlin; wann der Dienst tatsächlich beginnt (17–02, 15–03 Uhr oder anders), spielt dafür keine Rolle. Alle Vergleiche und Tagesreihen zählen Betriebstage, nicht Kalendertage. Eine offene Übersicht aktualisiert sich nach Kassierungen automatisch. Ohne Archivfreigabe liefert der Server weder Liste noch Details. Ein manueller Nachdruck ist in dieser Ansicht noch nicht freigegeben. In Bar und Theke werden Abschlüsse und Systemstatus beim Öffnen in die Stationsseite portaliert. Fokus, Kombi oder Bedienen bleiben deshalb exakt als aktuelle Hintergrundansicht erhalten; der Service-Iframe wird dafür nicht mehr auf Vollfläche vergrößert.

2. Setup & Betrieb

npm install     # nach jedem git pull auf einem neuen Rechner nötig (node_modules ist nicht getrackt)
npm start       # startet server.js, Standard-Port 3000 (per PORT-Env überschreibbar)

Unbehandelte Prozessfehler, fehlgeschlagene Datenbankverbindungen und HTTP-Startfehler beenden den Server nach einem geordneten Socket-/SQLite-Shutdown mit Exit-Code 1. Damit der Betrieb nach einem seltenen fatalen Fehler automatisch wieder verfügbar wird, liegt unter deploy/willyspub.service.example eine gehärtete systemd-Vorlage mit Restart=on-failure und geordnetem SIGTERM-Stopp. Sie geht von einem eigenen Nutzer willyspub, /opt/willyspub als App-Pfad und /mnt/willy-backups als externem Backup-Ziel aus. Nutzer, Pfade und RequiresMountsFor müssen vor der Installation an den echten Pi angepasst werden; die aktive Konfiguration ist anschließend mit systemctl cat <dienstname> zu prüfen.

npm run preflight

Die zugehörige secret-freie Environment-Vorlage liegt unter deploy/willyspub.env.example. Sie darf nicht unverändert gestartet werden: Alle CHANGE_ME-Werte ersetzen, als /etc/willyspub/willyspub.env ausschließlich root-lesbar ablegen (chmod 600) und die produktive Datei niemals committen. deploy/, scripts/ und tests/ werden vom Webserver grundsätzlich mit 404 abgewiesen.

Der rein lesende Produktionscheck prüft ohne Ausgabe von Secrets unter anderem: aktive Authentifizierung, getrennte Stationspasswörter, Chef-PIN, TLS-Paar und Schlüsselrechte, Datenbankrechte, Druckeradresse sowie Erreichbarkeit und Mount-Situation des Backup-Ziels. Blocker führen zu Exit-Code 1; die systemd-Vorlage führt den Check deshalb als ExecStartPre vor jedem Start aus. Eine bestehende pub.db muss wegen ihrer Betriebs- und Personaldaten chmod 600 besitzen. Der Check ersetzt keine Vor-Ort-Prüfung von Prozessneustart, Drucker, Zertifikatsvertrauen, PWA und Endgeräten.

2.1 Anwendungszugang konfigurieren

Der Server startet absichtlich nicht ohne konfigurierte Zugangsdaten. Auf dem Raspberry Pi werden ein zufälliges Token-Geheimnis und getrennte Passwörter für die drei Ansichten gesetzt:

export AUTH_TOKEN_SECRET="$(openssl rand -hex 32)"
export AUTH_SERVICE_PASSWORD="ein-langes-eigenes-service-passwort"
export AUTH_BAR_PASSWORD="ein-anderes-langes-bar-passwort"
export AUTH_THEKE_PASSWORD="ein-anderes-langes-theke-passwort"
export AUTH_PAYROLL_PIN="4729"
npm start

Die Werte gehören in die geschützte EnvironmentFile der systemd-Unit (Dateirechte 600), nicht ins Git-Repository. AUTH_PAYROLL_PIN besteht aus 4 bis 12 Ziffern und schützt die Chef-Abrechnung, die Chef-Planung des Dienstplans und das Abschlussarchiv. Die PIN wird serverseitig geprüft; fünf Fehlversuche sperren weitere Versuche derselben IP für 15 Minuten. Eine erfolgreiche Freigabe gilt 15 Minuten für die aktuelle Socket-Verbindung. Alle-Personen-Daten, Lohnwerte, Export, Monatsabschluss, der Abrechnungsbon sowie Abschlusslisten und -details sind ohne Freigabe gesperrt. Nach Ablauf, Verbindungsabbruch oder Verlassen der jeweiligen Chefansicht entfernt der Client die geladenen sensiblen Daten. Fehlt AUTH_PAYROLL_PIN, bleiben Service, Bar und Theke verfügbar; die geschützten Chefbereiche bleiben bis zur Konfiguration gesperrt. Nach erfolgreicher Stationsanmeldung speichert das Gerät pro Ansicht ein eigenes signiertes, 30 Tage gültiges Token. Dadurch können Service, Bar und Theke auch in mehreren Tabs desselben Browsers gleichzeitig geöffnet und unabhängig neu geladen werden. Die eingebettete Service-Karte in Bar/Theke verwendet das Token ihrer Elternstation. Eine Änderung von AUTH_TOKEN_SECRET meldet sofort alle Geräte ab; eine Passwortänderung betrifft die nächste Anmeldung. Fünf falsche Versuche von einer IP sperren weitere Anmeldungen für 15 Minuten.

Der Login-Layer wird vor der eigentlichen Oberfläche angezeigt und erst entfernt, wenn die authentifizierte Socket-Verbindung samt Anfangszustand bereit ist. Beim ersten Laden nach dem Umstieg wird das vorhandene alte Token in den passenden Stationsschlüssel übernommen; die anderen Ansichten müssen gegebenenfalls einmalig neu angemeldet werden.

Nur für lokale Entwicklung ohne echte Daten ist AUTH_DISABLED=true vorgesehen. Dieser Schalter darf auf dem Pub-Raspberry niemals gesetzt werden.

Socket.IO prüft das Token vor dem Senden des Anfangszustands und prüft danach jedes eingehende Ereignis gegen die Rolle. Bar/Theke dürfen wegen der dort eingebetteten Service-Ansicht auch Bestellungen bedienen; Stationsmeldungen sind jedoch an die passende Rolle gebunden.

Die betrieblichen iPhones, Android-Smartphones und Android-Tablets öffnen anschließend index.html, bar.html bzw. theke.html über die LAN-IP des Servers. Die ESC/POS-Bondrucker werden in server.js getrennt konfiguriert (TCP, normalerweise Port 9100): BAR_PRINTER_IP/BAR_PRINTER_PORT für Barbons und THEKE_PRINTER_IP/THEKE_PRINTER_PORT für Thekenbons, Ofenbons, Bewirtungsbelege und sonstige Ausdrucke. PRINTER_IP/PRINTER_PORT bleiben als kompatibler Fallback für den Thekendrucker erhalten. Ohne explizite Bar-Konfiguration wird der bestätigte Bardrucker 192.168.178.124:9100 verwendet. Beide Ziele besitzen getrennte serielle Queues, damit ein nicht erreichbarer Drucker den anderen nicht blockiert.

Produktionsbons an Bar und Theke drucken jede Position in 2× Breite und einfacher Höhe. Artikelname und linksbündiger Zeilengesamtpreis stehen fett und ohne redundantes PREIS untereinander; Sonderwünsche bleiben normalgewichtig und als zusammenhängender Text erhalten. Trennlinien mit je einer Leerzeile davor und danach gliedern mehrere Positionen. Der Umbruch trennt keine Wörter und behandelt passende Klammerzusätze als Einheit; passt ein Klammerblock nicht mehr in die 24 Zeichen breite Zeile, beginnt er vollständig in der nächsten.

2.2 HTTPS im LAN (Pflicht für die PWA-Installation)

Android erhält über theme-shell.js vor dem Rendern data-platform="android". Der Seitenrahmen unterbindet Browser-Overscroll (Pull-to-refresh und browserseitige Wisch-Navigation), auch im eingebetteten Service. Listen und Kategorien bleiben scrollbar. Der Ambiente-Glow bleibt auf allen Geräten statisch. Dialoge und Kombinationsauswahl verwenden auf Android, iOS und Desktop dieselbe Hintergrundunschärfe. Android-Systemgesten am Bildschirmrand werden dadurch nicht gesperrt; eine vollständige Gerätesperre erfordert eine gesonderte Kioskeinrichtung. Nach einem Update die App vollständig schließen und neu öffnen; eine Neuinstallation ist für diese CSS-/Script-Änderungen nicht erforderlich. Hardwareprüfung siehe ROADMAP.md.

Android und iOS installieren eine Web-App nur dann als echte PWA (Vollbild ohne Browser-Adressleiste), wenn sie über einen secure context ausgeliefert wird. Reines HTTP über die LAN-IP reicht nicht – dann entsteht nur ein Browser-Shortcut mit Adressleiste und der Service-Worker (sw.js) registriert sich nicht.

server.js startet automatisch als HTTPS-Server, sobald Zertifikat und Key vorliegen, sonst als HTTP (nur für lokale Entwicklung). Pfade über Env-Variablen, Default im Projektordner:

  • SSL_CERT – Default certs/cert.pem
  • SSL_KEY – Default certs/key.pem

certs/ ist bewusst nicht im Git und wird vom Static-Handler geblockt – der private Key darf nie per HTTP erreichbar sein.

Zertifikate erzeugen (auf dem Raspberry Pi, feste Server-IP 192.168.178.72):

sudo apt update && sudo apt install -y libnss3-tools
# mkcert-Binary passend zur Pi-Architektur (arm64 bei 64-bit Raspberry Pi OS; sonst arm):
curl -L "https://dl.filippo.io/mkcert/latest?for=linux/arm64" -o mkcert
chmod +x mkcert && sudo mv mkcert /usr/local/bin/

mkcert -install                       # erzeugt einmalig die lokale Root-CA
cd /pfad/zu/willyspub && mkdir -p certs
mkcert -cert-file certs/cert.pem -key-file certs/key.pem 192.168.178.72 localhost
npm start                             # startet nun via HTTPS

Aufruf danach: https://192.168.178.72:3000.

Root-CA auf jedem Android-Pad vertrauen (einmalig pro Gerät):

  1. rootCA.pem vom Pi holen (Ablageort zeigt mkcert -CAROOT) und auf das Pad übertragen.
  2. Einstellungen → Sicherheit → „Verschlüsselung & Anmeldedaten“ → „Zertifikat installieren“ → „CA-Zertifikat“ → rootCA.pem wählen (Warnung bestätigen; für eine eigene CA normal).
  3. Chrome öffnen, https://192.168.178.72:3000 laden – es darf keine Zertifikatswarnung kommen. Falls zuvor über HTTP getestet wurde: alten Shortcut entfernen und die Website-Daten der Seite in Chrome leeren.
  4. Chrome-Menü → „App installieren“ (nicht „Zum Startbildschirm hinzufügen“).

Danach starten Service/Bar/Theke auf Android im echten fullscreen-Anzeigemodus: ohne Adressleiste sowie ohne dauerhaft sichtbare Status- und Navigationsleiste. Die Android-Systemleisten lassen sich weiterhin mit einer Wischgeste vom Bildschirmrand vorübergehend einblenden. Nach einer Änderung des Manifest-Anzeigemodus muss eine bereits installierte Test-PWA entfernt und neu installiert werden; der Client-Code bleibt unverändert: Socket.IO nutzt relative URLs und wechselt unter HTTPS automatisch auf wss://. Die Manifeste verwenden echte 192- und 512-px-Kreisicons für purpose: any sowie ein separat komponiertes, vollflächiges 512-px-Icon für purpose: maskable. Service und Bar verwenden dabei Grün auf Gold, die goldene Theken-Grafik liegt kontrastreich auf Grün. iOS erhält pro Station ein eigenes 180-px-Touch-Icon. Neue Dateinamen verhindern, dass Android die früheren weich skalierten Manifest-Icons weiterverwendet; bereits installierte WebAPKs müssen trotzdem neu installiert werden. Für Chrome-PWAs genügt die User-CA; die „ab Android 7 nur System-CAs“-Einschränkung betrifft nur native Apps mit eigenem Netzwerk-Stack, nicht Chrome selbst.

2.3 Gesundheitsmonitor und automatische Backups

Ein Klick auf die Verbindungsampel öffnet den authentifizierten Gesundheitsmonitor. Er prüft Serverlaufzeit, Datenbankzugriff, freien Speicher, Erreichbarkeit des Bondruckers, verbundene Stationsgeräte und den Zustand der automatischen Datensicherung. Jeder Browser erhält dafür eine lokale, zufällige Geräte-ID; mehrere Verbindungen desselben Geräts werden zusammengefasst. Die eingebettete Service-Karte von Bar und Theke zählt nicht als zusätzliches Gerät. Der Druckertest öffnet nur eine TCP-Verbindung und erzeugt keinen Testbon; Papiermangel kann deshalb erst durch einen echten fehlgeschlagenen Druck auffallen. In Bar und Theke erweitert die eingebettete Service-Karte den Statusdialog vorübergehend auf die gesamte Stationsfläche; beim Schließen wird das normale Bon-/Service-Layout wiederhergestellt.

Der Server erzeugt beim ersten Betrieb ohne Sicherung und anschließend täglich ab 04:00 Uhr (Europe/Berlin) einen konsistenten, integritätsgeprüften SQLite-Snapshot. Standardziel ist runtime-backups/; über DB_BACKUP_DIR kann ein USB- oder NAS-Mount gesetzt werden. Die sieben neuesten Sicherungen samt SHA-256-Prüfsumme bleiben erhalten. Ablauf, manuelle Prüfung und Wiederherstellung stehen in docs/BACKUP_RESTORE.md.

runtime-backups/ ist von Git ausgeschlossen und durch das Static-Hardening nicht erreichbar. Eine lokale Sicherung auf derselben SD-Karte ersetzt kein externes Backup.

Reservierungsbuch für Service, Bar und Theke

Personen, Aufnahmeweg und Status verwenden die gestalteten Dropdowns der App. Bereichswahl, Tischwahl und Buchungsartwechsel geben direktes visuelles Feedback. Personenanzeige und Datum/Uhrzeit verwenden dieselbe Schriftgröße und Wertfarbe. Die Tischfläche verwendet dieselbe Grundfarbe, denselben Rand und dieselbe Rundung wie die Deckelübersicht. Der Zuordnungspfeil bleibt dem aufgeklappten Deckel-Popover vorbehalten. Native Zeitwerte mit angehängten Nullsekunden werden vor dem Speichern auf Stunden/Minuten normalisiert. Nach der Zeitwahl bleibt das Formular per Touch scrollbar; Tischzuordnung ist optional. Scrollleisten haben einen eigenen Abstand zum Inhalt, auch in beiden Tabletspalten. Innenabstand und Scrollleistenspur werden links und rechts optisch gleich ausgeglichen. In der Doppelansicht gilt das vom Dialogrand bis zum Inhalt und an der mittleren Trennlinie; das äußere Dialogpadding wird nicht ein zweites Mal addiert. Dieselbe Abstandsregel gilt für weitere Inhaltslisten der App. Die Systemeinstellung für reduzierte Bewegung wird berücksichtigt.

Über Admin → Reservierungen öffnen beide Tablets dasselbe interne Tagesbuch. Im Service öffnet der runde Kalender-Button im Stil der Hell-/Dunkel-Taste dasselbe Buch. Die kleine Zahl am Rand zählt heute noch erwartete Gruppen, nicht Personen. Bis 500 px füllt das Fenster den Bildschirm, mit denselben Funktionen und kompakteren Abständen. Im eingebetteten Service (Kombi/Bedienen) entfällt der zusätzliche Kalender-Button; der Einstieg liegt im Admin-Menü der Station. Bestellung, Tisch und Scrollposition bleiben erhalten. Bei Offline/fehlgeschlagener Zählerabfrage entfällt die Zahl; Reconnect, Tageswechsel und Live-Änderungen aktualisieren sie. Telefonische, per E-Mail eingegangene und vor Ort vereinbarte Reservierungen werden manuell eingetragen: Name, Datum, Uhrzeit, Personenzahl und explizite Raumwahl Theke, Bar oder Biergarten (geschlossene Veranstaltungen sind ganztägig). Die Personenwahl nutzt Plus/Minus und eine gestaltete Auswahl von 1–200 ohne Zahlentastatur. Neue Buchungen verlangen eine bewusste Auswahl; vorhandene Zahlen bleiben exakt erhalten. Kontakt und Notiz sind optional. Die Liste verwendet die Bereichskarten der Deckelübersicht: Theke, Bar und Biergarten, darunter die nach Ankunftszeit sortierten Buchungen. Bereich antippen fokussiert die Liste; „Alle Bereiche“ zeigt wieder alle. „Tisch offen“ liegt unter „Filter“ und bleibt bei aktiver Auswahl sichtbar. „Raum noch offen“ erscheint für uneindeutige Alt-Einträge. Das Buch öffnet mit „Erwartet“ und zeigt nur ausstehende Ankünfte. Mit dem Haken „Da“ wird die Gruppe direkt als angekommen bestätigt; nach Serverbestätigung verschwindet sie aus dieser Liste. „Angekommen“ und „Alle“ mit Anzahl erhalten den Tagesüberblick. „Zurück“ setzt versehentlich abgehakte Gruppen wieder auf erwartet. Stornierungen/Nicht-Erscheinen werden weiter im Formular gepflegt und bleiben unter „Alle“ sichtbar. Bereich und „Tisch offen“ filtern zusätzlich; die Tagessumme zählt erwartete/angekommene Reservierungen, keine Laufgäste.

Neue Buchungen starten immer mit „Tisch reservieren“: Datum, Uhrzeit, Personen und Theke/Bar/Biergarten mit einzelnen Tischflächen. „Bereich mieten“ ist eine getrennte Buchungsart. Der Mietumfang ist eine feste Radio-Auswahl: Gesamtes Pub, Bar, Theke, Biergarten. Gesamtes Pub steht immer zuerst; der gemeinsame Ganztagshinweis bleibt unter der Liste. Bei einer Auswahl öffnen oder verschieben sich keine Mietoptionen. Ein Bereichstipp ändert nie die Buchungsart. Bei einem Wechsel zur Miete und zurück bleiben Uhrzeit, gewählter Raum und Tische des normalen Entwurfs erhalten. Vorhandene Buchungen öffnen in ihrer gespeicherten Buchungsart. Eine neue Buchung startet auch nach einer Miete wieder als Tischreservierung.

Ein Tisch ist bei der Aufnahme nicht erforderlich. Beide Buchungsarten verwenden dieselben Radio-Zeilen für die Bereichswahl. SVG-Motive für Theke, Bar und Biergarten greifen die gefüllten Service-Symbole samt Iconflächen auf; ein Haus steht für das gesamte Pub. Platzsymbole stehen vor dem Namen, Radio- und Häkchenmarkierungen rechts. Deckel und Reservierungen nutzen dieselben Tisch-SVGs aus pub-tables.js und dieselbe Grundfläche mit Rahmen. Bei Deckeln steht rechts weiterhin Betrag/Status, bei Reservierungen die Auswahlmarkierung. Im Tischmodus stehen die optionalen Tische in einer eigenen Fläche unter der Bereichsliste: eckige Auswahlfelder mit Häkchen erlauben mehrere Tische. Erneutes Antippen eines Bereichs erhält die offene Tischfläche und ihre Auswahl. Ausgewählte Tische sowie alte Freitextzuordnungen erscheinen als Zusammenfassung. Theke/Bar-Wechsel erhält Tische; Innen-/Außenwechsel entfernt inkompatible Zuordnungen mit Hinweis. Die spätere Tischzuweisung, Änderung oder Aufhebung steht dem gesamten Team im Service und auf Bar- und Theken-Tablet ohne zusätzliche PIN offen, über eine Mehrfachauswahl der vorhandenen Plätze: drinnen Pub/Bar, draußen Biergarten. pub-tables.js liefert dieselben Tische und Gruppen wie die Service-Tischanlage. Vorhandene freie Tischangaben bleiben bei normalen Bearbeitungen erhalten, bis das Team sie ausdrücklich durch eine Auswahl ersetzt oder aufhebt. Mangels fester Tischkapazitäten und Aufenthaltszeiten findet keine automatische Belegungsprüfung einzelner Tische statt; ganztägige Bereichsbuchungen werden dagegen geprüft. E-Mails werden nicht automatisch importiert oder beantwortet.

Geschlossene Gesellschaften werden ganztägig mit einem der vier Mietumfänge gebucht:

  • Alles · gesamtes Pub: Bar, Theke und Biergarten zusammen.
  • Nur Bar.
  • Nur Theke: der bisher technisch als Pub geführte Bereich.
  • Nur Biergarten (Sommer).

Unter „Bereich mieten“ stehen alle vier Mietumfänge als eigene Bereichskarten bereit. Uhrzeit und einzelne Plätze entfallen, Name, Datum und Personenzahl bleiben erforderlich. Eine Buchung des gesamten Pubs erscheint einmal im Tagesbuch und kennzeichnet alle drei Bereiche als ganztägig gemietet. Auch nach Ankunft bleibt die Sperre bestehen. „Tisch offen“ zählt solche Veranstaltungen nicht als fehlende Zuweisung. Für den Sommerbetrieb werden keine unbestätigten festen Saisonmonate erzwungen; der Hinweis steht direkt bei der Biergartenmiete.

Der Server prüft den Mietumfang in beide Richtungen: „Alles“ kollidiert mit jeder aktiven Reservierung des Tages, eine Einzelmiete nur mit Reservierungen des betroffenen Bereichs. Bar, Theke und Biergarten können unabhängig voneinander vermietet werden. Mischzuweisungen und uneindeutige alte Inneneinträge werden berücksichtigt; letztere müssen vor einer betroffenen Bereichsbuchung geklärt werden. Storniert/Nicht erschienen gibt wieder frei; Wiederherstellen prüft erneut. Prüfung und Speichern laufen pro Datenbankverbindung serialisiert. Die Sperre betrifft das Reservierungsbuch, nicht das Bonieren auf vorhandene Deckel. Mietpreise, Zahlungen und Verträge werden hier nicht erfasst.

Der feste Kopf enthält „+ Neu“ für die häufige Aufnahme am Handy. Datum und Status bleiben beim Scrollen der Tagesliste sichtbar; Summen stehen einmal kompakt unter dem Datum. Die Zusatzfelder Kontakt, Notiz und Aufnahmeweg sind unter „Weitere Angaben“ gebündelt. Nach bestätigter Ankunft bietet „Ankunft rückgängig“ eine direkte Rücknahme mit dem bestätigten Versionsstand; parallele Änderungen bleiben konfliktgeschützt. Auf breiten Tablets (ab 1000 CSS-Pixeln Breite) bleibt beim Bearbeiten links die Tagesliste als nicht bedienbare Orientierung sichtbar; rechts scrollt das Formular unabhängig. „Zurück zur Liste“ beendet die Bearbeitung mit bestehendem Eingabeschutz.

Die feste Speichern-Leiste steht außerhalb des scrollenden Formularinhalts; die Scrollleiste endet oberhalb der Aktionen. Unter dem Button bleibt ein einzelner kompakter Abstand. Auf dem Handy bleibt die Reservierungsfläche auch bei kurzen Listen mindestens bildschirmhoch und schließt mit der vorhandenen Dialogfarbe bündig unten ab. Alle Änderungen werden erst nach Serverbestätigung übernommen und auf den anderen Geräten aktualisiert. Versionskonflikte erhalten die eigenen Eingaben und bieten den aktuellen Stand an. Bei einem Timeout wiederholt „Erneut bestätigen“ dieselbe Buchung, ohne Duplikat. Ohne Verbindung wird nichts als gespeichert ausgegeben. Unbestätigte Formulare werden vor dem Verwerfen geschützt. Die Daten liegen zentral in SQLite; reservations-store.js legt Tabelle/Index beim ersten Zugriff an und ergänzt Tischzuweisung, Raum, booking_type und rental_scope bei bestehenden Datenbanken automatisch. Alte Buchungen erhalten table; geschlossene Veranstaltungen verwenden exclusive und intern 00:00. rental_scope trennt all (gesamtes Pub) von Pub (nur Theke), Bar und Biergarten. Bestehende exklusive Pub-Einträge bleiben Thekenbuchungen und werden nicht auf alles erweitert. Das technische Feld area wird aus dem Raum abgeleitet; bestehende Clients bleiben kompatibel. Es gibt keine Kopplung an Schichtplan, Arbeitszeiten oder die Umsatzprognose.

2.4 Dienstplan und Verfügbarkeit

Derzeit nicht im Einsatz (2026-09-16): Dienstplan und Arbeitszeit sind aus dem Admin-Menü von Bar und Theke entfernt. Der beschriebene Funktionsumfang bleibt vollständig im Code (schedule.js, timesheet.js, schedule_*- und work_hours-Tabellen) und unverändert gültig; es fehlt nur der Einstieg. Wiedereinschalten heißt: je Station eine station-admin-action-Zeile mit WillySchedule.open() beziehungsweise WillyTimesheet.open() zurück ins Admin-Popover.

Bar und Theke bündeln Dienstplan und Arbeitszeit im einzeiligen Stationskopf unter einer goldenen „Admin“-Pille. Das kleine Menü lässt sich später um weitere Stationswerkzeuge wie eine Bestellverwaltung ergänzen, ohne den Bon-Titel oder den operativen Bon-Zähler zu verdrängen. Eine neue Woche erhält automatisch die normalen Pub-Einsätze: Montag, Mittwoch, Donnerstag und Sonntag 17:00–00:00 Uhr, Freitag und Samstag 17:00–01:00 Uhr; Dienstag bleibt als Ruhetag leer. Diese regulären Pub-Öffnungstage lassen sich nicht löschen oder umbenennen; bearbeitbar sind bei ihnen nur Beginn, Hinweis und der Personalbedarf. Zusätzliche Anlässe wie Konzerte werden als „Sondertermin“ am selben Tag ergänzt und sind als einzige frei umbenennbar, verschiebbar und löschbar. Als reine Startwerte tragen normale Wochen Montag/Sonntag vier, Mittwoch/Donnerstag fünf und Freitag/Samstag sechs Personen. Das sind keine festen Regeln: Der Chef ändert das Soll je Einsatz frei, etwa sonntags auf sechs Personen für ein WM-Finale.

Die Oberfläche trennt die drei Aufgaben konsequent in „Plan“, „Verfügbarkeit“ und die PIN-geschützte „Chef-Planung“; sichtbar ist immer nur der gewählte Arbeitsbereich. Der normale Dialog bleibt kompakt, während sich ausschließlich die Chef-Planung für die zweispaltige Arbeitsfläche verbreitert. Der veröffentlichte Plan erscheint als ruhige Tagesliste statt als horizontale Kartenwand. Der Dienstplan zeigt und bearbeitet ausschließlich den geplanten Beginn. Eine geplante Endzeit ist wegen der schwankenden Nacharbeiten kein Dienstplanwert; die tatsächliche Endzeit wird nur in der Arbeitszeiterfassung gespeichert. Das „bis“-Feld bleibt ausschließlich bei einer teilweisen Verfügbarkeit erhalten, weil es dort die persönliche zeitliche Grenze beschreibt.

Mitarbeiter wählen ihre Person und melden pro Öffnungstag über drei direkte Schalter „Ja“, „Teilweise“ mit Von-/Bis-Zeit oder „Nein“. Ein erneuter Tipp auf die aktive Wahl hebt sie wieder auf. Native Status-Dropdowns und die kaum genutzte Bemerkungszeile entfallen; die Zeitfelder erscheinen nur bei „Teilweise“ und liegen platzsparend unter dem Ja/Teilweise/Nein-Schalter; die Wochenliste bleibt bewusst auf eine ruhige Lesebreite begrenzt. Jede Angabe wird analog zur Arbeitszeit sofort gespeichert – es gibt keinen „Woche speichern“-Knopf mehr, sondern nur den Hinweis „Änderungen werden automatisch gespeichert“ und eine kurze „Gespeichert.“-Rückmeldung. Danach gibt es keinen weiteren Rückfluss: Die PIN-geschützte Chef-Planung teilt je Einsatz Personen durch direktes Antippen ein oder wieder aus. Sie ist in zwei Reiter getrennt: „Einteilen“ zeigt nur Einsatzliste und Einsatz-Editor mit Team, „Woche im Blick“ zeigt Wochenverteilung und die Liste „Noch keine Rückmeldung“; letztere nennt die Personen, die für die angezeigte Woche noch gar nichts gemeldet haben, damit gezieltes Nachfragen leichter fällt. So sieht der Chef beim Öffnen nur Woche, Einsatzliste und ein Team; die Auswertung ist einen Tipp entfernt. Hinzufügen lassen sich ausschließlich Personen mit „Ja“ oder „Teilweise“; das Backend lehnt fehlende und negative Rückmeldungen ebenfalls ab. Caro, Stefan und Mixi sind eine feste Ausnahme: Sie werden automatisch als fest angestellt angelegt, erscheinen in jeder Chef-Planung als „Immer verfügbar“ und brauchen weder eine Verfügbarkeitsmeldung noch eine Arbeitszeiterfassung. Deshalb stehen sie in den entsprechenden Mitarbeiter-Auswahlen und in der Monatsabrechnung nicht zur Wahl. Nach dem Antippen einer Person wählt der Chef den Beginn direkt in 30-Minuten-Schritten zwischen 17:00 und 21:00 Uhr; erst diese Zeitwahl speichert die Einteilung. Bei „Teilweise“ bleibt das gemeldete Verfügbarkeitsende erhalten. Bereits eingeteilte Personen erhalten dieselbe Startzeitleiste; eine geplante Endzeit wird weder angezeigt noch bearbeitet. Nach der Serverbestätigung zeigt der Teamkopf vier Sekunden lang Name und gespeicherte Startzeit. Die Chef-Planung ist in zwei Reiter getrennt. Im Reiter „Einteilen“ steht links die Einsatznavigation mit Datum und „eingeteilt / Soll“ neben dem jeweils ausgewählten Einsatz; rechts liegt genau ein Einsatz-Editor mit Team. Der Reiter „Woche im Blick“ zeigt separat die auswertenden Listen (Wochenverteilung und „Noch keine Rückmeldung“), damit sie den Arbeitsbereich nicht mehr überlagern. Zur besseren Übersicht ist der Einteilen-Reiter in klar abgegrenzte Zonen gegliedert: Die Einsatznavigation sitzt in einer eigenen Schiene mit je einer umrandeten Karte pro Einsatz, die das Datum, eine farbige Besetzungs-Pille (amber = noch nicht voll besetzt, grün = vollständig besetzt) und bei Sonderterminen ein „Sondertermin“-Kennzeichen trägt; die Teamliste ist eine geschlossene Fläche mit einem sichtbaren Trennband zwischen eingeteilten und noch verfügbaren Personen, und der Besetzungsstand erscheint als ablesbares Badge. „Veröffentlichen“ und der standardmäßig aufgeklappte Hinweis-Streifen bleiben über beiden Reitern sichtbar. „+ Sondertermin“ ist als Ausnahme direkt bei den Einsätzen eingeordnet. Die kompakte Maske fragt nur Datum, Bezeichnung, Beginn und Personenzahl ab und ersetzt währenddessen den normalen Editor, statt ein zweites Formular darüberzustapeln; sie liegt dabei als flache Fläche im Arbeitsbereich wie der reguläre Editor, nicht mehr als abweichende Karte. Angelegte Sondertermine sind in Navigation und Editor ausdrücklich gekennzeichnet und besitzen dort eine direkt sichtbare, zweistufig bestätigte Löschaktion. Im „Woche im Blick“-Block zeigt die geschützte Wochenverteilung ausschließlich eingeteilte Personen und für sie, an wie vielen unterschiedlichen Tagen sie eingeplant sind und an wie vielen Tagen sie sich verfügbar gemeldet haben. „Minijob“ und „Fest“ bleiben dabei sichtbar, damit angebotene Tage nicht versehentlich als gewünschte Anzahl von Arbeitstagen verstanden werden. Mehrere Einsätze am selben Kalendertag zählen als ein Arbeitstag; es gibt bewusst keine pauschale rote Grenze ohne Stundenbezug. Hinweise stehen direkt unter dem Wochenkopf und sind standardmäßig aufgeklappt. Mehrere Unterbesetzungen werden darin zu einem Hinweis zusammengefasst; die genauen Werte bleiben in der Einsatznavigation sichtbar. Fehlende Rückmeldung, Nichtverfügbarkeit, Zeitabweichungen und Doppelbelegung blockieren eine bewusste Veröffentlichung nicht. Ruhige Einblend- und Wechselanimationen unterstützen die Orientierung und werden bei reduzierter Bewegung abgeschaltet. Beim Öffnen folgt der Dienstplan derselben 250-ms-Fade-und-Scale-Bewegung wie Checkout, Rechner, Ofendialog und Arbeitszeit; seine internen Ansichts- und Größenwechsel bleiben bewusst kürzer, damit Navigation nicht träge wirkt. Wie alle echten App-Modals verwendet er außerdem die gemeinsame abgedunkelte und dezent weichgezeichnete Hintergrundebene. Jede Änderung an Einsatz, Soll oder Einteilung setzt die Woche wieder auf Entwurf. Mitarbeiter sehen keine Entwurfseinteilungen; erst „Plan veröffentlichen“ macht den fertigen Wochenplan für alle lesbar. Unterbesetzte Einsätze nennen auch im veröffentlichten Plan direkt unter den eingeteilten Personen, wie viele Personen noch fehlen. Eine zusätzliche Planbestätigung gibt es nicht. „Teilen“ steht nur für veröffentlichte Pläne bereit und übergibt den Plan als Text an die Systemfreigabe oder kopiert ihn in die Zwischenablage. Leere Von-/Bis-Felder zeigen je Öffnungstag direkt 17:00–00:00 Uhr beziehungsweise am Freitag und Samstag 17:00–01:00 Uhr; beim Status „teilweise“ werden diese Werte als Zeitfenster gespeichert. Vollständige Verfügbarkeiten werden beim Verlassen der Chef-Planung, beim Schließen, bei Verbindungsabbruch und nach Ablauf der 15-Minuten-Freigabe wieder aus dem Client entfernt.

Planzeiten und geleistete Zeiten bleiben getrennt. Das Arbeitsstunden-Modal zeigt den heutigen geplanten Einsatz nur als Hinweis; „Dienst starten“ und „Dienst beenden“ speichern weiterhin ausschließlich die tatsächlichen Zeiten. Beim Öffnen wird keine zuletzt verwendete Person automatisch übernommen; die Person wird bewusst gewählt. Danach bleibt zunächst der heutige Dienst sichtbar, während Monatsübersicht, Monatswahl und Personenverwaltung über sekundäre Schalter erreichbar sind. Mitarbeiterprofile unterscheiden „fest angestellt“ und „Minijob“ und können ein frei gepflegtes monatliches Planlimit tragen. Caro, Stefan und Mixi werden dauerhaft als fest angestellt und arbeitszeitbefreit geführt, alle übrigen als Minijob. Wie im Dienstplan begleiten kurze Einblendungen die Auswahl, Monatsdetails, Verwaltung und Chef-Abrechnung; Dialoghöhe und -breite wechseln kontrolliert statt zu springen. Bei aktivierter Systemoption „Bewegung reduzieren“ entfallen diese Effekte. Im Dienstplan ist davon ausschließlich die Beschäftigungsart in der PIN-geschützten Wochenverteilung sichtbar; Profilpflege und Monatslimit bleiben bei der Arbeitszeit.

Die Rückmeldung funktioniert derzeit innerhalb des Pub-Netzes. Ein Zugriff von zu Hause ist bewusst noch nicht freigeschaltet; dafür ist später ein getrennter, sicherer Mitarbeiterzugang statt einer Portfreigabe des Kassensystems nötig.

3. Design-System & UI-Richtlinien

Modernes Light/Dark-Theme, vollständig über CSS-Variablen gesteuert. Single Source of Truth ist style.css – dort stehen im :root-Block (Light) und im [data-theme="dark"]-Block (Dark) alle Tokens. Diese README beschreibt nur die Rollen; konkrete Hex-Werte immer in style.css nachschlagen bzw. dort pflegen. Die vollständige Rollen-/Verwendungskarte und die Migrations-Checkliste stehen in docs/DESIGN_SYSTEM.md.

3.1 Markenpalette

Das Design hat eine feste Identität aus drei Leitfarben plus Statusfarben:

Rolle Variable Light Dark Einsatz
Pub-Grün (Primär) --pub-green #1c4634 #2b6148 Marken-Grün, aktive Chips, Hintergrund-Glow
Gold (Akzent) --gold #c69c53 #dcb363 Flächen/Rahmen/Icons/Highlights. Preise/Zahlen als Text: --gold-text (lesbar, WCAG AA)
Happy-Hour (Sekundär-Akzent) --accent-hh #b55333 #db7651 HH-Kennzeichnung (--accent-hh-bg als Fläche)
Erfolg --success #27ae60 (geerbt) „Abholbereit“, positive Zustände
Gefahr/Storno --danger #e74c3c (geerbt) Stornos, Warn-Badges, Löschen

3.2 Token-Gruppen

Alle weiteren Variablen sind nach Funktion gruppiert (Light/Dark jeweils gesetzt):

  • Flächen: --bg-body, --bg-header, --bg-card, --bg-search, --bg-chip, --bg-chip-active, --bg-pv (Produktkachel), --bg-badge, --modal-backdrop.
  • Text: --text-primary, --text-secondary, --text-muted, --text-chip, --text-chip-active, --text-pv-size, --text-pv-price, --text-badge. Kontrast/Lesbarkeit: --gold-text (Gold als Text auf hellem Grund, WCAG AA), --on-gold / --on-green (Kontrasttext auf Gold-/Grün-Flächen statt #000/#fff).
  • Rahmen: --border-subtle, --border-strong, --border-chip, --border-pv.
  • Effekte/Glow: --glow-color, --bg-glow-green/--bg-glow-green-0, --bg-glow-gold/--bg-glow-gold-0, --shadow-card (zweischichtig), --shadow-search, --shadow-pv/--shadow-pv-hover, Elevation-Leiter --elev-1|2|3.
  • Geometrie/Bewegung: --radius-xs|sm|md|lg|xl|full, --transition, Easing --ease-standard|out|spring.
  • Toggle: --toggle-bg, --toggle-border.

3.3 Verbindliche Regeln

  • Keine Hardcoded-Farben (Hex/rgba) in HTML oder für wiederkehrende Muster. Immer Tokens aus style.css verwenden. Fehlt eine Rolle, erst dort ein Token anlegen.
  • Light + Dark gemeinsam pflegen: jedes neue Token in :root und in [data-theme="dark"] setzen, sonst bricht ein Theme.
  • Für wiederkehrende Muster semantische CSS-Klassen statt funktionaler Inline-Styles nutzen (z.B. .btn-gold, .badge-time).
  • Freitext (Deckelnamen, Notizen, Artikelnamen) vor der HTML-Ausgabe escapen (esc(...)), besonders in bar.html/theke.html.
  • Dialoge: Aktive Modals verwenden den gemeinsamen .modal-Baukasten, bleiben innerhalb von 100dvh/Safe Areas scrollbar und unterstützen Escape, Fokusfalle sowie Fokus-Rückgabe. Operative Touchziele sind mindestens 44 px groß; deaktivierte Aktionen nutzen echtes disabled. Beim Öffnen erhält der rahmenlose Dialogcontainer den Startfokus, nicht automatisch das X; dessen Goldring erscheint daher nur bei echter Tastaturnavigation. Modals nutzen den theme-spezifischen Backdrop-Token --modal-backdrop und die 250-ms-Modalbewegung.
  • Bewegung: Zustände (160 ms), Popover (180 ms), kontrollierte Größenwechsel (220 ms), Modals (250 ms) und neue Karten/Bons (500 ms) sind getrennte Rollen. Dauerbewegung kennzeichnet neben dem Ambiente bewusst ungelöste operative Zustände: offene Bons, offene Bon-Zähler und laufende Ofenaufgaben. prefers-reduced-motion schaltet sie ab.

Design-System-Stand: Farben laufen vollständig über Tokens (docs/DESIGN_SYSTEM.md). --bg-panel ist als warmer transluzenter Panel-Ton definiert (Light rgba(198,156,83,.05), Dark rgba(220,179,99,.05), Gold-Basis). Keine bekannten Altlasten mehr: theme-color wird mit dem aktiven Kopf-Theme synchronisiert, --bg-hover ist als eigenes Token definiert, --border → --border-subtle. Premium-Polish (2026-07-08): lesbares --gold-text für Preise (WCAG AA), Kontrast-Tokens --on-gold/--on-green, Elevation-Leiter, erweiterte Radius-/Easing-Skala sowie global prefers-reduced-motion, :focus-visible und ein deckender Sticky-Header. Details: CHANGELOG.md und docs/DESIGN_SYSTEM.md (Phase 4).

Auf iPhones nutzt die installierte Homescreen-App viewport-fit=cover zusammen mit der iOS-Safe-Area. Die Statusleiste (Uhrzeit, WLAN, Akku) setzt dadurch mit --bg-header die jeweilige Kopffläche fort: #faf8f1 im Light- und #0c0d0c im Dark-Mode. Das gemeinsame theme-shell.js synchronisiert beim Start und beim manuellen Theme-Wechsel auch theme-color; der iOS-Stil default wählt dazu kontrastierende Systemzeichen. Die drei Android-Manifeste verwenden für ihre Fullscreen-Shell weiterhin #0c0d0c. Auf Android überschreibt das dynamische Seiten-Meta diesen Manifest-Fallback zur Laufzeit: In Chrome sowie bei vorübergehend eingeblendeter Systemleiste setzt sich deshalb ebenfalls die aktive Light-/Dark-Kopffarbe fort. Ist die Service-Ansicht in Bar oder Theke eingebettet, übernimmt ausschließlich die Eltern-App diese obere Safe Area; das Service-Iframe zeichnet dort keinen zweiten Farbbalken. Auf Android verwenden die drei Stationsmanifeste dagegen display: fullscreen, weil die festen Pub-Tablets weder die obere Statusleiste noch den unteren Gesten-/Navigationsbereich dauerhaft zeigen sollen. Kritische App-Aktionen bleiben innerhalb der vorhandenen Safe-Area-Abstände.

Gestalteter Start und Öffnen ohne Verbindung

Service, Bar und Theke zeigen beim Start das vorhandene Pub-Logo auf dem gespeicherten Theme. Eine schmale goldene Ladebewegung begleitet die Verbindung, danach blendet die Startfläche zur bereiten App über. Es gibt keine künstliche Wartezeit. Während der laufenden Bedienung bleiben die bisherige Offline-Pille und der Systemstatus zuständig. Nach einmaliger erfolgreicher Service-Worker-Installation funktioniert auch der Offline-Start: Eine lokal hinterlegte, eigenständige Ansicht bietet WLAN-Hinweis, automatischen Wiederholversuch und Erneut versuchen. Sie benötigt weder Logo-Download noch externe Schriftdateien. Es werden keine App-Daten oder API-Antworten gecacht. Ein allererster Aufruf ohne vorherige Installation kann diesen Fallback noch nicht nutzen; der erste native PWA-Splash wird vom Betriebssystem gezeichnet.

Startgestaltung liegt in start-screen.css, die Offline-Wiederverbindung in offline-start.js. npm run build:start erzeugt aus diesen Quellen und den Tokens aus style.css die frühen, markierten Blöcke aller drei HTML-Seiten sowie offline.html und die passende SW-Cacheversion. Nach Änderungen an diesen Quellen neu generieren und die erzeugten Dateien mit committen. npm run check:syntax prüft zusätzlich, dass die Artefakte aktuell sind.

4. Datei-Übersicht

Datei Zweck
server.js Express, Socket.IO, SQLite, Druckerqueue, Ofenlogik
index.html Service/Kellner-Ansicht
bar.html Bar-Station
theke.html Theken-Station
station-admin.js Schließen und Tastaturverhalten des Admin-Menüs in Bar/Theke
station-workspace.js Willy Fokus, Kombi/Bedienen-Wechsel und responsive Fokus-Darstellung
timesheet.js Direktes gemeinsames Arbeitsstunden-Modal für Bar und Theke
timesheet.css Gemeinsames Layout der monatlichen Stundenliste
station-audio.js gemeinsamer geprüfter Tonzustand für Bon- und Ofensignale in Bar/Theke
pub-tables.js Gemeinsame Tischdefinitionen für Service, Reservierungsauswahl und serverseitige Validierung
reservations.js Gemeinsames Reservierungs-Popup für Service, Bar und Theke
reservations-store.js Validierung, SQLite-Ablage und Versionsschutz für Reservierungen
schedule.js Dienstplan, Verfügbarkeit, Chef-Planung und Teilen
schedule.css Gemeinsames tokenbasiertes Layout des Dienstplan-Modals
products.js Produktdaten (G + DATA), Browser- und Node-kompatibel
station-routing.js gemeinsame Stationsentscheidung Bar/Theke
sw.js / offline.html Netzwerkbasierte App-Navigation mit eigenständigem Offline-Startfallback
start-screen.css / offline-start.js Gemeinsame Startgestaltung und Offline-Wiederverbindung
scripts/build-start-screen.js Reproduzierbare frühe Theme-/Startblöcke und Offline-Artefakte
offline-outbox.js Ablauf, Quarantäne und bewusste Wiederherstellung lokaler Offline-Bons
style.css Design-System / CSS-Variablen
oven-store.js persistente Ofenbelegung: Tabelle, Schreiben je Änderung, Wiederherstellung beim Start
pub.db aktive SQLite-Datenbank (im Projektroot, nicht verschieben)
assets/ Bilder, Sounds, PWA-Manifeste
docs/sales/ Verkaufsunterlage: Systemanalyse mit Nutzen- und Preisbewertung, 69 echte Screenshots
scripts/sales-demo/ Entwicklungswerkzeuge für Demo-Betriebsstand, simulierte Bondrucker und Screenshot-Aufnahme (siehe dortige README.md)

5. Entwickler-Workflow

  • Projekt wird über die selbst gehostete Forgejo-Instanz synchronisiert: https://git.bude3000.de/emdev/willyspub.git als origin, Branch main. GitHub ist seit dem 2026-08-19 nicht mehr angebunden. Der HTTPS-Zugang läuft über einen Forgejo-Access-Token, der im lokalen Credential-Helper liegt und nicht in .git/config gehört. node_modules ist bewusst nicht getrackt – nach git pull immer npm install ausführen.
  • Sensible Runtime-Dateien (pub.db, server.js, package*.json, backup/, docs/, Dotfiles etc.) werden vom Static-Handler geblockt; betriebsrelevante HTML-, Asset- und Client-JS-Dateien bleiben erreichbar.
  • Vor Änderungen in dieser Reihenfolge .agents/AGENTS.md, dieses Handbuch, docs/AI_HANDOFF.md und ROADMAP.md lesen; die dort genannte Fachdokumentation kommt je nach Aufgabe hinzu. ROADMAP.md ist die einzige Quelle für offene Arbeiten, CHANGELOG.md enthält nur Umgesetztes. Validierungen laufen lokal vor dem Push: npm run check:syntax, npm test sowie Inline-Script-Parsing der HTML-Dateien (siehe docs/AI_HANDOFF.md). Eine serverseitige CI gibt es nicht: Die Workflowdatei liegt für Forgejo Actions unter .forgejo/workflows/ci.yml, auf der Instanz ist aber kein Forgejo Runner eingerichtet. Sie wird deshalb von niemandem ausgeführt und ersetzt die lokalen Prüfungen nicht.