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>
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>
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>
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>
- 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>
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>
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>