Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.laportenard.com/llms.txt

Use this file to discover all available pages before exploring further.

The frontend sync integration consists of three layers:
  1. State machine (syncMachine) — Tracks transport state, lease state, and outbox depth
  2. Provider (RealtimeProvider) — Creates the transport, wires event handlers, bridges to UI state
  3. UI components — Visual indicators for sync status, conflict dialogs, lease notifications

Sync state machine

nu_pos_react/src/state/machines/syncMachine.ts An XState machine managing the sync lifecycle:

States

                    ONLINE
                   +------+
          +------->|      |<--------+
          |        +--+---+        |
          |           |            |
       ONLINE    TRANSPORT_     TRANSPORT_
                 RECONNECTING  CONNECTED
          |           |            |
          |           v            |
          |     RECONNECTING       |
          |        +------+        |
          |        |      |--------+
          |        +--+---+
          |           |
       OFFLINE   TRANSPORT_
                 DISCONNECTED
          |           |
          v           v
        OFFLINE    OFFLINE
       +------+   (from reconnecting)
       |      |
       +------+
          |
        ONLINE
          |
          v
       HUB_OFFLINE
       +------+
       |      |---> ONLINE (on TRANSPORT_CONNECTED)
       |      |---> RECONNECTING (on TRANSPORT_RECONNECTING)
       +------+

Context

interface SyncContext {
  isOnline: boolean;
  lastSyncAt: number | null;
  lastSeenSyncId: number;
  appliedCount: number;
  lastError: string | null;
  transportMode: "direct" | "hub";
  hubUrl: string | null;
  leaseHeld: string | null;
  outboxDepth: number;
  connectedTerminals: number;
  transportState: TransportState;
}

Events

EventSourceEffect
ONLINEBrowser online eventTransition to online state
OFFLINEBrowser offline eventTransition to offline state
SYNC_APPLIEDRemote events appliedUpdate lastSeenSyncId, appliedCount, clear error
SYNC_ERRORSync failureSet lastError
TRANSPORT_CONNECTINGTransport state changeUpdate transportState
TRANSPORT_CONNECTEDTransport state changeUpdate transportState, return to online
TRANSPORT_DISCONNECTEDTransport state changeUpdate transportState
TRANSPORT_RECONNECTINGTransport state changeTransition to reconnecting
TRANSPORT_HUB_OFFLINETransport state changeTransition to hubOffline
LEASE_ACQUIREDLease grantedSet leaseHeld
LEASE_RELEASEDLease releasedClear leaseHeld
LEASE_LOSTLease revokedClear leaseHeld, set error
OUTBOX_CHANGEDOutbox depth changeUpdate outboxDepth
SET_TRANSPORT_MODEConfig changeSet transportMode, hubUrl

RealtimeProvider

nu_pos_react/src/app/providers/RealtimeProvider.tsx The main sync orchestrator. Creates the transport on mount and wires all event handlers:

Event wiring

  • transport.onStateChange() → Maps transport state to sync machine events
  • transport.onConflict() → Shows conflict dialog via SyncUIProvider
  • transport.onLeaseRevoked() → Sends LEASE_LOST to sync machine
  • transport.onOrderUpdated() → Applies remote order update to local state
  • transport.onReconnecting() → Sets isReconnecting in SyncUIProvider (shows overlay)
  • transport.onReconnected() → Clears isReconnecting (hides overlay)
  • transport.onStateChange() to disconnected while isReconnecting → Sets reconnectionFailed (session expired)
  • Browser online/offline events → ONLINE/OFFLINE to sync machine

Provider tree

<SyncUIProvider>          // Sync UI state (conflicts, lease notifications)
  <RealtimeProvider>      // Transport lifecycle, event handling
    <PosProvider>         // Order state, reducer
      <App />
    </PosProvider>
  </RealtimeProvider>
</SyncUIProvider>
SyncUIProvider wraps RealtimeProvider so that the realtime layer can push events into the UI context.

UI components

SyncStatusIndicator

A compact badge reading from the sync machine context:
StateIconColorLabel
connectedCloud checkGreen”Synced”
connectingCloudBlue (spinning)“Connecting”
reconnectingCloud offYellow (spinning)“Reconnecting”
hubOfflineCloud offOrange”Hub Offline”
disconnectedCloud offGray”Offline”
ErrorAlert triangleRed”Sync Error”
When outboxDepth > 0, the badge shows a count of pending items.

ConflictDialog

Modal dialog shown on version conflicts:
  • “Load Latest” → Calls reconcile() with accept_remote strategy
  • “Keep Mine” → Dismisses dialog, allows user to manually resolve

SyncOverlay

Component connecting SyncUIProvider state to visible UI:
  • Renders ConflictDialog when conflict is non-null
  • Renders ReconnectionOverlay for sleep/wake recovery (see below)
  • Shows a destructive toast when leaseRevoked is non-null (“Manager took over this order”)

ReconnectionOverlay

nu_pos_react/src/features/sales/components/reconnection-overlay.tsx A full-screen blocking overlay shown during sleep/wake WebSocket recovery. Reads isReconnecting and reconnectionFailed from SyncUIProvider:
StateDisplay
isReconnecting && !reconnectionFailedSpinner + “Restoring connection…” + “Please wait while we reconnect to the server”
isReconnecting && reconnectionFailed”Session expired. Redirecting to login…” (shown briefly before auth redirect)
!isReconnectingHidden (null)
The overlay uses z-50 with a semi-transparent backdrop to block interaction during reconnection.

TableLockedDialog

Floor plan modal shown when a table lock is denied:
  • “Dismiss” — Close dialog, release any pending lock
  • “Take Over” (managers only) — Force-acquire the lock

TableLockGuard

Renderless component on the order page managing table lock lifecycle:
  • Releases the lock on unmount
  • Re-acquires the lock after WebSocket reconnect
  • Redirects to the floor plan if the lock is revoked

Bridge components

Renderless components that wire hub transport events into the POS reducer. Must be rendered inside both <POSProvider> and <SyncTransportContext.Provider>.

HubOrderBridge

nu_pos_react/src/realtime/HubOrderBridge.tsx — Used on the order page:
  • Outbound: Builds OrderSnapshotData from active order on every state change → sends ORDER_SNAPSHOT via transport (with hash-based change detection to avoid redundant sends)
  • Inbound SYNC_INIT: Applies full order reconciliation on connect (gated on hydrated to wait for IndexedDB restore)
  • Inbound ORDER_UPDATED: Applies remote snapshots via applyRemoteSnapshot({ posReference, snapshot }) — auto-creates orders for the active table, closes orders with paid/closed status or empty lines
  • Lease lifecycle: Acquires lease when active order has items + posReference, releases on switch/unmount
  • Deletion detection: Sends closed snapshot to hub when local orders are removed

FloorPlanSyncBridge

nu_pos_react/src/realtime/FloorPlanSyncBridge.tsx — Used on the floor plan page:
  • Inbound SYNC_INIT: Full reconciliation (same as HubOrderBridge)
  • Inbound ORDER_UPDATED: Applies remote snapshots so table occupation status stays current across terminals. Closes orders with paid/closed status or empty lines

FloorPlanStructureSyncBridge

nu_pos_react/src/realtime/FloorPlanStructureSyncBridge.tsx — Used on the floor plan page:
  • Inbound FLOOR_PLAN_CHANGED: Updates floor plan table positions and structure when another terminal edits the layout
  • Outbound: Sends FLOOR_PLAN_UPDATE when the current terminal saves layout changes
APPLY_SYNC_INIT is a destructive full reconciliation that prunes all hubSynced orders not in the payload. Never use it for single-order updates — use APPLY_REMOTE_SNAPSHOT instead (safe single-order upsert).

How everything connects

Bootstrap config (Odoo)
  |
  v
createTransport() --> directTransport or hubTransport
  |
  v
RealtimeProvider
  |
  +-- transport.onStateChange() --> syncMachine (TRANSPORT_* events)
  +-- transport.onConflict()    --> SyncUIProvider.showConflict()
  +-- transport.onLeaseRevoked()--> SyncUIProvider.notifyLeaseRevoked()
  +-- transport.onOrderUpdated()--> apply to local order state
  +-- transport.onTableLocksState()--> update floor plan lock indicators
  +-- transport.onTableLockRevoked()--> TableLockGuard redirects to floor plan
  |
  v
syncMachine context
  |
  v
SyncStatusIndicator (reads transportState, outboxDepth, lastError)
ConflictDialog      (reads SyncUIProvider.conflict)
SyncOverlay         (reads SyncUIProvider.leaseRevoked, shows toast)

i18n

Translations are in the sync namespace (src/i18n/locales/{en,es}.json):
{
  "sync": {
    "connected": "Synced",
    "connecting": "Connecting",
    "reconnecting": "Reconnecting",
    "hubOffline": "Hub Offline",
    "offline": "Offline",
    "error": "Sync Error",
    "pendingItems": "{count} pending",
    "conflictTitle": "Version Conflict",
    "conflictAcceptRemote": "Load Latest",
    "conflictKeepLocal": "Keep Mine",
    "leaseRevokedTitle": "Order Taken Over",
    "leaseRevokedGeneric": "Another user took over this order",
    "reconnectionTitle": "Restoring connection...",
    "reconnectionSubtitle": "Please wait while we reconnect to the server",
    "reconnectionFailed": "Session expired. Redirecting to login..."
  }
}