Skip to main content
SmartMove uses localStorage as the primary data store. All reads and writes go through src/app/services/storage.ts, which provides typed accessors, dispatches change events, and handles cloud sync for authenticated users.

localStorage keys

Every key is prefixed with smartmove- to avoid collisions.
ConstantKeyStores
RECENT_STOPSsmartmove-recent-stopsRecently viewed stops
RECENT_CONNECTIONSsmartmove-recent-connectionsRecent origin–destination pairs
QUICK_ACCESSsmartmove-quick-accessPinned quick access items
ACTIVE_ROUTESsmartmove-active-routesCurrently followed routes
DEPARTURE_STATIONSsmartmove-departure-stationsSaved departure board stations
COMMUTER_ROUTESsmartmove-commuter-routesSaved commuter mode routes
LIKED_ROUTESsmartmove-liked-routesBookmarked routes
THEME_MODEsmartmove-themeSelected theme (light, dark, or system)
LANGUAGEsmartmove-languageSelected locale code
NOTIFICATIONSsmartmove-notificationsNotification preference
PRIVACY_LOCATIONsmartmove-privacy-locationLocation privacy setting
PRIVACY_ANALYTICSsmartmove-privacy-analyticsAnalytics privacy setting

Types

interface RecentStop {
  name: string;
  isFavorite: boolean;
  gtfsId?: string;
  coords?: { lat: number; lon: number };
}

interface RecentConnection {
  from: string;
  to: string;
  isFavorite: boolean;
  fromGtfsId?: string;
  toGtfsId?: string;
  fromCoords?: { lat: number; lon: number };
  toCoords?: { lat: number; lon: number };
  timestamp?: string;
}

interface QuickAccessItem {
  id: string;
  name: string;
  type: 'stop' | 'line' | 'route';
  typeLabel: string;
}

interface LikedRoute {
  id: string;
  from: string;
  to: string;
  mode: 'train' | 'bus' | 'seilbahn';
  duration: string;
  tripData?: any;
}

interface ActiveRoute {
  id: string;
  from: string;
  to: string;
  mode: 'train' | 'bus' | 'seilbahn';
  departureTime: string;
  arrivalTime: string;
  status: 'on-time' | 'delayed' | 'boarding';
  statusLabel: string;
  delay?: string;
  nextStop: string;
  progress: number;
  line: string;
}

interface DepartureStation {
  id: string;
  name: string;
}

Generic helpers

storage.ts exposes two generic read/write helpers used throughout the app:
// Read a value; returns fallback if the key is missing or unparseable
loadFromStorage<T>(key: string, fallback: T): T

// Write a value, then fire syncKeyToCloud()
saveToStorage<T>(key: string, data: T): void
saveToStorage calls syncKeyToCloud after every write. syncKeyToCloud checks for an active Supabase session and silently no-ops if the user is not authenticated.

Window events

Every meaningful write dispatches a custom window event so components can reactively update without a shared store:
EventFired when
recent-stops-changedRecent stops list is updated
recent-connections-changedRecent connections list is updated
liked-routes-changedA route is bookmarked or removed
active-routes-changedAn active route is added or cleared
commuter-routes-changedCommuter routes are saved
quick-access-changedQuick access items are updated
departure-stations-changedDeparture stations are updated
theme-changedTheme preference is changed
language-changedLanguage preference is changed
Components subscribe with:
useEffect(() => {
  const handler = () => { /* re-read from storage */ };
  window.addEventListener('liked-routes-changed', handler);
  return () => window.removeEventListener('liked-routes-changed', handler);
}, []);

Cloud sync

Two functions handle full bidirectional sync:
// Push all localStorage data to Supabase (called on significant local changes)
syncAllToSupabase(): Promise<void>

// Pull all Supabase data into localStorage (called on login)
syncAllFromSupabase(): Promise<void>
Incremental sync (syncKeyToCloud) runs automatically after every saveToStorage call when the user is authenticated. It maps each storage key to the corresponding Supabase table:
KeySupabase table
THEME_MODE, LANGUAGE, NOTIFICATIONS, PRIVACY_*user_preferences
RECENT_STOPSuser_stops
RECENT_CONNECTIONSuser_connections
QUICK_ACCESSquick_access
COMMUTER_ROUTEScommuter_routes
LIKED_ROUTESliked_routes
ACTIVE_ROUTESactive_routes
DEPARTURE_STATIONSdeparture_stations
Sync is fire-and-forget. It never blocks the UI. If a sync fails, the data remains intact in localStorage and will sync again on the next write or login.

Itinerary storage

Active route itineraries are stored separately to keep the ACTIVE_ROUTES list lean. Each itinerary is keyed by route ID:
saveFollowedItinerary(itinerary: any): void
loadFollowedItinerary(): any | null
clearFollowedItinerary(): void

saveRouteItinerary(routeId: string, itinerary: any): void
loadRouteItinerary(routeId: string): any | null
removeRouteItinerary(routeId: string): void
These use the keys smartmove-followed-itinerary and smartmove-route-itin-{routeId} respectively.

Database schema

Supabase table definitions and column types.

Row level security

How RLS policies isolate user data.