UI Refresh Component Library — Build Plan

Updated: 2026-07-15 22:39 ET · Audited: 2026-06-26 · Epic AB#1821 · Branch: imp/blazor-hybrid · Phase C complete as of 2026-06-22 · Phase D gate (WI-2087) cleared 2026-07-01

Architecture Decisions

DecisionIDSummary
Three-track component patternD17Track A: scaffold VMs + Razor; Track B: typed ContentView; Track C: deferred
Typed variant + ComponentStyleD18CardVariant enum; ComponentStyle value object of typed tokens — no CSS strings in VM
Vendor strategyD19Build primitives from scratch; own the markup; third-party only for complex generics, always wrapped
Component library is an RCLD20ALPAMobile.Presentation targets net10.0, no MAUI dep — consumed by both BlazorWebView host and ASP.NET Core RemoteHost

The typed surface binding is the ViewModel itself — a ComponentViewModel subclass is the single binding contract consumed by all surfaces (Razor [Parameter] Vm, XAML BindingContext, future surfaces).

What Already Exists

Razor Renderers (ALPAMobile.Presentation/Components/Library/ — RCL since 2026-07-15, WI-2254/D61)

Already built, bind via [Parameter, EditorRequired] public ComponentViewModel Vm { get; set; }:

Resolved 2026-07-15 (WI-2254/D61): the renderers now live where they belong — ALPAMobile.Presentation/Components/Library/, with the project converted to a Razor Class Library. The paragraph below records the pre-migration state for history: WI-2087 resolved 2026-07-01 (ViewModel/Presentation layer extraction), which cleared Phase D's gate; the renderer move itself then executed 2026-07-15 as WI-2254.

ViewModels — ✅ COMPLETE (Phase A, commit ac1f7f98)

ALPAMobile.Presentation/ViewModels/: full hierarchy built and tested. ALPAMobile/Components/ViewModels/ComponentViewModels.cs: all preflight fixes applied.

XAML Token File — ✅ COMPLETE (Phase A)

AlpaTheme.xaml ResourceDictionary built and merged in App.xaml. Dual mapping:

TokenXAMLRazor (CSS)
Surface/BrandDynamicResource SurfaceBrandvar(--surface-brand)
Base ALPA tokensStaticResourceCSS custom property
MEC-themed tokensDynamicResource — merged at runtimeCSS custom property swap

ViewModel Hierarchy

ComponentViewModel (abstract)              ← root surface binding type
└── SurfaceViewModel (abstract)            ← Background, CornerRadius, Padding, TapCommand
    ├── CardViewModel (abstract)           ← content surface base
    │   ├── CardHeroViewModel              ← D5 / hero-card spec  ✅
    │   ├── CardTextViewModel              ← D6 / text-card spec  ✅
    │   ├── CardSmallViewModel             ← D7 / card-small spec ✅
    │   └── ButtonCardViewModel            ← D15 / button-card spec ✅
    └── ButtonViewModel                    ← D8  ✅
ButtonGroupViewModel                       ← D9  ✅
CarouselViewModel                          ← D10 ✅
ListViewModel                              ← D11 ✅

ComponentStyle (D18), CardVariant, SpacingToken, ThemeColor value objects also built in ALPAMobile.Presentation/ValueObjects/.

Three-Track Status

TrackDescriptionGateStatus
AScaffold VMs in ALPAMobile.Presentation + Razor renderers consume themNone⚠ Partially accurate — see finding below — VM shapes exist in ALPAMobile.Presentation/ViewModels/ (Phase A + B, commits ac1f7f98, b3fad8f2), but Razor renderers do not consume them: every .razor file still binds to the legacy ALPADocs.Components.ViewModels namespace (ALPAMobile/Components/ViewModels/ComponentViewModels.cs), globally imported via _Imports.razor. Verified 2026-07-13: zero .razor files or production code reference ALPADocs.Presentation.ViewModels.
BTyped ContentView BindableProperty controls for domain typesNone✅ Complete — Phase C; all 6 Priority-1/2 controls built (commits c8f8ef50 · 2d524033 · e2260f11 · 08b72427 · 60149120)
CMigrate Razor renderers from head into ALPAMobile.Presentation RCLPhase 5 / WI-2087 complete✅ Delivered 2026-07-15 (WI-2254/D61) — all 25 renderers + ComponentView, the adapter factories, FavoriteToggleContext, and the ViewModel cutover (dual-write retired) migrated to the RCL

✅ Resolved Finding — Duplicate ViewModel Namespaces (2026-07-13 reconciliation; resolved 2026-07-15, WI-2254/D61)

Resolution: the staged copies were re-namespaced to ALPADocs.Components.ViewModels and the head bundle deleted (namespace-stable cutover, member parity verified across all 34 types). ALPAMobile.Presentation/ViewModels/ is now the single home; the dual-write convention is retired. The finding below is retained as written for history.

Two parallel definitions of the component-library ViewModels exist under different namespaces, with the same class names (CardViewModel, ButtonCardViewModel, ToggleSwitchViewModel, ButtonPillViewModel, PromoBannerViewModel, etc.):

Not a one-time snapshot — actively growing. New components are being added to both copies in the same commits: the PromoBanner (D43) and SearchSecondaryButton (D42) feature commits each touch ComponentViewModels.cs (legacy, live) and add a matching class to ALPAMobile.Presentation/ViewModels/ (target, unconsumed). Every new atom/primitive added this way risks the two copies drifting out of sync, since nothing enforces they stay identical.

This finding is not resolved here — recorded per the Component Library Reconciliation Procedure's rule to surface risks for a human decision, not act on them unilaterally. Open questions for whoever picks this up: is the ALPAMobile.Presentation copy intentional pre-staging for the eventual Track C cutover, or accidental drift? Have the two copies already diverged in behavior for any of the 5 newer components? Should new component work stop touching the unconsumed copy until Track C actually starts, to avoid growing the duplication further? Tracked as WI-2253.

Newer Library Additions (D42–D47, not previously tracked here)

Five components landed via the 2026-07-02 Flight Finder sync and 2026-07-06 Figma gap sync but were never added to this plan's tracking. All were built in the then-legacy location — the same Track C migration gap as the original Track A set — and migrated with the rest of the library 2026-07-15 (WI-2254/D61); the duplicate-namespace finding above (WI-2253) was resolved by the same cutover.

ComponentDecisionRazor rendererViewModel (legacy, live)
Search Secondary ButtonD42SearchSecondaryButton.razorSearchSecondaryButtonViewModel
Promo BannerD43PromoBanner.razorPromoBannerViewModel
ExpanderD44Expander.razorExpanderViewModel
Button PillD46composed within TabGroup.razor, no standalone renderer neededButtonPillViewModel
Toggle SwitchD47ToggleSwitch.razorToggleSwitchViewModel

Also found, not previously flagged: GridContainerViewModel (D25) and StackContainerViewModel (D30) exist only in the legacy ALPAMobile/Components/ViewModels/ComponentViewModels.cs — unlike the rest of the original Track A set, they were never given a matching entry in ALPAMobile.Presentation/ViewModels/ at all (not even an unconsumed copy). GridContainer.razor and Stack.razor renderers exist in the legacy location as expected. Folded into WI-2254 above.

Track B ContentViews — Priority 1 ✅ All Complete

ControlFigma nodeUsesDomain typePagesStatus
FlightCardView5452:67910+FlightJSFF Results✅ commit c8f8ef50
DutyPeriodCardView4617:138766DutyPeriodListViewItemViewModelFTDTFlightDutyPeriodListPage✅ commit 2d524033
FlightLegInfoView4325:63996+per-leg computed props from FlightJumpseatFlightFinderDetailsPage (3 legs)✅ commit e2260f11 — DQ-10 open

Track B ContentViews — Priority 2 ✅ All Complete

ControlFigma labelUsesStatusNotes
FormFieldListRowView"Master Form Field" (list-row variant)85✅ commit 08b72427Replaces inline DataTemplate in KCMAirportsPage, JumpseatAirlinePolicyPage. KCM Airlines check-grid stays page-owned (distinct table layout).
NotificationCardView"card-notification"7✅ commit 60149120Replaces inline BindableLayout DataTemplate in AdvocacyPage. PushNotificationsListPage keeps existing UnreadContentView.
FlightStatusNotificationCardView"notification card flight status"3✅ commit 60149120 (scaffold)No native host page yet — Notifications Flight tab migration pending.

Not ContentViews — Handled Differently

ControlWhy not a ContentView
SettingsToggleMAUI Style on a Switch + Label row — no custom ContentView needed
ProgressBar / StepperCustom control (FTDT + JSFF flow nav) — closer to a TemplatedView
Segmented / TabGroupPlatform control or SegmentedControl from CommunityToolkit.Maui
Form check / radioStandard CheckBox / RadioButton rows with a Style

Build Phases Summary

Renderer Audit (Verified 2026-06-22)

ComponentFileVerdict
Card.razorWelcome/Pilot CardMinor gaps
CardHero.razorcard-lgMinor gaps
CardSmall.razorcard-smAligned (renderer) — VM gap
Button.razorButtonMinor gaps
ButtonGroup.razor3-up tileAligned
Carousel.razorHorizontal FeedSignificant gaps

All ViewModel structural gaps fixed in Phase A. Key renderer gap: CardHero.razor AccentStyle still inlines raw CSS string — deferred to ComponentStyle wiring in Phase D.

Work Items to Update

WIAction
WI-2096Close/resolve — architecture gate answered by D20 (Blazor Hybrid Razor RCL). Update with D20 as the resolution.
WI-2087Phase 5 — Presentation Extraction. Resolved 2026-07-01 — was the gate for Phase D above; Phase D itself is now unblocked but not yet started/scoped.
WI-1821Epic — update description to reference D20 as the architecture anchor.

References