Narong (highlighted directive): “products must MIRROR NIX POS” + show a sync status. The
loyalty /sync/catalog was a plain upsert, so a product deleted in
cafe lingered in the loyalty catalog as active and kept showing in the app. Board item f3.
/sync/catalog is now a mirror (replace-set) in one
transaction — upsert the pushed menu, then soft-deactivate any catalog product absent from
the push, drop it from Popular Menu, and null its banner link so it disappears
from the app. Soft (not hard-delete) so an incomplete push is recoverable by the next full sync. Last-sync status
(time · count · removed) is recorded in tenant.settings and shown by the Sync button. No migration.
| Step | Result |
|---|---|
| Push [A, B, C] | upserted 3, deactivated 0 |
| Re-push [A, B] (C deleted in cafe) | upserted 2, deactivated 1 |
| Catalog after | A active · B active · C active=false |
| C dropped from Popular Menu | yes (0 rows) |
| C's banner deep-link | nulled |
| Sync status recorded | syncedAt set · count 2 · removed 1 |
| Idempotent re-push [A, B] | deactivated 0 |
| Reversible — re-add [A, B, C] | C active=true again |
Real SSO login → Loyalty settings → Sync catalog & stores → the deployed staging backend mirrors the menu and records status; the “Last synced” indicator renders and persists across reload. 5/5 (+ figma-layout regression 16/16, staging smoke 5/5).


nix-loyalty-backend 84b9f65 (staging Fly) · nix-cafe b7ade8c (nix-cafe-staging Worker). Parked on staging for the scheduled prod promote.