1Overview
What it is ยท why it exists ยท who it's for ยท core features
What it is
TripDataHub is an iOS app (formerly "BidProSchedule"; bundle ID still com.sfune.BidProSchedule) that gives airline/cargo pilots a timezone-aware view of their crew duty schedule. The pilot exports a schedule PDF from CrewAccess, shares it into the app via the iOS Share Sheet, and the app parses, stores, and renders it as a duty chronology (Timeline) and an 8-week Bid Period calendar grid (iPad).
The app is not affiliated with UPS, any airline, employer, union, or schedule provider, and never connects directly to airline systems. All import is manual and user-initiated. It's a personal utility layered on data the pilot already has legitimate access to โ not a scraper, not an alternative scheduling system, not a system of record.
Why it exists
CrewAccess presents schedules as PDFs with raw UTC and local times mixed together โ no easy chronological "what do my next two weeks look like" view, no countdown to the next flight, no way to see a colleague's schedule. TripDataHub turns that PDF into:
- A single, timezone-correct duty chronology (Timeline) you can read at a glance.
- A Bid Period calendar grid (iPad) showing the whole period at once, with manually tracked reserve / call-out / personal commitments alongside assigned trips.
- A countdown to the next flight (widget + Live Activity), so you don't have to mentally track time-to-report.
- Optional read-only schedule sharing with mutually-approved colleagues.
Target users
- Cargo/airline pilots who use CrewAccess (positioned in the App Store as "brown-tail cargo pilots").
- Pilots running reserve-heavy, timezone-heavy operations across multiple domiciles (currently ANC, SDF/SDFZ, ONT, MIA).
- Pilots who want to share their schedule read-only with approved colleagues.
Core features
- CrewAccess PDF import โ Share extension โ PDFKit text extraction โ structured trip/leg/duty JSON.
- Timeline tab โ chronological view of trips, flights, deadheads, layovers (with hotel info), and manual operational events, in airport-local time.
- Calendar tab (iPad) โ 8-week Bid Period grid with trip bars, operational events (RSV/LCO/HOT/RCID/CQ), and personal events (Commute/Medical/Appointment/Other), layered Operational > Bid > Personal.
- Next Flight Countdown โ Home Screen Widget (T-12hโT-6h) and Live Activity / Dynamic Island (T-6hโT+6h), auto-switching to "Delayed" after STD.
- Friend Sharing โ mutual GEMS-ID approval, read-only sharing via CloudKit Public Database.
- TripBoard "OpenTime" browsing โ optional, read-only, requires the user's own credentials; disabled in App Review demo mode.
- LogTen-compatible export.
- CloudKit device-to-device sync โ local-wins conflict resolution, tombstone deletes.
Status
- Version 1.2.1 (build 68) โ latest as of 2026-06-13 (today's commit on
main). See Current State for the note on the olderv2.xtags. - iOS 17.0+, iPhone (portrait-first) and iPad (landscape-optimized, distinct UI).
- All core v1 features are implemented and shipped.
- Recent work has centered on Friend Sharing / Profile CloudKit sync reliability, wrapped up in the v1.2.0/v1.2.1 releases.
2Architecture
Build targets ยท data flow ยท storage ยท CloudKit ยท invariants
Build targets
All targets are generated from project.yml via XcodeGen.
- TripDataHub โ main app (SwiftUI, iOS 17.0+, Swift 5.9, Team
54WZM2U9MK, bundlecom.sfune.BidProSchedule). - TripDataCountdownWidgetExtension โ Home Screen Widget + Live Activity for the countdown.
- TripDataShareActionExtension โ Share Sheet extension that receives CrewAccess PDFs.
- TripDataHubTests / TripDataHubUITests โ unit and UI tests.
Source layout (TripDataHub/)
Models/
Centered on TripModels.swift (PayPeriodSchedule, TripLeg, OpenTimeTrip, FriendConnection), plus CalendarModels, FlightCountdownSupport, FriendScheduleMatching, ProfileModels / ProfileSnapshot, DeviceScheduleModels, RosterParsingModels.
Services/ โ logic, parsing, sync
AppViewModel.swiftโ central state/orchestration (very large).CrewAccessPDFImportService+PDFTripParserโ PDFKit text extraction and line-by-line parsing.- CloudKit services โ
CrewAccessImportCloudKitService,DeviceScheduleCloudKitService,FriendScheduleCloudKitService,GEMSVerificationCloudKitService,ProfileCloudKitService. IATATimeZoneResolver(airport โ timezone),BidPeriodService(period boundary math),DomicileSupport.TripBoardSyncService/TripBoardAuthServiceโ OpenTime fetch (optional, user-credentialed).FlightCountdownCoordinator/NextReportNotificationService.TripScheduleSnapshotEncoder(serialize for web viewer / sharing),AppGroupImportHandoff(handoff from share extension),AppReviewDemo(demo-mode flag).
ViewModels / Views / app entry
- ViewModels/ โ MVVM root
AppViewModel,BrowserViewModel, preview data. - Views/ โ Timeline, Calendar (iPad-specific under
Views/iPad/), OpenTime, Settings, Profile, Friends, plus shared helpers (TimelineRowViews,CalendarSupport,MaterialIconView). - TripDataHubApp.swift โ entry point; handles deep link
tripdatahub://import?manifest=<id>.
Data flow โ โ import pipeline
- User shares a PDF โ share extension accepts
public.pdf, copies the file + manifest JSON into the App Group container, and deep-links into the main app. AppGroupImportHandoffreads the manifest/PDF.CrewAccessPDFImportService(PDFKit) extracts text;PDFTripParserparses line-by-line into trip/duty/leg/hotel/crew JSON.CrewAccessTripSummaryStorewrites the JSON toDocuments/CrewAccessImports/{date}_{tripId}.jsonโ this local JSON is the source of truth (INV-006).- On startup,
reconcileCrewAccessSchedulesWithImportFiles()rebuilds in-memory state from these files. - CloudKit sync (below) mirrors both the raw import files and the parsed snapshot for cross-device availability.
Data flow โก โ Timeline & Calendar rendering
PayPeriodScheduleholds a pay period's trips;TripLegis an individual flight segment (CrewAccess or OpenTime).- Timeline โ groups legs by
pairing(trip id), renders cards in local-airport time, sharing row components across iPhone/iPad (INV-005). - Calendar (iPad) โ
CalendarSupportnormalizes legs and computes the day index from Bid Period start in UTC (INV-009), rendering the 8-week grid layered Operational > Bid > Personal (INV-010).
Storage
- Local JSON (
Documents/CrewAccessImports/) โ source of truth for imported schedules. - App Group container โ share-extension โ main-app handoff and widget data.
- Keychain โ TripBoard credentials.
- UserDefaults / local cache via
ScheduleCacheService.
External services
- TripBoard (OpenTime) โ
tripboard.bidproplus.com/fltops-portal.ups.com. Optional, read-only, requires the user's own credentials. No automated login or MFA bypass. Disabled in demo mode. - No proprietary crew-system APIs and no CrewAccess scraping (CrewAccess sits behind Zscaler + MFA, so import is manual PDF share only).
CloudKit
Container iCloud.com.sfune.TimelineSchedule, Public Database only (no private DB, no CKShare). See docs/ADR/ADR-001-public-cloudkit-phase1.md.
| Record type | Role |
|---|---|
TDHGEMSVerification | Admin-written GEMS ID โ DOB-hash verification table (read by all users). |
TDHVerifiedUser | Created on first successful GEMS verification. |
TDHFriendLink | Pairing record for two mutually-approved users. |
TDHSharedSchedule | A user's parsed schedule exposed read-only to friends. |
TripScheduleSnapshot | Web-friendly serialized schedule (consumed by an external viewer). |
TDHDeviceScheduleSnapshot | One per user; device-to-device sync (local-wins). |
TDHCrewAccessImportFile | One per imported trip; raw JSON sync with tombstone deletes. |
Synchronization
- Own devices โ
DeviceScheduleCloudKitServicesyncsTDHDeviceScheduleSnapshot. Local-wins: if localupdatedAtis newer, the remote is rejected (INV-007). On startup, import files must be fetched/reconciled before the local snapshot is overwritten, or a fresh device would wipe its own data. - Import file sync โ
CrewAccessImportCloudKitServicesyncs per-trip raw JSON. Deletes use tombstones (deletedAt) rather than hard deletes, so other devices remove the local file (INV-008). - Friend sharing โ GEMS verification, friend-link approval, and shared schedule/profile push-pull are handled by the respective CloudKit services.
CloudKit Development vs Production schema drift is a recurring cause of friend-sharing bugs. New record types/fields must be manually deployed to Production before TestFlight/App Store builds will work (see docs/CLOUDKIT.md).
Countdown / Live Activity
The widget extension reads shared schedule data via the App Group. The Home Screen Widget covers T-12hโT-6h; the Live Activity (iOS 16.1+) covers T-6hโT+6h on Lock Screen / Dynamic Island, switching to "Delayed" after STD if the flight hasn't departed. Coordinated by FlightCountdownCoordinator / FlightCountdownSupport.
Invariants
Load-bearing rules every change must respect. Full text in docs/INVARIANTS.md.
| ID | Rule |
|---|---|
| INV-001 | UTC is the source of truth for all internal calculations; Calendar.current/TimeZone.current are forbidden. |
| INV-002 | All timezone conversions are explicit (formatters/calendars set timeZone explicitly). |
| INV-003 | Bid/Pay Period membership uses the 03:00 LDT (local domicile time) boundary, not UTC midnight. |
| INV-004 | Domicile (ANC, SDF, ONT, MIA; SDFZ normalized to SDF) controls the timezone for LDT conversions. |
| INV-005 | iOS and iPad keep UI/feature parity for shared concepts (Timeline rows, layover cards). |
| INV-006 | On-disk import JSON is the source of truth; CloudKit is a redundant sync layer. |
| INV-007 | CloudKit device sync is local-wins. |
| INV-008 | Cross-device deletes use tombstones, not hard deletes. |
| INV-009 | Calendar day indices are computed in UTC, not local startOfDay. |
| INV-010 | Calendar layers are strictly Operational > Bid > Personal; operational events never appear in Bid/Personal, personal events never in Operational/Timeline. |
| INV-011 | Timeline = Trips + Flights + Deadheads + manual Operational events only (excludes Bid/Personal). |
docs/ADR/*, docs/CLOUDKIT.md, docs/BID_PERIODS.md, docs/TERMINOLOGY.md were referenced but not read in full. Consult them directly for schema field lists and worked timezone examples.
3Current State
Version ยท production features ยท focus ยท watchOS ยท tech debt ยท tests
Current version
- Version 1.2.1 / build 68 โ confirmed in
project.yml(all 3 targets); matches the latest commit681ecba "Release v1.2.1 build 68"(2026-06-13, a freshly cut release). - Bundle ID
com.sfune.BidProSchedule(legacy "BidPro" naming kept at the bundle level). - Minimum iOS 17.0; iPhone + iPad with distinct UI.
v2.x tags
v2.0โv2.3 (FebโApr 2026) are older pre-1.0 legacy tags from before the rename to TripDataHub and the switch to 1.x versioning (the 1.0.0 rename was 2026-03-16). They do not represent newer versions than 1.2.1 โ ignore them for "current version" purposes.
CHANGELOG.md / RELEASE_NOTES.md stop at v1.0.0 (2026-03-16); v1.0.1โv1.2.1 are undocumented (only a v1.1 draft exists). Accurate notes would need reconstructing from git log. Also, build 68's App Store Connect status (Live / In Review / TestFlight only) can't be determined from the repo.
Production features
- CrewAccess PDF import (share extension โ App Group โ PDFKit extraction โ line-based parsing).
- Timeline tab (trips, flights, deadheads, layovers, manual operational events).
- iPad 8-week Bid Period calendar (Operational / Bid / Personal layering).
- Next Flight Countdown (widget T-12hโT-6h + Live Activity T-6hโT+6h, auto "Delayed" after STD).
- Friend Sharing (mutual GEMS ID approval, CloudKit Public DB, no CKShare).
- Manual operational events (RSV-A/B/C/D, LCO, HOT, RCID, CQ12/CQ6).
- Manual personal events (Commute, Medical, Appointment, Other).
- CloudKit device sync (local-wins, tombstone deletes).
- LogTen-compatible export.
- TripBoard "OpenTime" browsing (optional, user-credentialed, read-only, with Auto Fetch on app open).
- App Review demo mode (
APPSTORE_REVIEWflag โ bypasses GEMS, hides OpenTime/Friends, uses synthetic data; PDF import/Timeline/Calendar remain available).
Recent focus
The last ~12 commits (2026-05 to 06-13) center on Friend Sharing / Profile CloudKit sync reliability: friend timeline sync fixes, request sync recovery/restore, link-permission fallbacks, notify-on-match, preserve-request-on-account-delete. The 2026-06-10 "sync profiles and refresh demo data" change and the v1.2.0/v1.2.1 releases (build 67/68) are the stabilization and release wrap-up of that work.
Several claude/* and feature/* branches exist, but feature/statistics, refactor/consolidate-dedup-layers, etc. are fully merged history โ not active work (the exception is watchOS, below). The working tree also has some stale worktrees and untracked files (docs/SIMULATOR_TROUBLESHOOTING.md, TDH-icon-1024.png).
watchOS support DEFERRED
Complete and validated, but not yet integrated into main.
Don't assume watchOS exists in main โ there's no watch target or related files in the working tree. But don't assume it was abandoned โ a complete, validated, tested implementation lives on feature/watchos; it was deferred on priority, not cancelled. If watch work is requested, review feature/watchos first. Canonical record: ~/Documents/FNN-Obsidian/20_Projects/TripDataHub/releases/Deferred_Features.md.
What the investigation found
- A complete watchOS MVP exists only on
feature/watchos(single commitd08f733, "v1.0.3 build 55: watchOS MVP (PR1โPR7)", 2026-05-24). git merge-base --is-ancestor feature/watchos mainโ NO; never merged.- It diverges at
b1cf1ff(v1.0.3 build 54), now 20 commits behindmain(~3 weeks of Friend Sharing work since). - Current
mainhas zero watch files;project.ymldefines no watch target.
What's on the branch (for reference)
TripDataHubWatchApp/(watchOS 10.0, embedded) โ Trip/Reserve/Training/OffDuty mode views and a debug picker.TripDataHubShared/โ shared types (WatchOperationalModeand the various Payload types).- WatchConnectivity transport (iPhone generates a snapshot and sends via
updateApplicationContext; Watch dedupes/persists across cold launch). - Tests (
WatchSnapshotTestsetc., 102/0 passing at authoring) and branch-only docs (validation checklist, known limitations).
Reviving it means rebasing/merging feature/watchos onto current main, resolving ~3 weeks of drift (Friend Sharing and domicile model/service changes landed since b1cf1ff), then re-validating against the branch's checklist.
Known tech debt
sharedTimelineCards: [WebTimelineCard](TripModels.swift:156-187) is marked "remove in Phase B+". Unused in-app today; intended for the not-yet-built web viewer.- CloudKit Dev/Prod drift โ schema changes must be manually deployed to Production; mismatches are a named, recurring root cause of friend-sharing bugs (
docs/AI_CONTEXT_INDEX.md). - CHANGELOG / RELEASE_NOTES are stale (stop at v1.0.0); no notes for v1.1.2โv1.2.1.
- README.md is reference data (classification rules + BP/PP date table) under a "BidProSchedule Notes" heading โ not a project overview. Low-priority rewrite candidate.
- Calendar V2 items (fatigue overlays, trip editing, drag-and-drop, colleague overlays, real-time ops tracking, month view) are explicitly out of scope โ don't pitch them as quick wins.
- Stale worktrees and uncommitted image/doc files sit in the tree โ housekeeping, not blockers.
Next work
No forward-looking roadmap doc exists beyond the Roadmap section. The clearest signal is that Friend Sharing / Profile sync just reached a stable release point (v1.2.1 / build 68). Absent a new sync bug, focus is likely to shift toward roadmap items (web timeline viewer, additional countdown variants, Calendar V2).
Test coverage
Unit tests cover: countdown phases/duration/leg selection; calendar day-index, BP/PP membership, DST edge cases; PDF parser regression (golden JSON); device-sync local-wins; LogTen export format; import-file CloudKit sync; GEMS verification sync (incl. the domicile/schemaVersion: 2 field added in build 32+); device snapshot sync; import handoff; profile sync; friend request matching; snapshot encoding; App Group handoff; next-report window calculation. Plus a separate UI test target (TripDataHubUITests).
4Roadmap
Planned (scoped/in progress) ยท Proposed (future per specs) ยท Ideas (loose)
Derived from CALENDAR_IMPLEMENTATION_SPEC.md, docs/RELEASE_NOTES_v1.1_DRAFT.md, AGENTS.md, code TODOs, and commit history.
Plannedscoped / in progress
- Continue hardening Friend Sharing / Profile CloudKit sync (request matching, restore-after-account-delete, permission fallbacks, stale-notification fixes).
- Maintain CloudKit Dev/Prod schema parity for any new record types or fields before release.
- Continue timezone/DST regression coverage as new domiciles or Bid Period tables are added.
Proposedfuture per specs / out of V1 scope
From Calendar V2 (V1 non-goals):
- Fatigue overlays on the calendar.
- Trip editing UI (calendar/timeline are read-only today).
- Drag-and-drop rescheduling.
- Colleague/friend calendar overlays (beyond the current read-only friend timeline).
- Real-time operational / flight-status tracking.
- Month view (vs. the current 8-week grid).
From the v1.1 notes draft / AGENTS.md:
- Geopolitical warning engine ("later phase").
- Additional countdown variants โ Van Time, Report Time, "next operational event" (not just next flight).
- Web timeline viewer โ an external consumer of
TripScheduleSnapshot(the encoder side already exists).
Ideasloose / unscoped
- Remove the unused
sharedTimelineCardsfield once the web viewer consumes snapshots directly ("Phase B+"). - Add domiciles beyond ANC/SDF(Z)/ONT/MIA โ requires updates to
DomicileSupport,docs/BID_PERIODS.md,BidPeriodServiceTests, and the date table. - Update README to reflect current TripDataHub branding/features (still framed as BidProSchedule).
Check open issues/PRs on github.com/Tony747-G/TripDataHub and docs/AI_CONTEXT_INDEX.md for a more authoritative, current roadmap than what's inferable from specs and commit history.