Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Pewiz/ulagos360/llms.txt

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

ULagos 360° keeps every tutor’s browser in sync through a persistent WebSocket connection managed by useSocketConnection.js. Rather than polling the server for changes, the app maintains a long-lived Socket.IO connection to the Railway backend at https://ulagos360-backend-production.up.railway.app. When any tutor updates a space, the change propagates to all connected clients in real time, ensuring the shared view of the campus stays accurate throughout the open-day event.

Optimistic Updates

To keep the interface feeling instant even on slow connections, ULagos 360° uses an optimistic update strategy: the local Zustand store is updated immediately when a tutor makes a change, before the network round-trip completes. The full flow for a single status change is:
1

Tutor triggers an action

A tutor taps a button on a SpaceCard — for example, marking a room as ocupado.
2

Local state updates first

updateSpaceStatus in the Zustand store applies the change immediately. The UI reflects the new status with no perceptible delay.
3

Persisted to localStorage

The updated spaces map is written to localStorage under both the spaces-storage Zustand key and the ulagos360_spaces_backup emergency key.
4

update_space emitted to server

sendSpaceUpdate fires the update_space event over the Socket.IO connection, carrying the spaceId, the full spaceData updates, the userId, userName, sessionId, and a monotonic timestamp.
5

Server broadcasts space_updated

The backend retransmits a space_updated event to every other connected client.
6

Peers apply the incremental update

Each peer’s space_updated listener calls updateSpace(data.spaceId, data.updates) on its local Zustand store, bringing all browsers to the same state.

Client-Emitted Events

These are the Socket.IO events that the browser sends to the backend.
EventWhen it is emitted
register_userOn tutor identification or on reconnect when a session already exists in localStorage. Carries id, name, sessionId, and persistentSession: true.
get_all_spacesImmediately after connecting, to hydrate the local store with the server’s authoritative state. Also called by forceServerSync.
update_spaceEvery time a tutor changes the status of a space from the UI.
sync_spacesBackup upload triggered ~5 s after connect if the server has not yet responded with a full state. Sends the entire local spaces map.
user_logoutWhen a tutor explicitly logs out. Signals the server to clean up the session before the socket disconnects.
pongHeartbeat reply sent automatically whenever the server emits ping. Carries userId, sessionId, and the current timestamp.

Server-to-Client Events

These are the events the browser listens for.
EventEffect on the client
spaces_stateFull state hydration — iterates all entries and calls updateSpace for each. Also handled by all_spaces_state, full_state_response, spaces_sync_response, spaces_data, state_update, sync_response, and user_state_response.
space_updatedIncremental update for a single space. Applies data.updates to the matching data.spaceId in the Zustand store.
user_registered / login_successConfirms tutor registration. The client persists the returned user object to localStorage and updates the store’s currentUser. Also handled by register_response and user_confirmed.
pingServer keepalive probe. The client replies immediately with pong.
The client registers listeners for multiple event name aliases for the same logical action (e.g. spaces_state, all_spaces_state, full_state_response) to stay compatible with different versions of the Railway backend without requiring a coordinated deploy.

Connection Configuration

The Socket.IO client is initialised in useSocketConnection.js with the following options:
io("https://ulagos360-backend-production.up.railway.app", {
  transports: ["polling", "websocket"], // start with polling, upgrade to WS
  upgrade: true,
  timeout: 30000,
  reconnection: true,
  reconnectionDelay: 2000,
  reconnectionAttempts: 5,
  maxReconnectionAttempts: 5,
  reconnectionDelayMax: 10000,
  randomizationFactor: 0.5,
  forceNew: false,
  autoConnect: true,
  withCredentials: false,
  rememberUpgrade: false,
});
Key points:
  • Transport order — the connection begins as HTTP long-polling (polling) and automatically upgrades to a full WebSocket (websocket) once the handshake succeeds. This maximises compatibility with Railway’s proxy layer.
  • Reconnection — up to 5 attempts, starting 2 s after the first disconnect, backing off to a maximum of 10 s with a ±50 % jitter factor.
  • Server-initiated disconnect — if the server sends a disconnect reason of "io server disconnect", the client immediately calls socket.connect() to re-establish the link rather than waiting for the reconnection timer.

Conflict Resolution

Because multiple tutors can update the same space nearly simultaneously, the Zustand store uses a shouldAcceptServerUpdate guard before applying inbound space_updated payloads:
The conflict resolution rule is: the newer lastUpdate timestamp wins. If the difference between the server update’s timestamp and the local record’s timestamp is less than 1 second, the server update is accepted unconditionally (to avoid rejecting near-simultaneous changes). If the difference is 1 s or more, the update with the later timestamp is kept.
// src/stores/spacesStore.js — shouldAcceptServerUpdate
const timeDiff = Math.abs(serverTime.getTime() - localTime.getTime());
if (timeDiff < 1000) return true;      // within 1 s → accept server
return serverTime >= localTime;        // otherwise → newer wins

Build docs developers (and LLMs) love