# Externalize map tile URLs

**Task**: AZ-450_refactor_tile_urls
**Name**: Externalize OSM + Esri tile URLs
**Description**: Move the hardcoded OSM + Esri-satellite tile URLs into Vite env vars so the e2e profile's `tile-stub` can serve tiles deterministically and the air-gap test (AC-N3 / NFT-RES-03) does not trigger external network calls.
**Complexity**: 2 points
**Dependencies**: None
**Component**: `src/features/flights/types.ts`, `.env.example`, `src/vite-env.d.ts`
**Tracker**: AZ-450
**Epic**: AZ-447

## Problem

`src/features/flights/types.ts:56-57` inlines:

- `classic: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'`
- `satellite: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'`

These URLs:

- Break the e2e profile's `tile-stub:8082` intercept (Leaflet bypasses any local override path).
- Make AC-N3 / NFT-RES-03 (offline boot) flap because Leaflet attempts external tile fetches on the map mount.
- Violate restriction E1 (air-gap-friendly bundle).

## Outcome

- Both tile URLs are resolvable from `import.meta.env.VITE_OSM_TILE_URL` and `VITE_ESRI_TILE_URL`.
- Defaults preserve today's production URLs.
- The e2e profile redirects tile traffic to `tile-stub` without source edits.
- Call sites in `FlightMap.tsx` (and any other consumer of `TILE_URLS`) are unchanged.

## Scope

### Included
- `TILE_URLS` re-exported from `types.ts` as a const computed from `import.meta.env`.
- `.env.example` and `src/vite-env.d.ts` updated.

### Excluded
- Any Leaflet config change in `FlightMap.tsx`.
- Adding new tile providers.

## Acceptance Criteria

**AC-1: Default tiles preserved**
Given env vars unset,
When `FlightMap.tsx` mounts with `mapType='classic'`,
Then the Leaflet `TileLayer.url` resolves to `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`.

**AC-2: Override honored — classic**
Given `VITE_OSM_TILE_URL=http://tile-stub:8082/{z}/{x}/{y}.png` at build time,
When `FlightMap.tsx` mounts,
Then the Leaflet `TileLayer.url` resolves to the stub URL.

**AC-3: Override honored — satellite**
Given `VITE_ESRI_TILE_URL=http://tile-stub:8082/sat/{z}/{y}/{x}` at build time,
When `mapType='satellite'`,
Then the Leaflet `TileLayer.url` resolves to the stub URL.

**AC-4: Type declarations**
Given a TypeScript build,
Then `ImportMetaEnv` declares both fields as `string | undefined`.

## Blackbox Tests

| AC Ref | Initial Data / Conditions | What to Test | Expected Behavior | NFR References |
|--------|--------------------------|--------------|-------------------|----------------|
| AC-1   | env unset; FlightMap mounted | TileLayer URL (DOM `src` of the first tile request) | OSM URL | Compat |
| AC-2/3 | env set; FlightMap mounted | TileLayer URL captured at network boundary | Stub URL | E2E determinism |
| AC-4   | Build run | `vite-env.d.ts` declarations present | Pass | Compat |

## Constraints

- Leaflet's TileLayer API surface MUST NOT change.
- `TILE_URLS.classic | TILE_URLS.satellite` accessor pattern preserved.

## Risks & Mitigation

**Risk 1: Computed-at-module-load timing**
- *Risk*: `import.meta.env` is statically replaced at build time; module-load timing in tests using Vite's dev server should match.
- *Mitigation*: Standard Vite pattern — no special handling needed. Smoke-test by toggling the env var across two builds.
