Etap 30 — Cache-first kiosk (wydajność)¶
Zaimplementowane + WDROŻONE 2026-06-17 (kiosk 1.5.71)
Ekrany kiosku rysują z lokalnego cache NATYCHMIAST, a sieć dociąga w tle (stale-while-revalidate) — koniec czekania na rundę do backendu (i na 8 s timeout na słabym łączu) przy każdym otwarciu. Logowanie i media już tak działały; teraz tak samo wszystkie listy.
Geneza — audyt strategii odczytu¶
Etap-29 dorobił offline jako fallback (najpierw API, dopiero przy błędzie sieci → cache). To znaczy: na zdrowym łączu każdy ekran czekał ~100–300 ms na backend, a na migającym łączu do 8 s (timeout) zanim cokolwiek pokazał. Audyt:
| Przepływ | Było | Jest |
|---|---|---|
| Logowanie PIN/RFID | ✅ cache-first (bcrypt lokalnie) | bez zmian |
| Zdjęcia / PDF | ✅ cache-first (etap-28) | bez zmian |
| Wydawanie (katalog) | ❌ network-first | ✅ cache-first |
| Odbiór paczek | ❌ network-first | ✅ cache-first |
| Inwentaryzacja | ❌ network-first | ✅ cache-first |
| Uzupełnianie | ❌ network-first | ✅ cache-first |
| Wyszukiwarka odbiorcy | ❌ network-first | ✅ cache-first |
| Machine-info | ⚠️ snapshot + refresh w tle | bez zmian (już ok) |
Wzorzec (stale-while-revalidate)¶
1. render z offlineRepo/SQLite od razu (~10–50 ms) → _loading = false
2. w tle: api.get → podmień listę + (katalog) re-cache snapshotu
3. brak sieci → zostaje cache, BEZ spinnera
Per-ekran¶
- Wydawanie (
dispense_screen._load/_refresh):dispenseCatalogOfflinenatychmiast → online/dispense/me/catalogw tle (cacheUserDispenseSnapshot). Flaga_refreshingtłumi baner „offline — tylko uprawnione" podczas odświeżania (żeby nie mignął). - Inwentaryzacja (
inventory_screen): lista produktów cache-first + revalidate;_pickProductbuduje kolumny z cache od razu (start sesji bez czekania) — OCC guard (etap-29 5.1) pilnuje dryfu baseline przy zapisie. - Uzupełnianie (
replenish_screen): lista cache-first + revalidate. - Wyszukiwarka odbiorcy (
deposit_recipient_screen): lokalne dopasowania zcached_usersnatychmiast → pełny katalog z serwera w tle. - Odbiór (
user_menu._goPickup+PickupScreen): otwiera ekran z cache od razu;PickupScreen._refreshListwinitStatere-syncuje z/pickup/by-userw tle i zdejmuje nieaktualne pozycje — NIE nadpisuje wiersza, na którym operator jest w trakcie akcji (it.busy). Adresuje obawę v1.1.1 (stare tombstone) bez blokowania otwarcia. Brak cache → backend decyduje parcels vs no-parcels.
Znane kompromisy (świadome)¶
- Odbiór: nieaktualna pozycja może mignąć na ~200 ms zanim re-sync ją zdejmie; sam odbiór i tak re-waliduje online przy otwarciu skrytki.
- Wydawanie: cache-first renderuje listę offline (tylko w pełni uprawnione, w stanie), a online refresh dokłada pozostałe (zablokowane/wyszarzone) — lista „rośnie" po ~200 ms. Akcja wydania jest zawsze walidowana serwerowo, więc nieświeży render nie szkodzi.
- Inwentaryzacja: zestaw skrytek (kolumny) jest z cache (sync co 2 min) — świeżo dodana skrytka pojawi się po następnym syncu; quantity-drift łapie OCC guard.
Wzorzec na przyszłość: nowe ekrany kiosku czytające listy → zawsze cache-first + revalidate, nie network-first.