baustelle-pwa/ROADMAP.md
Eddy 707638808c
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 13s
Teilen aus WhatsApp: Bilder UND Text landen jetzt wirklich im Auftrag [deploy]
Jeder Teilen-Vorgang endete bisher in einer Sackgasse — Login-Hinweis oder
"Keine geteilten Fotos gefunden", und danach war das Geteilte weg. Zwei Ursachen:

1. share.html prüfte api.getToken() — ein JWT in IndexedDB, das es seit der
   SSO-Umstellung nicht mehr gibt (api.login() legt nur noch 'user' ab, die
   Sitzung steckt im HttpOnly-Cookie awl_sso). Der Wert war immer leer, also kam
   IMMER "Bitte zuerst anmelden"; der Weg "Zur App" verwarf das Geteilte.
   Jetzt: Cookie-Prüfung über api/verify.php wie in ensureAuth(), und die
   Anmeldung (Passwort + Passkey) läuft IN der Teilen-Seite — das Geteilte bleibt
   dabei gesichert und der Ablauf geht danach direkt weiter.

2. Der Service Worker sicherte nur Dateien. Titel, Text und Link aus dem
   share_target wurden weggeworfen, ein reiner Text-Share schrieb gar nichts.
   Jetzt wird das ganze Paket gesichert und immer weitergeleitet (absolute
   Redirect-URL, leere File-Platzhalter fliegen raus).

Die Teilen-Seite zeigt Bildvorschau, Betreff und Text zum Nachbessern und eine
Auftragssuche (ohne Suchbegriff die offenen Aufträge, mit Suchbegriff alle).
Beim Ablegen gehen die Bilder über die normale Upload-Queue (Persist-First),
der Text wird als notiz_*.txt über api/note.php gespeichert. Scheitert die
Notiz (kein Netz), bleibt der Text gesichert und der Vorgang ist wiederholbar.

Dazu ein Banner in der App, wenn noch ein Paket unabgelegt bereitliegt —
abgebrochene Vorgänge findet sonst niemand wieder. share.html liest auch den
alten IndexedDB-Schlüssel, damit der erste Share nach dem Deploy (noch alter
Service Worker) nicht verlorengeht.

Lokal durchgespielt: Bild+Text, nur Text, und abgemeldet teilen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 18:03:46 +02:00

8.4 KiB
Raw Blame History

ROADMAP — Baustelle PWA

Stand: 2026-08-22 · Gegenstück: data/bericht (Modul-Version 1.5.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.


1. Nachgezogen aus Bericht 1.5.0 (2026-08-22, Modul 1.5.1)

Das Modul hatte mit 1.5.0 Infrastruktur bekommen, von der die PWA nichts hatte. Die Beschreibungen unten schildern jeweils den Zustand davor.

1.1 Echte Thumbnails statt Vollbilder [API]

Bis dahin lud die PWA für jede Kachel das komplette Foto:

  • loadThumbs() ruft api.getPhotoBlobUrl(rel, 'small') auf → photo.php?size=small
  • photo.php liefert nur dann ein Thumbnail, wenn Dolibarr eines unter thumbs/<name>_small.<ext> vorgefertigt hat
  • Die PWA lädt ihre Fotos aber per orders.php?action=upload_photo hoch, und dort wird nie vignette() aufgerufen → es existiert nie ein Thumb → es kommt immer das Original
  • Bei 30 Baustellenfotos sind das im Mobilfunknetz 30 × 0,51 MB (bei über die Dolibarr-Oberfläche hochgeladenen Originalen entsprechend mehr)

Das Modul kann das seit 1.5.0 längst: bericht_attachment_thumb($full, $maxside) in lib/bericht.lib.php (GD, Cache unter bericht/thumbs/, EXIF-Rotation, Speicher-Notbremse). api/photo.php lädt diese lib bereits.

  • [API] photo.php um size=thumb&w=<px> erweitern → bericht_attachment_thumb(), mit ETag aus mtime und Cache-Control: private, max-age=…, immutable
  • loadThumbs() umbauen: <img src="…api/photo.php?…&size=thumb"> direkt setzen statt fetch → Blob → ObjectURL. Damit greifen HTTP-Cache und loading="lazy" wirklich; heute sind beide wirkungslos, weil der Blob schon vorher komplett geladen wird.
  • Sequenzielle Schleife auflösen — for (const t of thumbs) { await … } lädt Foto für Foto nacheinander. Beim <img>-Weg erledigt der Browser das parallel von selbst.
  • Doppel-Ladung entfernen: schlägt size=small fehl, wird das Original noch einmal geladen.

1.2 Aufnahmezeit statt Dateidatum [API]

1.5.0 hat einen eigenen EXIF-Parser bekommen (bericht_jpeg_taken_at() / bericht_file_taken_at()), weil im Prod-Container das exif-Modul fehlt. Der Editor sortiert Anhänge damit nach Aufnahmezeit. orders.php?action=photos lieferte nur filemtime.

  • [API] taken_at je Datei ausgeben
  • Fotoliste nach Aufnahmezeit sortieren (Auswahl gemerkt, wie im Editor)

1.3 Seiten-Titel kommt nie an [API]

api/pages.php kann title einer Seite setzen, die PWA rendert ihn (page-title-badge, openPageActionsModal(…, title)) — aber api/reports.php gibt title im pages-Array nicht aus. Ein im Editor gesetzter Titel ist in der PWA also unsichtbar, und ein in der PWA gesetzter Titel scheint nach dem Neuladen verschwunden.

  • [API] 'title' => $p->title in reports.php ergänzen (eine Zeile)

1.4 Bearbeitete Seiten und Raster-Layouts [API]

Seit 1.5.0 hat eine Seite composite_path (das im Editor gebaute Seitenbild) und layout (single, grid_2, grid_4, before_after, title_only, …). Die PWA zeigt stur source_path:

  • Nach Bearbeitung im Desktop-Editor zeigt die PWA weiter das Rohbild ohne Anmerkungen

  • Bei einer Rasterseite ist source_path nur eines von bis zu sechs Bildern

  • Bei title_only gibt es gar keine Quelle → -Kachel

  • [API] composite_path in reports.php mit ausgeben

  • PWA zeigt composite_path, wenn vorhanden, sonst source_path

  • Layout-Kennzeichen an der Kachel (z. B. „⊞ 4er-Raster"), damit klar ist, warum dort mehr drauf ist als das eine Foto


2. Verbesserungen unabhängig vom Modul

2.1 Browser-Dialoge raus (10 Stück)

confirm() / alert() / prompt() widersprechen der Hausregel und sehen in der Standalone-PWA auf Android wie ein Systemfehler aus. Ein sauberes Muster liegt schon vor: das „Bericht löschen"-Modal in router.on('/reports/:id').

Stelle Zeile (vor dem Umbau)
PIN stimmt nicht überein alert 295
Bericht finalisieren confirm 1111
PIN-Schutz deaktivieren confirm 1264
Material-Eintrag löschen confirm 1432
Seite aus Bericht entfernen confirm 1809
Foto löschen confirm 2701
Nicht hochgeladenes Foto löschen confirm 3019
Mess-Skala: Länge eingeben prompt 3409
Ungültiges Längenformat alert 3419
„Zuerst kalibrieren" alert 3440
  • Eine confirmDialog({title, text, danger})-Hilfsfunktion (Promise) + inputDialog() für die Längeneingabe, alle zehn Stellen darauf umstellen

2.2 Fotos offline sichtbar halten

Der Service Worker reichte alles unter /custom/bericht/api/ ungecacht durch. Auf der Baustelle ohne Netz ist damit kein einziges schon angesehenes Foto verfügbar.

  • Eigener Runtime-Cache baustelle-media für photo.php-GETs, cache-first mit Größenbegrenzung (LRU, z. B. 200 Einträge). Die Thumb-URLs aus 1.1 sind dafür der richtige Kandidat — klein und unveränderlich.
  • Vom App-Shell-Cache getrennt halten, damit ein Deploy die Medien nicht wegräumt

3. Textnotizen (2026-08-22)

Getippte Notiz zum Auftrag — für alles, was man lieber schreibt als diktiert: Zählernummern, Absprachen, Maße.

  • 📝 Notizen (n)" am Auftrag, direkt unter der Sprachnotiz
  • Liste mit Betreff, zweizeiliger Vorschau und Zeitstempel; Antippen öffnet zum Lesen und Ändern, 🗑 löscht (mit Rückfrage)
  • Editor: Betreff-Feld + Textfeld über die volle Höhe, Speichern über ✓ in der Kopfzeile, Rückfrage beim Verlassen mit ungespeicherten Änderungen
  • Ablage als notiz_<betreff>_<datum>.txt im Auftragsordner (api/note.php) — genau der Weg der Sprachnotiz. Damit auch im Dolibarr-Auftrag und in der Anhänge-Spalte des Bericht-Editors sichtbar.
  • Notizen sind aus „Weitere Dokumente" ausgefiltert — sie haben ihre eigene Sektion und stünden dort sonst doppelt

Nicht umgesetzt (und bewusst verworfen): eine Zeichenfläche für echte Handschrift. Am Handy ohne Stift ist das unbrauchbar.

4. Teilen aus WhatsApp (2026-08-30)

Bilder und Beschreibungstexte, die die Kundin per WhatsApp schickt, landen direkt im Auftrag. Vorher endete jeder Teilen-Vorgang in einer Sackgasse:

  • share.html fragte api.getToken() — ein JWT in IndexedDB, das es seit der SSO-Umstellung gar nicht mehr gibt (api.login() legt nur noch user ab, die Sitzung steckt im HttpOnly-Cookie awl_sso). Ergebnis: immer „Bitte zuerst anmelden", und der Weg „Zur App" verwarf das Geteilte.

  • Der Service Worker sicherte nur Dateien. Ein Text-Share (oder Bild + Bildunterschrift) schrieb nichts in IndexedDB → „Keine geteilten Fotos gefunden".

  • Anmeldung über api/verify.php (Cookie) statt über das nicht mehr existierende Token

  • Anmelden in der Teilen-Seite (Passwort + Passkey) — das Geteilte bleibt dabei gesichert

  • Service Worker sichert title, text und url mit; Weiterleitung auch ohne Datei

  • Auswahl-Seite: Bildvorschau, Betreff + Text zum Nachbessern, Auftragssuche (ohne Suchbegriff die offenen Aufträge, mit Suchbegriff alle)

  • Ablegen: Bilder über die normale Upload-Queue (Persist-First), Text als Notiz über api/note.php. Schlägt die Notiz fehl (kein Netz), bleibt der Text gesichert und der Vorgang ist wiederholbar

  • Banner in der App, wenn noch etwas Geteiltes unabgelegt bereitliegt

Erledigt

2026-08-22

Abschnitte 1, 2 und 3 vollständig. Dazu nebenbei behoben:

  • .modal-header, .close-btn und .modal-body hatten überhaupt kein CSS, obwohl das Markup sie seit Längerem nutzt (u. a. beim Löschen eines Berichts) — der Schließen-Knopf saß dadurch unter der Überschrift statt daneben.
  • Das Laden eines Thumbnails überschrieb den kompletten Kachel-Inhalt (t.innerHTML = …) und nahm die Overlays mit: Auswahl-Häkchen in der Mehrfachauswahl und Seitennummer der Bericht-Seiten waren weg, sobald das Bild da war.
  • Beim Abmelden bleiben keine Kundenfotos mehr auf dem Gerät (CLEAR_MEDIA an den Service Worker + clearPhotoCache()).

Davor

Siehe README (Feature-Liste) und die Commit-Historie: SSO-Migration auf awlauth, WebAuthn-Login, Neuer-Auftrag-Formular, Live-Kamera mit Persist-First-Queue, Lieferschein-Unterschrift.