Skip to main content
Table locks enforce exclusive access at the table level. Before a terminal can open or edit a table’s order, it must hold the lock for that table. Table locks operate independently from order leases. A lease governs write access to a specific order (snapshot level); a table lock governs who may navigate to a table at all (floor plan level). In practice, a terminal acquires the table lock first (on the floor plan), then acquires the order lease when editing.

How it works

Acquiring a lock

  1. Terminal taps a table on the floor plan
  2. Frontend sends TABLE_LOCK_ACQUIRE { tableId } via WebSocket
  3. Hub checks if the table is already locked:
    • Not locked — Grant. Returns TABLE_LOCK_GRANTED { tableId }
    • Locked by same terminal — Grant (re-entrant, extends TTL)
    • Locked by another terminal — Deny. Returns TABLE_LOCK_DENIED { tableId, lockedBy }
    • Locked but expired — Clean up expired lock, then grant
  4. On grant, hub broadcasts TABLE_LOCKS_STATE to all other terminals

Maintaining a lock

  • Locks expire after 45 seconds (TABLE_LOCK_TTL_MS) without renewal
  • Terminals send TABLE_LOCK_HEARTBEAT every 15 seconds
  • Each heartbeat resets the expiry to now + 45s
  • If a terminal crashes or loses connection, the lock auto-expires after the grace period

Releasing a lock

  • Terminal sends TABLE_LOCK_RELEASE { tableId } when navigating away
  • Hub broadcasts TABLE_LOCK_RELEASED { tableId } to all terminals
  • The TableLockGuard component releases the lock automatically on unmount

Lock expiry (tick)

The hub runs a periodic tick every 15 seconds (matching the heartbeat interval):
  1. Query all locks where expires_at <= now
  2. For each expired lock:
    • Send TABLE_LOCK_REVOKED { tableId, reason: "Lock expired" } to the holding terminal
    • Delete the lock from storage

Disconnect grace period

When a terminal’s WebSocket disconnects:
  1. Hub shortens lock expiry to now + 10s (grace period)
  2. If the terminal reconnects within 10 seconds, it can re-acquire
  3. If grace period expires, the normal tick process revokes the lock

Manager override (force acquire)

Managers can forcibly take over a table locked by another terminal:
1

Request

Manager terminal sends TABLE_LOCK_FORCE_ACQUIRE { tableId }
2

Validate

Hub validates that the requesting terminal has manager or group_pos_manager role (server-side check)
3

Revoke current holder

Hub sends TABLE_LOCK_REVOKED { tableId, reason: "Manager override" } to the current holder
4

Grant to manager

Manager receives TABLE_LOCK_GRANTED { tableId }. Hub broadcasts updated TABLE_LOCKS_STATE
The handler is the single authority for revoke notifications during force-acquire. The onRevoke callback is only for async events (TTL tick expiry) — never both, to prevent duplicate notifications.
Non-managers see a “Table Locked” dialog with only a “Dismiss” button. Managers see an additional “Take Over” button.

Protocol messages

Terminal to hub (4 message types)

Hub to terminal (5 message types)

Frontend lock handling

Hub transport

  • heldTableLocks: Set<number> — Table IDs currently locked by this terminal
  • pendingTableLocks: Map<number, { resolve, timer }> — In-flight acquire requests
  • Separate heartbeat timer: sends TABLE_LOCK_HEARTBEAT for all held locks every 15s
acquireTableLock() and forceAcquireTableLock() return a Promise<TableLockResult> that resolves on TABLE_LOCK_GRANTED or TABLE_LOCK_DENIED, or returns { granted: false } on timeout (fail-closed).

Direct transport

In direct mode, table locking is disabled. All acquireTableLock() calls return { granted: true } immediately.

TableLockGuard

Renderless component on the order page (realtime/TableLockGuard.tsx):

FloorPlanScreen

  • Double-tap guardpendingLockTableRef cancels stale in-flight acquires on new table tap
  • Acquire before navigate — Lock acquired, then navigate to order page on success
  • TableLockedDialog — Shown on deny with the holder’s name. Managers see “Take Over”
  • Cancel cleanup — Dismissing modals releases any pending lock
  • Transfer mode — Destination table lock acquired before transfer navigation
  • Lock state subscription — Locked tables show amber label text on the floor plan

Table locks vs order leases

Timing summary