#102 — Offline-Verfügbarkeit Phase 2 — Smart-Add & absolute Wertänderungen #105

Closed
opened 2026-08-18 13:14:05 +02:00 by lena · 1 comment
lena commented 2026-08-18 13:14:05 +02:00 (Migrated from git.butzei.de)

Story: Offline-Verfügbarkeit Phase 2 — Smart-Add & absolute Wertänderungen

As a Haushaltsmitglied,
I want to auch die restlichen Einkaufslisten-/Vorratsschrank-Aktionen offline nutzen können, die #97
(Phase 1) bewusst ausgeklammert hat,
so that Offline-Nutzung nicht auf Abhaken/Ankreuzen und einfaches Hinzufügen beschränkt bleibt.

Depends on: #97 (liefert die Basis-Infrastruktur: IndexedDB-Queue, Read-Cache, Sync-on-Reconnect,
Status-Banner — diese Story erweitert nur die Aktions-Whitelist und braucht kein neues Fundament).

Aus #97 übernommene, hier zu schließende Lücken:

  • Shopping's Smart-Add (SmartAddInput, ML-Worker-gestützt) und dessen einfacher Fallback
    (AddOrActivateShoppingProductCommand) offline nutzbar machen.
  • Befehle, die einen absoluten Wert überschreiben statt ihn relativ zu ändern
    (SetPantryProductTargetQuantityCommand, RenameShoppingProductCommand,
    RenamePantryProductCommand, Kategorie-Zuordnung/Move-Befehle) — diese brauchen laut #97's
    Architect-Hinweis Last-Write-Wins plus Sichtbarkeit im Aktivitäts-Feed, damit ein überschriebener
    Stand nicht spurlos verloren geht.
  • Barcode-Scan, CSV-Import/-Export, Mitglieder-/Einladungsverwaltung bleiben bewusst außerhalb dieser
    Story — deutlich seltener im "kein Empfang"-Moment gebraucht als die Kernaktionen.

Blocker/Voraussetzung: #101 ist inzwischen vollständig geliefert (inkl. #101d Aktivitäts-Feed) - der
ursprünglich hier vermerkte Blocker ist aufgelöst.

PO-Scope-Entscheidung (2026-08-10, beim Aufgreifen dieser Story): Recherche beim Aufgreifen ergab,
dass die ursprüngliche Annahme "Aktivitäts-Feed-Sichtbarkeit fehlt nur für Pantry" falsch war - Shopping
hat überhaupt keine Produkt-Aktivitäts-Feed-Infrastruktur (keine Entity, keine Query, kein UI-Panel),
während Pantry sie durch #101 bereits vollständig besitzt (PantryActivityEventEntity +
GetPantryActivityFeedQuery + PantryActivityFeedPanel, inkl. bereits laufender Emission aus
RenamePantryProductCommandHandler/SetPantryProductTargetQuantityCommandHandler). Ein Shopping-
Aktivitäts-Feed von Grund auf neu zu bauen (Entity + Migration + Handler + Query + UI-Panel) ist ein
eigenständiges, für sich stehendes Stück Arbeit und für diesen Zyklus nicht proportional - abgespalten
als eigene Folge-Story (docs/features/new/117_shopping_product_activity_feed.md). Diese Story liefert
für Shopping-seitige Rename/Move-Befehle Last-Write-Wins (keine Daten gehen verloren, der nächste
erfolgreiche Sync/Refetch versöhnt den Stand), aber noch ohne Aktivitäts-Feed-Sichtbarkeit - ein
bewusst offener, dokumentierter Punkt bis zur Folge-Story, nicht stillschweigend übersprungen.

Ebenfalls beim Aufgreifen entdeckt und in derselben Story behoben: AddOrActivateShoppingProductCommand
ist die einzige der hier ergänzten Befehle, die serverseitig einen neuen Datensatz anlegen kann (nicht
nur einen bestehenden überschreiben) - ein Offline-Create dieser Art gefolgt von einem sofortigen
Folgebefehl auf dieselbe (temporäre) Id (Kategorie zuweisen, Menge setzen) würde beim Replay nach
Wiederverbindung fehlschlagen, da die Warteschlange jede Aktion mit ihrem ursprünglichen Anfrageinhalt
wortwörtlich wiederholt, ohne Id-Umschreibung. Gelöst durch dasselbe Zugeständnis, das
CreatePantryProductCommands eigener Offline-Pfad in #97 bereits akzeptiert hat: ein Offline-Create
landet unkategorisiert/mengenlos, der nächste Sync/Refetch versöhnt den echten Stand - siehe
SmartAddInput.tsxs isOfflineCreate-Guard.

Acceptance criteria:

  • Smart-Add (inkl. Fallback) funktioniert offline für Shopping-Produkte, mit derselben
    Warteschlangen-/Sync-Mechanik wie #97. (AddOrActivateShoppingProductCommand neu in
    offlineConfig.ts; Folgebefehle auf ein Offline-Create werden übersprungen, s. o.)
  • Absolute-Wert-Befehle (Zielmenge setzen, Umbenennen, Kategorie verschieben) funktionieren offline
    per Last-Write-Wins. Für Pantry landet ein überschriebener Zwischenstand sichtbar im
    Aktivitäts-Feed (Infrastruktur bereits aus #101 vorhanden, Body um den alten Wert ergänzt). Für
    Shopping fehlt die Aktivitäts-Feed-Sichtbarkeit noch - bewusst an #117 delegiert, s. o.
  • Barcode-Scan, CSV-Import/-Export und Mitgliederverwaltung bleiben explizit "erfordert Verbindung"
    (kein stiller Fehlschlag, klare Fehlermeldung statt der generischen Netzwerkfehler-Toast) - neue
    requireOnline()-Vorprüfung in CsvExportDialog/CsvImportDialog/PantryBarcodeScanner/
    ShoppingBarcodeScanDialog/InvitePanel/MembersPanel.
# Story: Offline-Verfügbarkeit Phase 2 — Smart-Add & absolute Wertänderungen **As a** Haushaltsmitglied, **I want to** auch die restlichen Einkaufslisten-/Vorratsschrank-Aktionen offline nutzen können, die `#97` (Phase 1) bewusst ausgeklammert hat, **so that** Offline-Nutzung nicht auf Abhaken/Ankreuzen und einfaches Hinzufügen beschränkt bleibt. **Depends on:** `#97` (liefert die Basis-Infrastruktur: IndexedDB-Queue, Read-Cache, Sync-on-Reconnect, Status-Banner — diese Story erweitert nur die Aktions-Whitelist und braucht kein neues Fundament). **Aus `#97` übernommene, hier zu schließende Lücken:** - Shopping's Smart-Add (`SmartAddInput`, ML-Worker-gestützt) und dessen einfacher Fallback (`AddOrActivateShoppingProductCommand`) offline nutzbar machen. - Befehle, die einen absoluten Wert überschreiben statt ihn relativ zu ändern (`SetPantryProductTargetQuantityCommand`, `RenameShoppingProductCommand`, `RenamePantryProductCommand`, Kategorie-Zuordnung/Move-Befehle) — diese brauchen laut `#97`'s Architect-Hinweis Last-Write-Wins **plus** Sichtbarkeit im Aktivitäts-Feed, damit ein überschriebener Stand nicht spurlos verloren geht. - Barcode-Scan, CSV-Import/-Export, Mitglieder-/Einladungsverwaltung bleiben bewusst außerhalb dieser Story — deutlich seltener im "kein Empfang"-Moment gebraucht als die Kernaktionen. **Blocker/Voraussetzung:** `#101` ist inzwischen vollständig geliefert (inkl. `#101d` Aktivitäts-Feed) - der ursprünglich hier vermerkte Blocker ist aufgelöst. **PO-Scope-Entscheidung (2026-08-10, beim Aufgreifen dieser Story):** Recherche beim Aufgreifen ergab, dass die ursprüngliche Annahme "Aktivitäts-Feed-Sichtbarkeit fehlt nur für Pantry" falsch war - Shopping hat *überhaupt keine* Produkt-Aktivitäts-Feed-Infrastruktur (keine Entity, keine Query, kein UI-Panel), während Pantry sie durch `#101` bereits vollständig besitzt (`PantryActivityEventEntity` + `GetPantryActivityFeedQuery` + `PantryActivityFeedPanel`, inkl. bereits laufender Emission aus `RenamePantryProductCommandHandler`/`SetPantryProductTargetQuantityCommandHandler`). Ein Shopping- Aktivitäts-Feed von Grund auf neu zu bauen (Entity + Migration + Handler + Query + UI-Panel) ist ein eigenständiges, für sich stehendes Stück Arbeit und für diesen Zyklus nicht proportional - abgespalten als eigene Folge-Story (`docs/features/new/117_shopping_product_activity_feed.md`). Diese Story liefert für Shopping-seitige Rename/Move-Befehle Last-Write-Wins (keine Daten gehen verloren, der nächste erfolgreiche Sync/Refetch versöhnt den Stand), aber noch **ohne** Aktivitäts-Feed-Sichtbarkeit - ein bewusst offener, dokumentierter Punkt bis zur Folge-Story, nicht stillschweigend übersprungen. Ebenfalls beim Aufgreifen entdeckt und in derselben Story behoben: `AddOrActivateShoppingProductCommand` ist die einzige der hier ergänzten Befehle, die serverseitig einen *neuen* Datensatz anlegen kann (nicht nur einen bestehenden überschreiben) - ein Offline-Create dieser Art gefolgt von einem sofortigen Folgebefehl auf dieselbe (temporäre) Id (Kategorie zuweisen, Menge setzen) würde beim Replay nach Wiederverbindung fehlschlagen, da die Warteschlange jede Aktion mit ihrem ursprünglichen Anfrageinhalt wortwörtlich wiederholt, ohne Id-Umschreibung. Gelöst durch dasselbe Zugeständnis, das `CreatePantryProductCommand`s eigener Offline-Pfad in `#97` bereits akzeptiert hat: ein Offline-Create landet unkategorisiert/mengenlos, der nächste Sync/Refetch versöhnt den echten Stand - siehe `SmartAddInput.tsx`s `isOfflineCreate`-Guard. **Acceptance criteria:** - [x] Smart-Add (inkl. Fallback) funktioniert offline für Shopping-Produkte, mit derselben Warteschlangen-/Sync-Mechanik wie `#97`. (`AddOrActivateShoppingProductCommand` neu in `offlineConfig.ts`; Folgebefehle auf ein Offline-Create werden übersprungen, s. o.) - [x] Absolute-Wert-Befehle (Zielmenge setzen, Umbenennen, Kategorie verschieben) funktionieren offline per Last-Write-Wins. Für Pantry landet ein überschriebener Zwischenstand sichtbar im Aktivitäts-Feed (Infrastruktur bereits aus `#101` vorhanden, Body um den alten Wert ergänzt). Für Shopping fehlt die Aktivitäts-Feed-Sichtbarkeit noch - bewusst an `#117` delegiert, s. o. - [x] Barcode-Scan, CSV-Import/-Export und Mitgliederverwaltung bleiben explizit "erfordert Verbindung" (kein stiller Fehlschlag, klare Fehlermeldung statt der generischen Netzwerkfehler-Toast) - neue `requireOnline()`-Vorprüfung in `CsvExportDialog`/`CsvImportDialog`/`PantryBarcodeScanner`/ `ShoppingBarcodeScanDialog`/`InvitePanel`/`MembersPanel`.
lena commented 2026-08-18 13:14:05 +02:00 (Migrated from git.butzei.de)

design (102_offline_support_phase2_absolute_value_actions_design.md)

Design: #102 — Offline-Verfügbarkeit Phase 2

Was diese Story an #97's Fundament ändert

#97 baute die komplette Infrastruktur (offlineDb.ts localStorage-Queue+Read-Cache, offlineWrite.tss
optimistisches Apply, offlineSync.tss FIFO-Drain-on-reconnect, OfflineBanner.tsx) bereits fertig.
#102 ist reine Whitelist-Erweiterung in offlineConfig.ts plus punktuelle Frontend-Änderungen - keine
neue Infrastruktur, mit zwei Ausnahmen:

  1. offlineConfig.tss isCreate?: boolean-Flag ist weg. handleOfflineWrite entscheidet jetzt anhand
    von cached.some(p => p.id === updated.id), ob ein Datensatz angehängt oder ersetzt wird, statt
    anhand eines statischen Flags pro Befehl. Notwendig, weil AddOrActivateShoppingProductCommand
    pro Aufruf zwischen Erstellen und Aktivieren entscheidet (je nachdem, ob ein Namens-Match im Cache
    existiert) - ein statisches Flag kann das nicht ausdrücken. Verhalten für alle bestehenden Einträge
    unverändert (ein echter Create liefert immer eine neue negative Id, die nie im Cache steht; ein
    Update liefert immer dieselbe Id wie das gefundene Element).
  2. Neue offlineGuard.ts: requireOnline(actionLabel) prüft offlineSync.getSnapshot().isOnline und
    zeigt bei false einen spezifischen Toast statt callApis generischem
    "Failed to send the request..." - für die drei AC-benannten "erfordert Verbindung"-Aktionen.

Neue Whitelist-Einträge (offlineConfig.ts)

Befehl Mirrort Besonderheit
AddOrActivateShoppingProductCommand AddOrActivateShoppingProductCommandHandler.cs Einzige Konfiguration ohne findExisting - sucht selbst per case-insensitivem Namens-Match (nach stripQuantityPrefix, wiederverwendet aus utils/shoppingQuantityPrefix.ts, statt einer zweiten Kopie der Logik). Kein Treffer → neuer, bereits aktivierter, unkategorisierter Datensatz (negative Temp-Id).
RenameShoppingProductCommand RenameShoppingProductCommandHandler.cs Reiner Namens-Overwrite.
RenamePantryProductCommand RenamePantryProductCommandHandler.cs Reiner Namens-Overwrite. Der Aktivitäts-Event läuft serverseitig beim echten Replay, nicht optimistisch synthetisiert.
MoveShoppingProductCommand / MovePantryProductCommand jeweiliger Handler Bekannter Kompromiss: handleOfflineWrite kann pro Aufruf nur eine Cache-Zeile ersetzen - die serverseitige Geschwister-Neunummerierung (alte + neue Kategorie) wird offline nicht repliziert, nur categoryId/sortOrder des bewegten Produkts selbst. Andere Zeilen tragen bis zum nächsten Sync/Refetch einen veralteten sortOrder - dieselbe Art Zugeständnis wie checkOutPantryProducts bereits akzeptierter Low-Stock-Seiteneffekt.
SetPantryProductTargetQuantityCommand SetPantryProductTargetQuantityCommandHandler.cs Reiner Overwrite. Aktivitäts-Event-Body serverseitig um den alten Wert ergänzt (siehe unten).

Bewusst nicht ergänzt: CreateShoppingProductCommand (der zweistufige Create-dann-Activate-Pfad in
SmartAddInput.createInCategory würde einen zweiten Warteschlangen-Eintrag erzeugen, der beim Replay auf
die temporäre Id des ersten verweist - siehe "Temp-Id-Verkettung" unten). ImportShoppingProductsFromCsvCommand/
ImportPantryProductsFromCsvCommand (CSV bleibt laut AC explizit "erfordert Verbindung").

Temp-Id-Verkettung — die eine echte Falle dieser Story

offlineSync.tss drainQueue() wiederholt jede Warteschlangen-Aktion mit ihrem ursprünglichen
Anfrageinhalt, ohne Id-Umschreibung. Eine Kette aus zwei Offline-Aktionen, bei der die zweite auf die
(negative, temporäre) Id der ersten verweist, schlägt beim echten Replay fehl: der Server kennt die
temporäre Id nie, antwortet mit 404, und drainQueue()s bestehende 404-Regel (Produkt wurde
zwischenzeitlich gelöscht → verwerfen statt endlos wiederholen) verwirft die zweite Aktion still - ein
echter, sonst unsichtbarer Datenverlust, genau das, was diese Story mit "damit ein überschriebener Stand
nicht spurlos verloren geht" verhindern soll.

Das trifft SmartAddInput.tsx an zwei Stellen, die nach einem AddOrActivateShoppingProductCommand
sofort einen Folgebefehl auf dessen zurückgegebene Id schicken (Kategorie-Auswahl → MoveShoppingProductCommand,
Mengen-Popup → ActivateShoppingProductCommand). Fix: product.id < 0 (negative Temp-Id) überspringt
beide Folgeschritte - identisch zu createPantryProducts bereits akzeptiertem #97-Kompromiss
("unkategorisiert bis zum nächsten Refetch"). Eine vollständige Lösung (Warteschlange schreibt spätere
Einträge um, sobald ein Create real aufgelöst ist) wäre eine eigenständige Architektur-Erweiterung an
#97s Kern und für diesen Zyklus nicht verhältnismäßig.

Aktivitäts-Feed-Sichtbarkeit — Pantry ja, Shopping (noch) nein

Siehe die Story-Datei selbst für die volle Begründung. Kurzfassung: Pantry hatte die Infrastruktur
bereits aus #101, Shopping hat gar keine - letzteres als eigene Folge-Story #117 abgespalten statt
hier eine Mini-Version davon nebenbei zu bauen. SetPantryProductTargetQuantityCommandHandlers
Aktivitäts-Body zeigt jetzt zusätzlich den alten Wert ("changed target of 'X' from 3 to 5" statt nur
"set target of 'X' to 5"), damit ein Last-Write-Wins-Überschreiben im Feed nachvollziehbar bleibt, nicht
nur implizit aus einem früheren Feed-Eintrag rekonstruierbar.

requireOnline() — "erfordert Verbindung" statt genereller Fehler

Angewendet an sechs Stellen, jeweils am Anfang der auslösenden Aktion, bevor irgendein Netzwerk-Call
versucht wird: CsvExportDialog.handleExport, CsvImportDialog.handleImport,
PantryBarcodeScanner.submitBarcode/submitWithName, ShoppingBarcodeScanDialog.submitBarcode/
submitWithName, InvitePanel.handleGenerate/handleRevoke, MembersPanel.handleRemove. InvitePanel/
MembersPanel sind listentyp-übergreifend geteilte Komponenten (auch für Todo-Listen, die nie eine
eigene Offline-Unterstützung bekommen haben) - die klarere Meldung gilt dort einheitlich, statt nur für
Shopping/Pantry-Aufrufer bedingt zu gelten, was implementierungstechnisch unnötig verzweigt hätte und für
Todo-Nutzer ohnehin eine reine Verbesserung gegenüber der bisherigen generischen Meldung ist.

Security-Vorprüfung

Keine neue Backend-Angriffsfläche - alle sechs neuen offlineConfig.ts-Einträge mirrorn bereits
existierende, bereits autorisierte Commands 1:1 (dieselbe serverseitige WithAuthorization-Prüfung greift
beim tatsächlichen Replay unverändert). Der Client vertraut sich selbst nur optimistisch bis zum echten
Server-Roundtrip - identisch zu #97s bereits geprüftem Modell. requireOnline() ist reine UX, keine
Sicherheitsgrenze.

**design** (`102_offline_support_phase2_absolute_value_actions_design.md`) # Design: `#102` — Offline-Verfügbarkeit Phase 2 ## Was diese Story an `#97`'s Fundament ändert `#97` baute die komplette Infrastruktur (`offlineDb.ts` localStorage-Queue+Read-Cache, `offlineWrite.ts`s optimistisches Apply, `offlineSync.ts`s FIFO-Drain-on-reconnect, `OfflineBanner.tsx`) bereits fertig. `#102` ist reine Whitelist-Erweiterung in `offlineConfig.ts` plus punktuelle Frontend-Änderungen - **keine** neue Infrastruktur, mit zwei Ausnahmen: 1. `offlineConfig.ts`s `isCreate?: boolean`-Flag ist weg. `handleOfflineWrite` entscheidet jetzt anhand von `cached.some(p => p.id === updated.id)`, ob ein Datensatz angehängt oder ersetzt wird, statt anhand eines statischen Flags pro Befehl. Notwendig, weil `AddOrActivateShoppingProductCommand` *pro Aufruf* zwischen Erstellen und Aktivieren entscheidet (je nachdem, ob ein Namens-Match im Cache existiert) - ein statisches Flag kann das nicht ausdrücken. Verhalten für alle bestehenden Einträge unverändert (ein echter Create liefert immer eine neue negative Id, die nie im Cache steht; ein Update liefert immer dieselbe Id wie das gefundene Element). 2. Neue `offlineGuard.ts`: `requireOnline(actionLabel)` prüft `offlineSync.getSnapshot().isOnline` und zeigt bei `false` einen spezifischen Toast statt `callApi`s generischem "Failed to send the request..." - für die drei AC-benannten "erfordert Verbindung"-Aktionen. ## Neue Whitelist-Einträge (`offlineConfig.ts`) | Befehl | Mirrort | Besonderheit | |---|---|---| | `AddOrActivateShoppingProductCommand` | `AddOrActivateShoppingProductCommandHandler.cs` | Einzige Konfiguration ohne `findExisting` - sucht selbst per case-insensitivem Namens-Match (nach `stripQuantityPrefix`, wiederverwendet aus `utils/shoppingQuantityPrefix.ts`, statt einer zweiten Kopie der Logik). Kein Treffer → neuer, bereits aktivierter, unkategorisierter Datensatz (negative Temp-Id). | | `RenameShoppingProductCommand` | `RenameShoppingProductCommandHandler.cs` | Reiner Namens-Overwrite. | | `RenamePantryProductCommand` | `RenamePantryProductCommandHandler.cs` | Reiner Namens-Overwrite. Der Aktivitäts-Event läuft serverseitig beim echten Replay, nicht optimistisch synthetisiert. | | `MoveShoppingProductCommand` / `MovePantryProductCommand` | jeweiliger Handler | **Bekannter Kompromiss:** `handleOfflineWrite` kann pro Aufruf nur eine Cache-Zeile ersetzen - die serverseitige Geschwister-Neunummerierung (alte + neue Kategorie) wird offline nicht repliziert, nur `categoryId`/`sortOrder` des bewegten Produkts selbst. Andere Zeilen tragen bis zum nächsten Sync/Refetch einen veralteten `sortOrder` - dieselbe Art Zugeständnis wie `checkOutPantryProduct`s bereits akzeptierter Low-Stock-Seiteneffekt. | | `SetPantryProductTargetQuantityCommand` | `SetPantryProductTargetQuantityCommandHandler.cs` | Reiner Overwrite. Aktivitäts-Event-Body serverseitig um den alten Wert ergänzt (siehe unten). | **Bewusst nicht ergänzt:** `CreateShoppingProductCommand` (der zweistufige Create-dann-Activate-Pfad in `SmartAddInput.createInCategory` würde einen zweiten Warteschlangen-Eintrag erzeugen, der beim Replay auf die *temporäre* Id des ersten verweist - siehe "Temp-Id-Verkettung" unten). `ImportShoppingProductsFromCsvCommand`/ `ImportPantryProductsFromCsvCommand` (CSV bleibt laut AC explizit "erfordert Verbindung"). ## Temp-Id-Verkettung — die eine echte Falle dieser Story `offlineSync.ts`s `drainQueue()` wiederholt jede Warteschlangen-Aktion mit ihrem **ursprünglichen** Anfrageinhalt, ohne Id-Umschreibung. Eine Kette aus zwei Offline-Aktionen, bei der die zweite auf die (negative, temporäre) Id der ersten verweist, schlägt beim echten Replay fehl: der Server kennt die temporäre Id nie, antwortet mit 404, und `drainQueue()`s bestehende 404-Regel (Produkt wurde zwischenzeitlich gelöscht → verwerfen statt endlos wiederholen) verwirft die zweite Aktion still - ein echter, sonst unsichtbarer Datenverlust, genau das, was diese Story mit "damit ein überschriebener Stand nicht spurlos verloren geht" verhindern soll. Das trifft `SmartAddInput.tsx` an zwei Stellen, die nach einem `AddOrActivateShoppingProductCommand` sofort einen Folgebefehl auf dessen zurückgegebene Id schicken (Kategorie-Auswahl → `MoveShoppingProductCommand`, Mengen-Popup → `ActivateShoppingProductCommand`). Fix: `product.id < 0` (negative Temp-Id) überspringt beide Folgeschritte - identisch zu `createPantryProduct`s bereits akzeptiertem `#97`-Kompromiss ("unkategorisiert bis zum nächsten Refetch"). Eine vollständige Lösung (Warteschlange schreibt spätere Einträge um, sobald ein Create real aufgelöst ist) wäre eine eigenständige Architektur-Erweiterung an `#97s` Kern und für diesen Zyklus nicht verhältnismäßig. ## Aktivitäts-Feed-Sichtbarkeit — Pantry ja, Shopping (noch) nein Siehe die Story-Datei selbst für die volle Begründung. Kurzfassung: Pantry hatte die Infrastruktur bereits aus `#101`, Shopping hat gar keine - letzteres als eigene Folge-Story `#117` abgespalten statt hier eine Mini-Version davon nebenbei zu bauen. `SetPantryProductTargetQuantityCommandHandler`s Aktivitäts-Body zeigt jetzt zusätzlich den alten Wert (`"changed target of 'X' from 3 to 5"` statt nur `"set target of 'X' to 5"`), damit ein Last-Write-Wins-Überschreiben im Feed nachvollziehbar bleibt, nicht nur implizit aus einem früheren Feed-Eintrag rekonstruierbar. ## `requireOnline()` — "erfordert Verbindung" statt genereller Fehler Angewendet an sechs Stellen, jeweils am Anfang der auslösenden Aktion, *bevor* irgendein Netzwerk-Call versucht wird: `CsvExportDialog.handleExport`, `CsvImportDialog.handleImport`, `PantryBarcodeScanner.submitBarcode`/`submitWithName`, `ShoppingBarcodeScanDialog.submitBarcode`/ `submitWithName`, `InvitePanel.handleGenerate`/`handleRevoke`, `MembersPanel.handleRemove`. `InvitePanel`/ `MembersPanel` sind listentyp-übergreifend geteilte Komponenten (auch für Todo-Listen, die nie eine eigene Offline-Unterstützung bekommen haben) - die klarere Meldung gilt dort einheitlich, statt nur für Shopping/Pantry-Aufrufer bedingt zu gelten, was implementierungstechnisch unnötig verzweigt hätte und für Todo-Nutzer ohnehin eine reine Verbesserung gegenüber der bisherigen generischen Meldung ist. ## Security-Vorprüfung Keine neue Backend-Angriffsfläche - alle sechs neuen `offlineConfig.ts`-Einträge mirrorn bereits existierende, bereits autorisierte Commands 1:1 (dieselbe serverseitige `WithAuthorization`-Prüfung greift beim tatsächlichen Replay unverändert). Der Client vertraut sich selbst nur optimistisch bis zum echten Server-Roundtrip - identisch zu `#97s` bereits geprüftem Modell. `requireOnline()` ist reine UX, keine Sicherheitsgrenze.
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#105
No description provided.