baustelle-pwa/ROADMAP.md
Eddy aaed48682e
All checks were successful
Deploy baustelle-pwa / deploy (push) Has been skipped
Doku: README auf Code-Stand, ROADMAP entruempelt
README:
- "Verkleinerung auf 2000px" war veraltet - seit Bericht 1.7.0 im Admin
  einstellbar, Vorgabe 1:1
- fehlten: Background Sync / Periodic Sync, volle Kameraaufloesung ueber
  ImageCapture, Wake Lock, idempotenter Wiederhol-Upload (duplicate:true)
- Verzeichnisstruktur: Rollen von idb.js/offline.js/router.js/sw.js,
  icon.svg, Workflow, Hinweis auf Symlink der lokalen Testinstanz
- API-Liste geprueft: alle 18 Endpunkte stimmen

ROADMAP: Offenes nach oben, Abschnitt 6 (18.09.) bleibt ausfuehrlich,
Abschnitte 1-5 (alle erledigt) zu einer Tabelle mit Commit + KB-Verweis
gekuerzt. Bewusst-nicht-gebaut-Entscheidungen bleiben erhalten.
Gegenstueck Bericht 1.5.1 -> 1.7.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 23:57:47 +02:00

13 KiB
Raw Permalink Blame History

ROADMAP — Baustelle PWA

Stand: 2026-09-18 · Gegenstück: data/bericht (Modul-Version 1.7.1)

Die PWA hat kein eigenes Backend — alles läuft über custom/bericht/api/. Punkte, die eine API-Änderung brauchen, sind mit [API] markiert und stehen zusätzlich in der ROADMAP des Bericht-Moduls.


Offen

  • idb.queueAll() lädt alle Foto-Blobs in den Speicher — auch updateBadge(), das nur zählen will. Bei 20 Originalen à 8 MB sind das 160 MB je Badge-Update. Braucht ein Zähl-Verfahren ohne Werte (Schlüssel + kleiner Meta-Store); Schema-Änderung, weil sw.js/share.html die DB mit fester Version 1 öffnen.
  • Am Handy gegenprüfen (geht nur auf dem Gerät, am Schreibtisch nicht nachstellbar): echte Kamera-Hardware mit „Fertig" im Funkloch, und Background Sync bei ausgeschaltetem Display. Beides lief lokal nur mit nachgebildeter Kamera (canvas.captureStream()). Wenn etwas komisch aussieht: Tag + ungefähre Uhrzeit reichen, der Rest steht im Prod-Zugriffslog (Lesart siehe KB #208, Nachtrag 18.09.2026).

Bewusst nicht gebaut (Entscheidungen, nicht vergessen):

  • Keine Schreib-Warteschlange für Auftragsdaten — bräuchte Idempotenz und Konfliktbehandlung (zwei Geräte am selben Auftrag). Lesen geht ohne Netz, ändern nur mit Netz; Fotos sind die Ausnahme (Persist-First-Queue).
  • Keine Zeichenfläche für echte Handschrift — am Handy ohne Stift unbrauchbar. Notizen werden getippt.
  • Kein Web Lock (navigator.locks) für die Foto-Queue — ein eingefrorener Tab hielte ihn ewig; deshalb Lease mit Ablauf in IndexedDB (Abschnitt 6.1).

6. Rauswurf aus dem Auftrag + unsichtbare Warteschlange (gemeldet 2026-09-18)

Gemeldet von Eddy: am 17.09.2026 gegen 18 Uhr mehrere Fotos in einem Auftrag gemacht, „Fertig" getippt — die App stand danach auf der Auftragsliste statt im Auftrag. Wieder in den Auftrag gegangen: keine Fotos zu sehen, kein Hinweis, dass sie noch warten.

Befund (Prod-Zugriffslog 17.09., Auftrag 111 — kein Raten)

  • 17:30:56 POST orders.php?action=create — Auftrag 111 über den -Knopf angelegt. In derselben Sekunde laufen zwei Routen gleichzeitig: orders.php (Liste) und orders.php?id=111 (Detail). Das ist die Signatur des Fehlers.
  • 17:40:45 verify.php + orders.php (Liste) — der Rauswurf. Kein index.php davor, also kein Reload, kein SW-Update, kein Logout (kein 401), kein Absturz.
  • 17:40:47 Auftrag 111 erneut geöffnet, action=photos liefert eine leere Liste; die Uploads kommen erst 17:40:54 und 17:57:2517:58:22 an.

Ursache Rauswurf — History-Race in openNewOrderModal:

closeModal(modal);                       // -> history.back()   (asynchron!)
router.go('#/orders/' + res.order.id);   // -> location.hash = … (sofort)

history.back() wird erst nach dem Hash-Wechsel ausgeführt und springt dann hinter den neuen Eintrag zurück. Ergebnis: Auf dem Bildschirm steht der Auftrag (seine Route wurde als letzte fertig), in der Adresszeile steht aber #/orders. Alles, was danach router.navigate() ruft — z. B. „Fertig" in der Kamera — zeichnet die Auftragsliste. Nachgestellt in Chromium (18.09.2026): ohne Abwarten endet der Ablauf auf #/orders, mit abgewartetem popstate auf #/orders/111.

Dieselbe Falle an zwei weiteren Stellen: „Neuen Bericht anlegen" (#/reports/<id>) und Lieferschein-Unterschrift (#/shipments/<id>). Betroffen ist nur, wer den Auftrag in derselben Sitzung frisch angelegt hat — deshalb fiel es nicht früher auf.

Ursache „keine Fotos, keine Meldung": Die Auftragsseite zeigt ausschließlich, was der Server schon hat. Fotos in der Warteschlange erscheinen nirgends außer als Zahl im kleinen Badge oben rechts (🔴 3 / 🟡 3), und die Seite zeichnet sich nach einem im Hintergrund fertig gewordenen Upload nicht neu. Verloren ging nichts — alle Fotos kamen an.

Umsetzung (2026-09-18)

  • Race zentral behoben: closeModal() zählt selbst ausgelöste history.back() (_pendingBacks, Zähler statt Boolean, KB #1209 — der Boolean verschluckt bei zwei schnell nacheinander geschlossenen Dialogen ein popstate), router.go() wartet über window.historySettled(), bis alle durch sind, und wechselt erst dann den Hash (Schutzzeit 1,5 s, falls ein popstate ausbleibt). Alle Aufrufstellen bleiben wie sie sind. closeModal() nimmt den History-Eintrag jetzt vor dem Cleanup zurück, damit ein navigierender Cleanup den Rücksprung sieht; pushModal() wartet ebenfalls.
  • Sicherheitsnetz in Kamera und Galerie-Auswahl: nach „Fertig" ausdrücklich router.go('#/orders/<id>') statt router.navigate() — gezeichnet wird der Auftrag, für den fotografiert wurde, nicht „was gerade in der Adresszeile steht".
  • Wartende Fotos am Auftrag: Block „ Warten auf Upload (n)" über „Hochgeladene Fotos", mit Vorschaubildern direkt aus der Warteschlange (max. 12, danach „+n") und Klartext („📴 Kein Netz — 3 Fotos sind auf dem Gerät gesichert und werden gesendet, sobald wieder Netz da ist."), fehlgeschlagene rot markiert; Tipp öffnet die Warteschlange.
  • Auftragsseite zieht nach: bei photo-uploaded für diesen Auftrag wird nach 2,5 s Ruhe neu gezeichnet (Scrollposition bleibt) — nicht, solange ein Modal offen ist, die Mehrfachauswahl läuft oder eine Sprachnotiz abgespielt wird.
  • Hinweis beim Schließen der Kamera:📴 Kein Netz — n Fotos auf dem Gerät gesichert, Upload folgt automatisch" bzw. „ n Fotos warten noch auf den Upload".

Verifiziert (2026-09-18, lokale Testinstanz, Chromium, Auftrag 62)

  • Ablauf „Modal zu + router.go": endet auf #/orders/62 (vorher #/orders)
  • Echtes Funkloch (context.setOffline(true)): 3 Fotos gesichert, „Fertig" → bleibt im Auftrag, Offline-Band, Block mit 3 Vorschaubildern und Klartext, Badge 🔴 3
  • Netz zurück: Upload läuft an, Block verschwindet von selbst, „Hochgeladene Fotos" 2 → 5, Badge 🟢, Warteschlange leer. Testfotos danach wieder entfernt.
  • Nicht getestet: die echte Kamera (getUserMedia) am Handy — der Test hat den Kamera-Cleanup nachgestellt, nicht die Kamera selbst bedient.

6.1 Doppel-Uploads: Seite und Service Worker laden dasselbe Foto hoch

Log 17.09.: mehrere Antworten mit duplicate:true. syncQueue() (Seite) und drainQueue() (Service Worker, Background Sync) lesen dieselbe IndexedDB-Queue und wissen nichts voneinander. Dank md5-Abgleich (Bericht 1.6.0) folgenlos — kostet aber im Mobilfunk das doppelte Datenvolumen, bei 8-MB-Originalen spürbar.

Schlimmer, bisher unbemerkt: Ist im Admin ein Verkleinern eingestellt, sichert die Kamera erst das Original und ersetzt es danach durch die kleine Fassung. Greift der Service Worker dazwischen zu, lädt er das Original hoch, die Seite danach die kleine Fassung — unterschiedlicher Inhalt, der md5-Abgleich greift nicht, es liegen zwei Dateien im Auftrag.

Umgesetzt (2026-09-18) — Lease je Item in IndexedDB (kein Web Lock: ein eingefrorener Tab hielte den ewig, und genau für den eingefrorenen Tab gibt es den Background Sync):

  • idb.queueKeys() / queuePatch(id, fn) / queueClaim(id, owner, leaseMs) / queueRelease() — Lesen-Ändern-Schreiben in einer readwrite-Transaktion (über Kontexte hinweg atomar). Gegenstück in sw.js (queueClaim/queueRelease) — beide Stellen zusammen ändern, der Worker kann lib/idb.js nicht laden (window).
  • syncQueue() und drainQueue() holen sich jedes Item per Claim; ist es frisch vom anderen belegt, wird es übersprungen (im Worker zählt es als „offen", damit der Browser nachfasst). Lease 90 s (> längster Upload-Timeout 60 s), danach darf der andere übernehmen. Besitzer je Seiten-/Worker-Instanz eindeutig (page-… / sw-…). Nebeneffekt: es liegt nur noch ein Foto gleichzeitig im Speicher statt aller.
  • Fehlschlag gibt die Lease sofort frei; Versuchszähler/Quarantäne per Patch statt „ganzes Item zurückschreiben" (überschrieb bisher eine parallele Änderung). Ebenso retryFailed().
  • enqueuePhoto(…, {hold:true}) + releaseHold(): soll verkleinert werden, kommt das Foto mit Lease (page-…:resize) in die Queue; freigegeben wird im finally nach dem Verkleinern. Vergisst es jemand oder stirbt die Seite: Ablauf nach 90 s.
  • replaceQueuedBlob() ersetzt per Patch

6.2 SW-Update lädt mitten in der Arbeit neu

Beim Test beobachtet: controllerchangelocation.reload() (index.php, Muster KB #201) feuerte, während drei Fotos in der Warteschlange lagen; der Browser zeigte dazu seinen eigenen „Seite verlassen?"-Dialog (beforeunload). War am 17.09. nicht die Ursache (kein Deploy, kein index.php im Log), trifft aber jeden, der nach einem Deploy arbeitet: offene Kamera weg, getippte Notiz weg, halbe Unterschrift weg.

  • window.appBusy() in app.js: Modal offen, Mehrfachauswahl, laufender Upload (offline.isSyncing()), Eingabefeld im Fokus, laufende Sprachnotiz. Nicht beschäftigt: PIN-Sperre — dort ist der beste Moment zum Neuladen, sonst kommt die PIN-Abfrage zweimal. Suchfelder zählen nicht (behalten am Handy den Fokus, der Reload käme nie).
  • index.php: Reload nur, wenn nicht beschäftigt; sonst alle 3 s und bei jedem Sichtbarkeitswechsel erneut prüfen
  • Eigener Reload setzt window.__selfReloadbeforeunload warnt dann nicht (die Fotos liegen in IndexedDB, ein Reload verliert nichts). Die Warnung beim echten Schließen bleibt.

Verifiziert 6.1 + 6.2 (2026-09-18, lokale Testinstanz, Chromium, Auftrag 62)

  • Echte Kamera-Oberfläche mit nachgebildetem Kamerabild (canvas.captureStream() statt Hardware), offline: 3× Auslöser, „Fertig" → bleibt im Auftrag, Toast „📴 Kein Netz — 3 Fotos auf dem Gerät gesichert, Upload folgt automatisch", Block mit 3 Vorschaubildern, Badge 🔴 3
  • Gleichzeitiger Zugriff: Netz an, Seite und Service Worker greifen parallel zu — der Worker lud Foto 4 und 6, die Seite Foto 5. Serverlog: genau 3 upload_photo-POSTs für 3 Fotos, keine Antwort mit duplicate:true
  • Verkleinern: während des Verkleinerns ist das Item von page-…:resize belegt; auf dem Server liegt eine Datei mit 41.832 Bytes (die kleine Fassung), nicht das 208-KB-Original
  • Fremde Lease: frisch → wird respektiert (kein Upload, bleibt in der Queue); künstlich abgelaufen → wird übernommen und hochgeladen
  • Reload: controllerchange bei offenem Notiz-Dialog → 4,5 s kein Reload; Dialog zu → Reload nach 0,7 s, gleicher Auftrag, kein Browser-Dialog, obwohl ein Foto in der Warteschlange lag. Konsole ohne Fehler. Testdaten danach wieder entfernt.
  • Nicht getestet: echte Kamera-Hardware und echter Background Sync bei ausgeschaltetem Display am Handy — beides geht nur auf dem Gerät.

Erledigt (gekürzt — Einzelheiten stehen im Commit und in der KB)

Wann Was Commit KB
2026-09-18 Abschnitt 6 oben: Rauswurf nach „Fertig", wartende Fotos am Auftrag, keine Doppel-Uploads, Reload erst wenn nichts offen ist 64ebbf7, 30202f8 (deployt 292c251) #1444, #1445, #1446
2026-08-31 Offline lesbar statt „Failed to fetch": Netzfehler als offline = true, Datenspiegel in IndexedDB (Listen + 30 Details), Band „📴 Kein Netz — Stand von HH:MM", Suche im Spiegel, Spiegel weg beim Abmelden, SW liefert immer eine echte Response, nur vollständige Antworten cachen, Precache per PRECACHE-Nachricht nach Deploy a108664 #1261, #1037, #1041
2026-08-30 Teilen aus WhatsApp: Anmeldung über verify.php statt totem JWT, Anmelden in der Teilen-Seite, Text/Titel/Link werden mitgesichert, Bilder über die Queue, Text als Notiz, Banner für Unabgelegtes, „Zuletzt benutzt" + 30-Minuten-Vorauswahl 7076388, eaad796
2026-08-28 Foto-Serie übersteht Display-Aus: Persist-First streng (erst sichern, dann verkleinern), Schutzzeit im Verkleinern, Wake Lock, Background Sync, volle Kameraauflösung über ImageCapture 65bc09d, 130d9d1, c2bf4e7 #840
2026-08-22 Aus Bericht 1.5.x nachgezogen [API]: echte Thumbnails (photo.php?size=thumb), Sortierung nach Aufnahmezeit (taken_at), Seiten-Titel, composite_path + Layout-Kennzeichen 4a53ab0 #1142, #1133
2026-08-22 Zehn Browser-Dialoge durch confirmDialog/inputDialog/alertDialog ersetzt; Bild-Cache baustelle-media (cache-first, LRU 300, überlebt Deploys, leer beim Abmelden); Textnotizen (api/note.php, notiz_*.txt) 4a53ab0
2026-08-22 Nebenbei: .modal-header/.close-btn/.modal-body hatten gar kein CSS; Thumbnail-Laden überschrieb Auswahl-Häkchen und Seitennummer der Kachel 4a53ab0
davor SSO-Migration auf awlauth, WebAuthn-Login, Neuer-Auftrag-Formular, Live-Kamera mit Persist-First-Queue, Lieferschein-Unterschrift siehe git log #208, #841, #855