The decisions taken on the component library — naming, ViewModel hierarchy, and design scope — with the rationale behind each. This is the authoritative reference; the Naming Alignment Report holds the analysis that led to the naming set.
| Adopted Name | Previous Name | ViewModel | Category | Decision |
|---|---|---|---|---|
| Card | Card | CardViewModel | Content surface | Kept |
| CardHero | Hero Card | CardHeroViewModel | Content surface | Renamed |
| CardText | Text Card | CardTextViewModel | Content surface | Renamed |
| CardSmall | Small Card | CardSmallViewModel | List item / nav row | Renamed |
| Button (optional Icon) | Button Card Icon Button Card | ButtonViewModel | Action control | Merged |
| Button Group | Multi Button Card | ButtonGroupViewModel | Control container | Renamed |
| Carousel | Slider Card | CarouselViewModel | Layout container | Renamed |
| List | Stack Card | ListViewModel | Layout container | Renamed |
Added from the 2026-06-10 Figma re-sync (see decisions D12–D14 below). Full dispositions for all 34 Figma masters are in Component Changes.
| Adopted Name | Figma master | Tier |
|---|---|---|
| Segmented Control | tab group | Nav chrome (D9 amended) |
| Bottom Navigation | navBar-bottom | Nav chrome |
| Top Navigation | topNav | Nav chrome |
| Nav Controls | nav controls | Nav chrome |
| Navigation Drawer | hamburger menu | Nav chrome |
| NavItem (Primary/Top/Drawer) | NavItem-primary/-top, navItem hamburger | Atom |
| Form Field | Master Form Field | Primitive |
| Progress Bar | progress bar & labels | Primitive |
| Section Title | section title | Primitive |
| Badge | status | Atom |
| Info Block | profile info block | Atom |
| Endpoint | endpoint | Atom (flight) |
| Logo | alpa logo | Atom (brand asset) |
| Back | back | Atom (control) |
| Page Template | template | Page template |
| Interior Template | main view-interior | Page template |
| Feed Template | main-feed slot | Page template |
| Flight Card | flight card | Domain control (D11) |
| Flight Segment | flight segment card | Domain control (D11) |
| Flight Detail | flight detail flight info | Domain control (D11) |
| Duty Period | card-duty period-alt | Domain control (D11) |
Folded into variants, not new components: card-notification (Card layout variant), btn-pulldown / btn-sort&filter / horizontal button with icon / button-sm (Button variants), size collapse (card-md/card-lg → Card variant). Screens (out of library, D14): profile, menu-hamburger. Tablet (*-tablet): deferred-responsive.
CarouselViewModel).ListViewModel).VStack), which would mislead for a scrolling vertical collection. SMEs don't use either term, so the choice is made on engineering clarity. "List" (UITableView / SwiftUI List / web lists) is the dominant convention.Icon (ImageSource?) — absent = text-only, present = icon + text. The former Icon Tile is folded in as the icon presentation. Button Group remains the container that lays out multiple Buttons (renames Multi Button Card).MenuItemViewModel is one ViewModel where the icon is an optional Glyph/ImageSource, with no separate "IconMenuItem" type. Our Button and Icon Tile specs were the same horizontal layout differing only by icon presence, so they are one component. Button Group stays a container (sits with Carousel and List).✓ Resolved (2026-06-05): Applied — Icon Tile merged into Button with an optional Icon property. The separate Icon Tile component and its docs were removed. Component count: 9 → 8.
Card{Qualifier})Card type leads, the qualifier follows: CardSmall, CardHero, CardText (base = Card). ViewModels likewise: CardSmallViewModel, CardHeroViewModel, CardTextViewModel. Supersedes the earlier qualifier-first names (Small Card / Hero Card / Text Card).Card* prefix (sorts/reads together) and matches Figma's card-* names. Consistent with D5 — containers (Carousel / List / Button Group) are not cards, so they carry no Card prefix.CardSmall is the nav-tile content component, distinct from base Card with Variant = Small. Domain controls keep domain names (Pilot Card, Flight Card — named for meaning, not the card type).✓ Revised 2026-06-10. Vindicates the existing Blazor code (CardSmall.razor / CardSmallViewModel were already type-first). Doc-wide rename (card-hero/ → card-hero/, card-text/ → card-text/, card-small/ → card-small/ + ViewModel refs) is a follow-up pass.
ComponentViewModel, from which all components derive. Content cards derive from CardViewModel; the Button shares the same card-like chrome through a SurfaceViewModel base (see D10); containers derive from ComponentViewModel directly. Containers' Items hold ComponentViewModel, so any component is feed-placeable.CardViewModel (which previously caused a self-referential type — a Carousel that is a card while holding cards). A neutral renderable base names the truth and keeps the content/layout split clean at the type level. Building greenfield avoids dragging legacy ObservableObject/page-VM assumptions into the library base.ComponentViewModel, SurfaceViewModel, DocumentCardViewModel) are abstract — "inherit from me, don't instantiate."✓ Applied (2026-06-05): All component spec docs, property mappings, ViewModel code blocks, and collection element types updated to this hierarchy. BaseViewModel references replaced with ComponentViewModel; container Items and Button Group collections retyped.
i-heart) is not rendered on the components. The favorite feature is unchanged — IsFavorite / FavoriteIcon persists as client state (local Settings); only the on-element heart indicator is dropped.favorite / i-heart element across components. Layer trees and the "Favorite icon" dimensions are kept faithful to Figma for provenance, with an exclusion note. When importing future Figma revisions, drop the heart visual — it is intentionally excluded, not missed.✓ Applied (2026-06-05): Heart removed from all rendered diagrams and previews (index, card-small, backend report, property-mapping ASCII). IsFavorite retained as state in the mappings. Exclusion notes added to the Design Tokens Reference and the affected component pages.
TextCardVariant { Description, Title, Label }. The small label above the title — initially called Eyebrow — is renamed Label.label typography role and the MAUI Label control — and it matches the existing Label property in our spec (IsLabelVisible), so the variant name and the code stop diverging. ("Overline" is deprecated Material 2 and absent on iOS.)--font-eyebrow-* token id (other/eyebrow). The mobile / web-dev / editorial comparison is recorded in the Naming Alignment Report (§5 addendum).✓ Applied (2026-06-05): Renamed across the Text Card spec, index preview labels, and property mapping (variant note, references, TextCardVariant enum). Convention itself (lead-element naming) is unchanged.
Segmented Control, Page Control, Cell) and web (Jumbotron, Overline). The current set already satisfies this.Label element choice (D8) is universal-compatible and stands.Label element → Overline (MUI)Amended 2026-06-10: Segmented Control is adopted as a scoped exception to this rule for the in-screen single-select segment (Figma tab group) — it is the clearest name, matches the design system's own Segmented Control token, and disambiguates from Bottom Navigation (the app's real tab bar). Page Control remains avoided.
SurfaceViewModel base for Card and ButtonSurfaceViewModel : ComponentViewModel base that carries the card-like chrome shared by tappable tiles — BackgroundToken, CornerRadius, Padding, and the tap target. Both CardViewModel and ButtonViewModel now derive from it. The Button is thereby aligned with the cards in the tree without becoming a card.card-btn — a card-shaped surface — and the former Icon Tile was folded into it (D3), so Button and Card genuinely share a surface. But they do not share content: CardViewModel carries Header / ContentText / Link, which a Button has no use for. Parenting ButtonViewModel : CardViewModel would make Button inherit dead content properties and break is-a (an action control is not a content card). Factoring the shared chrome into a neutral SurfaceViewModel names exactly what is common — and gives the containers a coherent "holds surfaces" story.SurfaceViewModel holds presentation chrome only — BackgroundToken: string, CornerRadius, Padding, TapCommand. CardViewModel adds content (Header / ContentText / Link); ButtonViewModel adds Label / Command / optional Icon. Containers derive from ContainerSurfaceViewModel (D26) — they arrange surfaces, they are not surfaces.✓ Applied (2026-06-05): Inserted SurfaceViewModel into the D6 hierarchy; reparented CardViewModel and ButtonViewModel across the Card/Button specs, property mappings, ViewModel code blocks, the index hierarchy tree, and the Component Architecture page.
RawRepresentationFactory<T>-style mapper. PilotCard = (UserInfo + contract DocumentItem) → a configured CardViewModel. The scaffold (ComponentViewModel → SurfaceViewModel → CardViewModel / ButtonViewModel / …) stays domain-free. Inheritance is reserved for shared presentation state; domain behavior / data is composed.PilotCardViewModel : CardViewModel injected IAuthentication + DataManager, ran an async contract lookup, and applied a business rule (omit rank) — all inside a scaffold subtype, with async work in a constructor. That is the fragile base class trap and it pulls domain dependencies into the library base. Microsoft's DI guidelines call for declaring dependencies by constructor injection and warn against direct instantiation of dependent classes within services — which is exactly what the subclass did. Composition through an injected service/factory is the modern-.NET model (a DI container composes graphs by constructor injection — that is composition).IServiceProvider and resolve dependencies at runtime; the DI guidelines list "injecting a factory that resolves dependencies at runtime" as a service-locator anti-pattern to avoid. The async gathering (auth + contract lookup) lives in a normal DI-injected domain service; the factory only maps. If a VM ever needs the container to build it with runtime args, the supported mechanism is ActivatorUtilities.CreateInstance (objects created outside the container). Register the factory/service; never register the CardViewModel.✓ Applied (2026-06-05): PilotCardViewModel removed from the D6 scaffold tree; Pilot Card recast as a domain control in its data-source doc and listed in the new Domain Controls layer. Migrated (2026-06-05): all four current data bindings now follow this pattern — Pilot Card (Card), Document Hero (Hero Card), Menu Text (Text Card), Did You Know List (List); each scaffold ViewModel stays domain-free, with mapping in a factory and async gathering in a domain service. Catalog: Domain Controls.
Variant enum (e.g. CardVariant { Small, Medium, Large }) that the SDUI layer sets to pick which variant renders. The ViewModel carries a typed style surface — a ComponentStyle value object of design-token types (ThemeColor, typed scale units) — not CSS strings.IOptionsMonitor<MecThemeOptions>, live-reload). An IThemeResolver maps (component type, Variant, Style, active theme) → the class="…" string only at the view edge; the domain layer never holds CSS. Extends D6/D10 — typed chrome becomes token defaults the theme overrides.✓ Adopted 2026-06-10 (Figma re-sync). Originally numbered D12 in the June 10 capture — renumbered D18 when the 2026-06-17 ingest reserved D12–D17 for component-naming decisions. See capture §5d · Component Changes.
MudTheme as the theming system.✓ Adopted 2026-06-10. Originally numbered D13 — renumbered D19 (see D18 note). See capture §5e.
net10.0 with no MAUI dependency, consumed by both the MAUI Hybrid BlazorWebView host and the ASP.NET Core RemoteHost/SDUI. Nav chrome, page templates, primitives, atoms, and their ViewModels live in it; screens stay app-side.Microsoft.Maui.Graphics); platform concerns are injected interfaces implemented in the host (D11 boundary).✓ Adopted 2026-06-10. Implementation gated on AB#2087. Originally numbered D14 — renumbered D20 (see D18 note). See capture §5f.
Button is now one component with an optional Icon property (absent → text-only, present → icon button). Icon Tile was removed. Grounded in the existing MenuItemViewModel idiom where the icon is optional data.ComponentViewModel root adopted. Containers derive from it directly; the Button and the content cards share a SurfaceViewModel chrome base (D10); content cards derive from CardViewModel : SurfaceViewModel; Items hold ComponentViewModel. Applied across all docs.listy card generic, card-btn, slider). Recommend asking the designer to align Figma names to this adopted set so design and code share one vocabulary.card-lg/card-md/card-sm split in code).CardSmall / CardHero / CardText (the Blazor CardSmall code was already correct). The docs need the rename: dirs hero-card/→card-hero/, text-card/→card-text/, small-card/→card-small/, and ViewModel refs HeroCardViewModel→CardHeroViewModel, TextCardViewModel→CardTextViewModel, SmallCardViewModel→CardSmallViewModel across the spec docs. Follow-up pass. (Arrows corrected 2026-07-15 — this note originally listed the post-rename name on both sides of each arrow, a transcription slip. The follow-up pass itself finally completed 2026-07-15: D59.)slider-card/→carousel/, stack-card/→list/, multi-button-card/→button-group/, button-card/→button/ (with matching file renames).icon-button-card/) into button/ as an optional Icon property; the separate directory was removed (D3).index.html links, headings, and regrouped components: content surfaces → action controls → containers.ALPAmobileWtabletThe 2026-06-17 Figma export (ALPAmobileWtablet_20260617, exported 2026-06-16T22:36) introduces a components-tablet section, a restructured flight-card family, and naming changes on the top nav. Decisions D12–D15 resolve the 🚩 flags captured in figma-component-specs.json during the inventory pass. Source: binary extraction from canvas.fig; designer annotations quoted verbatim.
navBar-bottom-tablet, topNav-tablet, flight card-TABLET, template-tablet, template-tablet - home) are a deferred-responsive tier — catalogued separately in figma-component-specs.json under "_revision":"2026-06-17", but not added to the scaffold ViewModel hierarchy or the main component grid in index.html until the responsive layout system is defined.components-tablet section." These are structurally independent components, not size variants of the same ViewModel. However, implementation is gated on the presentation-extraction epic (AB#2087) — adding them to the scaffold hierarchy prematurely would force changes at implementation time. Capturing them in the JSON inventory with a revision tag preserves the Figma truth without committing the hierarchy.design-tokens.html under "Tablet Typography Mode."TabletNavBarViewModel; reuse NavBarViewModel with a layout/size discriminator.Status: Adopted 2026-06-22. Tablet entries added to figma-component-specs.json. design-tokens.html to be updated with tablet typography delta in the tokens pass (step 3). No changes to architecture.html, index.html, or scaffold ViewModel tree until AB#2087 is delivered.
-og suffix: Figma informal → normalize at code boundary-og suffix (informal for "original") to distinguish the base/phone version from new tablet and variant-family siblings. This informal suffix is not adopted in code names or ViewModel identifiers. The mapping is:| Figma name (2026-06-17) | Spec / code name | Rationale |
|---|---|---|
topNav-og (4153:3892) | topNav / TopNavViewModel | The phone top nav is the canonical version; no suffix needed. Old topNav entry (4864:32242) now labeled topNav/old in Figma — it is retired. |
flight card-og (5452:679) | Verify vs flight segment card (4555:7483) — see D15 | Pending screenshot confirmation before retiring the old entry. |
figma-component-specs.json preserves Figma layer names as-is (including -og). The normalization applies only to the code-facing names in specs, property mappings, and ViewModel declarations. Per D9, component names should be universally recognized — "og" is internet slang and not universally recognized in a UI naming context.topNav entry in figma-component-specs.json is flagged 🚩 and carries a "_flag" note. When the deep-dive confirms topNav-og matches the old 393×98 dimensions, the old entry is retired and the topNav-og entry is promoted to the canonical phone top nav. All spec docs referencing topNav remain valid — no rename propagation needed since code names are already normalized.Status: Adopted 2026-06-22. figma-component-specs.json updated. No spec HTML renames required — code names were already normalized. Confirm topNav-og dimensions in sizing deep-dive.
card-button is a new scaffold component: CTA Card (ButtonCardViewModel)card-button (5452:434) is a distinct new scaffold component — not a rename or variant of card-btn. The two components are structurally different:card-btn (4737:13038, 116×106 px) — standalone icon-tile navigation button. The existing Button scaffold component (D3). Short single-word labels: "Committees", "Reps", "Hotels". Used inside Button Group.card-button (5452:434) — content card with an embedded CTA button. Master has 2 variants (Property 1: Default / Variant2). All instances carry a description paragraph plus a short action label: "Secure your flight with Jumpseat. / Jump Seat", "View committee work, volunteer resources and MEC information. / Committees", "Advocate for aviation safety and contribute to the PAC. / Take Action". Phone-scoped (i-phone context).ButtonCardViewModel : CardViewModel. Inherits Header / ContentText from CardViewModel; adds CallToAction : ButtonViewModel for the embedded action. The two variants (Default / Variant2) likely represent button-style differences (primary vs secondary CTA) — confirm in sizing deep-dive.ButtonCardViewModel, category: Content surface with action). Added to the catalogue in this record; HTML grid in index.html and architecture.html updated in the doc roll-up pass.Buttons/Primary Button / Secondary Button / Tertiary Button token paths found in the same revision are standalone button-style tokens. They apply to the embedded CallToAction button inside ButtonCard, but are not themselves the definition of card-button. Update design-tokens.html with these paths in the tokens pass.Status: Adopted 2026-06-22. Binary evidence (string extraction from canvas.fig): master definition confirmed at 0x1b39d0; 4 instance occurrences with body-text + CTA-label pattern; card-btn master independently confirmed at 0x4d4922 with navigation-tile instances. 🚩 flag on card-button in figma-component-specs.json to be cleared and size filled in sizing deep-dive.
flight card-og = flight segment cardflight card-og is the 2026-06-17 revision of flight segment card. Dimensions and styling match exactly — both 353×111, fill #ffffff, stroke #e3e3e3, strokeWeight 1. The designer renamed and regrouped the component as part of the flight-card family in this revision; the -og suffix marks it as the base/phone version (D13). The new entry adds a swipe variant to the existing Default + expanded states — an addition, not a redesign. Old flight segment card entry (4555:7483) is retired; flight card-og (5452:679) is the canonical phone master going forward. The existing child tree extracted from flight segment card (trip row, date/time row, leg detail, layover, Close Details button) carries over as the authoritative structure.| Figma master | Maps to | Notes |
|---|---|---|
flight card-og (5452:679, 353×111) | Flight Card — base / phone | Replaces flight segment card (4555:7483). States: Default (collapsed) · Expanded · Swipe. Carries trip row, date/time, multi-leg detail, "Close Details" underline button. |
flight card-TABLET (5500:44) | Flight Card — tablet tier (D12) | States: Default · Swipe · Expanded. Variant4/Variant5 — map by appearance in sizing deep-dive. |
flight card-recent searches (4441:398) | Flight Card — Recent Searches variant | JSFF feature. Swipe-to-reveal. State: Default · Swipe. |
flight card-saved flight | Flight Card — Saved Flight variant | JSFF feature. State: Default · Swipe. |
flight card-saved searches | Flight Card — Saved Searches variant | States: Default (collapsed) · Expanded · Swipe. |
overlay- naming convention: frames prefixed overlay- (overlay-flight finder filter, overlay-kcm filter, overlay-notification filters) are modal sheets layered over a base screen. Build as overlays, not separate screens. Screen-architecture note only; no scaffold additions required.card-notification serves both — Comms is a style/content variant, not a separate component. Captured in screen-mapping.html.Status: Adopted 2026-06-22. Binary cross-check confirmed: flight card-og and flight segment card share identical size/fill/stroke values (extracted from figma-component-specs.json). 🚩 flag on flight segment card in figma-component-specs.json updated to retired status. Domain controls catalogue (domain-controls.html) to be updated with the Flight Card domain control entry and variant table in the doc roll-up pass.
FlightSegmentCardViewModel : CardViewModel — rather than force-fitting data into the base CardViewModel.Origin, Destination, DepartureTime, ArrivalTime, Duration, FlightNumber, AircraftType, StatusBadge. Each renders in a discrete visual slot (city codes, time row, leg detail) — they are never concatenated in the view.CardViewModel provides only 3 properties: Header, ContentText, Link. Mapping 8 distinct fields into these 3 buckets would require the factory to concatenate at least 5 pieces of domain data into ContentText. The DQ-9 threshold was ≥ 3–4 concatenations; this exceeds it by a clear margin.StatusBadge carries both text and a status color — not representable in a plain string without in-band markup or secondary properties, making CardViewModel structurally inadequate.{Binding Origin} is unambiguous; (c) PCL MAUI HeightRequest is fixed-per-variant (189/129 px for ButtonCard, 164/1361 px for flight card) — typed data ensures the correct variant is selected.| Property | Type | Source |
|---|---|---|
Origin | string | Departure IATA code (e.g. "ORD") |
Destination | string | Arrival IATA code (e.g. "SFO") |
DepartureTime | string | Formatted time (e.g. "6:13 AM") |
ArrivalTime | string | Formatted time (e.g. "8:43 AM") |
Duration | string | Formatted duration (e.g. "4h 30m") |
FlightNumber | string | Airline + flight number (e.g. "UAL 423") |
AircraftType | string | Aircraft model (e.g. "Boeing 737") |
StatusBadge | string | Status text (e.g. "ON TIME", "DELAYED") — color mapping handled by factory / XAML trigger |
Inherits Header, ContentText, Link from CardViewModel. These are available but are not the primary rendering surface for flight data.
FlightCardFactory type updated to RawRepresentationFactory<FlightSegmentCardViewModel>.flight-segment-card/flight-segment-card-component.html to be added in the next doc roll-up pass.Status: Adopted 2026-06-22. Amended by D17 (2026-06-22): FlightSegmentCardViewModel is demoted from the scaffold hierarchy — the Flight Card family moves to Track B (typed domain components). The ViewModel properties table above remains accurate as a description of the display data required; it is now the domain of a typed ContentView rather than a ComponentViewModel subclass.
ComponentViewModel scaffold hierarchy (domain-agnostic, renderable in any generic container). Track B is typed domain components — ContentView subclasses that bind directly to a domain model type, bypassing the scaffold. A Track C (post-AB#2087) migrates Track B components to native Blazor Razor components.HeroCardViewModel can be placed in a Carousel, a List, or a standalone slot because it carries only generic display strings. This value is zero for a component that is always and only rendering one specific domain type.Origin, StatusBadge with color, Gate, etc.). Abstracting these into string properties produces a leaky scaffold, weakens type safety, and creates mapping overhead with no reuse benefit. There is no scenario where a flight card appears inside a Carousel of mixed content types.DataTemplate pattern (MAUI) and @parameter binding (Blazor) are idiomatic, well-understood mechanisms for exactly this pattern — they don't require a shared ViewModel base class.| Track | When to use | MAUI implementation | Blazor implementation (post-AB#2087) |
|---|---|---|---|
| A — Scaffold | Component is domain-agnostic AND may appear in a generic container (Carousel / List) alongside other types | ComponentViewModel subclass + RawRepresentationFactory<T> | Shared Razor component receiving a typed ViewModel via @parameter |
| B — Typed domain component | Component is always bound to one specific domain type AND has ≥ 5 domain-specific fields | ContentView subclass + BindableProperty per display slot + DataTemplate registration keyed to domain type | Razor component receiving the domain model directly via @parameter Flight Flight |
| Figma component | Domain type | MAUI control | Spec |
|---|---|---|---|
flight card-og family (D15) | Flight / Leg | FlightCardView : ContentView | spec (amended) |
card-duty period-alt | DutyPeriod / DutyPeriodExpanded | DutyPeriodCardView : ContentView | spec pending |
flight detail flight info | Flight / Leg | FlightLegInfoView : ContentView | spec pending |
FlightSegmentCardViewModel (D16) is demoted: removed from the ComponentViewModel hierarchy. The Flight Card family moves entirely to Track B. Scaffold count: 10 → 9.FlightCardFactory : RawRepresentationFactory<FlightSegmentCardViewModel> is replaced by FlightCardView.xaml.cs — a typed ContentView whose BindableProperty fields correspond to the 8 display slots identified in D16.flight detail flight info) → resolved: Track B typed component, not a new scaffold subclass.DutyPeriodCardView, no separate scaffold or Track B component needed.Status: Adopted 2026-06-22. Architecture.html, index.html, domain-controls.html, design-questions.html updated. D16 amended.
GridContainerViewModel: new Track A scaffold for MEC button grid patterncard-btn grid pattern found in the My MEC screen (MEC Actions, Quick Links) is formalised as a new Track A scaffold component: GridContainerViewModel : ContainerSurfaceViewModel (base updated from ComponentViewModel to ContainerSurfaceViewModel by D26). Code name: GridContainer. Spec: grid-container-component.html.card-btn items. This is a distinct layout pattern not covered by ButtonGroupViewModel (single horizontal row) or CarouselViewModel (horizontal scroll).ButtonCardViewModel items only, Columns=3 fixed, CellHeight=84, Gap=8, phone only. MAUI: CollectionView + GridItemsLayout(span:3).DynamicResource Surface/Brand — the dark-navy MEC colour is a theme token, not a grid property.Status: Adopted 2026-06-24. Spec: grid-container-component.html. Implementation gated on Epic AB#2087. Figma source: MEC screen node 20785:1418, "row" frame pattern — no published component_set for this container; it is ALPA-defined.
ALPAMobile2026-06-23The 2026-06-23 Figma export (ALPAMobile2026-06-23, exported 2026-06-23T05:25:43Z) captures the current published component library state. All library components were last updated 2026-06-03. Sixteen new components were discovered that were not inventoried in the 2026-06-17 sync. Source: Figma MCP library search against R6hPjBdjSIn6946qTJasUC; local binary at ~/ALPAMobile2026-06-23/canvas.fig (fig-kiwij format, 27 MB).
flight card: new distinct component_set master, separate from flight card-ogflight card (componentKey 8e152db607…) is a new component_set master distinct from flight card-og. Both coexist in the published library. Provisional classification: scaffold/composite — a redesigned flight information card that may supersede flight card-og over time. Code name candidate: FlightCard (without the -og suffix). Pending designer confirmation of the intended relationship between the two masters.flight card and flight card-og as separate component_sets in the library is unambiguous (different componentKeys). The -og suffix (per D13 and D15) marks the original/legacy variant. The new flight card without suffix is likely the current design direction. No dimensions available from this sync — dimension audit deferred until MCP page access is restored.flight card binds the same Flight/Leg domain type as flight card-og, it remains Track B (typed domain component). Does not change the scaffold count.Status: Adopted provisional 2026-06-23. Pending designer confirmation of flight card vs flight card-og relationship. figma-component-specs.json updated.
topNav / topNav-og replaced by nav controls + NavItem-primarytopNav and topNav-og are removed from the published library in the 2026-06-23 revision (neither appears in the MCP library search). The open 🚩 flags on both entries are resolved: topNav is retired; topNav-og is retired. Two new nav-chrome components replace the atomic behaviors: nav controls (componentKey 7b56460e…, tier nav-chrome, code name NavControls) and NavItem-primary (componentKey 2d0a7265…, tier nav-chrome, code name NavItemPrimary). Note: topNav-tablet and navBar-bottom / navBar-bottom-tablet are also not surfaced as library components — they remain in the file canvas but are not published; no retirement action needed, they retain their existing notes.Status: Adopted 2026-06-23. figma-component-specs.json updated: topNav and topNav-og _flag cleared → _note. nav controls and NavItem-primary added with provisional tier.
Toolbar - Top - iPhone / Toolbar - Top - Sheet: vendor reference, excluded from ALPA scaffoldToolbar - Top - iPhone (componentKey 1831b6d0…) and Toolbar - Top - Sheet (componentKey 21388da3…) are Apple iOS design-kit components (their Figma description explicitly links to feedbackassistant.apple.com/new?form=developertools.fba, which is Apple's design resource feedback channel). Classification: vendor/reference — catalogued in figma-component-specs.json for provenance but not added to the ALPA scaffold hierarchy, ViewModel tree, or domain-controls catalog. Pending designer confirmation: if these were imported for design reference only, no action needed; if the designer intends to use them as the ALPA top-nav chrome, a separate decision is required.nav controls and NavItem-primary components.Status: Adopted provisional 2026-06-23. Pending designer confirmation. figma-component-specs.json entries added with vendor/reference note.
flight card-saved & recent searches component_setflight card-recent searches, flight card-saved flight, flight card-saved searches) are superseded by the new flight card-saved & recent searches component_set (componentKey 9dfaa1ba…). The three old entries are flagged for retirement — they had null dimensions and no node IDs in the previous sync. The consolidated set is the authoritative Figma master for the JSFF saved/recent search domain control. Code name candidate: FlightCardSavedAndRecentSearches. The variant breakdown (Saved Flight vs Saved Search vs Recent Search) is handled as states within the component_set, not separate scaffold masters.flight card-saved & recent searches set confirms the designer consolidated what were previously modelled as three separate components. This matches the established pattern (D15: flight card-og is a component_set with Default/Swipe/Expanded states).Status: Adopted 2026-06-23. figma-component-specs.json: 3 old entries flagged; new consolidated entry added. Retire old entries after designer confirms.
flight finder filters, notification card flight status, search secondary buttons| Figma name | componentKey | Provisional code name | Rationale |
|---|---|---|---|
flight finder filters | c3045b66… | FlightFinderFilters | Filter controls bound to JSFF flight-search state; domain-specific fields |
notification card flight status | 7b28221c… | NotificationCardFlightStatus | Flight-status notification variant; extends card-notification scaffold? |
search secondary buttons | 93779985… | TBD | Secondary action buttons on search/JSFF screen — may be scaffold/atom if domain-agnostic |
notification card flight status composes card-notification scaffold (Track A chain) or is a standalone Track B typed component. Confirm whether search secondary buttons carries domain data.Status: Adopted provisional 2026-06-23. figma-component-specs.json entries added. Dimension audit and domain-controls stubs pending.
| Figma name | componentKey | Code name | Type |
|---|---|---|---|
Master Form Field | a8a39ae1… | FormField | component_set |
toggle-settings | 2f0062fc… | ToggleSettings | component_set |
hamburger menu | 2b49fcdf… | HamburgerMenu | component |
alpa logo | eb48b025… | AlpaLogo | component |
radio button | 5865b23b… | RadioButton | component_set |
i-favorites | e61f830f… | IFavorites | component |
NavItem-primary | 2d0a7265… | NavItemPrimary | component (nav-chrome) |
nav controls | 7b56460e… | NavControls | component (nav-chrome) |
NavItem-primary and nav controls are classified as nav-chrome sub-tier (not general scaffold/atom) since they are navigation-specific. Implementation gated on AB#2087.Status: Adopted 2026-06-23. figma-component-specs.json entries added. Dimension audit pending (null sizes).
Primary button: new scaffold/atom, relationship to button-sm TBDPrimary button (componentKey d5499f97…) is classified as scaffold/atom tier, code name PrimaryButton. The existing button-sm entry (id 4128:7717, 151×36, fill #007bc2) is not retired — it is in the Figma file canvas but was not published to the library in the 2026-06-23 sweep. The two components may overlap in role. Pending designer confirmation: does Primary button replace button-sm, or are they parallel (e.g., button-sm is a small variant and Primary button is the full-size CTA)?button-sm was in the original spec as a 151×36 blue action button. Primary button is a new component_set likely covering multiple size/state variants. The naming pattern (Primary button) aligns with the intent of a standard scaffold button atom.Status: Adopted provisional 2026-06-23. figma-component-specs.json: Primary button entry added. button-sm entry unchanged pending confirmation. Dimension audit pending.
figma-component-specs.json: 19 original entries preserved; 14 new entries added (card-sm, card-button, card-duty period, card-duty period-alt, flight card-* family, navBar-bottom-tablet, topNav-og, topNav-tablet, template-tablet, template-tablet - home). All 🚩 flags converted to _note fields with D-number and date.card-button is a new scaffold component — Button Card (ButtonCardViewModel). Spec page: button-card/button-card-component.html.flight card-og confirmed = flight segment card (353×164 Default/Swipe, 353×1361 Expanded). Old entry retired in figma-component-specs.json.FlightSegmentCardViewModel initially adopted as 10th scaffold component, then demoted to Track B (typed domain component) — see D17. Spec page preserved: flight-segment-card/flight-segment-card-component.html.get_metadata. design-tokens.html Component Dimensions table updated; two new rows added (flight card-recent searches 353×115, card-duty period 345×238/1010). navBar-bottom-tablet height remains TBD (tablet deferred per D12).topNav-og = topNav confirmed (both 393×98). topNav-og is authoritative per D15 pattern.get_variable_defs. All five tokens updated in design-tokens.html with inline swatches.FlightSegmentCardViewModel with 8 typed properties.domain-controls.html — 5-master variant map, rendered preview, data flow, D15/D16 notes. Duty Period Card and Button Card instances remain placeholder stubs (spec pending).Buttons and Form Fields/*, Component Widths/*, Spacing/*, Radius/Circle); Buttons/Primary Button, Secondary Button, Tertiary Button paths.ContainerSurfaceViewModel: new abstract intermediate for paintable containers; dual-target surface model applied to both surface basesContainerSurfaceViewModel : ComponentViewModel (abstract) as the new base for all four container ViewModels. The four containers (ButtonGroupViewModel, GridContainerViewModel, CarouselViewModel, ListViewModel) are reparented from ComponentViewModel to ContainerSurfaceViewModel. This gives containers a paintable wrapper surface — background color, corner radius, and padding — so MEC design variations can apply brand color and layout customizations at the container level, independent of the cell surfaces.BackgroundToken: string — default "Transparent". A semantic token name from the Surface/* namespace (e.g. "Surface/Brand", "Surface/Default"). Not a Color.CornerRadius: double — default 0. Concrete value; does not participate in MEC theme swapping.Padding: Thickness — default Thickness.Zero. Concrete value; does not participate in MEC theme swapping.ResourceDictionary.MergedDictionaries — the MEC theme dictionary overrides token values at runtime. Only bindings that dereference a token key at render time (via IThemeResolver) observe the merge. A ViewModel holding a resolved Color freezes the value at construction time; the runtime theme swap has no effect on it.var(--surface-brand)). If the ViewModel holds a concrete Color value, the renderer must inject it as an inline style (style="background-color:#05273E"), which overrides every CSS custom property due to specificity. Per-MEC brand selectors (:root[data-mec="UAL"] { --surface-brand: … }) cannot override an inline hex value.BackgroundToken via IThemeResolver.Resolve(token) → Color at bind time. CSS resolves it via token.ToCssVar() → var(--surface-brand) at render time. A shared TokenNameHelper utility (post-AB#2087) will provide both conversions.SurfaceViewModel.Background: Color updated to BackgroundToken: string. Both surface bases now hold the same token-name pattern — factories and domain controls pass a token from the Surface/* namespace, not a resolved color. The 6 leaf classes (CardViewModel, HeroCardViewModel, TextCardViewModel, SmallCardViewModel, ButtonCardViewModel, ButtonViewModel) inherit BackgroundToken without change. Full model in architecture.html §5.SurfaceViewModel is for tappable surfaces (cards, buttons) and includes a TapCommand. ContainerSurfaceViewModel is for layout wrappers that hold collections — they are never directly tappable. The tap handler lives on the items inside, not the container. This distinction is intentional and reflects the Figma model: MEC section headers are screen-level composition, not library components.Status: Adopted 2026-06-24. Implementation gated on Epic AB#2087. Architecture doc updated: architecture.html §1 (hierarchy), §2 (reference table), §5 (dual-target surface model), §6 (overlaps). Grid Container spec updated: grid-container-component.html.
GridContainerViewModel added as new Track A scaffold. ViewModel base updated from ComponentViewModel to ContainerSurfaceViewModel per D26.ContainerSurfaceViewModel : ComponentViewModel (abstract) introduced. Adds BackgroundToken: string, CornerRadius: double, Padding: Thickness. Four container ViewModels reparented. BackgroundToken is a semantic token name (not a resolved Color) to support both XAML IThemeResolver and CSS var(--token) rendering paths.SurfaceViewModel.Background: Color updated to BackgroundToken: string. Both surface bases now aligned. All 6 leaf classes inherit the token property without change. D6 hierarchy tree, D10 scope note, and architecture.html §5 updated to reflect resolved state.ContainerSurfaceViewModel code block added, XAML wrapper updated with outer Border binding ResolvedBackground.figma-component-specs.json: 33 original entries preserved; 16 new entries added from 2026-06-23 library sweep. Total: 49 entries. All 19 🚩 flags from this revision resolved to _note fields with D-numbers and date.flight card is a new distinct scaffold/composite master (componentKey 8e152db6…), separate from flight card-og. Code name candidate: FlightCard. Pending designer confirmation of relationship between the two masters.topNav and topNav-og open flags resolved — both retired from published library. Replaced by nav controls (NavControls) and NavItem-primary (NavItemPrimary), both nav-chrome tier.Toolbar - Top - iPhone and Toolbar - Top - Sheet classified as vendor/reference (Apple iOS design kit). Excluded from ALPA scaffold hierarchy. Pending designer confirmation.flight card-saved & recent searches component_set supersedes the 3 individual placeholder entries. Three old entries flagged for retirement.flight finder filters, notification card flight status, search secondary buttons. Domain-control tier provisional; domain binding to be confirmed.Primary button added as scaffold/atom (PrimaryButton). Relationship to button-sm TBD.get_metadata call per new componentKey is required to populate the Component Dimensions table in design-tokens.html. MCP page access was limited to thumbnail-only in this sync.ALPAMobile2026-06-23 and export date. Check for new token paths from the new components.Toolbar - Bottom/Top - iPhone → NavBarBottom / TopNav; -og suffix dropped (per D13); manu-hamburger → HamburgerMenu.Font/Heading and Font/Body slots added to the theme-endpoint contract. Base: Futura PT Book (400) + Bold (700). Per-MEC: UAL = League Spartan, DAL = Bebas Neue (headings) + League Spartan (body); FDX = TBD (DQ-14 open).Text/On-Brand is MEC-themeable. Base: #ffffff (white). UAL override: #bed6fb. Must be a DynamicResource in all templates, not a static hex.bg-main (#f5f7fa) as the page background with a watermark image overlay at mix-blend-multiply. Not Surface/Brand (#05273e) — that is reserved for navBar-bottom only.Text/On-Brand MEC-themeable note + Font/Heading / Font/Body token slots to be added (Task 6).i-heart, 18×18) reinstated on all favoritable controls. Supersedes D7 (2026-06-05). Affected controls: CardSmall, CardText/TextCard, Card/CardHero, List, ButtonCard.PageViewModel introduced as page root — outside the ComponentViewModel hierarchy. Home.razor consumes it directly; Components: ObservableCollection<ComponentViewModel> iterates through the existing ComponentView.razor dispatcher."Carousel", "Grid", "ButtonGroup", "Stack") and item type discriminators ("PilotCard", "HeroCard", "CardSmall", "CardText", "ButtonCard", "Button", "EmergencyButton") locked as the API contract. Factory skips unknown types rather than throwing.GridContainerViewModel.Items widened from ObservableCollection<ButtonCardViewModel> to ObservableCollection<ComponentViewModel>. Resolves the D25 deferred extension. XAML DataTemplate selector updated to dispatch on ComponentViewModel type at bind time."Stack" container maps to new StackContainerViewModel : ContainerSurfaceViewModel — surface-neutral, carries only Items: ObservableCollection<ComponentViewModel>. ListViewModel (D10/D11) unchanged; it remains Track B.PageViewModel: page root outside the ComponentViewModel hierarchyDecision: Adopted 2026-06-24. Introduce PageViewModel : ObservableObject as the top-level page model. It is not a ComponentViewModel subclass — it is not renderable by ComponentView.razor and cannot appear as an item inside a container. It is the root that Home.razor receives from PageFactory.
Properties: PageId: string?, Title: string?, Components: ObservableCollection<ComponentViewModel>. Components holds the mapped container ViewModels (ContainerSurfaceViewModel subclasses), which the existing ComponentView.razor dispatcher already handles.
Why outside the hierarchy: A page is not a component — it has no background token, no tap command, no IsVisible. Making it a ComponentViewModel subclass would allow it to appear inside containers, which is never correct. Keeping it separate preserves the invariant that every ComponentViewModel is a renderable leaf or container node.
Factory: PageFactory.Create(PageDto) → PageViewModel. Delegates to ContainerFactory for each ContainerDto. Implementation gated on Epic AB#2087 and the backend endpoint delivery.
Decision: Adopted 2026-06-24. These are the canonical values for ContainerDto.ContainerType and ItemDto.ItemType. The backend emits them; the client factory maps them to ViewModels. Unknown strings are skipped with a warning — never throw.
Container types
| Figma name | Discriminator string | ViewModel | Notes |
|---|---|---|---|
slider | "Carousel" | CarouselViewModel | Horizontal scroll; carries Title + view-all |
| MEC grid pattern | "Grid" | GridContainerViewModel | n-column layout; see D25, D29 |
multi-button-card | "ButtonGroup" | ButtonGroupViewModel | Single horizontal row of Buttons |
stack-card | "Stack" | StackContainerViewModel | Surface-neutral vertical stack; see D30 |
Item types
| Figma name | Discriminator string | ViewModel | Notes |
|---|---|---|---|
welcome thing | "PilotCard" | CardViewModel | Pilot welcome card with chevron link |
card-hero | "HeroCard" | CardHeroViewModel | Large image card with eyebrow + title |
card-small | "CardSmall" | CardSmallViewModel | Compact card |
card-text | "CardText" | CardTextViewModel | Text-only card (no image) |
card-button (D14) | "ButtonCard" | ButtonCardViewModel | Body text + CTA label; used in Grid rows |
card-btn (D3) | "Button" | ButtonViewModel | Icon-tile nav button; used in ButtonGroup |
| emergency variant | "EmergencyButton" | ButtonViewModel | IsEmergency = true |
Forward-compatibility: factories skip unknown type strings (log warning, no throw) so the backend can add new types before the client ships support.
Legacy aliases: ItemComponentFactory retains prototype strings ("SingleButtonLarge", "SliderImages", etc.) during transition. New endpoint uses canonical names only.
Full contract: dynamic-feed/dynamic-feed-api-contract.html §4–§5.
GridContainerViewModel.Items widened to ObservableCollection<ComponentViewModel> (resolves D25 deferral)Decision: Adopted 2026-06-24. The D25 deferred extension — widening GridContainerViewModel.Items from ObservableCollection<ButtonCardViewModel> to ObservableCollection<ComponentViewModel> — is now resolved.
Reason: The server-driven feed contract (D28) allows any item type inside a "Grid" container, not just ButtonCard. A typed ObservableCollection<ButtonCardViewModel> would require the factory to silently drop non-ButtonCard items or throw. Widening to ComponentViewModel is the correct extension path documented in D25 §6.
Rendering impact: The XAML CollectionView (or Blazor iterator) switches from a single fixed DataTemplate to a DataTemplateSelector (XAML) or <ComponentView Vm="item" /> iterator (Blazor). The Blazor path already works — ComponentView.razor dispatches on type. The XAML path requires a ComponentDataTemplateSelector (post-AB#2087).
v1 scope unchanged: the MEC screen grid remains 3-column ButtonCard items. The widening is an API / ViewModel contract change — the Figma design scope for v1 does not change.
StackContainerViewModel: surface-neutral vertical stack container (resolves D28 placeholder)Decision: Adopted 2026-06-25. Introduce StackContainerViewModel : ContainerSurfaceViewModel as the ViewModel for the "Stack" container type. Replaces the temporary ListViewModel placeholder established when D28 locked the discriminator strings.
Shape: Items: ObservableCollection<ComponentViewModel> only. No layout properties (orientation, spacing, alignment) — these are surface concerns. The bound rendering surface (Stack.razor for Blazor; a VerticalStackLayout-backed template for XAML post-AB#2087) owns layout entirely.
Why not widen ListViewModel: ListViewModel (D10/D11) is a Track B domain control — it models a typed date · headline · subtitle row for domain-specific use cases (Did You Know list, notification rows). It belongs in Track B (domain controls), not Track A (server-driven scaffolds). Widening it to carry ObservableCollection<ComponentViewModel> would cross the D17 Track A/B boundary and give a domain control a generic container role. A new Track A type is the correct extension.
Precedent: Same pattern as GridContainerViewModel (D25) — a new ContainerSurfaceViewModel subclass rather than widening an existing type.
Source files changed: ComponentViewModels.cs (new class) · Stack.razor (new component) · ComponentView.razor (dispatch case added).
Spacing/xtiny through Spacing/xxlarge and Spacing/page-margins) are included in every ThemeResponse payload, set to ALPA base values for all MECs by default. A MEC can override them server-side without an app store release.Spacing/xtiny (4 px) · Spacing/tiny (8 px) · Spacing/xxsmall (12 px) · Spacing/xsmall (16 px) · Spacing/small (20 px) · Spacing/medium (24 px) · Spacing/large (28 px) · Spacing/xxlarge (40 px) · Spacing/page-margins (16 px). Nine tokens; total payload rises to 39.✓ Applied (2026-06-26): theme-endpoint-contract.html token table + example payloads updated. design-tokens.html namespace table updated. OT-5 closed.
Text/On-Brand is MEC-themeable; must use DynamicResourceText/On-Brand is not a static white — it is an MEC theme override slot. Base value: #ffffff. UAL override: #bed6fb (light periwinkle blue, per MEC Figma node 20657:502). All templates that render text on brand-colored surfaces must bind via DynamicResource to the Text/On-Brand token, not to a hardcoded hex.Surface/Brand or per-MEC brand fill. Does not apply to Surface/Default (white) backgrounds — those use Text/Primary.theme response delivers text-on-brand as part of the per-MEC color block. The client applies it as a CSS custom property or XAML merged dictionary. See theme-endpoint-contract.html.Applied (2026-06-26): Decision recorded. theme-endpoint-contract.html update pending (Task 6).
Font/Heading and Font/Body slotsFont/Heading and Font/Body.FuturaPTBook.otf and FuturaPTBold.otf. Anything labeled "heavy" or "medium" in the Figma variable names refers to optical-weight within these two files, not separate font files. To achieve visually heavier text at the same font size, use Bold (700). Do not specify weight values other than 400 or 700 for Futura PT.21247:2907)Applied (2026-06-26): Decision recorded. DQ-14 filed in design-questions.html. theme-endpoint-contract.html Font/Heading + Font/Body slots pending (Task 6).
| Figma name (raw) | ALPA-neutral name | Reason |
|---|---|---|
Toolbar - Bottom - iPhone | NavBarBottom | Apple HIG label; platform-neutral name used everywhere |
Toolbar - Top - iPhone | TopNav | Apple HIG label; platform-neutral name used everywhere |
name-og | name (suffix dropped) | -og = "original" — Figma artefact; D13 established drop rule |
manu-hamburger | HamburgerMenu | Typo in Figma ("manu" = "menu"); correct to standard term |
btn-flight finder interior nav | InteriorTabNav | Figma name encodes context; neutral reusable name preferred |
figma-component-specs.json holds the Figma name as figmaName and the ALPA name as the object key.Applied (2026-06-26): Decision recorded. Normalization will be applied during the next figma-component-specs.json Phase 3 pass.
i-heart, 18×18) IS rendered on all favoritable components. Amends and supersedes D7 (2026-06-05 — "favorite heart not rendered").card-sm) · CardText / TextCard (card-md) · Card / CardHero (card-lg) · List (listy card generic) · ButtonCard (card-btn). The i-heart in Button and ButtonGroup is the button's own icon image, not a favorites toggle — those are unaffected by this decision.i-heart layer is intentional and faithful to the design.Settings.FavoriteItemIdsJSON)...IsFavorite is now sourced from the live API (IFavoritesQueries), not local storage.i-heart on future Figma imports — it is intentional. The D7 import note ("drop the heart visual") is rescinded.✓ Applied (2026-06-26): Heart indicator reinstated in all affected component specs and property-mapping docs (card-small, small-card, card-text, text-card, list, domain-controls, screen-mapping, home-screen-data-gap, home-page-audit, membership-screen-data-gap, backend-api-mapping-report, index). design-tokens.html import note updated. design-questions.html status updated. Supersedes D7.
✓ Implemented (2026-07-08, WI 2182): Interactive heart-toggle wired (auth-gated, ShowFavorite/IsFavorite/OnFavoriteToggle parameters) in CardSmall.razor, HeroCard.razor, Card.razor, and ButtonCard.razor. Not yet wired: List.razor — flagged as a remaining gap, not silently dropped. CardText/TextCard also not yet wired (no consuming screen needed it this pass).
PageDto.Containers and ContainerDto.Items pre-sorted in admin-defined order. That order is authoritative. Device users may reorder containers and items within their local cache. IsSortable=false on a ContainerDto or ItemDto locks that element's position — the user cannot move it. Default is true.IsSortable=false per item when pinning is required. This gives the comms team guaranteed placement without blocking user personalisation of other items.BackgroundToken, PaddingToken, and CornerRadiusToken in ContainerDto are populated server-side from a component-type → theme mapping. Content editors do not set them directly. They are global theme decisions. Fields are retained explicitly in the DTO for backwards compatibility — if a future admin UI exposes limited controls, the surface is already there.CornerRadius: double? → CornerRadiusToken: string?; BorderRadius/* namespace added to theme endpointContainerDto.CornerRadius (a raw double? pixel value) is renamed to CornerRadiusToken (a string? semantic key, e.g. "BorderRadius/M"). This makes it consistent with BackgroundToken and PaddingToken. The client resolves the key via the theme endpoint.BorderRadius/None (0px), BorderRadius/S (4px), BorderRadius/M (8px, default fallback), BorderRadius/L (12px), BorderRadius/XL (16px), BorderRadius/Full (50%). Base values apply to all MECs today; MEC-overridable server-side.WidgetDto → ItemDto; WidgetComponentFactory → ItemComponentFactoryWidgetDto to ItemDto for consistency: the containing property is ContainerDto.Items (not Widgets), and the factory is ItemComponentFactory. The JSON field name "items" is unchanged. The Blazor alignment doc references the pre-rename name as it describes the existing codebase being refactored.FlightCardView is its own ContentView, not force-fit into CardViewModel): once a control's shape/fields diverge past the DQ-9 reuse threshold, it gets its own implementation.StatusBadge, EmptyState) and design tokens (color · spacing · typography) for visual consistency. What is rejected is a shared base class/wrapper that couples unrelated controls — the atoms/tokens are a shared vocabulary, not a substrate. (Prevents drift, e.g. the per-page --kcm/--cass badge-color reuse the scaffold accumulated.)Saved Search Card is a standalone control (a saved query); Saved Flights reuses Flight Segment Card by composition where the Figma frame matches (else standalone); both compose the shared action-column atom. There is no "Saved Item Card" base control.flight finder filter and sort: duplicate node confirmed — 4450:11374 canonical, 4705:8702 flagged as stale copyowEYzHf7FrHRvWC2u82UOl) carries flight finder filter and sort as a COMPONENT_SET at two node IDs: 4450:11374 and 4705:8702. A full structural + text diff shows the two are byte-identical — same 2 states (Property 1=filter 393×499, Property 1=sort 393×480), same child instances, same text content (Airline / Aircraft / Flight Status / Aircraft filter rows; Arrival Time / Departure Time / Connection Time / Number of Connections / Total Trip Time sort options). This rules out "different variant states" or an "in-progress split" — it is a genuine duplicate. 4450:11374 is adopted as canonical; 4705:8702 is flagged for designer cleanup and is not catalogued as a separate library entry.4450:11374's two children were born in two different sessions (filter = 4370:8880, older; sort = 4450:11375, added later) — consistent with a master that was built up incrementally over time, the normal growth pattern. 4705:8702's two children (4705:8703, 4705:8715) were both born together in one later session — consistent with a single duplicate/paste operation performed after 4450:11374 already existed in full. This was originally an evidence-based call pending design-team confirmation.4705:8702 is a duplicate of 4450:11374. The evidence-based call above is now verified, not inferred — no further designer sign-off needed on the duplicate-node question itself.overlay-flight finder filter reference (screen-mapping.html, node 0x44de70, from the now-dropped .fig binary extraction) — the Filter state's Apply/Clear CTAs and check/radio form controls match that earlier overlay note exactly.flight finder filters (D22, existing domain-control — 3 instances in the Filter state, one per field) + radio button (5 instances in the Sort state) + tab group (Sort/Filter pill toggle) + nav controls (close X) + Primary button (Apply) + Underline Button (clear). Code name: FlightFinderFilterSort.JumpseatFlightFinderResultsFiltersPageViewModel.cs. Every filter/sort field the Figma capture lists (Airline / Aircraft / Flight Status filters; Arrival Time / Departure Time / Connection Time / Number of Connections / Total Trip Time sort) exists as a bound property there, driven by an in-memory FilterSettings object and broadcast via FilterSettingsHaveChangedMessage. Filter choices (airline/aircraft lists) come from the existing flight-search response (flightSearchResponse.Airlines, JumpseatFlightFinderResultsPageViewModel.cs), not a separate endpoint. No backend gap — confirmed by a doc-audit assessment (2026-07-06) that checked this and 4 other newly-synced components (D42, D44, D47, D48) for unresolved backend work; only D43/Change 5 needs backend action.Status: Adopted 2026-07-02 · Confirmed 2026-07-06 · domain binding confirmed 2026-07-06. figma-component-specs.json: flight finder filter and sort entry added (canonical id 4450:11374, _duplicateNode: 4705:8702). Spec: flight-finder-filter-sort-component.html. No open items.
search secondary buttons: resolves D22's open TBD — scaffold/atom tier, code name SearchSecondaryButtonsearch secondary buttons as domain-control tier with code name TBD, pending confirmation of whether it "carries domain data." Live capture (componentKey region, node 4381:10609) shows both Property 1 variants carry identical generic content — an icon-generic placeholder glyph + a "Recent" text label, no flight/domain-specific fields. Resolved: scaffold/atom tier, code name SearchSecondaryButton — a reusable secondary-action chip (icon + label). "Recent" is example content; the same chip is expected to also render "Favorites" and similar quick-access affordances on the search/JSFF screen.#5d6471, stroke #05273e.Status: Adopted 2026-07-02. figma-component-specs.json: search secondary buttons entry added. design-tokens.html 2026-06-23 New Components table row updated (tier + code name + dimensions). domain-controls.html: placeholder stub removed from the domain-controls catalog (reclassified out). Spec: search-secondary-button-component.html.
banner: domain-control tier, code name PromoBanner — maps onto the existing PageBanner APIbanner (component_set, node 5512:12925) is a new master not present in the 2026-06-17 or 2026-06-23 sweeps. It is a dismissible promotional/announcement banner: sample content is a UPA27 union survey promo ("Survey Now Open" / "Closes June 10 at 11:59PM PT"), a logo image, and a close (X) control. The two Property 1 states are a layout re-order (close-left vs close-right), not different content or a size variant. Classified domain-control tier — the title/subtitle/image are dynamic content, analogous to notification card flight status (D22).#05273e (navy) on both states — a solid fill, not a background photo.ItemDto and not a dedicated announcements/surveys service — the two candidates originally floated. This is the richer version of the existing, already-shipped PageBanner model (ALPAMobile.Domain/Data/Models/PageBanner.cs), served by GET /api/pagebanner/list and resolved per page in BannerPageViewModel via PageBanner.GetBannerForPage(PageName, banners) — a generic per-page banner slot already used by ~20+ pages (Home, Jumpseat, Known Crewmember, Member Resources, MEC/LEC, Flight Search, Settings, Notification Center, Advocacy). Today's implementation renders it as a single flat image (FeaturedImageLink) with the title/subtitle baked into the image pixels — confirmed live in the app, no separate text fields exist yet. This also resolves the D40 "static content, exempt from the library" question raised below: it is not one-off content — it's a recurring, already-multi-page pattern, so it correctly stays a catalogued domain control. Required field additions (Title, Subtitle, LogoImageLink) and the open dismiss-persistence question are tracked as Change 5 in the Backend API Mapping Report.Status: Adopted provisional 2026-07-02 · domain source confirmed 2026-07-06 · dismiss behavior resolved 2026-07-06 (client-side only — per-session by default, per-device local storage if permanent dismiss is needed; no backend involvement). figma-component-specs.json: banner entry added. Spec: promo-banner-component.html. Remaining open item: the PageBanner API field additions (Change 5) — additive, not yet requested from backend.
expander-flight finder-advanced search: confirms the Expander's open content gap; new domain-control layer FlightFinderAdvancedSearch4361:5537) confirms the expanded content: Avoid Connection field, Connect Via field, Min Connection Time range slider, Length of Travel range slider (both reuse the existing slider master, 4317:5239, already in figma-component-specs.json), three checklist rows (Search by Arrival Date / Include Cargo Carriers / Show Codeshare), and a Secondary button labelled "Save Search to Favorites" (collapsed-then-expand copy shortens to "Save Search" in the open state).FlightFinderAdvancedSearch, that composes Expander + Master Form Field + slider + checklist + Secondary button. This follows the same domain-control-composes-scaffold pattern as flight finder filters (D22) — same domain (JSFF flight-search filter state).JumpseatFlightFinderSearchPageViewModel.cs. Every field matches 1:1: AvoidConnectionCities, ConnectVia, MinConnectionTime, MaxLengthOfTravel, IsSearchByArrivalDate, IncludeCargo, ShowCodeshare — all map directly into the existing FlightSearchRequest sent to the real flight-search API. No backend gap (same doc-audit assessment as D41).#efefef on both states.Status: Adopted 2026-07-02 · domain binding confirmed 2026-07-06. figma-component-specs.json: expander-flight finder-advanced search entry added. expander/expander-component.html updated — open-content warning resolved, Usage table updated, links to the new domain control. Spec: flight-finder-advanced-search-component.html. No open items.
card-duty period confirmed retired (absent from live file); card-duty period-alt confirmed present (filed under FTDT, no change)owEYzHf7FrHRvWC2u82UOl, "app" page) for any node named card-duty period or card-duty period-alt returns 16 matches, all card-duty period-alt — the master component_set (4617:13876, in "components ftdt" section 4543:5876, 3 states: Default 345×237 / Variant2-expanded 345×1073 / delete-demo 345×237) and its instances across the FTDT dashboard, Duty Periods list, archived Duty Periods, and the tablet variant. No occurrence of the bare card-duty period name exists anywhere in the live file.card-duty period-alt, and domain-controls.html's Duty Period placeholder already cites only node 4617:13876 (3 states matching this capture exactly). card-duty period (non-alt) was never adopted as a separate component — this decision formally closes the "confirm retired or renamed" flag per the sync procedure's Removed-master rule, rather than introducing new scope.Status: Adopted 2026-07-02. No figma-component-specs.json changes — card-duty period-alt's existing entries (from the dated-snapshot file, ids 5515:52/5515:55) are unaffected; this decision only confirms live-file disposition. No other doc changes required — existing domain-controls.html and naming-decisions-record.html §1 references were already correct.
button pill: live-file capture confirms the already-adopted Button (Pill) master; corrects a word-order transcription in the 2026-06-17 dispositions table4245:5754 named button pill (COMPONENT_SET, 167×158 overall — Property 1=default 127×31 white/navy text, Property 1=selected 109×31 blue #007bc2/white text) as not matching the 2026-06-17 revision's dispositions-table entry, which was recorded as pill button (reversed word order). Comparing the two: this is the same component, not a rename. The dispositions table's word order was a transcription slip — the live Figma layer name has always been button pill, confirmed by (a) the three button pill instance children already present under the existing tab group entry's slot in figma-component-specs.json, unchanged since the original capture, and (b) the hand-authored spec page button-pill/button-pill-component.html (written 2026-06-26), which already used the correct word order and correct Figma provenance.5048:13572/5048:13573, file psH738AqHDxuMyFm897f9r). This sync adds the live-file master capture (4245:5754, file owEYzHf7FrHRvWC2u82UOl) as a second, more authoritative provenance record — the actual COMPONENT_SET with both variant states, rather than two separate instance ids.ButtonPillViewModel). No dimension conflict — both states match the shape described in the existing spec page (segmented pill, active/inactive).Status: Adopted 2026-07-06. figma-component-specs.json: new top-level button pill entry added with live node id and both states. button-pill/button-pill-component.html: live node id added alongside the existing dated-snapshot reference. screen-mapping.html registry: node updated from null to 4245:5754.
Toggle - Switch: new scaffold/atom, distinct from toggle-settings — the bare on/off switch toggle-settings composes4128:7733, Toggle - Switch (COMPONENT_SET, 104×120 overall — State=Off 64×28 fill #d2d4d6, State=On 64×28 fill #007bc2; both states are a Frame (21×10, AX label) + Knob (39×24, white)) as a node distinct from the already-catalogued toggle-settings (D23, node 4875:11208, code name ToggleSettings). Structural comparison confirms these are genuinely different components in a compose relationship, not the same node under two names: toggle-settings' own two states (Property 1=Default 299×28, Property 1=Variant2 244×44) each contain a Toggle - Switch INSTANCE sized 64×28 with the identical Frame(21×10)+Knob(39×24) structure as this master's On state, plus a sibling "Lorem ipsum dolor sit amet" label text node. In other words: toggle-settings = label text + an instance of this Toggle - Switch atom; this atom is the bare switch control itself.ToggleSettings (which composes it with a label and remains a separate, already-adopted catalog entry — its own tier/dimensions are unchanged by this decision).Status: Adopted 2026-07-06. figma-component-specs.json: new top-level Toggle - Switch entry added. New spec page: toggle-switch/toggle-switch-component.html. screen-mapping.html registry: new entry added (node 4128:7733); existing toggle-settings entry (still node null) is unchanged — its own node-id gap remains open, out of scope for this sync. index.html: added to the Atoms spec grid.
flight card-saved & recent searches: live node confirmed for the already-adopted D21 consolidation; two sibling usage frames documented, not separate mastersflight card-saved & recent searches (componentKey 9dfaa1ba…) as the consolidated component_set superseding three placeholder JSFF entries, but recorded no node id (figma-component-specs.json never got a top-level entry; screen-mapping.html's registry carried "node": null). The gap audit's live-file node 4450:9884 closes this: COMPONENT_SET, 393×465 overall — Property 1=Default 353×61 (collapsed/compact card: trip row + Underline Button 320×38 fill #efefef + paramaters [sic] rows) and Property 1=Variant2 353×340 (expanded parameters view, Underline Button unfilled).flight card-saved searches (FRAME, node 4450:10395, 353×115) and flight card-recent searches (FRAME, node 4450:11208, 353×115). Direct structural capture shows both are identically structured composites: a swipe panel + an INSTANCE of this D21 master's Default state (353×61) + an expanded detail flight-leg panel (234×1066, matching flight detail flight info content). The only difference between the two frames is the swipe-reveal action icon — i-trash (delete, red #c02126) on the "saved searches" frame vs i-favorites (save, #464b55) on the "recent searches" frame. Both are FRAME type, not COMPONENT/COMPONENT_SET — i.e. they are not published library masters; they are per-context usages of the one master, exactly as D21's original rationale predicted ("the variant breakdown is handled as states within the component_set, not separate scaffold masters").figma-component-specs.json (flight card-recent searches, old id 4441:398; flight card-saved flight, id null; flight card-saved searches, id null) are confirmed retired by this capture — none of their old ids match the current live structure, and the live file's actual shape (one master + two usage frames) fully accounts for the "saved flight / saved searches / recent searches" distinction without needing three separate scaffold masters.Status: Adopted 2026-07-06, confirms D21. Tier unchanged (domain-control). Code name unchanged: FlightCardSavedAndRecentSearches. figma-component-specs.json: new top-level entry added with confirmed node id, dimensions, and a _usageFrames provenance note for the two sibling frames. screen-mapping.html registry: node updated from null to 4450:9884. domain-controls.html: placeholder stub added. design-tokens.html: dimensions updated from TBD to confirmed. architecture.html §6: cross-reference added. Stale-language audit (2026-07-06): flight-segment-card-component.html's Figma Variant Map table still showed the three superseded entries as active/TBD rows, missed by the original D48 roll-up — now updated to mark them superseded and add the consolidated master's own row.
button link ("Forgot Member Number"): one-off screen content, not catalogued — per D405251:15088, button link — a FRAME (not a COMPONENT/COMPONENT_SET — no published library master exists at this node), 218×24, containing the text "FORGOT MEMBER NUMBER" (203×24, #007bc2) and a small chevron/caret vector icon (9.4×8.1, #007bc2). This sits on the Log In screen next to the "Member Number or ALPA Email" credential field (see login-screen-data-gap.html), as a sibling to the already-documented, likewise-uncatalogued "Forgot Password" link.FRAME, never published as a reusable Figma component/component_set; (2) its sample content is real, specific screen copy ("FORGOT MEMBER NUMBER"), not a lorem-ipsum placeholder — the same treatment already given to "Forgot Password" on the same screen. This is the mirror-image case of D43 (banner), where D40 was raised as a candidate exemption but the evidence (recurring, ~20+ page usage) pointed the other way, toward cataloguing. Here the evidence (unpublished frame, one specific screen, parallels an existing uncatalogued precedent) points toward exemption.Mobile Button Text style (design-tokens.html --font-mobile-button) and the Aqua Blue fill style resolves to #007bc2, already tokenized as Text/Link / --text-links. No design-tokens.html changes required.Status: Adopted 2026-07-06. figma-component-specs.json: entry added for provenance, tier screen-out, explicitly marked not-catalogued (no spec page created). login-screen-data-gap.html: cross-reference added alongside the existing "Forgot Password" note. No domain-controls.html / index.html / architecture.html changes (nothing to catalogue).
card-button: syncs figma-component-specs.json / screen-mapping.html to the already-adopted live-file node from DQ-5 (2026-06-22); adds a data point to the existing "CTA button height: TBD" gap4104:5451 (component_set, file psH738AqHDxuMyFm897f9r) as an uncatalogued second card-button master alongside the existing D14 entry (5452:434, dated-snapshot id from the 2026-06-17 .fig capture, size: null). Cross-check found this is not a new component — 4104:5451 is the exact node button-card-component.html and design-tokens.html already cite as the source for card-button's confirmed dimensions, resolved back on 2026-06-22 as DQ-5 ("Live Figma metadata (node 4104:5451) confirms two variants — Default 353×189px, Variant2 353×129px"). This is the same dated-snapshot-vs-live-file split as D46's button pill case: the .fig binary capture assigned card-button the id 5452:434; the live MCP capture assigns the same component 4104:5451. figma-component-specs.json's top-level entry and screen-mapping.html's registry were simply never updated when DQ-5 resolved elsewhere — an internal-consistency gap, not an open design question about identity.button-card-component.html already carries an open CTA button height: TBD row. get_screenshot on 4104:5451 shows both variants' live example content (an "Accident Hotline" icon+title+body card with a plain bottom divider) with no distinct embedded button/action-label element visible — differing from D14's original description of instances following a "description paragraph + short action label" pattern (e.g. "Secure your flight with Jumpseat. / Jump Seat"). This doesn't reopen the node/size identity (independently confirmed by 3 sources: DQ-5, design-tokens.html, button-card-component.html) but is worth the design team's attention when the existing CTA-button-height TBD is resolved — the master's own example content may simply be generic placeholder that real instances retarget, or the CTA button may only render conditionally.Status: Adopted 2026-07-13. figma-component-specs.json: D14's card-button entry size filled in (353×189 Default / 353×129 Variant2, from the already-confirmed live node); _flag converted to _note citing D50 and the live node id. screen-mapping.html registry node updated from the stale 5452:434 to 4104:5451. button-card-component.html and architecture.html: stale 5452:434 references normalized to cite 4104:5451 as the live/authoritative node, with 5452:434 kept as the original dated-snapshot provenance. design-tokens.html already correct — no change needed. Remaining open item (pre-existing, not new): button-card-component.html's own CTA button height: TBD row — now with the added observation that the live example content shows no visible CTA element.
Figma Phase 1 capture pass triggered by a full intake sweep (Figma/Teamwork/Comms). Live file psH738AqHDxuMyFm897f9r. Of 5 items an initial scouting pass flagged as new/uncatalogued, 4 were false positives — the scouting pass compared only against this file's capped _resync.dispositions summary table (D32/2026-06-17) instead of the full JSON body, which already carried D33–D49 (resolved through 2026-07-06). banner=D43, expander-flight finder-advanced search=D44, search secondary buttons=D42, and button link=D49 were already resolved; the flight-finder-filter-and-sort duplicate was already resolved (D41); the button-pill naming question was already resolved (D46); all 5 foundation pages plus Icons were already captured in foundations-reconciliation.html (2026-06-29/06-30); all 3 MEC per-airline pages were already re-checked (2026-07-06). The fifth item, card-button, was likewise a false alarm on "new master" but surfaced a real doc-sync gap (D50).
card-button — figma-component-specs.json / screen-mapping.html synced to the live node (4104:5451) already adopted elsewhere via DQ-5 (2026-06-22); no new component, no naming collision. Added a data point to the pre-existing "CTA button height: TBD" gap.figma-component-specs.json (D14 entry) · this record (D50) · screen-mapping.html · button-card-component.html · architecture.html.button-card-component.html's pre-existing CTA-button-height TBD carries one added observation; not a blocker introduced by this sync.EmptyState atom implemented: corrects the 2026-06-29 spec-only draft's Icon/CTA/target-project shape against real usage (WI-2255)EmptyState as one of two shared atoms (alongside StatusBadge) that standalone domain controls compose. A spec-only draft was written the same day, before any real consumer was audited. Implementation (WI-2255, Component Library Reconciliation follow-up) found and fixed three shape mismatches between that draft and the 4 real hand-rolled consumers it was meant to dedupe (FavoritesPage.razor ×2, JumpseatSavedFlightsPage.razor, JumpseatSavedSearchesPage.razor, DocumentsListPage.razor ×2):DocumentsListPage.razor instances) render no icon at all. The original draft's Icon: string (required) would have forced a placeholder on those.RenderFragment? ChildContent slot, not a fixed CtaLabel/OnCta button pair. None of the 4 real consumers use a CTA today — a fixed button API would have been unvalidated speculation. The generic slot matches the already-shipped Expander.razor content-slot precedent (same codebase, same pattern, already proven).ALPAMobile/Components/Library/ (the actual live Blazor component location, globally imported via _Imports.razor's @using ALPADocs.Components.ViewModels) — not ALPAMobile.HybridUi. (Clarified 2026-07-15: HybridUi is the pre-development prototype codename for the shared component-library RCL, used throughout the planning-era spec docs' "Target home" lines — not an authoring error as this entry originally implied. No project by that name was ever created; the RCL materializing under that plan is ALPAMobile.Presentation per D20/WI-2254.) This atom follows the current dual-write convention pending WI-2253's broader namespace-consolidation decision. (Consolidation landed: since D61 (2026-07-15, WI-2254) the single home is ALPAMobile.Presentation — Components/Library/ for razors, ViewModels/ for the ViewModels — and the dual-write convention is retired.)StatusBadge, EmptyState"). Corrected to scaffold/atom to match D40.docs/AB1821-intake-sync-01).Status: Adopted 2026-07-14. EmptyStateViewModel added to both ALPAMobile/Components/ViewModels/ComponentViewModels.cs (live) and ALPAMobile.Presentation/ViewModels/EmptyStateViewModel.cs (staged copy). EmptyState.razor added to ALPAMobile/Components/Library/, registered in ComponentView.razor's dispatcher. .alpa-fav-empty* CSS renamed to .alpa-empty-state* in alpa-components.css (4 consuming pages rewired, 0 remaining references). Spec: empty-state-component.html corrected in place.
StatusBadge atom implemented for the 6 light-surface consumers; dark-surface fdetail-badge flagged, not merged (WI-2255)StatusBadge as one of two shared atoms. A spec-only draft was written the same day, before real consumers were audited, and included a Size parameter (Sm/Md) meant to let one Kind-driven component cover both the light-surface alpa-policy-badge* pages and the dark-surface alpa-fdetail-badge* usage on JumpseatDetailsPage.razor.Kind=Kcm resolves to a different colour depending on surface — light-surface Kcm is a green tint (#e8f5e9/#2a8a4c, "eligibility marker" meaning), but the dark-surface "KCM Eligible" badge on the flight-details hero is solid blue (var(--alpa-blue)/white). The same enum value would need to mean two different colours depending on a second parameter (Size) — this breaks the component's own stated principle ("a badge's colour is chosen by its meaning") and the D40 rule it exists to enforce ("never reuse another domain's badge class for its colour"). Making Size silently override Kind's colour would reproduce the exact drift this atom is supposed to eliminate.StatusBadge for the 6 real light-surface consumers only (JumpseatAirlinePoliciesPage, AdvocacyPage, MecEventsPage, MecHotelsPage, KcmAirlinesPage, KcmPoliciesSearchPage — 8 badge usages total). The dark-surface alpa-fdetail-badge* usage (JumpseatDetailsPage.razor, 2 badges: "KCM Eligible" + a plain date chip) is left untouched, not merged in. Size is not implemented.MarginTop bool parameter, replacing the scaffold's alpa-policy-badge--mt spacing modifier (used by 2 of the 6 real consumers, MEC Events and MEC Hotels).Status: Adopted 2026-07-14. StatusBadgeViewModel + StatusBadgeKind enum added to both ALPAMobile/Components/ViewModels/ComponentViewModels.cs (live) and ALPAMobile.Presentation/ViewModels/StatusBadgeViewModel.cs (staged copy). StatusBadge.razor added to ALPAMobile/Components/Library/, registered in ComponentView.razor's dispatcher. .alpa-policy-badge* CSS renamed to .alpa-status-badge* in alpa-components.css (6 pages rewired, 0 remaining references). .alpa-fdetail-badge* untouched. Spec: status-badge-component.html corrected in place.
ActionColumn atom built for the first time; not a fixed heart/bell/X trio (WI-2257)JumpseatRecentSearchesPage.razor, JumpseatSavedSearchesPage.razor, JumpseatSavedFlightsPage.razor) found each has exactly 2 action buttons, but the pair differs per page — Recent Searches: heart-toggle "save" (♡/♥) + "search again" (↻); Saved Searches: static-heart "remove" (♥, no toggle) + "search again" (↻); Saved Flights: bell-toggle "alert" (🔔/🔕) + "delete" (✕). No fixed heart+bell+X trio exists anywhere. The atom is modeled as a generic Items collection instead — each consumer supplies its own icon, label, and click behavior via an ActionKey string, rather than the component prescribing named slots.alpa-saved-card-action-btn* and a byte-identical duplicate under alpa-saved-flight-card-actions) plus a colour/size coupling (--delete was both red and smaller) that doesn't generalize — a future compact-but-blue or full-size-red action wasn't expressible. Renamed to alpa-action-column* with Variant (Neutral/Accent/Highlight, colour) and Compact (size) as independent, orthogonal parameters.ActionColumnViewModel/ActionColumnItemViewModel, ActionColumn.razor, CSS. The 3 consumer pages are not rewired here; that happens in the WI-2256 follow-up (Saved Search Card / Saved Flights), so the click-behavior wiring lands together with the domain-control that owns it rather than being split across two commits.Status: Adopted 2026-07-14. ActionColumnViewModel + ActionColumnItemViewModel + ActionColumnItemVariant enum added to both ALPAMobile/Components/ViewModels/ComponentViewModels.cs (live) and ALPAMobile.Presentation/ViewModels/ActionColumnViewModel.cs (staged copy). ActionColumn.razor added to ALPAMobile/Components/Library/, registered in ComponentView.razor's dispatcher (display-only — OnItemClicked isn't wired through that path). .alpa-saved-card-action-btn* / .alpa-saved-flight-card-actions CSS renamed to .alpa-action-column*. Spec: action-column-component.html. Amended by D54 — Icon's type changed from a raw glyph string to a typed asset enum.
Icon: raw glyph/emoji → typed SVG mask-icon asset (amends D53, WI-2257)color), an emoji carries its own fixed multicolour rendering and cannot follow ActionColumnItemVariant — it read as a stock emoji dropped into a flat-icon design system, not a matching icon.TopNav.razor's alpa-top-nav-icon--*, the favorite heart (heart.svg / heart-selected.svg) on Hero/CardSmall/Button/Card — icons are exported Figma vectors applied via CSS mask-image + currentColor, so a single asset recolors per state/variant. The exported set already has exact matches confirmed against asset-inventory.html: heart.svg/heart-selected.svg (save), notifications.svg (alert — same asset TopNav uses), search-history.svg (documented there as "Recent-search affordance" — confirmed by its own aria-label="Search History", a magnifying glass + circular-arrow glyph), trash.svg (delete — the same i-trash referenced in the original saved-search-card spec's swipe-affordance note).ActionColumnIconAsset enum: Heart, HeartSelected, Notifications, SearchHistory, Trash — replaces ActionColumnItemViewModel.Icon's string type. This narrows D53's "fully generic" framing slightly (Icon is now a closed set, not arbitrary text) but that's a closer match to how every other icon in the app works — a governed set of exported vectors, not free-text glyphs — and keeps ActionKey/Title/Variant/Compact exactly as generic as D53 specified.Notifications has no separate "off" asset in the exported set (unlike heart's two-asset on/off pair) — the off state is colour-only (Neutral variant, same glyph). Documented interim limitation, same pattern as StatusBadge's D52 fdetail-badge caveat.mask-size: contain, which renders inconsistently across icons because the exports have slightly different aspect ratios (heart/bell/trash 22×22, search-history 21×23) and are edge-tight — each scales to fill its own viewBox at a different apparent size. Switched to the exact fixed-pixel-size technique alpa-top-nav-icon already uses (mask: var(--icon) center / 18px 18px no-repeat, 12px for the --compact modifier) — confirmed live, all four icons now read as one consistent size.Status: Adopted 2026-07-14. ActionColumnIconAsset enum added to both ViewModel copies; ActionColumnItemViewModel.Icon retyped from string to the enum. ActionColumn.razor renders a masked <span> per item instead of glyph text. CSS: .alpa-action-column-icon + 5 --{asset} modifier classes added, using the alpa-top-nav-icon fixed-mask-size technique. All 3 Jumpseat pages (Recent Searches, Saved Searches, Saved Flights) updated to the new enum values. Live-validated on iOS simulator — all icons render correctly sized and themed, toggle/remove/delete behavior unchanged.
SavedSearchCardViewModel (Recent Searches + Saved Searches — a saved query, D40) and SavedFlightCardViewModel (Saved Flights — a saved flight). Both compose the shared Action Column atom instead of exposing named OnToggleSave/OnReSearch parameters as the original spec drafted — each page builds its own 2 ActionColumnItemViewModel entries and a single OnActionClicked callback switches on ActionKey, matching D53's generic-Items design rather than hard-coding per-page action semantics into the card itself.FlightCardView is a native XAML ContentView (Track B) — ALPAMobile/Components/ContentViews/FlightCardView.xaml — with no Blazor port anywhere in the codebase. Composition is not mechanically possible today. SavedFlightCardViewModel is a standalone Blazor implementation instead, matching the existing hand-rolled alpa-saved-flight-card shape exactly. Documented deviation — reconcile once Flight Segment Card gets its own Blazor port (separate, unscoped work); this spec's "Related — Saved Flights" section is corrected in place to describe what is actually implemented rather than the original composition plan..alpa-saved-flight-card-actions (byte-identical to .alpa-action-column, flagged as deferred cleanup in D53) removed now that the page composes <ActionColumn> directly — no wrapper class needed.ComponentView.razor's dispatcher: unlike the shared atoms (EmptyState, StatusBadge, ActionColumn), these two are page-composed domain controls with no ItemComponentFactory-driven dynamic-feed path — all 3 Jumpseat lists are local hardcoded mock records (no SavedSearch/SavedFlight domain type exists), same pattern D40 and D42/D43 established for other domain controls. Matches how PromoBannerViewModel/SearchSecondaryButtonViewModel — which are dispatcher-registered — are driven by real ItemComponentFactory data, a path these controls don't have yet.Status: Adopted 2026-07-14. SavedSearchCardViewModel + SavedFlightCardViewModel added to both ALPAMobile/Components/ViewModels/ComponentViewModels.cs (live) and ALPAMobile.Presentation/ViewModels/ (staged copies). SavedSearchCard.razor + SavedFlightCard.razor added to ALPAMobile/Components/Library/. JumpseatRecentSearchesPage.razor, JumpseatSavedSearchesPage.razor, JumpseatSavedFlightsPage.razor rewired off hand-rolled markup onto the new components. Live-validated on iOS simulator — toggle-save, remove, search-again, toggle-alert, and delete all confirmed working on-device. Spec: saved-search-card-component.html, corrected in place.
ALPADocs.Components.ViewModels (legacy, live — every .razor renderer, ItemComponentFactory.cs, FavoritesViewModel.cs, and the unit tests bind to this copy) against ALPADocs.Presentation.ViewModels (staged, target RCL — confirmed zero .razor/production consumers) found the two had diverged well beyond what WI-2253's original finding described. Reconciled the staged copy to match legacy in every case below — legacy is authoritative; the staged copy's differences were incomplete/stale drift, not deliberate improvements to carry forward.SurfaceViewModel/ContainerSurfaceViewModel had retyped BackgroundToken/Padding from string?/double to ThemeColor/SpacingToken enums. This reads like a "no magic strings" upgrade, but D26 (adopted 2026-06-24) already resolved BackgroundToken as a semantic token-name string (e.g. "Surface/Brand") specifically so XAML (IThemeResolver.Resolve(token)) and CSS (token.ToCssVar() → var(--surface-brand)) can both resolve the same value from an open, server-driven, per-MEC token set — a closed C# enum can't represent that. Same pattern as D38's CornerRadiusToken. The staged enums are reverted to legacy's string/double shape, not adopted.CardViewModel abstract and dropped ContentText entirely (pushing Title/Image onto the base instead). ItemComponentFactory.cs directly instantiates plain CardViewModel today — it cannot be abstract. Legacy's shape (concrete, HeaderText/ContentText/LinkText/Link, no Title/Image on the base) restored. IsLinkVisible reverted from staged's narrower Link-only check to legacy's Link OR LinkText — this exact property was already the subject of a docs-consistency fix earlier in this session's intake pass (pilot-card IsLinkVisible ASCII-diagram correction), reinforcing legacy's logic as the deliberately-audited one.HeroCardViewModel : DocumentCardViewModel : CardViewModel) by redeclaring Eyebrow/Description/AccentColor directly on HeroCardViewModel. It's explicitly named in the documented scaffold tree (D6), not incidental — recreated as its own file, HeroCardViewModel reverted to an empty terminal subclass.Title/Image down into DocumentCardViewModel, CardTextViewModel, and CardSmallViewModel individually rather than hoisting them onto CardViewModel. Staged's CardTextViewModel and CardSmallViewModel were relying on the (now-removed) base-class Title/Image — added directly to each, matching legacy. CardSmallViewModel.IconMaskUrl was missing entirely from staged; added — FavoritesViewModel.cs sets it and UnitTest/FavoritesViewModelTests.cs asserts it, a real functional gap, not a style choice.CarouselViewModel.Header (staged's rename of Title — no backing decision; ItemComponentFactory.cs and the D21-era decision text both use Title live today) reverted to Title. ButtonGroupViewModel.Title and ToggleSwitchViewModel.IsEnabled (staged-only additions with no corresponding legacy property, no consumer, no decision) removed. ButtonViewModel's local IsEnabled redeclaration and new ICommand? TapCommand re-exposure removed — both become redundant once the shared ComponentViewModel.IsEnabled exists and SurfaceViewModel.TapCommand is inherited normally, matching legacy exactly.GridContainerViewModel (D25), StackContainerViewModel (D30), ActionButtonViewModel, WelcomeBannerViewModel — none had any staged counterpart at all. Added as new files, verbatim from legacy.ComponentViewModel gained Id : string? (server-assigned reconciliation key — cache diffing, favorites, reorder persistence) and IsEnabled : bool, both missing from staged.ALPAMobile.Presentation/ViewModels/* — no legacy-copy changes (legacy is authoritative and live), no .razor file moves, no RCL-project conversion. A follow-up investigation found ALPAMobile.Presentation isn't actually configured as a Razor Class Library yet (no RCL SDK, no Microsoft.AspNetCore.Components package refs, zero .razor files exist there today) — that prerequisite conversion, plus the actual renderer migration, is deferred as separate work (WI-2254, description updated with this finding).Status: Adopted 2026-07-14. All edits confined to ALPAMobile.Presentation/ViewModels/*. dotnet build clean on both ALPAMobile.Presentation.csproj and ALPAMobile.csproj (net10.0-ios); dotnet test UnitTest/UnitTest.csproj — 468/468 passing (the one test file touching either namespace, FavoritesViewModelTests.cs, binds only to the untouched legacy copy). No simulator validation needed — the staged copy has no .razor consumers yet, so nothing renders from it today; it is now correctly shaped and ready for whenever WI-2254 wires it up.
Favorite.ItemTypeId (int — FavoriteItemTypes.MenuItem=1, Document=2, NotificationCenterMessage=3) + Favorite.ItemId (string) is the confirmed discriminator + reference pair (ALPAMobile.Domain/Data/Models/Favorite.cs). FavoritesViewModel.HydrateAsync resolves it via IMenuQueries.GetMenuItemAsync / IDocumentsQueries.GetDocumentByFileIdAsync and hand-builds CardSmallViewModel/HeroCardViewModel — working in production today, just not extracted into a reusable factory (that inline logic is the WI-2262 starting point for the adapter layer, not a gap in the contract itself).ItemDto, a server-pre-flattened "flat bag, not a union type" (dynamic-feed contract §3.3) discriminated by ItemDto.ItemType — not a raw MenuItem/DocumentItem passthrough. ItemComponentFactory.Create(ItemDto) already handles every component type in §4's mapping table. Whatever turns a MenuItem/DocumentItem into an ItemDto happens server-side — not a client adapter-layer concern.MenuItem/DocumentItem content outside both Favorites and the dynamic feed (e.g. a new MEC Blazor page listing real documents by Scope/Category directly). This is WI-2262's real remaining scope: extract FavoritesViewModel.HydrateAsync's inline MenuItem→CardSmallViewModel / DocumentItem→HeroCardViewModel mapping into reusable, named factories in ALPAMobile.Presentation or .Application, so both Favorites and any future direct consumer call the same code.HeroCardViewModel (subclasses DocumentCardViewModel) contradicts "the one adapter explicitly designed in the docs, DocumentHeroFactory ... design decision D11." DocumentHeroFactory is not named anywhere in this record (naming-decisions-record.html) — that specific attribution to a decision entry doesn't exist. It is documented elsewhere (architecture.html, domain-controls.html, card-hero/card-hero-property-mapping.html) — but under the stale pre-rename type name CardHeroViewModel, not the shipped HeroCardViewModel. That's a separate, larger, pre-existing gap: a 2026-06-10 "type-first" rename decision (CardSmall / CardHero / CardText) was recorded with an explicit "docs need the rename ... follow-up pass" note that was never carried out — roughly a dozen spec files (including a dedicated card-hero/ pair) still say CardHeroViewModel against code that ships HeroCardViewModel. Out of scope for this pass; tracked separately, not by WI-2262. D11's composition-not-subclass rule is scoped to domain controls (its own example: Pilot Card = (UserInfo + DocumentItem) → CardViewModel via a mapper) — it does not govern the scaffold's own internal tier hierarchy. HeroCardViewModel : DocumentCardViewModel : CardViewModel is scaffold architecture under D6 ("Content cards derive from CardViewModel"), independently reconfirmed in D56 as "explicitly named in the documented scaffold tree (D6), not incidental." HeroCardViewModel subclassing is correct as shipped — no reconciliation needed there. Domain controls that compose the scaffold (Pilot Card, Saved Search Card per D55) remain correctly composition-based; the two patterns were never in conflict.Status: Adopted 2026-07-15. docs/component-specifications/content-endpoint-contract.html §7 updated in place with these findings and code citations; status line updated. The adapter/factory implementation this cleared the way for landed the same day — see D58. Amended by D59: this entry's references to the "shipped name HeroCardViewModel" were accurate when written; the D4-revision rename was executed later the same day, so the shipped type is now CardHeroViewModel.
FavoritesViewModel.HydrateAsync's inline content→component mapping extracted into two reusable factories in ALPAMobile/Components/, following ItemComponentFactory's exact precedent (sealed class, DI singleton, pure mapper — no IServiceProvider, no async; the data lookup stays with the caller, per the RawRepresentationFactory rule and D11).MenuItemCardFactory: MenuItem → CardSmallViewModel — carries the web-servable-image check (native-only ImageSource names never reach the WebView <img>) and the MenuItemIconMask fallback so a menu-backed tile never renders an empty image box. The bare MenuItem type name is aliased in the factory (using MenuItem = ALPADocs.Data.Models.MenuItem) — the MAUI global usings make it ambiguous with Microsoft.Maui.Controls.MenuItem in the head project.DocumentHeroFactory: DocumentItem → HeroCardViewModel, plus a static OpenRoute(fileId) that owns the /document-open?fileId= policy — previously duplicated verbatim in FavoritesViewModel and DocumentsListPage.razor (the P1 never-a-raw-Path-href rule now has one home). Create's link parameter is caller-supplied because fileId keying differs per context: Favorites keys by Favorite.ItemId, direct consumers by DocumentItem.FileID.ALPAMobile.Presentation copy. The live component-library ViewModels the factories construct are head-only types (the same reason ItemComponentFactory and FavoritesViewModel live in the head). The dual-write convention used for ViewModels (D51–D55) was not extended to these factories: unconsumed staged copies are exactly the drift surface D56 spent a pass cleaning up, and ItemComponentFactory — the closest precedent — has no staged copy either. The factories migrate to Presentation together with the renderers in WI-2254.FavoritesViewModel (both mappings + route builder; two new ctor dependencies, DI-registered as singletons next to ItemComponentFactory) and DocumentsListPage.razor's ResolveHref (route builder only — its row mapping targets ListViewModel, not a hero card, and stays as-is). The native-XAML DocumentsListPageViewModel.PopulateDocumentList named in WI-2262's evidence is not rewired: it maps to UnreadListItemViewModel (a native list-row VM with an embedded TapCommand) — a different output tier with behavior baked in, not servable by a pure component-library mapper.Status: Adopted 2026-07-15. dotnet test 474/474 passing (6 new factory-contract tests in ContentComponentFactoryTests.cs; the 16 existing FavoritesViewModelTests pass unchanged as the behavior-preservation net). Live-validated on iOS simulator against production data: favorited a real document from /home-preview → hydrated on /favorites as a full hero card with link /document-open?fileId={escaped GUID} → removed → empty state restored. The MenuItem path is unit-covered (7 tests); no menu-favorite affordance exists in the scaffold UI to drive it live. docs/detail/N-TIER-ARCHITECTURE.md gained a "Content-to-Component Mapping" section and its Phase 5 status was corrected from "pending" to "staged" (WI-2262 item 4). Amended by D59: the HeroCardViewModel type this entry references was renamed to CardHeroViewModel later the same day. Amended by D61: the "head-only placement" in this entry's title was the placement at adoption time; WI-2254 executed 2026-07-15 and the factories now live in ALPAMobile.Presentation/Components/.
HeroCardViewModel → CardHeroViewModel, HeroCard.razor → CardHero.razor); leftover pre-rename doc directories deletedCardSmall / CardHero / CardText) and recorded a "follow-up pass" to carry the rename through — which never happened for the hero card. CardSmallViewModel/CardTextViewModel shipped type-first, but the hero shipped and stayed HeroCardViewModel/HeroCard.razor, forking the family and leaving ~13 spec docs (already written type-first per the decision) contradicting the code. Resolved by executing the recorded decision in code rather than back-dating the docs to match the drift: HeroCardViewModel → CardHeroViewModel (10 files: ComponentViewModels.cs, the staged Presentation copy, ItemComponentFactory, DocumentHeroFactory, FavoritesViewModel, ComponentView.razor, ComponentSkeleton.razor, FavoritesPage.razor, tests) and HeroCard.razor → CardHero.razor (tag usages updated). Wire-format strings were untouched — ItemDto.ItemType discriminators ("SingleButtonLarge"/"DocumentHero") are quoted literals outside the type rename.hero-card/, small-card/, text-card/ lingered alongside the canonical card-hero/, card-small/, card-text/ (byte-identical modulo names for small/text; for hero, the old dir was the stale pre-revision snapshot while card-hero/ carried the 2026-06-10 typography revisions). All three deleted. index.html had grown contradictory dual entries for the hero spec — one marking card-hero/ "legacy, superseded" (backwards: it was the newer revision) and one presenting the stale hero-card/ snapshot as current — consolidated to a single Card Hero entry pointing at card-hero/.architecture.html (which additionally carried stale TextCardViewModel/SmallCardViewModel for the two classes that had already shipped type-first), page-compositions.html, scaffold-component-reconciliation.html, content-endpoint-contract.html, ui-refresh-1821/component-library-plan.html, detail/N-TIER-ARCHITECTURE.md, this record's D6 tree annotation, and the D4-revision follow-up note (whose rename arrows were also a transcription slip — both sides listed the post-rename name). Historical decision text (D26/D40/D56/D57/D58 bodies) left as written; D57/D58 carry amendment notes. naming-alignment-report.html (dated 2026-06-26 report) left as a dated snapshot.card-hero → wire type "HeroCard", but ItemComponentFactory matched only "SingleButtonLarge" or "DocumentHero". This entry originally flagged it as needing backend coordination — over-cautious: no backend implements the feed yet (AB#2133 undelivered), and the dynamic-feed contract's own §5 alias note already prescribed the fix (canonical values + legacy aliases in the factory). Executed as D60.Status: Adopted 2026-07-15. dotnet test green after the rename (474/474 — the suite builds the head project including the razor tag rename). Completes the D4-revision follow-up recorded 2026-06-10 and closes the naming fork surfaced by the 2026-07-15 doc-consistency audit.
"HeroCard" had only legacy aliases ("SingleButtonLarge"/"DocumentHero"); "CardText" and "ButtonCard" had no case at all and threw NotSupportedException — the contract's own §6 example payload (which uses ButtonCard six times) would have crashed the shipped factory. No backend coordination was required: the feed endpoint doesn't exist yet (AB#2133), and the contract's §5 alias note already prescribed exactly this fix ("the new endpoint should use the canonical values ... the factory will continue to support the legacy aliases for transition").ALPAMobile/ApiModels/ItemDto.cs was the prototype wire shape (Title/Blurb/CtaText/CtaLink/Icon); the contract §3.3 defines a richer canonical shape. Added the 7 missing nullable fields (HeaderText, Description, Eyebrow, Label, Image, Link, LinkText) — backward-compatible additions; Blurb/Icon retained and documented as legacy → Description/Image transition fields.Description ?? Blurb, Image ?? Icon, Link ?? CtaLink per-type), so both payload generations render identically. New arms: "HeroCard" (joins the hero alias set, now also maps Eyebrow/Link), "CardText" → CardTextViewModel (Label · Title · Description · Link), "ButtonCard" → ButtonCardViewModel (HeaderText · Description→ContentText slot · composed CallToAction ButtonViewModel, null when no CTA fields present).Button/EmergencyButton rows listed Title/Link as the label/target fields, contradicting §3.3's own field comments (CtaText — "CTA button label (ButtonCard, Button)") and the shipped factory. Rows corrected to CtaText/CtaLink (+ Image for the icon, with legacy Icon fallback)."Feed"/"Slider*"/"Carousel" as an ItemType, while the contract models carousels as containers (ContainerDto.containerType) — a prototype-era flattening the mocks depend on. Restructuring to the three-level container model is real AB#2133-integration work, not a naming fix; covered by the §5 alias note until then.Status: Adopted 2026-07-15. dotnet test 505/505 — every §5 canonical discriminator and every legacy alias is pinned in UnitTest/ItemComponentFactoryTests.cs, including canonical-vs-legacy field-precedence cases. Contract doc updated in place (§3.3 shipped-DTO note, §5 row fix + implementation note, status line).
ALPAMobile.Presentation RCL; ViewModel dual-write retired (this is the library planning-era docs call the "scaffold PCL" and prototype-era docs call "ALPAMobile.HybridUi")ALPAMobile.Presentation converted from Microsoft.NET.Sdk to Microsoft.NET.Sdk.Razor (a Razor Class Library), with Microsoft.AspNetCore.Components.Web pinned at 10.0.0 — the exact version the head already resolves transitively through Microsoft.AspNetCore.Components.WebView.Maui, so no version drift. All 25 Library razors (including ComponentView.razor, the dispatcher) moved via git mv to ALPAMobile.Presentation/Components/Library/; the three content→component adapter factories (ItemComponentFactory, MenuItemCardFactory, DocumentHeroFactory), MenuItemIconMask, and FavoriteToggleContext moved to ALPAMobile.Presentation/Components/.ALPADocs.Presentation.ViewModels → ALPADocs.Components.ViewModels and the head's live ComponentViewModels.cs bundle was deleted in the same change. Every consumer (28 pages, factories, tests, _Imports.razor) kept compiling untouched because every type kept its namespace — only the defining assembly moved. Member-level parity across all 34 types (32 classes + enums) was verified programmatically before the cutover. This resolves WI-2253's duplicate-namespace finding for the component set and retires the D51–D55 dual-write convention.ItemDto → ALPAMobile.Application/ApiModels/: the dynamic-feed wire contract now lives beside the Application query ports (namespace ALPADocs.ApiModels unchanged) — unblocking ItemComponentFactory's migration and establishing where WI-2270's PageDto/ContainerDto will land.FavoriteToggleContext retargeted to the port: its constructor now takes IFavoritesQueries (Application) instead of the head's FavoritesQueriesRouter; the router implements the interface, so call sites pass unchanged.AlpaScreen.razor (composes head-only TopNav/BottomNav and injects ContentQueriesRouter — page chrome, not a library atom) and FavoritesViewModel (injects the head's *QueriesRouter Mock/Live services). Both follow when their dependencies gain ports.Status: Adopted 2026-07-15. dotnet test 525/525 (including the N-Tier architecture suite — the RCL stays MAUI-free and Infrastructure-free). Live-validated on iOS simulator across 15 routes (/home, /home-preview, /favorites, jumpseat search/saved/saved-searches, /kcm, /mec, /mec/events, /documents, /notifications, /advocacy, /customize-nav): all render with no Blazor error bar; /home and Saved Searches visually spot-checked pixel-correct.
FavoriteItemTypeId); FavoriteKey composite ItemId convention adopted; DocumentCategory favorites (type 4) implemented client-firstComponentViewModel gains a nullable FavoriteItemTypeId, stamped by the adapter that knows the content source: MenuItemCardFactory → MenuItem (1), DocumentHeroFactory → Document (2), the new CategoryCardFactory → DocumentCategory (4), and ItemComponentFactory passes ItemDto.FavoriteItemTypeId through from the feed. ComponentView now reads the ViewModel's declared type with per-component historical defaults instead of hardcoding a favorite type per visual component. Rationale: a CardSmall can render a menu item or a document category — the visual component type must not imply the content's favorite identity; the adapter layer owns content identity (the WI-2262 principle).FavoriteKey — the wire ItemId convention: a new Domain helper (beside FavoriteItemTypes) owns the composite {typePrefix}-{rawId} convention matching the backend's production Favorites rows (menu-/doc-/msg-, new cat-). Composition happens at the favorites boundary (FavoriteToggleContext on toggle/membership checks; FavoritesViewModel parses on hydration); ViewModels and adapters carry raw ids. Parsing splits on the first dash only (document fileIds are GUIDs and contain dashes) and tolerates legacy un-prefixed values (they pass through unchanged and degrade to a failed lookup, never a crash). This closes the bare-id mismatch — the pre-convention client stored raw ids, which would not have matched backend/legacy-written composite rows at the AB#2237 mock→live flip.FavoriteItemTypes.DocumentCategory = 4 (backend handoff pending — the live API must accept the type before the flip). Raw id is {scope}|{categoryKey}, owned by CategoryCardFactory (which also owns the /documents?scope=&category= deep-link shape). FavoritesViewModel gains the category hydration arm — resolving the friendly name via CategoryItem.DisplayTitle from the GetDocuments tree, degrading to unresolved when the category is gone or the raw id is malformed — and the new FavoriteItemKind.Category renders the card-sm row on FavoritesPage alongside menu-item favorites. The home-preview quick-list category rows now carry Id + FavoriteItemTypeId and render hearts when authenticated.Status: Adopted 2026-07-16. dotnet test 544/544 (19 new pins: FavoriteKey compose/parse incl. GUID raw ids and legacy tolerance, FavoriteToggleContext composite storage and backend-row matching, CategoryCardFactory mapping and raw-id round-trip, factory stamping, ItemDto passthrough, category hydration resolved/gone/malformed arms). Live-validated on iOS simulator against the mock-pinned favorites store: 6 category hearts render on /home-preview when authenticated, toggle → /favorites hydrates "Canada Limits" with its friendly name and /documents deep link → remove restores the empty state.
PageDto/ContainerDto → PageFactory/ContainerFactory); /home-preview leads (WI-2270)PageDto/ContainerDto land in ALPAMobile.Application/ApiModels/ beside ItemDto; the new ContainerFactory (ALPAMobile.Presentation/Components/) dispatches the §4/D28 discriminators ("Carousel" / "Grid" / "ButtonGroup" / "Stack") onto the D28–D30 ContainerSurfaceViewModel subclasses, delegating every item to ItemComponentFactory; unknown container types skip (TryCreate → null) per the §4 forward-compatibility rule — one level up from CreateAll's per-item degrade. PageFactory maps the root onto the new PageViewModel (ALPADocs.Components.ViewModels — the component-tree root a presenter binds to, not a page presenter).IHomeFeedQueries Application port (GetPageAsync(pageId) → PageDto?) behind HomeFeedQueriesRouter, mock-pinned (ScaffoldDataSource.HomeFeedUseLive = false — no live endpoint exists; AB#2133's route/auth are still open contract items). MockHomePreviewFeedService reshaped to the canonical PageDto — a Carousel container of HeroCard items (ALP MAG articles, DocumentHeroFactory.OpenRoute links, Document favorite identity) plus a Stack container of DocumentCategory quick-list rows (D62 identity intact) — and HomePreviewPage renders PageViewModel.Components./home-preview leads — /home and MockHomeFeedService stay on the legacy flat CreateAll path so other devs roll up on their own schedule; the flat path and the "Feed"/"Slider*" ItemType arms remain documented transition fallbacks. No container-aware skeletons — HomeSkeleton unchanged; loading polish is deferred to the real home page's design pass. ButtonGroup now exists as BOTH a ContainerType and the legacy ThreeUpButton ItemType producing the same ViewModel — canonicity ask recorded on AB#2270. Explicitly deferred: PageId-keyed caching + ?since differential (wire with the AB#2133 flip), user reordering UI (IsSortable respected render-side only), theme-token resolution (AB#2185–2187; tokens pass through as names).Status: Adopted 2026-07-17. dotnet test 552/552 (8 new pins: all four ContainerType discriminators, Grid 3-column default + D29 mixed items, ButtonGroup button-only filter as tiles, Stack server-order authority, unknown-container skip at both container and page level, D62 favorite identity riding through containers, and the contract §6-shaped camelCase end-to-end via AlpaWireJson). Live-validated on iOS simulator: /home-preview renders through the container path (4-hero carousel with resolved header, 6 category rows, 10 hearts through the cascade), category-favorite toggle→hydrate→remove round trip intact, /home legacy path unaffected.
FlightCard.razor + FlightCardViewModel + FlightCardFactory, inline expand supersedes the details page (AB#2299)FlightCardView : ContentView): the Blazor results/saved-flights pages now get their own Track A implementation of the flight card-og master (4713:19868, Default/swipe/expanded variants) — ALPAMobile.Presentation/Components/Library/FlightCard.razor with FlightCardViewModel (D16 display fields adapted to the live master) and the pure-mapper FlightCardFactory (FlightItineraryDto → VM; policy-route lookup injected as a delegate). Track B native remains untouched; the two tracks share the Figma master as source of truth./jumpseat/details page (audit FF-1) — results no longer navigate. Amended by D66 (2026-08-14): a dedicated Blazor detail screen now exists for tapped flight alerts. Results still expand inline exactly as decided here — what changed is that a push tap has somewhere better to land than a list.alpa-fcard-* subsystem prefix; the retired .alpa-flight-card* and .alpa-fdetail-* blocks are removed (the KCM airport-detail page's borrowed .alpa-fdetail-leg-* rows became KCM-owned .alpa-kcmad-*).FlightCard held open (ExpandLocked), addressed by schedule key (AB#2461, AB#2465)/jumpseat/details mock and concluded "results no longer navigate". That holds for the results list — cards still expand inline, unchanged. What D64 did not cover is the push entry point: a member who taps "DL5328 now departs from gate A17" was landing on a list of alerts and having to find the flight in it. New page JumpseatFlightDetailPage (/jumpseat/flight-detail) is where that tap goes.FlightCard through a new ExpandLocked parameter — the expanded variant with no chevron at either end, so the detail cannot be collapsed by a stray tap on a screen whose entire purpose is that detail. A page-specific layout was rejected: it would be a third place the master's anatomy has to be kept true (after FlightCard and the native XAML JumpseatFlightFinderDetailsPage), and the two would drift on the first design change. The lock is a render decision and never writes Vm.IsExpanded, so a VM shared with a results row leaves that row's own expand state alone./jumpseat/flight-detail/{Index:int} — a position in the results snapshot, meaningful only within the snapshot that produced it (an expired snapshot lands on the empty state, never a wrong flight). /jumpseat/flight-detail/alert/{ScheduleKey} — a tapped alert, which names its flight and knows nothing of any search: the key is looked for in the snapshot first, then in Saved Flights, where a subscribed flight always is because subscribing to alerts saves it. Snapshot-resolved flights share the cached card VM with the results row (save/alert state cannot diverge); saved-list flights get a page-local VM and fetch the airline reference list so the Jumpseat Policy link resolves as it does in results.FindFlightInfoAsync lookup the native JumpseatFlightFinderDetailsPageViewModel makes, on landing and on pull-to-refresh (new reusable alpa-pull-refresh.js; .NET is called once, on release past threshold). Added beyond native: a refresh on app resume, debounced 60s — a member who backgrounds the app at the gate and comes back is exactly who a stale gate or delay reading misleads. Refreshed itineraries are written back into the results snapshot so the row behind the page shows the same reading.FindFlightInfoAsync answers bare null for signed-out, offline, no-such-flight and a failed call alike, so every one of them read as "couldn't refresh" — a cancelled flight looked like an app failure. FlightStatusRefresh/FlightStatusCurrency (ALPAMobile.Presentation/Components) is a pure classifier that separates what device state can explain, through ports that already exist (IAuthentication, IConnectivityService) plus the itinerary's own arrival time. The residual pair — 503-exhausted vs genuinely-not-in-schedule — is filed as a bug against the shared service (AB#2463); the web-view leak found beside it is AB#2464.correlationKey is the fs-{ScheduleKey} audience it was addressed to. The visible-iOS hub registration template never carried that field (only Android and silent-iOS did), so an iOS tap had nothing to route by; added, and asserted by test across all three templates. A payload without it still degrades to the alert list.Status: Adopted 2026-08-14. Unit suite 944/944. Validated live on both platforms: a real notification tapped in the Android OS drawer opens the correct flight with the card held open and back landing on the Flight Finder alert tab; all three refresh paths (landing, pull, resume) captured on the wire against production; offline and failed-refresh status lines both walked with the network disabled and restored.
Scoped sync of 4 items surfaced by a gap-audit pass against the current figma-component-specs.json baseline, captured from the live Figma file (owEYzHf7FrHRvWC2u82UOl, "ALPA mobile app") via direct REST nodes calls (no .fig binary export available this pass — Dev-Mode annotation pins could not be extracted; noted as a gap, not fabricated).
button pill (node 4245:5754) — confirmed the same already-adopted Button (Pill) master; the docs' "pill button" spelling was a word-order transcription slip, not a Figma rename. No new tier/naming decision.Toggle - Switch (node 4128:7733) — genuinely new scaffold/atom, distinct from toggle-settings (which composes it as a child instance + label). Code name ToggleSwitch.flight card-saved & recent searches (node 4450:9884) — confirms the live node id for the already-adopted D21 consolidation; documents two sibling usage frames (4450:10395, 4450:11208) as per-context instances of this master, not separate masters — confirming retirement of the three stale 2026-06-17 placeholder entries.button link (node 5251:15088, "Forgot Member Number") — one-off Log In screen content, not catalogued, per D40. No new tokens (reuses existing Text/Link + Mobile Button Text).figma-component-specs.json (4 new entries) · this record (D46–D49) · design-tokens.html (new gap-sync table) · screen-mapping.html (registry + component inventory) · domain-controls.html (FlightCardSavedAndRecentSearches stub) · architecture.html (§6 cross-reference) · index.html (Toggle Switch atom card) · button-pill/button-pill-component.html (live node id) · login-screen-data-gap.html (Forgot Member Number cross-reference) · 1 new component spec page (toggle-switch)..fig binary export available). toggle-settings' own node id remains unconfirmed (null in the registry) — out of scope for this pass, tracked separately. No other open items.Scoped sync of 4 newly-discovered Flight Finder components from the live Figma file (owEYzHf7FrHRvWC2u82UOl, "ALPA mobile app") — distinct from the dated-snapshot file (psH738AqHDxuMyFm897f9r) that figma-component-specs.json's _resync block records as baseline. Not a full re-diff of the file.
flight finder filter and sort duplicate node resolved — 4450:11374 canonical, 4705:8702 flagged as a stale copy (byte-identical content; node-ID session evidence points to 4705:8702 being a later full duplicate).search secondary buttons — resolves D22's open TBD. Scaffold/atom tier (not domain-control), code name SearchSecondaryButton.banner — new component, provisional domain-control, code name PromoBanner. Domain source unconfirmed.expander-flight finder-advanced search — confirms the open "expanded content not shown" gap on the existing Expander primitive spec; new domain-control layer FlightFinderAdvancedSearch added on top of the unchanged Expander primitive.card-duty period confirmed retired (absent from the live file, 0 of 16 duty-period-name matches); card-duty period-alt confirmed present (filed under the "components ftdt" section, not the main Components section) — no change to existing Duty Period domain-control documentation, which already cited the correct node.figma-component-specs.json (4 new entries) · this record (D41–D45) · design-tokens.html (new components table + League Spartan flag) · screen-mapping.html (inventory + node registry + overlay cross-reference) · index.html · architecture.html · domain-controls.html · expander/expander-component.html (gap resolved) · sort-filter-bar/sort-filter-bar-component.html (cross-reference) · 4 new component spec pages (promo-banner, flight-finder-advanced-search, search-secondary-button, flight-finder-filter-sort)..fig binary export available — MCP/REST cannot surface Dev-Mode pins). The duplicate-node resolution (D41) was human-confirmed 2026-07-06 and is no longer open. Banner's domain source (D43) was confirmed 2026-07-06 (maps onto the existing PageBanner API) and its dismiss behavior was resolved the same day (client-side only, no backend involvement) — the one remaining open item is the required PageBanner field additions, tracked as Change 5.