ALPAMobile.Presentation/Components/Pages/, including LoginPage and the full
Jumpseat suite — pages this guide files under Tier 4 ("Defer Late") and Tier 5
("Keep Native permanently"). Those tier assignments are superseded. The live state of the UI is the
ALPAMobile.Presentation project itself; read this document for the migration rationale and
per-page traps, not for what remains to migrate.
Direction resolved 2026-06-08: ALPA Mobile is moving to Blazor Hybrid. This guide is the canonical reference for migrating the existing MAUI/XAML pages to Razor components. See also migration-estimate.html for LOE and the straggler-migration pattern overview.
| Tier | Label | Pages | Effort |
|---|---|---|---|
| 1 | Migrate Now | 16 | ~6–8 weeks |
| 2 | Migrate Soon | 16 + 1 control | ~6–8 weeks |
| 3 | Defer | 17 pages/controls | ~8–10 weeks |
| 4 | Defer Late | 5 | 4–6 weeks |
| 5 | Keep Native | 5 | N/A |
Total estimated effort: 7–10 person-months. See estimate doc for full LOE breakdown, risk factors, and key migration traps.
Before any page migration can begin, five infrastructure layers must be in place:
| Priority | Prerequisite | What it unlocks |
|---|---|---|
| P1 | BlazorWebView integration in AppShell — register services, wire RootComponent | Tier 1 pages (shell navigation to Razor) |
| P2 | CSS design system — Figma token variables, responsive layout, light/dark | Consistent visual output across all tiers |
| P3 | Service abstraction — INavigationService, IDialogService, ILauncherService | All page migration; removes MAUI-only API calls from Razor |
| P4 | State management — AppStateContainer or equivalent replacing WeakReferenceMessenger | Tiers 2–4 where cross-page state flows |
| P5 | Navigation dual-routing — Shell route → Blazor component mapping | Deep-link compatibility during straggler period |
Low-complexity pages with minimal native coupling. Good candidates to prove out the infrastructure and build team familiarity.
| # | Page | Complexity | Effort | Key work |
|---|---|---|---|---|
| 1.1 | AboutPage | EASY | 1–2 days | Static HTML content; version/build info from injected service |
| 1.2 | HelpPage | EASY | 1–2 days | Static content; external links via ILauncherService |
| 1.3 | FAQPage | EASY | 1–2 days | Accordion FAQ list; <details>/<summary> HTML |
| 1.4 | InCaseOfAccidentPage | EASY | 1–2 days | Static steps, phone number tap via ILauncherService |
| 1.5 | OrangeCardPage | EASY | 1 day | Two-sided card carousel; phone launch |
| 1.6 | FTDTUnAugmentedPage | LOW | 2–3 days | Calculator input form; result display grid |
| 1.7 | FTDTAugmentedPage | LOW | 2–3 days | Same as 1.6 with augmentation toggle |
| 1.8 | NewPage | EASY | 1 day | Simple XAML shell |
| 1.9 | PilotGroupCommitteePage | LOW | 2–3 days | List + detail via Razor component params |
| 1.10 | PilotGroupCommitteeDetailsPage | LOW | 1–2 days | Read-only detail view |
| 1.11 | PilotGroupRepresentativesPage | LOW | 2–3 days | Directory with search filter |
| 1.12 | JumpseatInfoPage | LOW | 1–2 days | Info/links card list |
| 1.13 | JumpseatEtiquettePage | LOW | 1–2 days | Embedded video + text |
| 1.14 | JumpseatMissionPage | EASY | 1 day | Static text page |
| 1.15 | JumpseatFlightFinderFrequentSearchesPage | LOW | 2–3 days | Editable list; CRUD via service |
| 1.16 | MainPage | LOW | 2–3 days | Entry host page; navigates to sub-pages |
Medium complexity. Native coupling present but well-understood. Prerequisite services (P3/P4) must be in place.
| # | Page | Complexity | Effort | Key work |
|---|---|---|---|---|
| 2.1 | ContactsPage | MEDIUM | 2–3 days | Grouped list by category; search |
| 2.2 | UserProfilePage | MEDIUM | 2–3 days | Profile display; quick-action links |
| 2.3 | MembershipCardPage | MEDIUM | 2–3 days | Two-sided card; device-idiom layout |
| 2.4 | OrangeCardPage (dynamic) | MEDIUM | 1–2 days | Dynamic content version of Tier 1.5 |
| 2.5 | PushNotificationsSettingsPage | MEDIUM | 3–4 days | Toggle subscriptions; clipboard; connectivity |
| 2.6 | MemberResourcesPage | MEDIUM | 2–3 days | Dynamic resource list; external URLs |
| 2.7 | KCMAirportDetailsPage | MEDIUM | 2–3 days | Detail view; connectivity check |
| 2.8 | FTDTOperationTypeSelectionPage | MEDIUM | 2–3 days | Operation type picker → navigation |
| 2.9 | FTDTSplitDutyPeriodPage | MEDIUM | 3–4 days | Split duty form; time calculations |
| 2.10 | PilotGroupEventsPage | MEDIUM | 2–3 days | Event list; unread badges; preferences |
| 2.11 | KCMHomePage | MEDIUM | 3–4 days | Hub page; PDF file access |
| 2.12 | JumpseatAirlinePolicyPage | MEDIUM | 2–3 days | Policy HTML display |
| 2.13 | JumpseatFlightFinderSavedFlightsPage | MEDIUM | 3–4 days | Saved list; connectivity; notification opt-in |
| 2.14 | DocumentsListPage | MEDIUM | 3–4 days | Scoped doc list; unread; offline |
| 2.15 | InCaseOfAccidentPage (dynamic) | MEDIUM | 2–3 days | Dynamic content variant |
| 2.16 | CheckboxControl | MEDIUM | 1–2 days | Custom Razor checkbox with @bind and EventCallback |
High complexity. Significant refactoring required before migration. Total estimated effort: 8–10 weeks.
320+ lines XAML, 5+ commands, geolocation, permissions, messenger. Extract geolocation into IGeolocationService; replace messenger with state container; replace Expander with HTML accordion. Effort: 4–5 days
Server-driven layout with drag-and-drop tile reordering, 7+ messenger registrations. Extract home layout into service; replace drag-and-drop with CSS drag API; replace messenger with state container. Effort: 5–7 days
Web content loading with 4-step single-threaded render pipeline. Extract loading logic; replace CollectionView with Razor list; replace unread badge with Razor component. Effort: 4–5 days
Grouped CollectionView with 5-scope document hierarchy. Grouped list → nested Razor components; auth guards → Blazor AuthorizeView; extract ShowContent(). Effort: 4–5 days
330+ line ViewModel, 5-way content switch (Document / WebLink / DeepLink / FlightAlert / Unknown). Extract into IContentRouterService; content routing from Razor to native page. Effort: 4–5 days
250+ lines XAML, PAC membership tiers, 4 WebView targets. PAC tier display → @switch; WebView targets → inline HTML or <iframe>; parallel data loading via Task.WhenAll. Effort: 5–7 days
Grouped CollectionView, background filtering, country detail HTML, platform SearchBar. Grouped list → continent/country nesting; background filtering → debounced component-level search; country detail → @((MarkupString)html). Effort: 3–4 days
Heavy messenger usage (DocumentsReady, EventsReady), Launcher. Replace all messenger registrations with state container subscriptions; grouped menu → nested Razor; unread badges → reusable Razor badge component. Effort: 4–5 days
445 lines XAML, Expander + SwipeView + grouped CollectionView, Thread.Sleep in background tasks. Replace Expander with <details>/<summary>; replace SwipeView with action buttons; remove Thread.Sleep → Task.Delay. Effort: 5–7 days
400 lines XAML, SegmentedControl + DatePicker, 25+ bindings, timezone matching, scroll handlers. Segment state machine → @switch; replace scroll commands with JS scrollIntoView; timezone → ITimeZoneService. Effort: 5–7 days
Reflection-based property updates (GetProperties().Where(...)). Replace reflection with explicit property setters; policy WebView → inline HTML. Effort: 3–4 days
Toolkit Expander, Messenger pattern. State container replacing FilterSettingsHaveChangedMessage; Expander → HTML accordion; modal → Blazor overlay. Effort: 3–4 days
800+ line ViewModel, pagination, sorting, SwipeView, reflection-based property sync. Replace SwipeView with action buttons; pagination → load-more pattern; replace reflection with explicit mapping. Effort: 5–7 days
Platform-specific map URLs (geo: vs maps.apple.com), grouped collection. IMapLauncherService abstraction; grouped → nested Razor; complex filtering → component state. Effort: 3–4 days
Code-behind-driven state, icon manipulation, navigation logic. Replace with Razor tab/navigation component; state → component parameters + EventCallbacks; icons → CSS classes. Effort: 2–3 days
Complex form with timezone selection, nested conditional sections. Timezone → <select> from ITimeZoneService; conditional sections → @if; date/time → HTML5 inputs. Effort: 3–4 days
Required by FTDT pages — migrate early. Replace with HTML5 <input type="time"> + Blazor @bind; expose ValueChanged EventCallback. Effort: 1 day
Very high complexity. Require architectural decisions, significant refactoring, or deep inheritance hierarchies. Total estimated effort: 4–6 weeks.
500+ line XAML ControlTemplate, reflection-based view discovery (GetTemplateChild()), base class for 3 duty period variants. Decompose into 5+ child Razor components; replace inheritance with composition (RenderFragment); replace reflection with explicit property updates; replace scroll commands with JS scrollIntoView. Effort: 8–10 days
650+ lines XAML, most complex FTDT page, event subscription chains (DutyPeriod → DutyCollection → Child), reserve operation branching. Break into 6+ child Razor components; replace event chains with cascading parameters; replace SwipeView with action buttons. Effort: 8–10 days
1100+ line ViewModel (largest in the app), geolocation, debounced airport search, date range restrictions, CancellationTokenSource pattern (Feature #1839). Extract into IAirportSearchService, IFlightSearchService, IGeolocationService; search form → Blazor EditForm; airport picker → autocomplete with debounce. Effort: 8–12 days
Entire UI built programmatically (no XAML); 16 specialized card ViewModels. Convert to declarative Razor component tree; each card ViewModel → individual Razor component. Effort: 4–5 days
Platform-specific WebView configuration for local PDF files. Decision required: (A) Keep as native page — 0 days; (B) pdf.js via JS interop in Blazor component — 3–5 days; (C) Server-side PDF rendering. Recommendation: Option A during straggler period; revisit after Tier 3.
These remain as native MAUI pages permanently. Blazor components navigate to them via INavigationService.
| Page | Reason | Strategy |
|---|---|---|
| LoginPage | MSAL + JWT dual-mode, 10+ error scenarios, SecureStorage, MSAL PCA, platform browser. Auth failure = total app failure. | Keep native. Blazor components consume auth state via injected IAuthenticationService. |
| AutoLoginWebViewContentView | 11-stage state machine with platform JS bridges ([JavascriptInterface] on Android, WKScriptMessageHandler on iOS). These break inside BlazorWebView. | Keep as native control. Only used by LoginPage and WebViewPage. |
| WebViewPage | General-purpose web content viewer used by 10+ pages; deep AutoLogin integration; FAB UI; conditional visibility. | Keep native initially. Blazor navigates to it via INavigationService. Revisit after Tier 3 determines how much WebView content can be inlined into Razor. |
| AppShell | App-level navigation container; Shell flyout; 47 registered routes; dynamic menu. | MAUI Shell remains the navigation host. Blazor components live inside BlazorWebView within Shell pages. |
| UnreadContentView / UnreadCountContentView | Reusable badge indicators used by both native and Blazor pages during transition. | Create Razor equivalent for Blazor pages; keep native version for unmigrated pages. Deprecate native after all consumers migrate. |
Before pages in Tiers 2–4 can migrate, these platform-abstraction services must be created:
| Service | Wraps | Used by |
|---|---|---|
INavigationService | Shell.GoToAsync, NavigationManager | All pages |
IDialogService | DisplayAlert, DisplayActionSheet | Most pages |
ILauncherService | Launcher.OpenAsync | 8+ pages |
| Service | Wraps | Used by |
|---|---|---|
IClipboardService | Clipboard.SetTextAsync | PushNotificationsSettings |
IConnectivityService | Connectivity.Current | KCMAirportDetails, SavedFlights |
IPreferencesService | AppProperties, Settings | PilotGroupEvents, HomePageSettings |
IDeviceInfoService | DeviceInfo.Idiom, DeviceDisplay | MembershipCard, OrangeCard |
IFileAccessService | Platform PDF paths | KCMHomePage |
| Service | Wraps | Used by |
|---|---|---|
IGeolocationService | Geolocation, Permissions | SettingsPage, FlightFinderSearch |
IPermissionsService | Permissions.RequestAsync | SettingsPage, FlightFinderSearch |
IMapLauncherService | Platform map URLs | PilotGroupHotels |
IContentRouterService | ShowContent 5-way switch | PushNotificationsList |
ITimeZoneService | TimeZoneHelpers | FTDT pages |
IFTDTCalculatorService | FTDTCalculator_USA/CAN | FTDT pages |
AppStateContainer | WeakReferenceMessenger | 10+ pages |
| Service | Wraps | Used by |
|---|---|---|
IAirportSearchService | Airport location search | FlightFinderSearch |
IFlightSearchService | Search execution, cancellation | FlightFinderSearch |
IDutyPeriodFactory | DutyPeriod type creation | FTDTOperationTypeSelection |
GetService<T> lazy accessors must be converted to ActivatorUtilities factory injection. See GAP-1 work.AppStateContainer (scoped DI state) before migrating those pages.@foreach + StateHasChanged() — no direct equivalent; refactor to plain List<T> + explicit re-renders.[JavascriptInterface] / WKScriptMessageHandler) break if moved inside BlazorWebView. Keep AutoLoginWebViewContentView native (Tier 5).StackLayout usage in native pages that host a BlazorWebView should be converted to VerticalStackLayout / HorizontalStackLayout as part of the surrounding page cleanup.| Document | Location |
|---|---|
| LOE estimate and straggler-migration pattern | blazor-hybrid-migration-estimate.html |
| N-Tier Architecture Refactor (prerequisite) | n-tier-orchestration-handoff.html |
| Component specifications (AB#1821 baseline) | component-specifications/index.html |
| UI Refresh epic roadmap | detail/roadmap.html |