04. Architecture - khalillabban/Snorting-Code GitHub Wiki

Overview of the architecture

Snorting-Code is a React Native (Expo) app: campus maps, Google directions (including a custom shuttle strategy), indoor floor graphs, Google Calendar schedule, and accessibility-oriented theming. Screens stay thin; services wrap external APIs; utils hold persistence, parsing, and pathfinding; components are shared UI.


Domain Model

domain model

Component Diagram

component diagram

Class diagram

1

Design Patterns

Persistence Pattern

Location: khalillabban/Snorting-Code/utils/parseCourseEvents.ts

Commit: https://github.com/khalillabban/Snorting-Code/pull/251/changes/5c96e51c98d7763f413689fa8358b1a99dd5466e

The saveSchedule, loadCachedSchedule, and getNextClass functions define the contract for schedule persistence operations. These utility functions abstract AsyncStorage operations without exposing implementation details to callers.

app/schedule.tsx calls these functions to persist and retrieve cached schedule data, plugging schedule items into AsyncStorage. The storage mechanism (AsyncStorage) can be swapped without needing to change the caller.

Persistence pattern diagram

Strategy Pattern

Location: services/Routing.ts + constants/strategies.ts (interface + concrete strategies); context: services/GoogleDirectionsService.ts

Commit: https://github.com/khalillabban/Snorting-Code/commit/fbc60fdb89598e0ba8fa6b53c9db33853e4806f7

The RouteStrategy interface defines the contract; the five concrete strategies (WALKING_STRATEGY, BIKING_STRATEGY, DRIVING_STRATEGY, TRANSIT_STRATEGY, SHUTTLE_STRATEGY) are interchangeable at runtime.

GoogleDirectionsService.ts accepts any RouteStrategy and plugs its mode into the API call (with special handling for shuttle: multiple legs composed in the app). The algorithm (route mode) is swapped without needing to change the caller.

Related UI: components/StrategyModeSelector.tsx centralizes the mode picker for NavigationBar and NextClassDirectionsPanel β€” https://github.com/khalillabban/Snorting-Code/commit/8a330c8359be3de6b4a03b0147d02aa891f324cc

Strategy pattern diagram

Facade Pattern

Location: services/GoogleDirectionsService.ts

Commit: https://github.com/khalillabban/Snorting-Code/commit/cc26213fc190c5fec2cbacccc1d8f979696db7b3

The file hides complexity behind two public functions (getOutdoorRoute, getOutdoorRouteWithSteps). Consumers do not see URL construction (buildDirectionsUrl), API key validation (requireGoogleApiKey), polyline decoding (decodePolyline), or HTML stripping (stripHtml).

The rest of the app treats routing as a single, simple call.

Facade pattern diagram

Composite β€” EPIC 4 (US-4.1, 4.4, 4.5)

Location: utils/IndoorMapComposite.ts

Commit: https://github.com/khalillabban/Snorting-Code/commit/f00eb99b1d369b92791e4788be6285870e226056

PR (file diff): https://github.com/khalillabban/Snorting-Code/pull/278/changes/f00eb99b1d369b92791e4788be6285870e226056#diff-2ec54d7de67f9aa60f264c4e84cd1064c756a6aa3c0368c1c8353d7a8ec6b1ab

Indoor maps are inherently hierarchical. The Composite pattern lets you treat the whole tree uniformly:

IndoorMapNode (interface: getFloors(), getRooms(), getPOIs())

  • Building β€” contains floors
  • Floor β€” contains rooms + POIs + hallways
  • Room β€” leaf (name, number, type)
  • POI β€” leaf (e.g. washroom, elevator, fountain)

US-4.4 (find room) and US-4.5 (filter POI categories) both traverse this model in documentation and tests. US-4.3 (accessible routes) can filter nodes by an accessible flag. In the shipping app, shortest-path routing runs on bundled JSON graphs via utils/indoorPathFinding.ts; IndoorMapComposite is implemented and tested (__tests__/IndoorMapComposite.test.tsx) as the hierarchical OO view of the same domain.

Composite pattern diagram

Navigation context (State-like) β€” EPIC 4 (US-4.6, 4.7, 4.8)

Location: utils/routeTransition.ts

Commit: https://github.com/khalillabban/Snorting-Code/commit/bf7436b2f9d5e3b918a935f4ff31fb476c65dfa4

Historical PR (diff): https://github.com/khalillabban/Snorting-Code/pull/312/changes/bca7ae2a54d27c187e248b111bb3246101ea36d3#diff-360e40569b2c6433668789a45b6c704b05decf8a6c7c3d897f3e594c6241d3e8

These user stories require the app to behave differently depending on where in the navigation journey the user is. The codebase does not use separate classes such as IndoorState / OutdoorState with render(). Instead it uses a discriminated union of transition payloads (mode: "indoor_to_outdoor" | "cross_building_indoor", etc.), parseTransitionPayload / serializeTransitionPayload, and branching in CampusMapScreen / IndoorMapScreen. That is State-like (encode the phase, switch behavior) without a classic GoF State class hierarchy.

Without this structured context, US-4.7 (indoor β†’ outdoor) and US-4.8 (cross-building) would rely on even deeper nested conditionals inside those screens.

Navigation context diagram

Provider pattern (React Context)

Location: contexts/ColorAccessibilityContext.tsx (ColorAccessibilityProvider, useColorAccessibility); root wrap in app/_layout.tsx; palette types in constants/theme.ts.

Commit: https://github.com/khalillabban/Snorting-Code/commit/fadf47a10eb468e5031643a5cc6be1e8095dd202

Screens and shared components read useColorAccessibility() for palette and mode instead of threading colors through props. The provider persists the user’s mode in AsyncStorage.

provider class diagram

From Another Angle

1. Persistence Pattern: Cache-Aside with AsyncStorage

What is it?

The Cache-Aside pattern (also called Lazy Loading) stores data in a local cache so the app can display it immediately, even before a network call completes or if the user is offline. The cache is populated after a fresh fetch, and cleared when the user disconnects.

Relevant files:

  • utils/parseCourseEvents.ts β€” persistence logic
  • app/schedule.tsx β€” consumer (load on mount, save after fetch, clear on disconnect)
  • constants/type.ts β€” ScheduleItem type and SCHEDULE_ITEMS storage key

The three core functions

All persistence logic lives in utils/parseCourseEvents.ts:

saveSchedule(items) β€” Write to cache

await AsyncStorage.setItem(SCHEDULE_ITEMS, JSON.stringify(items));

Called right after parseCourseEvents() succeeds. Serializes the ScheduleItem[] array as JSON and stores it under the key "scheduleItems".

loadCachedSchedule() β€” Read from cache

const raw = await AsyncStorage.getItem(SCHEDULE_ITEMS);
return JSON.parse(raw).map((item) => ({
  ...item,
  start: new Date(item.start),
  end: new Date(item.end),
}));

Reads and deserializes the data. The critical step is reviving Date objects; JSON does not preserve Date, so start and end come back as strings and must be converted. The real implementation wraps this in try/catch, clears the key on failure, and also revives optional fields such as kind β€” see parseCourseEvents.ts.

getNextClass() β€” Query the cache

items
  .filter((item) => item.start > now)
  .sort((a, b) => a.start.getTime() - b.start.getTime())[0];

Loads the cache and queries it in memory like a small local database.

The serialization type bridge

A key design detail is the SerializedScheduleItem type (see parseCourseEvents.ts for the full shape, including kind):

type SerializedScheduleItem = Omit<ScheduleItem, "start" | "end"> & {
  start: string;
  end: string;
};

This models the difference between in-memory state (Date objects) and stored state (ISO strings) and keeps deserialization type-safe.

The full data flow

App launch
  └─> loadCachedSchedule() ──────────────────> Show stale schedule immediately
  └─> getGoogleAccessToken()

User connects / app refreshes
  └─> fetchCalendarEventsInRange()
  └─> parseCourseEvents(filteredEvents)
  └─> saveSchedule(items) ────────────────────> Overwrite cache with fresh data
  └─> setUi({ status: "ready", items })

User disconnects
  └─> AsyncStorage.removeItem(SCHEDULE_ITEMS) -> Cache is cleared

Why Cache-Aside?

Problem Solution
Google Calendar API is slow / requires auth Show cached data instantly on load
Date objects don't survive JSON serialization SerializedScheduleItem + manual revival
Stale data after logout Explicit cache clear on disconnect
Corrupt or invalid cache loadCachedSchedule catches errors and clears storage

2. Strategy Pattern: Route transport modes

What is it?

The Strategy Pattern defines a family of interchangeable algorithms behind a common interface, so the client can swap them at runtime without changing surrounding logic. Here, each transport mode (walk, bike, drive, transit, shuttle) is a strategy.

Relevant files:

  • services/Routing.ts β€” strategy interface
  • constants/strategies.ts β€” concrete strategies
  • services/GoogleDirectionsService.ts β€” context (executes the strategy)
  • components/NavigationBar.tsx / app/CampusMapScreen.tsx β€” runtime selection
  • components/StrategyModeSelector.tsx β€” shared mode chip row

The three parts

1. The strategy interface β€” services/Routing.ts

export interface RouteStrategy {
  mode: TransportMode; // 'walking' | 'bicycling' | 'driving' | 'transit' | 'shuttle'
  label: string; // shown in UI
  icon: string; // icon name
  extraParams?: Record<string, string>; // optional API params (e.g. transit preferences)
}

This is the contract every strategy fulfills. The rest of the codebase depends on this interface, not on a concrete strategy.

2. The concrete strategies β€” constants/strategies.ts

export const WALKING_STRATEGY: RouteStrategy = { mode: "walking", label: "Walk", icon: "walk" };
export const BIKING_STRATEGY: RouteStrategy = { mode: "bicycling", label: "Bike", icon: "bicycle" };
export const DRIVING_STRATEGY: RouteStrategy = { mode: "driving", label: "Car", icon: "car" };
export const TRANSIT_STRATEGY: RouteStrategy = { mode: "transit", label: "Transit", icon: "bus" };
export const SHUTTLE_STRATEGY: RouteStrategy = { mode: "shuttle", label: "Shuttle", icon: "bus-clock" };

export const ALL_STRATEGIES = [
  WALKING_STRATEGY,
  BIKING_STRATEGY,
  DRIVING_STRATEGY,
  TRANSIT_STRATEGY,
  SHUTTLE_STRATEGY,
];

Plain data objects; no class hierarchy. ALL_STRATEGIES lets the UI render the mode picker without hardcoding modes.

3. The context β€” services/GoogleDirectionsService.ts

export async function getOutdoorRouteWithSteps(
  origin: LatLng,
  destination: LatLng,
  strategy: RouteStrategy = WALKING_STRATEGY,
): Promise<OutdoorRouteResult>;

The function accepts any RouteStrategy and behaves according to strategy.mode. Shuttle composes multiple sub-legs (e.g. walk β†’ shuttle segment β†’ walk). The caller does not need to know those details.

The runtime flow

User picks a mode in NavigationBar (or Next Class panel)
  └─> setSelectedStrategy(strategy)       // swaps the active strategy

User confirms route
  └─> handleConfirmRoute(start, dest, strategy)
  └─> getOutdoorRouteWithSteps(origin, dest, strategy)
        └─> strategy.mode === "shuttle"? β†’ compose walk + shuttle + walk
        └─> otherwise?                   β†’ build URL with strategy.mode, call Google API

Key talking points

Concept How it appears in the code
Interface RouteStrategy in Routing.ts
Concrete strategies Five constant objects in strategies.ts
Context getOutdoorRouteWithSteps() in GoogleDirectionsService.ts
Runtime selection useState<RouteStrategy> in NavigationBar, CampusMapScreen, etc.
Open/Closed Principle New mode β‰ˆ add one object to strategies.ts and wire labels/icons
Data-driven UI ALL_STRATEGIES.map(...) β€” avoid duplicating mode lists

The shuttle case is a composite routing behavior inside the service: the caller still passes SHUTTLE_STRATEGY and receives one unified OutdoorRouteResult.


3. Facade Pattern: GoogleDirectionsService.ts

What is it?

The Facade Pattern provides a single, simplified interface over a complex subsystem. Here, that subsystem is the Google Directions API: HTTP, encoded polylines, HTML in step text, URL construction, and error handling. The facade hides that behind two functions.

Relevant files:

  • services/GoogleDirectionsService.ts β€” the facade
  • components/NavigationBar.tsx / components/CampusMap.tsx β€” callers that use only the public surface

The subsystem (hidden complexity)

The file contains private helpers that callers never touch, for example:

Function What it does
requireGoogleApiKey() Validates the env var; fails fast with a clear error
buildDirectionsUrl() Builds the full Directions URL with query params
decodePolyline() Decodes Google’s encoded polyline to LatLng[]
stripHtml() Removes HTML from step instructions
parseToMinutes() / formatMinutes() Normalize duration strings

None of these are exported.

The facade (public interface)

Only two functions are exported:

getOutdoorRouteWithSteps(origin, destination, strategy) β€” full result: coordinates, segments, steps, duration, distance. Handles shuttle multi-leg composition and missing API key gracefully.

getOutdoorRoute(origin, destination, strategy) β€” thin wrapper for callers that only need the polyline:

export async function getOutdoorRoute(
  origin: LatLng,
  destination: LatLng,
  strategy: RouteStrategy = WALKING_STRATEGY,
): Promise<LatLng[]> {
  const { coordinates } = await getOutdoorRouteWithSteps(origin, destination, strategy);
  return coordinates;
}

The full picture

Callers (NavigationBar, CampusMap)
         β”‚
         β”‚  getOutdoorRouteWithSteps(origin, dest, strategy)
         β”‚  getOutdoorRoute(origin, dest, strategy)
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          GoogleDirectionsService.ts         β”‚  ← FACADE
β”‚                                             β”‚
β”‚  requireGoogleApiKey()                      β”‚
β”‚  buildDirectionsUrl()                       β”‚  ← hidden subsystem
β”‚  fetch() + error handling                   β”‚
β”‚  decodePolyline()                           β”‚
β”‚  stripHtml()                                β”‚
β”‚  parseToMinutes() + formatMinutes()         β”‚
β”‚  Shuttle multi-leg composition              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
   Google Directions API (HTTP)

Key talking points

  1. Encapsulation of complexity β€” Shuttle may require several API calls and merging geometry; callers only pass SHUTTLE_STRATEGY.
  2. Layered facade β€” getOutdoorRoute sits on top of getOutdoorRouteWithSteps.
  3. Graceful degradation β€” Missing API key yields an empty-but-valid result instead of crashing the UI.
  4. No leaking abstractions β€” Raw response shapes and helpers stay inside the module.
  5. Testability β€” Consumers mock getOutdoorRouteWithSteps without stubbing HTTP directly.

4. Composite Pattern: Indoor map hierarchy

What is it?

The Composite Pattern represents hierarchical structures where individual objects and groups are treated uniformly. Indoor maps naturally follow this structure: buildings contain floors, floors contain rooms and points of interest.

Relevant files:

  • utils/IndoorMapComposite.ts
  • utils/indoorPathFinding.ts
  • __tests__/IndoorMapComposite.test.tsx

Structure

interface IndoorMapNode {
  getFloors(): Floor[];
  getRooms(): Room[];
  getPOIs(): POI[];
}

Concrete elements:

  • Building contains floors
  • Floor contains rooms, POIs, hallways
  • Room is a leaf
  • POI is a leaf

How it is used

  • Room search traverses the structure recursively
  • POI filtering applies the same logic across all nodes
  • Accessibility filtering can be applied at any level

Key idea

The system can treat a full building the same way it treats a single floor or room when querying data.

Why it matters

Problem Solution
Indoor maps are nested and complex Composite provides a uniform traversal model
Features need to work at multiple levels Same interface works for all nodes
Future expansion of map data New node types plug into the same structure

5. Navigation context: State-like routing transitions

What is it?

This is a State-like pattern where navigation behavior changes depending on the current routing phase, but without full class-based State objects.

Relevant files:

  • utils/routeTransition.ts
  • app/CampusMapScreen.tsx
  • app/IndoorMapScreen.tsx

Core idea

Instead of separate state classes, the app uses a discriminated union:

type TransitionPayload =
  | { mode: "indoor_to_outdoor"; data: ... }
  | { mode: "cross_building_indoor"; data: ... };

How it is used

  • parseTransitionPayload interprets the current navigation state
  • Screens branch behavior based on mode
  • Different routing flows are triggered depending on transition type

Example flow

User selects destination in another building
  └─> mode = "cross_building_indoor"
  └─> IndoorMapScreen handles exit path
  └─> CampusMapScreen handles outdoor route
  └─> IndoorMapScreen handles final entry

Key idea

Behavior changes based on structured state instead of deeply nested conditionals.

Why it matters

Problem Solution
Multiple navigation flows Encoded as explicit modes
Hard to manage conditional logic Centralized transition parsing
Extending routing behavior Add new mode instead of rewriting logic

6. Provider Pattern: Global accessibility state

What is it?

The Provider Pattern uses React Context to share global state across the app without prop drilling.

Relevant files:

  • contexts/ColorAccessibilityContext.tsx
  • constants/theme.ts
  • app/_layout.tsx

Core structure

const ColorAccessibilityContext = createContext(...);

export function useColorAccessibility() {
  return useContext(ColorAccessibilityContext);
}

How it is used

  • App is wrapped in ColorAccessibilityProvider
  • Components call useColorAccessibility()
  • Theme and mode are applied dynamically

Example usage

const { colors, mode } = useColorAccessibility();

No need to pass theme props through multiple components.

Persistence

  • User preference is stored in AsyncStorage
  • Mode is restored on app launch

Key idea

Global UI state is centralized and accessible anywhere in the component tree.

Why it matters

Problem Solution
Prop drilling across many components Context provides global access
User preferences need persistence Stored and restored automatically
Consistent theming across screens Single source of truth

External libraries

Major runtime dependencies (see package.json for exact versions):

Area Packages
App shell expo, expo-router, react, react-native
Maps react-native-maps
Storage / OAuth helpers @react-native-async-storage/async-storage, expo-secure-store, expo-auth-session, expo-web-browser
Location expo-location
Networking axios
UI / motion react-native-reanimated, react-native-gesture-handler, react-native-screens, react-native-safe-area-context, @expo/vector-icons, react-native-svg, expo-image
Analytics @react-native-firebase/app, @react-native-firebase/analytics, react-native-smartlook-analytics

Dev / test: jest, jest-expo, @testing-library/react-native, typescript, eslint, eslint-config-expo. E2E flows use Maestro (flows under .maestro/, not an npm dependency in this repo).


Technical decisions and architecture

This section outlines technical choices for the project, including alternatives that were considered and the decisions that were taken.


1. Mobile framework

Candidates considered

React Native + Expo

  • Pros: Single codebase for iOS and Android; fast iteration; strong ecosystem for maps, location, storage, and UI.
  • Cons: Advanced native features (e.g. precise indoor positioning) may need custom native modules.

Flutter

  • Pros: Good performance; consistent UI.
  • Cons: Dart learning curve; less team familiarity.

Native iOS / Android (Swift / Kotlin)

  • Pros: Full native API access; best raw performance.
  • Cons: Two codebases; higher maintenance.

Decision

React Native + Expo was selected for development speed and maintainability within project scope and team experience.


2. Mapping and map rendering

Candidates considered

react-native-maps (Google Maps / Apple Maps)

  • Pros: Widely used; markers, polylines, overlays; good for outdoor campus maps.
  • Cons: No built-in indoor navigation; limited advanced styling / offline.

Mapbox

  • Pros: Styling; offline.
  • Cons: Setup and licensing; heavier Expo integration.

Leaflet (via WebView)

  • Pros: Flexible.
  • Cons: Mobile performance and UX limits.

Decision

react-native-maps is used. Mapbox was evaluated but not required for current requirements.


3. Location services

Candidates considered

expo-location

  • Pros: Expo-native; simple permissions; fine for outdoor GPS.
  • Cons: Limited accuracy indoors.

react-native-geolocation-service

  • Pros: More control in bare React Native.
  • Cons: Extra native configuration.

Decision

expo-location is used for foreground location. Indoor navigation uses graph / building context rather than indoor positioning hardware.


4. Outdoor directions and shuttle support (feature #2)

Candidates considered

Google Directions API

  • Pros: Reliable walking and transit routing; well documented.
  • Cons: Quotas, cost, network required.

Mapbox Directions API

  • Pros: Pairs with Mapbox maps.
  • Cons: Ecosystem lock-in.

Open-source engines (OSRM, OpenRouteService)

  • Pros: Control and openness.
  • Cons: Hosting and operations.

Decision

Google Directions API backs walking, bicycling, driving, and transit. Shuttle is not only β€œwhatever Google transit returns”: the app implements a composed shuttle strategy (walk + shuttle leg + walk) using project shuttle stops and multiple Directions calls where needed, coordinated inside GoogleDirectionsService.ts.


5. Indoor routing and accessibility (feature #4)

Indoor navigation needs a map representation and a routing approach.

Indoor map representation

Custom JSON graph (nodes and edges per floor)

  • Pros: Transparent; easy to annotate accessibility; fits campus scale.
  • Cons: Manual authoring and updates.

GeoJSON-based indoor maps

  • Pros: Good for drawing.
  • Cons: Extra step to build a routable graph.

Standards (e.g. IndoorGML)

  • Pros: Industry-oriented.
  • Cons: Too heavy for a course-scale project.

Routing algorithms

Client-side Dijkstra / A*

  • Pros: No backend; easy to explain; fine for building-sized graphs.
  • Cons: Implementation care required.

JS graph libraries

  • Pros: Less custom graph code.
  • Cons: Accessibility rules still need custom logic.

Backend routing (e.g. Python + NetworkX)

  • Pros: Clear server-side model.
  • Cons: Requires hosted backend.

Decision

The app uses client-side shortest-path routing on bundled JSON graphs (utils/indoorPathFinding.ts, data under assets/maps/). IndoorMapComposite documents and tests a hierarchical Composite view of the same domain; runtime routing uses the graph representation.


6. Calendar integration (feature #3)

Candidates considered

Google Calendar API

  • Pros: Stable; familiar to students; matches requirements.
  • Cons: OAuth friction for some users.

Device-native calendar

  • Pros: No Google dependency.
  • Cons: Platform differences.

Decision

Google Calendar API is the calendar integration used in the app.


7. Local storage and data persistence

Candidates considered

AsyncStorage

  • Pros: Simple key-value store; good for cache and preferences.
  • Cons: Not for huge datasets.

SQLite (expo-sqlite)

  • Pros: Structured queries.
  • Cons: Extra setup.

Realm

  • Pros: Performance.
  • Cons: Another stack to learn.

Decision

Bundled JSON for static campus data; AsyncStorage for schedule cache, color-accessibility mode, and other small preferences.


8. Backend (optional)

Candidates considered

No backend

  • Pros: Simplest deployment; fits client-only navigation.
  • Cons: Data updates ship with app releases.

Django + DRF / Node (Express, Nest)

  • Pros: Remote data and admin.
  • Cons: Operational and course-scope cost.

Decision

No backend in the shipping architecture; optional backend remains a possible future extension.


9. Authentication (optional)

Candidates considered

No authentication

  • Pros: Simplest.
  • Cons: No cross-device account story.

Google Sign-In

  • Pros: Aligns with Calendar.
  • Cons: Google-only.

Firebase Auth

  • Pros: Fast to add.
  • Cons: Vendor coupling.

Decision

There is no separate app-wide auth product; Google OAuth is used where needed (e.g. calendar).


10. UI component library

Candidates considered

React Native Paper β€” Material Design, theming.
NativeBase β€” Large surface, opinionated.
Tamagui β€” Design-system focus, learning curve.
Custom components β€” Full control.

Decision

The app ships custom React Native UI with shared theme and palette (constants/theme.ts) and ColorAccessibilityContext for accessibility modes. React Native Paper is not a package.json dependency; Paper-style kits were considered but the implemented UI is in-house.


11. Testing strategy

Unit and component testing

  • Jest
  • React Native Testing Library

End-to-end testing

  • Maestro (flows under .maestro/)

Decision

Jest and RNTL cover units and components; Maestro covers core user journeys end-to-end.


Commit link cheat sheet

Area URL
parseCourseEvents.ts https://github.com/khalillabban/Snorting-Code/commit/94c3301aa1c80e5b67fa55854e285c613c4b09d2
Routing.ts / strategies.ts https://github.com/khalillabban/Snorting-Code/commit/cde0e9e54894daa6b16dea3505483daeff51364d
GoogleDirectionsService.ts https://github.com/khalillabban/Snorting-Code/commit/cc26213fc190c5fec2cbacccc1d8f979696db7b3
IndoorMapComposite.ts https://github.com/khalillabban/Snorting-Code/commit/f00eb99b1d369b92791e4788be6285870e226056
routeTransition.ts https://github.com/khalillabban/Snorting-Code/commit/bf7436b2f9d5e3b918a935f4ff31fb476c65dfa4
ColorAccessibilityContext.tsx https://github.com/khalillabban/Snorting-Code/commit/fadf47a10eb468e5031643a5cc6be1e8095dd202
StrategyModeSelector.tsx https://github.com/khalillabban/Snorting-Code/commit/8a330c8359be3de6b4a03b0147d02aa891f324cc

⚠️ **GitHub.com Fallback** ⚠️