Skip to content
version: 1.1.1

ERO Implementation

How the product is built. This document describes the modules, their contracts, and where they live. It does not reproduce source code, because a copy of the code in documentation is stale the moment the code changes.

The design being implemented is in ERO Architecture. Field definitions are in ERO Reference.

1. Offline database

apps/event-route-optimiser/web/src/lib/db.ts initialises the Dexie database EventRouteOptimiserDB with stores for festivals, stages, and bands, mirroring the D1 contract.

The Act schema carries the four local override fields. These exist only on device and are never sent as master data.

2. Synchronisation

apps/event-route-optimiser/web/src/lib/sync.ts implements two operations.

  • hydrateSchedule() calls GET /api/data and replaces the local festivals, stages, and bands tables with the master dataset.
  • pushLocalChanges() selects user routes where sync_status is pending, pushes them to the API, and marks them synced on success. Failures retain the pending state for the next attempt.

2.1 Network listeners

Registered in apps/event-route-optimiser/web/src/pages/index.astro.

  • window.addEventListener('online') triggers pushLocalChanges().
  • window.addEventListener('offline') records the state transition.
  • On first load, hydrateSchedule() runs when the Dexie tables are empty and the device is online.

hydrateSchedule() overwrites master tables wholesale, so it must apply the differential merge rule and leave any row with is_local_override set to true untouched. This is the single most important invariant in the client.

3. Now and Next query

apps/event-route-optimiser/web/src/utils/nowAndNext.ts accepts the current timestamp and queries Dexie for acts where the user highlight is greater than zero. It returns the currently playing act, the next act, and the transition time between their stages.

Where is_local_override is true, it prefers local_start_time and local_stage_id over master schedule values.

4. Routing engine

apps/event-route-optimiser/api/src/utils/optimiser.ts imports the FESTIVAL_EDGES matrix from distanceMatrix.ts and implements Dijkstra shortest path with weighted interval scheduling.

It accepts the user’s chosen bands with start times, end times, and stage identifiers, calculates travel time between consecutive acts, penalises overlaps, and returns a departure time that minimises missed music.

GET /api/route/:username in apps/event-route-optimiser/api/src/index.ts fetches the user’s highlighted bands from D1, passes the schedule into the optimiser, and returns the itinerary payload documented in ERO Reference.

5. Clashfinder ingestion

POST /api/ingest/clashfinder in the Worker accepts { "festivalId", "username" } and fetches the upstream event JSON.

  1. Map the response onto the IClashfinderEvent contract.
  2. For each stage, INSERT ... ON CONFLICT DO NOTHING into the D1 stages table.
  3. For each act with highlight greater than zero, INSERT ... ON CONFLICT DO UPDATE into the bands table, converting dates to epoch milliseconds so Dexie and D1 stay in parity.
  4. Return the count of stages and acts imported or updated.

6. Authentication

6.1 Edge middleware

apps/event-route-optimiser/api/src/auth.ts intercepts protected routes, extracts the Authorization: Bearer <token> header, and validates the token.

Validation is dual-loop. Local development accepts a mock token so the inner loop needs no network. Production performs JWKS verification with WebCrypto using RSASSA-PKCS1-v1_5 and SHA-256. Requests lacking a required scope are rejected with 403 Forbidden.

6.2 Client

apps/event-route-optimiser/web/src/lib/auth.ts wraps MSAL and handles login, logout, and token acquisition. The API fetch layer appends the bearer token to every outbound /api/* request. A login component triggers the MSAL popup or redirect flow.

MSAL must be declared in apps/event-route-optimiser/web/package.json.

7. Schedule grid and notification planner

The Clashfinder schedule grid renders an offline-first timetable and persists alert preferences on device.

ModulePathResponsibility
Typessrc/types/schedule.tsBandSet, UserPreferences, and ScheduledAlert contracts
Storesrc/stores/scheduleStore.tsReads and writes preferences and scheduled alerts, with defaults applied on parse failure
Settings viewsrc/components/SettingsView.tsxAlert lead time, vibration, sound, and preferred stage selection
Schedule gridsrc/components/ScheduleGrid.tsxTimetable grid rendering and clash presentation
Settings pagesrc/pages/settings.astroRoute shell hosting the settings view
Index pagesrc/pages/index.astroRoute shell hosting the grid, PWA registration, and network listeners
Edge configwrangler.jsoncWorker bindings and deployment configuration

7.1 Preference contract

UserPreferences holds an alert lead time in minutes, vibration and sound flags, and a list of preferred stages. Defaults are a ten minute lead time, vibration and sound enabled, and all stages selected. A corrupt or absent stored value falls back to the defaults rather than failing.

Storage keys are namespaced sknx_ero_user_prefs and sknx_ero_user_alerts.

7.2 Open issue

The preference store uses localStorage while the rest of the client uses Dexie. localStorage is synchronous, size limited, and outside the Dexie transaction boundary, so preferences cannot participate in the synchronisation engine or in conflict resolution. Preferences should move into Dexie before they need to sync across devices.

8. Interactive behaviour

Interactive components are React TypeScript JSX. Astro remains the page and routing shell and carries no client-side interaction logic of its own. This is AG-ARC-002 in Platform Requirements.