> ## 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.

# Frontend integration

> State machine, providers, UI components, and i18n for the sync system.

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

```typescript theme={null}
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

| Event                    | Source                 | Effect                                               |
| ------------------------ | ---------------------- | ---------------------------------------------------- |
| `ONLINE`                 | Browser online event   | Transition to `online` state                         |
| `OFFLINE`                | Browser offline event  | Transition to `offline` state                        |
| `SYNC_APPLIED`           | Remote events applied  | Update `lastSeenSyncId`, `appliedCount`, clear error |
| `SYNC_ERROR`             | Sync failure           | Set `lastError`                                      |
| `TRANSPORT_CONNECTING`   | Transport state change | Update `transportState`                              |
| `TRANSPORT_CONNECTED`    | Transport state change | Update `transportState`, return to `online`          |
| `TRANSPORT_DISCONNECTED` | Transport state change | Update `transportState`                              |
| `TRANSPORT_RECONNECTING` | Transport state change | Transition to `reconnecting`                         |
| `TRANSPORT_HUB_OFFLINE`  | Transport state change | Transition to `hubOffline`                           |
| `LEASE_ACQUIRED`         | Lease granted          | Set `leaseHeld`                                      |
| `LEASE_RELEASED`         | Lease released         | Clear `leaseHeld`                                    |
| `LEASE_LOST`             | Lease revoked          | Clear `leaseHeld`, set error                         |
| `OUTBOX_CHANGED`         | Outbox depth change    | Update `outboxDepth`                                 |
| `SET_TRANSPORT_MODE`     | Config change          | Set `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:

| State          | Icon           | Color             | Label          |
| -------------- | -------------- | ----------------- | -------------- |
| `connected`    | Cloud check    | Green             | "Synced"       |
| `connecting`   | Cloud          | Blue (spinning)   | "Connecting"   |
| `reconnecting` | Cloud off      | Yellow (spinning) | "Reconnecting" |
| `hubOffline`   | Cloud off      | Orange            | "Hub Offline"  |
| `disconnected` | Cloud off      | Gray              | "Offline"      |
| Error          | Alert triangle | Red               | "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`:

| State                                   | Display                                                                              |
| --------------------------------------- | ------------------------------------------------------------------------------------ |
| `isReconnecting && !reconnectionFailed` | Spinner + "Restoring connection..." + "Please wait while we reconnect to the server" |
| `isReconnecting && reconnectionFailed`  | "Session expired. Redirecting to login..." (shown briefly before auth redirect)      |
| `!isReconnecting`                       | Hidden (`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

<Warning>
  `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).
</Warning>

## 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`):

```json theme={null}
{
  "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..."
  }
}
```
