Doku: README auf Code-Stand, ROADMAP entruempelt
All checks were successful
Deploy baustelle-pwa / deploy (push) Has been skipped
All checks were successful
Deploy baustelle-pwa / deploy (push) Has been skipped
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>
This commit is contained in:
parent
292c25187c
commit
aaed48682e
2 changed files with 53 additions and 244 deletions
33
README.md
33
README.md
|
|
@ -22,10 +22,14 @@ Mobile Progressive Web App für die Baustellen-Doku — Foto-Upload, Sprach- und
|
||||||
|
|
||||||
**Foto-Upload**
|
**Foto-Upload**
|
||||||
- ✅ Live-Kamera-Modal (getUserMedia): bleibt für Serien-Aufnahmen offen — Auslöser + Filmstreifen mit Upload-Status je Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig), Front-/Rückkamera-Umschaltung, Fehlschuss löschbar
|
- ✅ Live-Kamera-Modal (getUserMedia): bleibt für Serien-Aufnahmen offen — Auslöser + Filmstreifen mit Upload-Status je Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig), Front-/Rückkamera-Umschaltung, Fehlschuss löschbar
|
||||||
|
- ✅ **Volle Kameraauflösung**: ausgelöst wird über `ImageCapture.takePhoto()` (native JPEG der Kamera inkl. EXIF) — ein Standbild aus dem Video-Stream hat nur Stream-Auflösung und ist nur noch die Rückfallebene (iOS/Safari, ältere Browser)
|
||||||
- ✅ Alternativ Aufnahme aus der Galerie
|
- ✅ Alternativ Aufnahme aus der Galerie
|
||||||
- ✅ Clientseitige Bild-Verkleinerung auf 2000px (JPEG q=0.85)
|
- ✅ **Fotoqualität im Admin einstellbar** (Bericht-Modul → Fotoqualität, `api/config.php`): Vorgabe ist **1:1, gar nicht verkleinern**; optional längste Seite + JPEG-Qualität. Gesichert wird immer zuerst das Original, die kleine Fassung ersetzt es danach — hängt das Verkleinern (Display aus), bleibt das Original
|
||||||
|
- ✅ **Wake Lock** während Foto-Serie und Upload: schaltet sich das Display ab, friert der Browser laufende Uploads und das Verkleinern ein
|
||||||
- ✅ **Persist-First / Datenverlust-Schutz**: jedes Foto wird beim Auslösen ZUERST in IndexedDB gesichert, Upload erst danach; Queue-Item wird nur nach bestätigtem Upload (HTTP 2xx mit gültigem `relpath`) gelöscht — überlebt fehlendes/schwaches Netz, hängende Uploads (45s-Timeout) und App-Kill. Erkennt 2xx-HTML (abgelaufene Session/Proxy-Loginseite) als Fehler statt als Erfolg
|
- ✅ **Persist-First / Datenverlust-Schutz**: jedes Foto wird beim Auslösen ZUERST in IndexedDB gesichert, Upload erst danach; Queue-Item wird nur nach bestätigtem Upload (HTTP 2xx mit gültigem `relpath`) gelöscht — überlebt fehlendes/schwaches Netz, hängende Uploads (45s-Timeout) und App-Kill. Erkennt 2xx-HTML (abgelaufene Session/Proxy-Loginseite) als Fehler statt als Erfolg
|
||||||
- ✅ Auto-Sync bei "online", periodisch (15s) und bei App-Fokus; Status-Badge (🟢 alles gesichert / 🟡 lädt hoch / 🔴 offline / ⚠️ fehlgeschlagen)
|
- ✅ Auto-Sync bei "online", periodisch (15s) und bei App-Fokus; Status-Badge (🟢 alles gesichert / 🟡 lädt hoch / 🔴 offline / ⚠️ fehlgeschlagen)
|
||||||
|
- ✅ **Background Sync**: der Service Worker lädt die Warteschlange auch dann hoch, wenn die App gar nicht offen ist (Display aus, weggewischt) — Tag `photo-queue`, dazu Periodic Background Sync (15 min), wo der Browser ihn gewährt. Anmeldung läuft über das `awl_sso`-Cookie mit
|
||||||
|
- ✅ **Wiederholter Upload ist idempotent**: geht die Antwort nach dem Speichern verloren, wird erneut gesendet — der Server vergleicht den Inhalt (md5, seit Bericht 1.6.0), legt nichts doppelt ab und antwortet mit `duplicate: true`
|
||||||
- ✅ Recovery: Tipp auf das Status-Badge öffnet die Warteschlange → erneut senden / teilen; Warnung beim Schließen mit noch ungesicherten Fotos
|
- ✅ Recovery: Tipp auf das Status-Badge öffnet die Warteschlange → erneut senden / teilen; Warnung beim Schließen mit noch ungesicherten Fotos
|
||||||
- ✅ **Seite und Service Worker laden nichts doppelt hoch**: wer ein Foto sendet, belegt es in IndexedDB (Lease, 90 s); der andere überspringt es und übernimmt erst nach Ablauf. Beim Verkleinern bleibt das Foto belegt, bis die kleine Fassung in der Queue liegt — sonst gingen Original **und** kleine Fassung raus. Logik steht zweimal (`lib/idb.js` + `sw.js`), immer zusammen ändern
|
- ✅ **Seite und Service Worker laden nichts doppelt hoch**: wer ein Foto sendet, belegt es in IndexedDB (Lease, 90 s); der andere überspringt es und übernimmt erst nach Ablauf. Beim Verkleinern bleibt das Foto belegt, bis die kleine Fassung in der Queue liegt — sonst gingen Original **und** kleine Fassung raus. Logik steht zweimal (`lib/idb.js` + `sw.js`), immer zusammen ändern
|
||||||
- ✅ **App-Update lädt nicht mitten in der Arbeit neu**: nach einem Deploy wartet der Reload, bis kein Dialog (Kamera, Notiz, Unterschrift) offen ist, nichts getippt wird und kein Upload läuft (`window.appBusy()`); der eigene Reload löst keinen Browser-Dialog aus
|
- ✅ **App-Update lädt nicht mitten in der Arbeit neu**: nach einem Deploy wartet der Reload, bis kein Dialog (Kamera, Notiz, Unterschrift) offen ist, nichts getippt wird und kein Upload läuft (`window.appBusy()`); der eigene Reload löst keinen Browser-Dialog aus
|
||||||
|
|
@ -121,21 +125,30 @@ baustelle-pwa/
|
||||||
├── share.html Web-Share-Target-Empfänger
|
├── share.html Web-Share-Target-Empfänger
|
||||||
├── manifest.webmanifest PWA Manifest
|
├── manifest.webmanifest PWA Manifest
|
||||||
├── sw.js Service Worker (Network-First für Assets, API pass-through,
|
├── sw.js Service Worker (Network-First für Assets, API pass-through,
|
||||||
│ cache-first für photo.php?size=thumb → Cache baustelle-media)
|
│ cache-first für photo.php?size=thumb → Cache baustelle-media,
|
||||||
├── app.js Hauptlogik (Routes, Views, Dialoge, Notizen)
|
│ Share-Target, Background Sync der Foto-Queue inkl. Lease)
|
||||||
|
├── app.js Hauptlogik (Routes, Views, Dialoge, Notizen, Modal-Stack, appBusy)
|
||||||
├── app.css Mobile-first Styling
|
├── app.css Mobile-first Styling
|
||||||
├── lib/
|
├── lib/
|
||||||
│ ├── idb.js IndexedDB-Wrapper (Foto-Queue)
|
│ ├── idb.js IndexedDB-Wrapper: kv-Store (Benutzer, Datenspiegel) + Foto-Queue
|
||||||
│ ├── api.js REST-API-Client (SSO-Cookie same-origin, JSON-Guard, Timeout)
|
│ │ (queuePatch/queueClaim/queueRelease — atomar, Gegenstück in sw.js)
|
||||||
│ ├── offline.js Offline-Queue + Sync (Persist-First)
|
│ ├── api.js REST-API-Client (SSO-Cookie same-origin, JSON-Guard, Timeout,
|
||||||
│ ├── router.js Hash-Router
|
│ │ Netzfehler → `offline = true`)
|
||||||
|
│ ├── offline.js Offline-Queue + Sync (Persist-First, Lease), Wake Lock, Datenspiegel
|
||||||
|
│ ├── router.js Hash-Router (wartet vor dem Hash-Wechsel auf ausstehende history.back())
|
||||||
│ ├── pdf.min.js PDF.js (Lieferschein-Vorschau)
|
│ ├── pdf.min.js PDF.js (Lieferschein-Vorschau)
|
||||||
│ └── pdf.worker.min.js PDF.js Worker
|
│ └── pdf.worker.min.js PDF.js Worker
|
||||||
└── icons/
|
├── icons/
|
||||||
├── icon-192.png
|
│ ├── icon.svg
|
||||||
└── icon-512.png
|
│ ├── icon-192.png
|
||||||
|
│ └── icon-512.png
|
||||||
|
├── .forgejo/workflows/ deploy.yml — rsync nach Prod bei `[deploy]`, meldet per Ntfy
|
||||||
|
├── ROADMAP.md Offene Punkte + Befunde/Verifikation der letzten Runden
|
||||||
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Lokal testen:** `/var/www/dolibarr/custom/baustelle` ist ein Symlink auf dieses Repo — Änderungen sind sofort unter `http://localhost:8080/custom/baustelle/` live. Nach Code-Änderungen die Seite wirklich neu laden (nicht nur den Hash wechseln), sonst läuft der alte Stand.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
Die App spricht die Bericht-API unter `/custom/bericht/api/`:
|
Die App spricht die Bericht-API unter `/custom/bericht/api/`:
|
||||||
|
|
|
||||||
264
ROADMAP.md
264
ROADMAP.md
|
|
@ -1,6 +1,6 @@
|
||||||
# ROADMAP — Baustelle PWA
|
# ROADMAP — Baustelle PWA
|
||||||
|
|
||||||
Stand: 2026-09-18 · Gegenstück: `data/bericht` (Modul-Version 1.5.1)
|
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
|
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
|
API-Änderung brauchen, sind mit **[API]** markiert und stehen zusätzlich in der ROADMAP des
|
||||||
|
|
@ -8,222 +8,29 @@ Bericht-Moduls.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Nachgezogen aus Bericht 1.5.0 ✅ (2026-08-22, Modul 1.5.1)
|
## Offen
|
||||||
|
|
||||||
Das Modul hatte mit 1.5.0 Infrastruktur bekommen, von der die PWA nichts hatte.
|
- [ ] `idb.queueAll()` lädt **alle** Foto-Blobs in den Speicher — auch `updateBadge()`, das
|
||||||
Die Beschreibungen unten schildern jeweils den Zustand davor.
|
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).
|
||||||
|
|
||||||
### 1.1 Echte Thumbnails statt Vollbilder ✅ [API]
|
**Bewusst nicht gebaut** (Entscheidungen, nicht vergessen):
|
||||||
|
|
||||||
Bis dahin lud die PWA für **jede Kachel das komplette Foto**:
|
- 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
|
||||||
- `loadThumbs()` ruft `api.getPhotoBlobUrl(rel, 'small')` auf → `photo.php?size=small`
|
Ausnahme (Persist-First-Queue).
|
||||||
- `photo.php` liefert nur dann ein Thumbnail, wenn Dolibarr eines unter
|
- Keine Zeichenfläche für echte Handschrift — am Handy ohne Stift unbrauchbar. Notizen werden getippt.
|
||||||
`thumbs/<name>_small.<ext>` **vorgefertigt** hat
|
- Kein Web Lock (`navigator.locks`) für die Foto-Queue — ein eingefrorener Tab hielte ihn ewig;
|
||||||
- Die PWA lädt ihre Fotos aber per `orders.php?action=upload_photo` hoch, und dort wird
|
deshalb Lease mit Ablauf in IndexedDB (Abschnitt 6.1).
|
||||||
**nie `vignette()` aufgerufen** → es existiert nie ein Thumb → es kommt immer das Original
|
|
||||||
- Bei 30 Baustellenfotos sind das im Mobilfunknetz 30 × 0,5–1 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.
|
|
||||||
|
|
||||||
- [x] **[API]** `photo.php` um `size=thumb&w=<px>` erweitern → `bericht_attachment_thumb()`,
|
|
||||||
mit ETag aus mtime und `Cache-Control: private, max-age=…, immutable`
|
|
||||||
- [x] `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.
|
|
||||||
- [x] 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.
|
|
||||||
- [x] 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`.
|
|
||||||
|
|
||||||
- [x] **[API]** `taken_at` je Datei ausgeben
|
|
||||||
- [x] 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.
|
|
||||||
|
|
||||||
- [x] **[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
|
|
||||||
|
|
||||||
- [x] **[API]** `composite_path` in `reports.php` mit ausgeben
|
|
||||||
- [x] PWA zeigt `composite_path`, wenn vorhanden, sonst `source_path`
|
|
||||||
- [x] 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 |
|
|
||||||
|
|
||||||
- [x] 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.
|
|
||||||
|
|
||||||
- [x] 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.
|
|
||||||
- [x] 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.
|
|
||||||
|
|
||||||
- [x] „📝 Notizen (n)" am Auftrag, direkt unter der Sprachnotiz
|
|
||||||
- [x] Liste mit Betreff, zweizeiliger Vorschau und Zeitstempel; Antippen öffnet zum Lesen
|
|
||||||
und Ändern, 🗑 löscht (mit Rückfrage)
|
|
||||||
- [x] Editor: Betreff-Feld + Textfeld über die volle Höhe, Speichern über ✓ in der Kopfzeile,
|
|
||||||
Rückfrage beim Verlassen mit ungespeicherten Änderungen
|
|
||||||
- [x] 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.
|
|
||||||
- [x] 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".
|
|
||||||
|
|
||||||
- [x] Anmeldung über `api/verify.php` (Cookie) statt über das nicht mehr existierende Token
|
|
||||||
- [x] Anmelden **in** der Teilen-Seite (Passwort + Passkey) — das Geteilte bleibt dabei gesichert
|
|
||||||
- [x] Service Worker sichert `title`, `text` und `url` mit; Weiterleitung auch ohne Datei
|
|
||||||
- [x] Auswahl-Seite: Bildvorschau, Betreff + Text zum Nachbessern, Auftragssuche
|
|
||||||
(ohne Suchbegriff die offenen Aufträge, mit Suchbegriff alle)
|
|
||||||
- [x] 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
|
|
||||||
- [x] Banner in der App, wenn noch etwas Geteiltes unabgelegt bereitliegt
|
|
||||||
- [x] **Mehrere Nachrichten hintereinander**: die letzten drei Aufträge stehen unter „Zuletzt benutzt"
|
|
||||||
oben; innerhalb von 30 Minuten ist der letzte vorausgewählt (danach nur noch Schnellzugriff —
|
|
||||||
eine Dauer-Vorauswahl würde irgendwann im falschen Auftrag landen)
|
|
||||||
|
|
||||||
## 5. Offline lesbar statt „Failed to fetch" ✅ (2026-08-31)
|
|
||||||
|
|
||||||
Gemeldet von Eddy am 2026-08-31: Die App zeigte beim normalen Öffnen nur
|
|
||||||
**„⚠️ Failed to fetch"**, während das Teilen aus WhatsApp weiter funktionierte.
|
|
||||||
|
|
||||||
### Befund
|
|
||||||
|
|
||||||
- Prod-Deploy vollständig, alle Dateien liefern 200, die API antwortet korrekt
|
|
||||||
(403 ohne `X-Requested-With`, 401 ohne Anmeldung) — **kein Server- oder Deploy-Fehler.**
|
|
||||||
- Im frischen Desktop-Chromium lief der ganze Weg sauber durch
|
|
||||||
(`auth.php` → `verify.php` → `orders.php?open=1`, alle 200).
|
|
||||||
- „Failed to fetch" ist die *rohe Browsermeldung* eines `fetch()`, das gar nicht bis zum
|
|
||||||
Server kam. Sie stand 1:1 im UI, weil die Routen im `catch` stumpf `e.message` anzeigten.
|
|
||||||
- Warum es beim Teilen nicht auffiel: seit `eaad796` merkt sich `share.html` die letzten
|
|
||||||
Aufträge lokal und kommt ohne Netz aus. Die Hauptapp hatte für die Listen
|
|
||||||
**keinerlei Offline-Fallback** — `lib/offline.js` puffert nur Foto-Uploads.
|
|
||||||
|
|
||||||
### Umgesetzt (Muster aus KB #1037 / #1041, dort im Stundenzettel verifiziert)
|
|
||||||
|
|
||||||
- [x] **Netzfehler kenntlich machen** — `lib/api.js` wandelt den `TypeError` aus `fetch()`
|
|
||||||
in einen eigenen Fehler mit `offline = true` und deutschem Text
|
|
||||||
(„Keine Verbindung zum Server" / „Zeitüberschreitung"). Ein 4xx/5xx trägt die
|
|
||||||
Markierung **nicht** und wird weiterhin als Serverfehler gemeldet.
|
|
||||||
- [x] **`isTransient()` in `lib/offline.js` nachgezogen** — die Funktion erkannte Netzfehler
|
|
||||||
an `err instanceof TypeError`. Ohne Anpassung hätte ein Foto aus dem Funkloch als
|
|
||||||
*dauerhaft* fehlgeschlagen gegolten und wäre nach sechs Versuchen in der
|
|
||||||
Quarantäne gelandet, statt hochgeladen zu werden.
|
|
||||||
- [x] **Datenspiegel** (`offline.mirrorSave/mirrorLoad/mirrorClear`) — jede erfolgreiche
|
|
||||||
Antwort wird in IndexedDB gespiegelt: Auftragsliste, Kundenliste, Berichte sowie
|
|
||||||
Auftrags- und Kundendetails (letztere auf 30 Einträge begrenzt, der gerade
|
|
||||||
geschriebene Schlüssel ist beim Aufräumen geschützt — KB #1037).
|
|
||||||
- [x] **Sichtbares Band** „📴 Kein Netz — angezeigt wird der Stand von HH:MM Uhr".
|
|
||||||
Ohne den Hinweis hält man alte Zahlen für aktuell. Es gehört zur Ansicht und wird
|
|
||||||
vom Router bei jedem Routenwechsel zurückgesetzt.
|
|
||||||
- [x] **Suche ohne Netz** filtert im gespiegelten Stand statt ins Leere zu laufen —
|
|
||||||
mit einmaligem Hinweis, dass nur der gespeicherte Stand durchsucht wird.
|
|
||||||
- [x] **Beim Abmelden wird der Spiegel gelöscht** — sonst sähe der nächste Benutzer am
|
|
||||||
selben Gerät offline die Aufträge, Kunden und Preise des vorherigen (KB #1041).
|
|
||||||
- [x] **`sw.js`: echter Fallback.** Der Same-Origin-Zweig endete auf
|
|
||||||
`caches.match(e.request)`; fand der Cache nichts, wurde `respondWith(undefined)`
|
|
||||||
aufgerufen — und die Anfrage scheiterte erneut mit genau der Meldung, die der
|
|
||||||
Fallback verhindern sollte. Jetzt kommt immer eine echte Response: Seite →
|
|
||||||
App-Hülle aus dem Cache, sonst eine erklärende Seite; API → 503 mit JSON.
|
|
||||||
- [x] **Nur vollständige Antworten cachen** (`ok && status === 200 && type !== 'opaque'`) —
|
|
||||||
eine 206 (Teilinhalt beim Audio-Seek) liefert der Worker später als kaputte Datei aus.
|
|
||||||
- [x] **Precache nach Deploy** — der Cache trägt nach jedem Deploy einen neuen Namen und
|
|
||||||
startet leer, `app.js`/`app.css` inklusive. Eine feste Liste hilft nicht, weil die
|
|
||||||
URLs `?v=<mtime>` tragen. Die Seite meldet dem Worker nach dem Laden, was sie
|
|
||||||
tatsächlich geholt hat (`PRECACHE`-Nachricht), er cacht es einzeln nach (KB #1041).
|
|
||||||
|
|
||||||
### Verifiziert (2026-08-31, lokale Testinstanz, Chromium)
|
|
||||||
|
|
||||||
- Spiegel wird geschrieben (`mirror:orders:open`, `:customers`, `:reports`,
|
|
||||||
`:detail:order:<id>`, `:detail:customer:<id>`)
|
|
||||||
- `fetch` auf Fehler gesetzt: Auftragsliste zeigt 6 Aufträge aus dem Spiegel + Band
|
|
||||||
„Stand von 19:03 Uhr"; Kundenliste ohne Spiegel zeigt die verständliche Meldung
|
|
||||||
statt „Failed to fetch"
|
|
||||||
- Suche „Rolfs" ohne Netz → 2 Treffer aus dem Spiegel + Hinweis-Toast
|
|
||||||
- Band verschwindet auf Routen ohne Spiegel und nach Rückkehr des Netzes
|
|
||||||
- **Echtes Funkloch** (`context.setOffline(true)` + `reload()`): App startet aus dem
|
|
||||||
Cache, zeigt Band und 6 Aufträge; im Cache liegen `app.js`, `app.css` und alle
|
|
||||||
`lib/*.js` mit ihrem `?v=` — vorher wären sie nach einem Deploy nicht dagewesen
|
|
||||||
- Konsole beim Offline-Start: nur Netzfehler, keine JS-Fehler
|
|
||||||
|
|
||||||
### Bewusst nicht gebaut
|
|
||||||
|
|
||||||
Keine Schreib-Warteschlange für Auftragsdaten. Die bräuchte Idempotenz und
|
|
||||||
Konfliktbehandlung (zwei Geräte am selben Auftrag). Für Fotos existiert sie längst
|
|
||||||
(Persist-First-Queue), für alles andere gilt: lesen ja, ändern nur mit Netz.
|
|
||||||
|
|
||||||
## 6. Rauswurf aus dem Auftrag + unsichtbare Warteschlange (gemeldet 2026-09-18)
|
## 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,
|
Gemeldet von Eddy: am 17.09.2026 gegen 18 Uhr mehrere Fotos in einem Auftrag gemacht,
|
||||||
|
|
@ -363,28 +170,17 @@ Kamera weg, getippte Notiz weg, halbe Unterschrift weg.
|
||||||
- **Nicht** getestet: echte Kamera-Hardware und echter Background Sync bei ausgeschaltetem
|
- **Nicht** getestet: echte Kamera-Hardware und echter Background Sync bei ausgeschaltetem
|
||||||
Display am Handy — beides geht nur auf dem Gerät.
|
Display am Handy — beides geht nur auf dem Gerät.
|
||||||
|
|
||||||
### Offen (beobachtet, nicht Teil dieser Runde)
|
---
|
||||||
|
|
||||||
- [ ] `idb.queueAll()` lädt **alle** Foto-Blobs in den Speicher — auch `updateBadge()`, das
|
## Erledigt (gekürzt — Einzelheiten stehen im Commit und in der KB)
|
||||||
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.
|
|
||||||
|
|
||||||
## Erledigt
|
| Wann | Was | Commit | KB |
|
||||||
|
|---|---|---|---|
|
||||||
### 2026-08-22
|
| 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 |
|
||||||
Abschnitte 1, 2 und 3 vollständig. Dazu nebenbei behoben:
|
| 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` | — |
|
||||||
- `.modal-header`, `.close-btn` und `.modal-body` hatten **überhaupt kein CSS**, obwohl das
|
| 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 |
|
||||||
Markup sie seit Längerem nutzt (u. a. beim Löschen eines Berichts) — der Schließen-Knopf
|
| 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 |
|
||||||
saß dadurch unter der Überschrift statt daneben.
|
| 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` | — |
|
||||||
- Das Laden eines Thumbnails überschrieb den kompletten Kachel-Inhalt (`t.innerHTML = …`)
|
| 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` | — |
|
||||||
und nahm die Overlays mit: Auswahl-Häkchen in der Mehrfachauswahl und Seitennummer der
|
| davor | SSO-Migration auf awlauth, WebAuthn-Login, Neuer-Auftrag-Formular, Live-Kamera mit Persist-First-Queue, Lieferschein-Unterschrift | siehe `git log` | #208, #841, #855 |
|
||||||
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.
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue