Offline-Bearbeitung beim Wiederverbinden und Sync bei nicht erreichbarem Server #228

Closed
opened 2026-10-01 14:46:41 +02:00 by lena · 3 comments
Collaborator

Beobachtung

1. Bearbeiten beim Wiederverbinden schlägt fehl

  • Komplett offline (z. B. Flugmodus): Einkaufsliste/Vorrat lassen sich bearbeiten, Änderungen werden vorgemerkt - funktioniert.
  • Sobald die App versucht, sich neu zu verbinden (Gerät meldet "online", Server antwortet aber nicht), schlagen Hinzufügen und Abhaken fehl.

2. Handy und Laptop zeigen unterschiedliche Einkaufszettel

  • Auf dem Handy wurde offline ein Produkt hinzugefügt, danach wurde das Handy zum Synchronisieren wieder online genommen.
  • Handy zeigt den aktuellen Stand, der Laptop einen Stand von gestern.
  • Auslöser war (Stand 2026-10-01) ein Ausfall des Servers (s1.butzei.de / git.butzei.de / todo.moekies.de komplett nicht erreichbar). Die Änderung liegt vermutlich noch in der Offline-Warteschlange des Handys. Die App macht diesen Zustand aber für niemanden sichtbar: beide Geräte zeigen still ihre lokale Kopie, als wäre es der Serverstand.

Vermutete Ursachen (aus dem Code, nicht verifiziert)

  1. offlineSync.ts drainQueue() läuft nur beim online-Event und beim App-Start. Schlägt dieser eine Versuch fehl (auf Mobilgeräten kommt das Event oft, bevor die Verbindung wirklich steht, oder der Server ist schlicht down), wird bis zum nächsten offline->online-Wechsel bzw. App-Neustart nicht erneut gesendet.
  2. callApi reiht Schreibaktionen nur ein, wenn fetch() selbst rejected. Antwortet ein Zwischenserver (Traefik) mit 502/503/504, oder hängt die Anfrage (kein Timeout), wird nichts vorgemerkt - die Änderung schlägt fehl bzw. es passiert nichts.
  3. callApiCached / Read-Cache-Fallback zeigt bei nicht erreichbarem Server still die lokale Kopie, ohne Hinweis, dass es sich um einen alten Stand handelt.

Akzeptanzkriterien

  • Vorgemerkte Änderungen werden so lange erneut gesendet, bis sie angekommen sind - nicht nur beim offline->online-Wechsel, sondern auch per Timer (mit Backoff), beim Zurückkehren in die App (visibilitychange) und sobald der Server wieder erreichbar ist (backendReachability meldet Erfolg).
  • Hinzufügen/Abhaken werden auch vorgemerkt, wenn das Gerät online ist, der Server aber nicht erreichbar: Netzwerkfehler, Zeitüberschreitung, 502/503/504. Fachliche Fehler (z. B. 400) werden weiterhin normal angezeigt.
  • Anfragen bekommen ein Zeitlimit, damit die App nicht unbegrenzt hängt.
  • Solange Änderungen vorgemerkt sind, ist das deutlich sichtbar (Anzahl, "wird synchronisiert"). Gelingt die Übertragung längere Zeit nicht, gibt es einen sichtbaren Hinweis, damit nichts still verloren geht.
  • Zeigt die App wegen nicht erreichbarem Server nur die lokale Kopie, ist das erkennbar (z. B. "Stand von , Server nicht erreichbar").
  • E2E-Test: offline ein Produkt hinzufügen, wieder online, erster Sync-Versuch schlägt fehl (Server simuliert down / 503) -> nach Wiedererreichbarkeit kommt das Produkt ohne App-Neustart auf dem Server an und ist in einem zweiten Browser-Kontext sichtbar.
  • Unit-Tests für Retry-Logik und die Einordnung der Fehlerarten (vormerken vs. Fehler anzeigen).

Abgrenzung

  • Hinzufügen/Abhaken in normalen Todo-Listen und Prioritätslisten (dort offline heute nur lesbar) ist nicht Teil dieses Issues - eigenes Feature-Issue.
  • Offen: nach Wiederherstellung des Servers prüfen und hier nachtragen, ob die Handy-Änderung von selbst ankam oder hängen blieb.
## Beobachtung **1. Bearbeiten beim Wiederverbinden schlägt fehl** - Komplett offline (z. B. Flugmodus): Einkaufsliste/Vorrat lassen sich bearbeiten, Änderungen werden vorgemerkt - funktioniert. - Sobald die App versucht, sich neu zu verbinden (Gerät meldet "online", Server antwortet aber nicht), schlagen Hinzufügen und Abhaken fehl. **2. Handy und Laptop zeigen unterschiedliche Einkaufszettel** - Auf dem Handy wurde offline ein Produkt hinzugefügt, danach wurde das Handy zum Synchronisieren wieder online genommen. - Handy zeigt den aktuellen Stand, der Laptop einen Stand von gestern. - Auslöser war (Stand 2026-10-01) ein Ausfall des Servers (`s1.butzei.de` / `git.butzei.de` / `todo.moekies.de` komplett nicht erreichbar). Die Änderung liegt vermutlich noch in der Offline-Warteschlange des Handys. Die App macht diesen Zustand aber für niemanden sichtbar: beide Geräte zeigen still ihre lokale Kopie, als wäre es der Serverstand. ## Vermutete Ursachen (aus dem Code, nicht verifiziert) 1. `offlineSync.ts` `drainQueue()` läuft nur beim `online`-Event und beim App-Start. Schlägt dieser eine Versuch fehl (auf Mobilgeräten kommt das Event oft, bevor die Verbindung wirklich steht, oder der Server ist schlicht down), wird bis zum nächsten offline->online-Wechsel bzw. App-Neustart nicht erneut gesendet. 2. `callApi` reiht Schreibaktionen nur ein, wenn `fetch()` selbst rejected. Antwortet ein Zwischenserver (Traefik) mit 502/503/504, oder hängt die Anfrage (kein Timeout), wird nichts vorgemerkt - die Änderung schlägt fehl bzw. es passiert nichts. 3. `callApiCached` / Read-Cache-Fallback zeigt bei nicht erreichbarem Server still die lokale Kopie, ohne Hinweis, dass es sich um einen alten Stand handelt. ## Akzeptanzkriterien - [ ] Vorgemerkte Änderungen werden so lange erneut gesendet, bis sie angekommen sind - nicht nur beim offline->online-Wechsel, sondern auch per Timer (mit Backoff), beim Zurückkehren in die App (visibilitychange) und sobald der Server wieder erreichbar ist (`backendReachability` meldet Erfolg). - [ ] Hinzufügen/Abhaken werden auch vorgemerkt, wenn das Gerät online ist, der Server aber nicht erreichbar: Netzwerkfehler, Zeitüberschreitung, 502/503/504. Fachliche Fehler (z. B. 400) werden weiterhin normal angezeigt. - [ ] Anfragen bekommen ein Zeitlimit, damit die App nicht unbegrenzt hängt. - [ ] Solange Änderungen vorgemerkt sind, ist das deutlich sichtbar (Anzahl, "wird synchronisiert"). Gelingt die Übertragung längere Zeit nicht, gibt es einen sichtbaren Hinweis, damit nichts still verloren geht. - [ ] Zeigt die App wegen nicht erreichbarem Server nur die lokale Kopie, ist das erkennbar (z. B. "Stand von <Zeitpunkt>, Server nicht erreichbar"). - [ ] E2E-Test: offline ein Produkt hinzufügen, wieder online, erster Sync-Versuch schlägt fehl (Server simuliert down / 503) -> nach Wiedererreichbarkeit kommt das Produkt ohne App-Neustart auf dem Server an und ist in einem zweiten Browser-Kontext sichtbar. - [ ] Unit-Tests für Retry-Logik und die Einordnung der Fehlerarten (vormerken vs. Fehler anzeigen). ## Abgrenzung - Hinzufügen/Abhaken in normalen Todo-Listen und Prioritätslisten (dort offline heute nur lesbar) ist nicht Teil dieses Issues - eigenes Feature-Issue. - Offen: nach Wiederherstellung des Servers prüfen und hier nachtragen, ob die Handy-Änderung von selbst ankam oder hängen blieb.
lena self-assigned this 2026-10-02 16:41:32 +02:00
Author
Collaborator

Claimed by session "Offene Issues [08a09a]"

Starte mit Design: Retry der Offline-Warteschlange (Timer mit Backoff, visibilitychange, Server wieder erreichbar), Vormerken auch bei Timeout/502/503/504, Zeitlimit fuer Anfragen, sichtbare Anzeige vorgemerkter Aenderungen und veralteter lokaler Kopie.

Claimed by session "Offene Issues [08a09a]" Starte mit Design: Retry der Offline-Warteschlange (Timer mit Backoff, visibilitychange, Server wieder erreichbar), Vormerken auch bei Timeout/502/503/504, Zeitlimit fuer Anfragen, sichtbare Anzeige vorgemerkter Aenderungen und veralteter lokaler Kopie.
Author
Collaborator

Design (Architect) + Security-Vorpruefung, Session "Offene Issues [08a09a]"

Fehlerarten (rawApi.ts): Ein Request gilt als "Server nicht erreichbar", wenn fetch rejected, nach 20 s abgebrochen wird (AbortController) oder 502/503/504 liefert. Nur dann wird vorgemerkt bzw. die lokale Kopie gezeigt; alle anderen Status (400, 401, 403, 404, 500) laufen wie bisher als Fehler.

callApi: 502/503/504 nehmen denselben Pfad wie ein Netzwerkfehler (Vormerken bzw. Read-Cache). Health-Ping bekommt ebenfalls ein Zeitlimit (5 s), sonst haengt die Ausfallerkennung.

Warteschlange (offlineSync.ts): Neuer Versuch per Timer mit Backoff (5 s, verdoppelt, max. 60 s), solange noch etwas vorgemerkt ist und der letzte Versuch an der Erreichbarkeit scheiterte; ausserdem bei visibilitychange->sichtbar und sobald backendReachability wieder Erfolg meldet. State bekommt syncFailing und oldestPendingAt.

Smart-Add: Ein neues Produkt nimmt den vormerkbaren Weg (AddOrActivate) auch, wenn der Browser online, der Server aber als nicht erreichbar bekannt ist.

Sichtbarkeit: OfflineBanner zeigt bei online + vorgemerkt die Anzahl und "wird gesendet, sobald der Server erreichbar ist"; nach >10 min ohne Erfolg eine Warnung mit Uhrzeit. Read-Cache speichert pro Eintrag den Zeitpunkt; zeigt die App wegen Nichterreichbarkeit lokale Daten, nennt das Banner "Stand von ".

Security: keine neuen Endpunkte/Daten; Queue-Verhalten bei 401/403 unveraendert (nicht verwerfen). Bekanntes Restrisiko: bei 504/Zeitlimit kann der Server die Anfrage doch verarbeitet haben -> Wiederholung koennte bei +/-1-Aktionen (Vorrat ein/aus) doppelt zaehlen. Bei 502/503/Verbindungsfehler wurde nichts verarbeitet. Bewusst akzeptiert, im Abschluss dokumentiert.

Design (Architect) + Security-Vorpruefung, Session "Offene Issues [08a09a]" **Fehlerarten** (`rawApi.ts`): Ein Request gilt als "Server nicht erreichbar", wenn `fetch` rejected, nach 20 s abgebrochen wird (AbortController) oder 502/503/504 liefert. Nur dann wird vorgemerkt bzw. die lokale Kopie gezeigt; alle anderen Status (400, 401, 403, 404, 500) laufen wie bisher als Fehler. **callApi**: 502/503/504 nehmen denselben Pfad wie ein Netzwerkfehler (Vormerken bzw. Read-Cache). Health-Ping bekommt ebenfalls ein Zeitlimit (5 s), sonst haengt die Ausfallerkennung. **Warteschlange** (`offlineSync.ts`): Neuer Versuch per Timer mit Backoff (5 s, verdoppelt, max. 60 s), solange noch etwas vorgemerkt ist und der letzte Versuch an der Erreichbarkeit scheiterte; ausserdem bei visibilitychange->sichtbar und sobald `backendReachability` wieder Erfolg meldet. State bekommt `syncFailing` und `oldestPendingAt`. **Smart-Add**: Ein neues Produkt nimmt den vormerkbaren Weg (AddOrActivate) auch, wenn der Browser online, der Server aber als nicht erreichbar bekannt ist. **Sichtbarkeit**: OfflineBanner zeigt bei online + vorgemerkt die Anzahl und "wird gesendet, sobald der Server erreichbar ist"; nach >10 min ohne Erfolg eine Warnung mit Uhrzeit. Read-Cache speichert pro Eintrag den Zeitpunkt; zeigt die App wegen Nichterreichbarkeit lokale Daten, nennt das Banner "Stand von <Zeit>". **Security**: keine neuen Endpunkte/Daten; Queue-Verhalten bei 401/403 unveraendert (nicht verwerfen). Bekanntes Restrisiko: bei 504/Zeitlimit kann der Server die Anfrage doch verarbeitet haben -> Wiederholung koennte bei +/-1-Aktionen (Vorrat ein/aus) doppelt zaehlen. Bei 502/503/Verbindungsfehler wurde nichts verarbeitet. Bewusst akzeptiert, im Abschluss dokumentiert.
Author
Collaborator

Umgesetzt in 8752494f (Frontend) und 75f2ca93 (E2E-Test).

Scope

  • Jeder Request hat ein Zeitlimit (20 s, Health-Ping 5 s). Netzwerkfehler, Zeitlimit und 502/503/504 gelten als "Server nicht erreichbar": vormerkbare Aenderungen werden vorgemerkt, gecachte Listen angezeigt. 400/401/403/404/500 laufen unveraendert als Fehler.
  • Warteschlange: nach einem Fehlschlag neuer Versuch per Timer (5 s, verdoppelt bis 60 s), bei Rueckkehr in die App (visibilitychange) und sofort, sobald der Server wieder antwortet (neues offline/connectivity.ts, loest auch ein Neuladen der angezeigten Daten aus).
  • Smart-Add nimmt den vormerkbaren Weg auch, wenn der Browser online, der Server aber als nicht erreichbar bekannt ist (vorher oder durch den fehlschlagenden Vorschlags-Request).
  • Anzeige: "N Aenderungen auf diesem Geraet vorgemerkt - werden gesendet, sobald der Server antwortet"; nach 10 min ohne Erfolg rote Warnung mit Uhrzeit; "N Aenderungen werden synchronisiert". Zeigt die App wegen des Ausfalls nur die lokale Kopie, nennt das Banner "lokaler Stand von " (Zeitstempel pro Cache-Eintrag in localStorage, wird beim Abmelden mitgeloescht).

Tests

  • Unit: Fehlerarten in callApi (502/503/504 vormerken bzw. Cache, 400 weiterhin Fehler, Zeitlimit gesetzt), Retry mit Backoff/Deckel, retryNow, Vormerken loest Retry aus, Zeitpunkt der aeltesten Aenderung, Banner-Zustaende, Wiederverbindung, Smart-Add bei nicht erreichbarem Server. 1557/1557 gruen, Build gruen.
  • E2E e2e/offline-sync.spec.ts: offline hinzufuegen -> online, Proxy antwortet 503 -> Server wieder da -> Produkt kommt ohne Neuladen an und ist in zweitem Browser-Kontext sichtbar. Lokal in Chromium gruen (Firefox startet auf diesem Rechner nicht, laeuft in der CI).
  • Manuell im Review-Container geprueft (Ausfall per 503 simuliert, Banner und Nachlieferung).

Security-Review: keine Findings. Bekanntes, bewusst akzeptiertes Restrisiko (siehe Design-Kommentar): bei 504 oder Zeitlimit kann der Server die Anfrage doch verarbeitet haben; eine Wiederholung zaehlt dann bei nicht-idempotenten Befehlen doppelt (Vorrat ein/aus/Undo, CreatePantryProduct, Mengen-Merge bei AddOrActivate). Ausschliessen ist im Browser nicht moeglich, da ein toter Host und ein langsamer Server gleich aussehen. Saubere Loesung waere ein Idempotenz-Schluessel pro vorgemerkter Aktion, den das Backend dedupliziert - Vorschlag fuer ein eigenes Issue.

Offen aus der Beschreibung: ob die Handy-Aenderung vom 2026-10-01 von selbst ankam, kann nur auf dem Geraet geprueft werden.

Umgesetzt in 8752494f (Frontend) und 75f2ca93 (E2E-Test). **Scope** - Jeder Request hat ein Zeitlimit (20 s, Health-Ping 5 s). Netzwerkfehler, Zeitlimit und 502/503/504 gelten als "Server nicht erreichbar": vormerkbare Aenderungen werden vorgemerkt, gecachte Listen angezeigt. 400/401/403/404/500 laufen unveraendert als Fehler. - Warteschlange: nach einem Fehlschlag neuer Versuch per Timer (5 s, verdoppelt bis 60 s), bei Rueckkehr in die App (visibilitychange) und sofort, sobald der Server wieder antwortet (neues `offline/connectivity.ts`, loest auch ein Neuladen der angezeigten Daten aus). - Smart-Add nimmt den vormerkbaren Weg auch, wenn der Browser online, der Server aber als nicht erreichbar bekannt ist (vorher oder durch den fehlschlagenden Vorschlags-Request). - Anzeige: "N Aenderungen auf diesem Geraet vorgemerkt - werden gesendet, sobald der Server antwortet"; nach 10 min ohne Erfolg rote Warnung mit Uhrzeit; "N Aenderungen werden synchronisiert". Zeigt die App wegen des Ausfalls nur die lokale Kopie, nennt das Banner "lokaler Stand von <Zeit>" (Zeitstempel pro Cache-Eintrag in localStorage, wird beim Abmelden mitgeloescht). **Tests** - Unit: Fehlerarten in callApi (502/503/504 vormerken bzw. Cache, 400 weiterhin Fehler, Zeitlimit gesetzt), Retry mit Backoff/Deckel, retryNow, Vormerken loest Retry aus, Zeitpunkt der aeltesten Aenderung, Banner-Zustaende, Wiederverbindung, Smart-Add bei nicht erreichbarem Server. 1557/1557 gruen, Build gruen. - E2E `e2e/offline-sync.spec.ts`: offline hinzufuegen -> online, Proxy antwortet 503 -> Server wieder da -> Produkt kommt ohne Neuladen an und ist in zweitem Browser-Kontext sichtbar. Lokal in Chromium gruen (Firefox startet auf diesem Rechner nicht, laeuft in der CI). - Manuell im Review-Container geprueft (Ausfall per 503 simuliert, Banner und Nachlieferung). **Security-Review**: keine Findings. Bekanntes, bewusst akzeptiertes Restrisiko (siehe Design-Kommentar): bei 504 oder Zeitlimit kann der Server die Anfrage doch verarbeitet haben; eine Wiederholung zaehlt dann bei nicht-idempotenten Befehlen doppelt (Vorrat ein/aus/Undo, CreatePantryProduct, Mengen-Merge bei AddOrActivate). Ausschliessen ist im Browser nicht moeglich, da ein toter Host und ein langsamer Server gleich aussehen. Saubere Loesung waere ein Idempotenz-Schluessel pro vorgemerkter Aktion, den das Backend dedupliziert - Vorschlag fuer ein eigenes Issue. **Offen aus der Beschreibung**: ob die Handy-Aenderung vom 2026-10-01 von selbst ankam, kann nur auf dem Geraet geprueft werden.
lena 2026-10-02 17:05:40 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#228
No description provided.