Commit Graph

17 Commits

Author SHA1 Message Date
Trey T 9e1dbfbf90 Re-enable flights:// URL scheme (with UISceneConfigurations)
Switching the app target's Info.plist back to a manual file at
Flights/Info.plist so we can register CFBundleURLTypes for the
flights:// scheme. The Info.plist now includes UISceneConfigurations
inside UIApplicationSceneManifest — missing that key was the likely
cause of the device-launch crash on the previous attempt.

Verified launch + xcrun simctl openurl flights://import?... on the
iPhone 17 Pro simulator. App stays running after URL handoff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 10:27:25 -05:00
Trey T f40b02f68d Fix launch crash: drop CloudKit init, lazy Wallet, revert plist
Three changes that together stop the crash-on-launch the previous
build hit on device:

1. FlightsApp: stop attempting `cloudKitDatabase: .private(...)`
   when the iCloud entitlement isn't set — SwiftData's
   ModelContainer init can fatalError validating the cap (not just
   throw), so try? doesn't save us. Go straight to local config,
   with an in-memory fallback if the disk store is incompatible
   with the current schema.

2. WalletPassObserver: don't touch PKPassLibrary in init().
   `@StateObject` accesses .shared at view body time, which on
   first launch can race against PassKit subsystem init. Move the
   library bring-up into an explicit start() called from
   RootView's .task.

3. Flights app target Info.plist: revert from manual
   INFOPLIST_FILE back to GENERATE_INFOPLIST_FILE = YES (with the
   INFOPLIST_KEY_* entries restored). The manual plist I wrote
   was missing some auto-generated keys the device launch path
   needs. Loses the custom URL scheme — `.onOpenURL` handler
   stays in code but won't fire until we re-add the scheme via a
   manual plist that's been verified end-to-end.

Verified launch on iPhone 17 Pro simulator — scene becomes key
window, no fatalError. The earlier on-device crash was almost
certainly the CloudKit init.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 10:21:47 -05:00
Trey T d444a5caac Share Extension: back out target, keep URL scheme
The signed export needs a provisioning profile for the extension's
bundle id (com.flights.app.share) which the dev portal hasn't
provisioned for this team. Reverted the pbxproj surgery that added
the FlightsShareExtension target.

Kept:
- Flights/Info.plist with CFBundleURLTypes registering flights://
- `.onOpenURL` handler in RootView that consumes
  `flights://import?carrier=...&num=...&dep=...&arr=...&date=...`
- FlightsShareExtension/ source files (ShareViewController.swift +
  Info.plist) in the repo for later — when the extension bundle id
  is provisioned, the target can be re-added in Xcode UI in a few
  minutes (no need to redo the Swift work)

Until the extension is built, users can still get Mail integration
by installing a one-time Apple Shortcut that takes selected text
and opens `flights://import?...`. The host app handles the rest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 09:55:09 -05:00
Trey T 803c812f86 History v2: everything — Wallet auto-prompt, age, track replay, share
Adds the deferred pieces from the v1 ship, plus a Mail Share
Extension target so the iOS share sheet picks up flight emails.

Track replay
- `LoggedFlight.icao24` field — populated from FR24 enrichment on
  live-tap adds.
- HistoryDetailView's track query now fires for any flight younger
  than 7 days that has an icao24, pulling the actual flown path
  from OpenSky's /tracks/all endpoint. Falls back to a clean
  great-circle arc otherwise.

Wallet auto-prompt
- RootView subscribes to WalletPassObserver.shared. When the user
  adds a boarding pass to Apple Wallet, the observer's published
  `pendingPass` flips and we present AddFlightView pre-filled with
  the parsed origin / destination / flight # / date.

Airframe age + first-flight date
- `AirframeMetadataService` queries OpenSky's
  /api/metadata/aircraft/icao/{icao24} endpoint. Caches results in
  the existing `AirframeMetadata` SwiftData model so we never
  re-fetch the same airframe twice. (jetphotos and planespotters
  pages are both Cloudflare-gated; OpenSky's metadata API is the
  cleanest free source.)
- HistoryDetailView fires the lookup on appear and persists the
  result; the aircraft card already renders "Age" when a date is
  cached.

Mail Share Extension
- New `FlightsShareExtension` Xcode target (app-extension product
  type) built into the app bundle via an Embed Foundation
  Extensions copy phase.
- `ShareViewController` (SLComposeServiceViewController) parses
  shared text + URLs for flight-shaped codes ("AA 2178"), route
  hints ("DFW → ORD"), and date strings.
- On Save, the extension builds a `flights://import?carrier=…&num=
  …&dep=…&arr=…&date=…` URL and opens it via the responder-chain
  openURL trick (Share Extensions can't access UIApplication
  directly).
- Host app handles the URL via `.onOpenURL` in RootView, switches
  to the History tab and presents AddFlightView prefilled.
- App now has an actual Info.plist (CFBundleURLTypes registered
  for `flights://`); switched from GENERATE_INFOPLIST_FILE to
  INFOPLIST_FILE for the app target.

If the dev portal hasn't registered bundle id
`com.flights.app.share` for the team, the signed archive will
fail. In that case the simpler URL-scheme path still works —
users can hit `flights://import?...` from a Shortcut.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 09:51:30 -05:00
Trey T 847e5c6035 Flight History (v1): logbook, stats, animated route map, year-in-review
Adds a new History tab implementing the core of Flighty's Passport
feature set, free + iCloud-synced.

Data layer
- `LoggedFlight` and `AirframeMetadata` @Model classes (SwiftData)
- ModelContainer with CloudKit private DB; falls back to local-only
  when CloudKit cap isn't provisioned so the app stays functional.
- `FlightHistoryStore` wraps the ModelContext for save/delete +
  dedupe + great-circle distance / duration helpers + tail-repeat
  counting.

History UI
- `HistoryView` — list grouped by year, totals strip at top, swipe
  to delete, empty state with instructions.
- `HistoryRowView` — airframe photo thumbnail (planespotters),
  flight#, route, type, date.
- `HistoryDetailView` — title → route → photo → flown-path / great-
  circle map → aircraft card (type, tail, age, "Nth time on this
  airframe") → editable notes → delete.

Add paths
- "+ Add to my flights" button on the live aircraft sheet —
  pre-fills the form from FR24 enrichment (carrier, flight#,
  route, aircraft type, tail).
- Manual entry form (`AddFlightView`) with route-explorer autofill
  via `searchSchedule(carrierCode:flightNumber:startDate:endDate:)`.
- Calendar scan (`CalendarFlightImporter` + `CalendarImportView`) —
  EventKit access prompt → regex-detect flight-shaped events
  across last 5 years → dedupe → batch-confirm with route-explorer
  enrichment.
- `WalletPassObserver` (PassKit) — observes the library for new
  boarding passes and parses origin/destination/flight#/seat.
  Service is wired; explicit UI prompt deferred to follow-up.

Stats + visualization
- `StatsEngine` — totals (flights / miles / hours / airports /
  airlines / aircraft / countries) + narrative stats (top airline,
  top route, top airport, longest, shortest, repeated tails).
- `LifetimeStatsView` — big-number tile grid + highlights cards +
  repeated airframes list.
- `HistoryRouteMapView` — every great-circle arc the user has
  flown, animating in oldest → newest on first appear. Airport
  dots sized log-scale by visit count.
- `YearInReviewView` — Spotify-Wrapped-style horizontal card deck
  for the current year: total miles, airports + countries, hours
  airborne, top airline, top route, longest flight.

Entitlements
- New `Flights.entitlements` with `iCloud.com.flights.app` CloudKit
  container.

Risk note: the build falls back to local-only SwiftData if the
CloudKit container isn't provisioned for team V3PF3M6B6U / bundle
id com.flights.app. The History feature works fully either way;
sync requires the cap to land.

Deferred to follow-ups
- Wallet auto-prompt UI binding (service exists, view hook TBD)
- Mail Share Extension (separate app-extension target)
- Jetphotos first-flight-date scraping
- OpenSky historical track replay (great-circle fallback ships)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 09:34:38 -05:00
Trey T 16b874a7ad Detail sheet: hero aircraft photo via planespotters
Adds a 200pt-tall hero image at the top of the detail sheet showing
the actual airframe (registration-keyed), surfacing special liveries
naturally — photographers chase one-off paint jobs first, so the
most recent photo is usually the most recent livery scheme.

AircraftPhotoService wraps planespotters.net's public API:
  - Lookup by registration (FR24 enrichment) first
  - Falls back to ICAO24 hex when no registration
  - In-memory cache (hits and misses) so we never re-query the same
    airframe twice in a session
  - User-Agent includes contact URL per planespotters' TOS
  - Photographer attribution rendered in the AIRCRAFT section,
    tap to open the planespotters page

Sheet hides the banner entirely when no photo exists.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 08:45:28 -05:00
Trey T 92bc6ed52e Live feed: FR24 primary, OpenSky fallback
OpenSky's free anonymous tier has sparse ground coverage — at DAL it
returned a single airborne aircraft when there were 3+ SWA jets
visibly parked on the apron. FR24's feed.js aggregates ASDE-X, MLAT,
and multiple community ADS-B feeds and reliably surfaces ground
aircraft at major airports. We now query FR24 first and fall back to
OpenSky only when FR24 errors.

FR24's payload also carries departure + arrival IATA + flight number
+ aircraft type + tail number inline, so we shortcut the
route-explorer schedule lookup in the detail sheet: a new
`LiveAircraft.Enrichment` struct holds those fields, and the
ResolvedRoute cascade gains a `.fromFR24` first-tier case that uses
them directly. The `typeCode` and `airlineICAO` computed properties
prefer enrichment values over the AircraftDatabase / callsign-prefix
heuristics — this also fixes the case where FR24 callsigns use the
IATA carrier ("AA0013") which our 3-letter-prefix derivation would
have rejected.

OpenSky still owns trail polylines and recent-flights history; only
the live position fetch swapped sources.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 07:52:56 -05:00
Trey T dee6df1ac6 Live map: center on user, persist region, zoom-based aircraft cap
- Initial region cascade: restore last viewed region → on location
  grant, animate to a city-level view centered on user; otherwise
  fall back to the saved/continental default. User pans are detected
  via a center-delta threshold so a late location grant doesn't yank
  the camera away from where the user is looking.
- LocationService: thin one-shot CLLocationManager wrapper with
  CheckedContinuation. Added NSLocationWhenInUseUsageDescription via
  INFOPLIST_KEY_ build setting.
- Visible-aircraft cap scales with zoom (<2°: uncapped, <8°: 100,
  <25°: 150, else 200). Active filters bypass the cap entirely so
  every match always renders. When capped, we keep the N closest to
  the map center.
- Footer shows "Showing N of M" when the cap clips, "N aircraft"
  otherwise.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 07:37:34 -05:00
Trey T 390a158487 Live tab: restore type filter via bundled aircraft DB + sheet pickers
Two issues:

1) Menu list stutters. SwiftUI's Menu renders all rows in a
   non-virtualized popover. With 30+ airlines and Buttons containing
   Labels, scrolling jank starts immediately. Switched the airline +
   type filters to a sheet-based picker (LiveFilterPicker) backed by
   a real List — virtualized scrolling, plus a search bar that
   filters as you type.

2) Type filter was non-functional because OpenSky's anonymous tier
   returns ADS-B emitter category as null for most aircraft.
   Replaced with a real type-code lookup: bundled aircraftDB.json
   (1.5MB slimmed copy of OpenSky's aircraft metadata, 100k
   commercial-class airframes, filtered to skip GA / gliders /
   ultralights). AircraftDatabase.shared.typeCode(forICAO24:)
   returns the ICAO type designator (B738, A21N, etc.).
   AircraftDatabase.displayName(forTypeCode:) maps the top ~130
   common codes to friendly names ("B738" → "Boeing 737-800").

LiveAircraft now exposes a `typeCode` computed property that
indexes into the DB. The type filter chip → sheet flow uses the
same LiveFilterPicker as airlines, with multi-select + counts +
search.

Both pickers keep the "Selected (N)" group pinned at the top so
the user always sees what they have active without scrolling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 07:21:49 -05:00
Trey T 6b33a104c8 Live tab: bundled airline DB, OpenSky login, in-flight trails
Three follow-ups to the live tab landed together:

1) Bundled airline registry
   - airlines.json (208KB, 2,695 entries sourced from FR24's
     /mobile/airlines feed and slimmed to {icao,iata,name,logo}).
   - AircraftRegistry rewritten as an instance singleton that loads
     the bundle at startup, indexes by both ICAO and IATA, and falls
     back to a small hardcoded subset if the bundle is unavailable.
   - Detail sheet now shows the airline's logo (loaded from FR24's
     CDN via AsyncImage) alongside the callsign. Filter chips use
     the real names everywhere.

2) OpenSky account login
   - OpenSkyCredentials: Keychain wrapper that stores username +
     password using SecItem APIs. Posts a notification on change so
     the OpenSkyClient can refresh its in-memory copy.
   - OpenSkyClient now sends HTTP Basic auth when credentials are
     present. Anonymous fallback unchanged.
   - OpenSkySettingsView: tap the gear in the footer to sign in.
     Credentials are verified against /states/all before being
     stored; sign-out clears Keychain. Raises the quota from ~100
     to ~4000 requests/day.

3) Flight trails
   - AircraftTrack model decodes OpenSky's /tracks/all heterogeneous
     path array into typed TrackPoint entries.
   - OpenSkyClient.track(icao24:) fetches the current/most-recent
     track for an aircraft.
   - LiveFlightsView renders a MapPolyline trail along the path of
     whichever aircraft is currently selected. Cleared on
     deselection; race-guarded against rapid selection changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 06:16:49 -05:00
Trey T 888943deb4 Add Live Flights tab: real-time aircraft map with filters + tap detail
New top-level TabView (RootView) splits the app into:
  Tab 1 (Search): existing RoutePlannerView home
  Tab 2 (Live):   live flight tracker

Live tab features:
- MapKit map showing every aircraft in the visible viewport, rotated
  to true heading. Color-coded by vertical state: climbing/level/
  descending/on-ground.
- Auto-refresh every 15s + on map pan/zoom (debounced); manual
  refresh button. Rate-limit aware (60s backoff on HTTP 429).
- Tap any aircraft → modal sheet with live state grid (altitude,
  speed, heading, vertical rate, squawk, last-contact), current
  route (lazily fetched per-aircraft from OpenSky's /flights/
  aircraft endpoint, mapped from ICAO to IATA airport codes), and
  recent flight history (up to 8 prior legs).
- Filters: airline (multi-select from currently visible callsigns,
  with counts), aircraft type (ADS-B emitter category), airborne-
  only toggle. All filters render as horizontal chips and clear
  with a single tap.
- Search bar: callsign/flight number — submitting centers the map
  on the match and opens its detail sheet.

Data source: OpenSky Network REST API. Free, anonymous (~100 req/
day cap), JSON. Same ADS-B data FR24 starts with — without satellite
ADS-B coverage but more than enough for the in-flight tracker use
case. Reviewed FR24's APK and confirmed they migrated their live
feed to gRPC+protobuf with anti-bot device-id headers; OpenSky's
plain JSON is the right tradeoff for our build.

Implementation:
- LiveAircraft model: decodes OpenSky's mixed-type position arrays
  into a typed struct; computed properties for ft/knots/heading and
  airline ICAO extracted from callsign.
- OpenSkyClient: actor with /states/all + /flights/aircraft. Bbox
  query, throttle-aware errors.
- AircraftRegistry: ~80 ICAO → (IATA, name) entries for the major
  carriers; everything else falls through to the raw ICAO code.
- LiveFlightsView: the main map + filter UI.
- LiveFlightDetailSheet: tap modal with live state + route history.
- RootView: TabView wrapping RoutePlannerView (Search) and the new
  LiveFlightsView (Live).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 06:08:58 -05:00
Trey T 62729213d7 Add FlightsTests target + fix AA load fetcher (Android UA version bump)
AA was silently returning nil because the server now rejects User-Agent
"Android/2025.31" with HTTP 403 ("Please update your version of the
American Airlines app"). Bumped to "2026.14" (matches the APK in
airlines/) and centralized to a constant so the next bump is one line.
Added comprehensive logging to fetchAmericanLoad (was zero) so the next
breakage won't be silent — including an explicit ⚠️ when the server
returns the "update your version" payload.

New FlightsTests target with AirlineLoadIntegrationTests — hits live
airline APIs to verify each fetcher still returns data. Per-airline
strategy:
- Try route-explorer /departures from carrier hubs for a flight in the
  next 24h (works for AA/UA/AS/B6).
- Fall back to a known-good daily flight when route-explorer doesn't
  have the carrier in its data (NK/EK/KE — ULCC + some intl carriers).
- B6/EK/NK are status-only by design (no standby data without a PNR);
  asserted as non-nil only.
- XE (JSX) skipped: needs WKWebView host.

Retries on route-explorer 429 by parsing the `retryAfter` field and
sleeping the indicated number of seconds. Static-shared client+services
across tests so the token cache survives.

Results 2026-05-26 (xcodebuild test -scheme Flights):
   AA, AS, B6, EK, KE, UA   NK  ⏭️ XE
NK (Spirit) is now broken: GetFlightInfoBI returns HTTP 403 with
{"getFlightInfoBIResult":null}. APIM key still accepted (401 without
it), but the call itself is rejected. Documented in
AIRLINE_INTEGRATION_GUIDE.md as a known regression to fix; likely
needs reverse-engineering against the current Spirit APK in airlines/.

Also: enable shared schemes in .gitignore so `xcodebuild test` works
out of the box for anyone cloning the repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:14:09 -05:00
Trey t 0c4777216e Make RoutePlannerView the home; merge "Where can I go?" into it
Single unified search at the app root. TO is optional: filled goes through
/route (connections); blank flips to /departures with a time-window picker
("Where can I go?"). Same per-leg load card detail screen for any tap, so
direct flights and multi-stop connections share the same UX.

- Drop ContentView entirely (favorites + browse + entry cards). FlightsApp
  instantiates RoutePlannerView directly.
- Delete WhereToGoView; DepartureLegRow is inlined into RoutePlannerView
  as the where-can-I-go result row.
- SearchRoute enum trimmed to just the cases DestinationsListView still
  references and moved to its own file (Models/SearchRoute.swift).

Sort bar moved out of the controls cards into a dedicated row between the
Search button and the results — only visible once results exist. Switched
from segmented to dropdown menu picker. Options narrowed to the four
the user asked for: Departure Earliest / Departure Latest / Fewest Stops
/ Most Stops in connection mode, just the two time-based options for
where-can-I-go (single-leg, stop-count is meaningless). All sorts apply
client-side; upstream still gets `departure_time` for a stable base order.

Two real bugs fixed in connection search:
- Past flights weren't filtered. Same-day searches return mostly already-
  departed itineraries because the API sorts earliest-first. Added a
  `firstDeparture > now` filter applied before sort. Header surfaces the
  dropped count ("12 itineraries · 38 already departed"). When every
  result is past, the error message says so explicitly instead of going
  blank.
- 100-result limit was way too low for hub→hub with maxStops:2 — the
  combinatorial explosion of valid permutations filled the cap with
  morning flights and never reached afternoon. Bumped to 500.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:50:02 -05:00
Trey t df4a74726c Route Explorer: unified per-leg load card + multi-leg fan-out
Single ConnectionLoadDetailView is now the universal detail screen for
both Find Connections (1+ legs) and Where Can I Go (single-leg). For
multi-stop connections it fetches each leg's load in parallel via
withTaskGroup so the slowest carrier doesn't block the rest. Each leg
card shows airline + flight + IATAs + airport names + aircraft + an
open/standby summary, with a "Full details" drill-down to
FlightLoadDetailView for waitlists/passenger lists.

Bug fixes along the way:
- Empty origin/destination in carrier API URLs (HTTP 400 from AA): the
  4 separate @State vars feeding .sheet(item:) raced — sheet captured
  empty strings before the other writes settled. Bundled into one
  Identifiable RouteLoadDetailRequest / ConnectionLoadRequest so updates
  are atomic.
- Flight numbers rendered with locale separators ("AA 6,380", "3,189").
  Text("\(int)") resolves to the LocalizedStringKey initializer; switched
  to Text(verbatim:).
- "Load data not available for {airline}" was misleading when the
  airline IS supported but a specific flight has no data. Reworded to
  flight-scoped copy.
- AA fetcher had no logging — added URL/status/body/keys diagnostics
  matching the UA pattern.

UI cleanup:
- DepartureLegRow: big IATAs on their own row, full airport names on a
  middle-truncated subtitle, aircraft pill single-line tail-truncated.
- LegSummary (ConnectionRow): airport-name subtitle line below
  times+IATAs row.
- airportName priority: bundled airports.json first ("Dallas-Fort
  Worth") over the route-explorer appendix ("Dallas Dallas/Fort Worth
  Intl") which truncated to garbage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:19:20 -05:00
Trey T b403bfd970 Add route-explorer.com integration: connection finder + departures board
- RouteExplorerClient: anonymous HMAC token (route-explorer.com/api/token,
  IP rate-limited 10/min), POST /api/flight-search with X-API-Token; auto
  retry on 401/403 token rotation. Wraps the SuperJSON {json:{...}} envelope
  for the upstream tRPC endpoints.
- RouteExplorerModels: Codable types for /route, /departures responses
  (RouteConnection, RouteFlight, cabins, appendix). Custom ISO-8601
  decoder for the dateTime-with-offset timestamps. Bridge helper
  RouteFlight.toFlightSchedule(...) so route-explorer legs reuse the
  existing FlightLoadDetailView and AirlineLoadService flow for
  supported carriers (UA/AA/NK/KE/B6/AS/EK/XE).
- RoutePlannerView: feature (a) — direct + multi-stop A→B routing via
  /route with maxStops 0/1/2, sortBy departure_time/duration, optional
  interline-only filter. Renders one ConnectionRow per itinerary with
  chained legs and layover indicators.
- WhereToGoView: feature (b) — "where can I go" departures board for an
  airport over a 2/4/6/12/24h window. Capacity pills (F/J/W/Y), color-
  coded countdown, cross-midnight rollover. Tap any leg → load detail.
- IATAAirportPicker: lightweight local-only picker against
  AirportDatabase (no flightconnections roundtrip needed since
  route-explorer keys on IATA, not FC IDs).
- ContentView: two new entry-point cards (Find Connections, Where can I
  go?) above the favorites list.
- api_docs/route_explorer_api.md + captures: full endpoint reference and
  representative response samples (DFW→LAS direct, DFW→KOA 1-stop,
  LBB→KOA 2-stop, AA2178 schedule, DFW departures).

No tests yet — project has no test target and adding TDD would require
scaffolding XCTest first. Worth backfilling tests for the date decoder,
layover math, and toFlightSchedule bridge using the saved fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:23:17 -05:00
Trey T 847000d059 Land local WIP on top of JSX rewrite + wire JSXWebViewFetcher into target
Resolves the working tree that was sitting uncommitted on this machine
when the JSX rewrite (77c59ce, c9992e2) landed on the gitea remote.

- Adds favorites flow (FavoriteRoute model, FavoritesManager service,
  ContentView favorites strip with context-menu remove).
- Adds FlightLoad model + FlightLoadDetailView sheet rendering cabin
  capacity, upgrade list, standby list, and seat-availability summary.
- Adds WebViewFetcher (the generic WKWebView helper used by the load
  service for non-JSX flows).
- Adds RouteMapView for destination map mode and threads it into
  DestinationsListView with a list/map toggle.
- Adds AIRLINE_API_SPEC.md capturing the cross-airline load API surface.
- Wires JSXWebViewFetcher.swift into the Flights target in
  project.pbxproj (file was added to the repo by the JSX rewrite commit
  but never registered with the Xcode target, so the build was broken
  on a fresh checkout).
- Misc Airport/AirportDatabase/FlightsApp/FlightScheduleRow/
  RouteDetailView tweaks that the rest of this WIP depends on.

Build verified clean against the iOS Simulator destination.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:55:15 -05:00
Trey t 3790792040 Initial commit: Flights iOS app
Flight search app built on FlightConnections.com API data.
Features: airport search with autocomplete, browse by country/state/map,
flight schedules by route and date, multi-airline support with per-airline
schedule loading. Includes 4,561-airport GPS database for map browsing.
Adaptive light/dark mode UI inspired by Flighty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:01:07 -05:00