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.

During a live open-day event, network reliability cannot be guaranteed — Wi-Fi handoffs, browser tab switches, and brief server restarts are all real scenarios. Losing the occupancy state of dozens of spaces mid-event would create chaos for tutors and coordinators. ULagos 360° addresses this with three independent storage layers that continue writing data locally regardless of connection status, plus an automatic reconciliation mechanism that merges local and server state cleanly when connectivity is restored.

Storage Layers

ULagos 360° writes space data to three separate localStorage locations. Each layer has a different update frequency and a different purpose.

Layer 1 — Zustand Persist Middleware (spaces-storage)

The Zustand store is configured with the persist middleware from zustand/middleware. On every state change the middleware serialises a partial snapshot — spaces, currentUser, and sessionId — and writes it under the key spaces-storage.
// src/stores/spacesStore.js
persist(
  (set, get) => ({ /* store definition */ }),
  {
    name: "spaces-storage",
    partialize: (state) => ({
      spaces: state.spaces,
      currentUser: state.currentUser,
      sessionId: state.sessionId,
    }),
    version: 1,
  }
)
This layer is the primary state source. When the app reloads, Zustand rehydrates from spaces-storage before any network request is made, so tutors see a familiar view immediately.

Layer 2 — Primary Spaces Backup (ulagos360_spaces_backup)

Every call to updateSpace or updateSpaceStatus in the Zustand store directly writes the entire spaces map to ulagos360_spaces_backup as a side-effect — synchronously, in the same set call. This means the backup is never more than one operation behind the in-memory state. usePersistentBackup also refreshes this key every 15 seconds via setInterval.

Layer 3 — Emergency Backup (ulagos360_emergency_backup)

usePersistentBackup writes a richer snapshot every 15 seconds that wraps the spaces map with a human-readable timestamp and a version tag:
// src/hooks/usePersistentBackup.js
localStorage.setItem(
  "ulagos360_emergency_backup",
  JSON.stringify({
    spaces,
    timestamp: new Date().toISOString(),
    version: "2.0",
  })
);
This snapshot is also written immediately when the browser tab is hidden (visibilitychange) or when the page is about to unload (beforeunload), capturing any last-second changes before a potential crash.
The 15-second interval means the worst-case data loss during a silent browser crash is at most 15 seconds of updates — typically just one or two space changes during a busy event.

What Happens on Disconnect

When the Socket.IO connection drops, no space data is lost:
  • The Zustand store remains fully populated in memory and in spaces-storage.
  • Tutors can continue reading the last-known state and even make local updates (the store will write them to localStorage and queue them).
  • usePersistentBackup keeps its 15-second interval running; both backup keys continue to be refreshed.
  • The connection indicator in the header changes to show an offline state, alerting tutors that changes are not yet propagating to peers.
While offline, updates made by other tutors on their own browsers will not appear until the connection is restored. Tutors should coordinate verbally if the outage lasts more than a few seconds to avoid conflicting changes.

What Happens on Reconnect

Socket.IO will attempt to reconnect automatically (up to 5 times). Once the connection is re-established, useSocketConnection triggers the following sequence:
1

Re-register the tutor

register_user is emitted with the saved sessionId and user object recovered from localStorage.
2

Request full state from server

get_all_spaces is emitted immediately. The server responds with spaces_state (or one of its aliases), which the client applies to the Zustand store.
3

Upload local state if server is slow

If the server has not responded within ~5 seconds, sync_spaces is emitted with the entire local spaces map, the userId, sessionId, and a current timestamp. This ensures the server has the freshest data even if it restarted during the outage.
4

Conflict resolution

For each incoming space_updated event, shouldAcceptServerUpdate compares lastUpdate timestamps. The newer change wins; changes within 1 second of each other favour the server update.

Manual Recovery

If spaces appear empty after a reconnect (for example after a hard reload or a server-side state reset), tutors can manually restore data from the local backup without refreshing the page. In the header, click the (RotateCcw) button. This calls recoverFromBackup() from usePersistentBackup, which attempts restoration in priority order:
  1. Parse ulagos360_spaces_backup — if it contains a non-empty spaces object, return it immediately.
  2. If the primary backup is empty or corrupt, fall back to ulagos360_emergency_backup and extract the spaces field from the timestamped snapshot.
  3. If both are empty, return null and the UI will prompt the user to contact support.
// src/hooks/usePersistentBackup.js — recoverFromBackup
const primaryBackup = localStorage.getItem("ulagos360_spaces_backup");
if (primaryBackup) {
  const spacesData = JSON.parse(primaryBackup);
  if (spacesData && Object.keys(spacesData).length > 0) return spacesData;
}

const emergencyBackup = localStorage.getItem("ulagos360_emergency_backup");
if (emergencyBackup) {
  const backupData = JSON.parse(emergencyBackup);
  if (backupData.spaces && Object.keys(backupData.spaces).length > 0)
    return backupData.spaces;
}
return null;

localStorage Keys Reference

KeyContentsWhen Updated
spaces-storageFull Zustand state snapshot: spaces map, currentUser object, and sessionIdEvery state change via Zustand persist middleware
ulagos360_spaces_backupPlain spaces object (all space records)On every updateSpace / updateSpaceStatus call, and every 15 s by usePersistentBackup
ulagos360_emergency_backup{ spaces, timestamp, version: "2.0" }Every 15 s, plus immediately on tab hide and page unload
ulagos360_current_userCurrent tutor object { id, name, … }On login or when the server confirms user registration
ulagos360_session_idSession identifier string (session_<timestamp>_<random>)On first app load; persists for the lifetime of the session
sessionIdLegacy session identifier (same value as ulagos360_session_id)Written by useSocketConnection on each successful connection
currentUserLegacy tutor object copyWritten by useSocketConnection on register and on each user event
lastStateSyncEpoch millisecond string of the last full-state response from the serverAfter each successful spaceStateEvents handler run
logout_in_progressPresence flag (no value needed) set before socket disconnectSet by logout flow; cleared on next successful connect event
All persistent state keys are cleared during a deliberate logout. spacesStore.logout removes ulagos360_current_user, ulagos360_session_id, spaces-storage, ulagos360_spaces_backup, ulagos360_emergency_backup, and lastStateSync. useSocketConnection.logout additionally removes sessionId. The result is a fresh tutor identification screen on the next page load with no residual data.

Build docs developers (and LLMs) love