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()callsGET /api/dataand replaces the localfestivals,stages, andbandstables with the master dataset.pushLocalChanges()selects user routes wheresync_statusispending, pushes them to the API, and marks themsyncedon 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')triggerspushLocalChanges().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.
- Map the response onto the
IClashfinderEventcontract. - For each stage,
INSERT ... ON CONFLICT DO NOTHINGinto the D1stagestable. - For each act with
highlightgreater than zero,INSERT ... ON CONFLICT DO UPDATEinto thebandstable, converting dates to epoch milliseconds so Dexie and D1 stay in parity. - 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.
| Module | Path | Responsibility |
|---|---|---|
| Types | src/types/schedule.ts | BandSet, UserPreferences, and ScheduledAlert contracts |
| Store | src/stores/scheduleStore.ts | Reads and writes preferences and scheduled alerts, with defaults applied on parse failure |
| Settings view | src/components/SettingsView.tsx | Alert lead time, vibration, sound, and preferred stage selection |
| Schedule grid | src/components/ScheduleGrid.tsx | Timetable grid rendering and clash presentation |
| Settings page | src/pages/settings.astro | Route shell hosting the settings view |
| Index page | src/pages/index.astro | Route shell hosting the grid, PWA registration, and network listeners |
| Edge config | wrangler.jsonc | Worker 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.