App-wide component data requirements, API gaps, and model extension recommendations for the backend engineering team. Part 1 is the app-wide ask: the required API changes, the model rationale, and the rolled-up screen-set findings. Part 2 is the original Home deep dive, kept as an appendix.
MenuItem and DocumentItem serve distinct, non-overlapping roles across the app.
card-sm rows are MenuItem-backed navigation tiles.
card-lg feed cards are DocumentItem-backed content cards.
Keep them as separate models — neither should absorb the other's fields, and the two should
not be merged. The changes below are small, additive model extensions (nullable,
backward-compatible — not a merge): (1) add Rank (string, nullable) to UserInfo for
the Welcome card greeting, (2) add Description (string, nullable) to MenuItem for the
TextCard variant, and (3) a new shared, member-scoped Favorites capability keyed on
(itemType, itemId) — the one cross-control gap. Separately, (4) a new DYK/FAQ content
endpoint is required for the new "Did You Know?" feature (a new build, not a model change). See
Part 1 §1.
This report is supplemental to AB#1709 "Pilot Group Customizations — Consolidate data sources."
Several findings here are alignment opportunities for that effort: pilot identity is split across
UserInfo (auth: FirstName/LastName/MEC/LEC/BaseAirport) and Member
(/api/member/getuser: memberNumber/preferences/mecLinks), and neither carries pilot
Rank (Change 1). The per-pilot-group customizations map cleanly to the Mobile Menu API
(MenuItem) — the same single-source direction AB#1709 is pursuing — rather than spreading across
Member.MECLink / Preferences / hardcoded items. Use §1–§2 here as the alignment checklist.
This tracker is the canonical record of what has actually been communicated to the backend team — distinct from the technical analysis below, which captures what the mobile side has worked out regardless of whether it has been handed off yet.
| Change | Ask (one line) | Handoff status | Next step / owner |
|---|---|---|---|
Change 1 — UserInfo.Rank |
Add Rank (string, nullable) to the UserInfo auth/profile response for the Welcome card greeting. |
Handed off | In progress with backend team. |
Change 2 — MenuItem.Description |
Add description (string, nullable) to the MenuItem API response for the TextCard variant. |
Handed off | In progress with backend team. |
| Change 3 — Favorites API | New member-scoped favorites/bookmarks capability keyed on (itemType, itemId). |
Client delivered 2026-07-08 Gateway rollout pending | Shipped: MobileMenu.ApiClient 1.0.7, mobile side wired (WI 2182) — Favorites screen, card-library heart-toggle, item-unavailable fallback. Live validation 2026-07-09: the endpoints 404 on the production gateway — service not reachable there yet; mobile side verified against mock pending backend rollout. |
| Change 4 — DYK/FAQ endpoint | New endpoint returning the DYK/FAQ feed (net-new build). | Abandoned — no handoff | None — feature dropped 2026-06-24; kept here for the record only. |
Change 5 — PageBanner Title/Subtitle/LogoImageLink |
Add three additive/nullable fields to the PageBanner response for the PromoBanner design (D43). |
ON HOLD — pending design-intent confirmation (DQ-26) | Await the design answer to DQ-26 (is the structured banner a design requirement, or is a flat image per campaign adequate?). If a flat image is adequate, Change 5 is withdrawn; if structure is required, Change 5 proceeds as documented — backend team then pointed at the Change 5 handoff package section of this doc via the AB#1821 work item, no separate send. |
The cross-cutting backend work, ordered for action: the concrete API asks first, the model rationale behind them, then the screen-by-screen evidence and remaining questions. These apply across the whole app — the Home screen is detailed separately in Part 2.
Rank to UserInfo Blocking In progress — backend teamWhy: Figma design shows "Welcome Captain Johnson". Rank is a first-class design element, not metadata.
Change: Add Rank (string, nullable) to the UserInfo auth/profile API response.
Possible values: "Captain", "First Officer", "Second Officer", null (omit greeting prefix if absent).
Impact: Auth service only. The client reads UserInfo.Rank when composing the welcome card (the Pilot Card domain control, which maps onto a Card). If null, the greeting falls back to "Welcome {FirstName} {LastName}".
Mobile model: UserInfo.cs — add public string? Rank { get; set; }
Description to MenuItem Minor In progress — backend teamWhy: CardTextViewModel (backed by MenuItem) needs a description/subtitle field for the "Eyebrows" and "Simple" variants. card-sm itself doesn't show description, but TextCard does.
Change: Add description (string, nullable) to the MenuItem API response.
Impact: Menu API endpoint only. No schema-breaking change — nullable field, existing consumers unaffected.
Mobile model: MenuItem.cs — add public string Description { get; set; } = string.Empty;
Delivered 2026-07-08: MobileMenu.ApiClient 1.0.7 — GetItemTypesAsync/GetFavoritesAsync/AddFavoriteAsync/RemoveFavoriteAsync. Mobile side wired the same day (WI 2182): IFavoritesQueries port, the Favorites screen (heterogeneous card-lg/card-sm rendering + remove + empty state), heart-toggle across the card library (D31), and the Off the Radar item-unavailable fallback.
⚠ Document imagery gap (found in live validation 2026-07-09): every document in the
live GET /v2/api/doc/getdocuments response carries image: null (42/42 sampled) —
the card-lg document carousel has no real cover art to render and falls back to placeholder imagery.
The Home feed design (card-lg) presumes per-document images; raise with backend alongside the AB#2133
dynamic-feed work so magazine covers/article art ship with the content.
⚠ Gateway rollout pending (found in live validation 2026-07-09): the delivered endpoints are not reachable on the production gateway — GET https://gateway.alpa.org/v2/api/mobilecontent/favorites, …/itemtypes, and POST all return 404 (verified with a valid member bearer token). This affects the whole MobileMenu.ApiClient surface: …/mobilecontent/menuitems/getallforuser 404s too (the app falls back to its offline menu list). The mobile slice is validated against the mock router; live end-to-end (including the dead-favorite → Off the Radar flow, which needs a server-seeded favorite) is blocked until the backend deploys the service behind the gateway. Follow up with the backend team on rollout timing.
Why (historical): The favorite (heart) affordance appears across many controls — MEC card-btn, card-md and media card-lg, FAQ / "Did You Know", and others. On Home it is hidden (decision D7); later screens show it. This was the single highest-leverage backend gap surfaced by the screen-set analysis — solved once, as a shared capability, not per control.
Delivered contract:
POST /api/mobilecontent/favorites { itemTypeId, itemId, sortOrder, title } and DELETE /api/mobilecontent/favorites/{itemTypeId}/{itemId}.GET /api/mobilecontent/favorites → id/userId/itemTypeId/itemTypeDescription/itemId/sortOrder/title + audit fields (ID-only-ish, not a hydrated payload — see below). GET /api/mobilecontent/favorites/itemtypes → the known item types.1=Menu Item, 2=Document Item, 3=Notification Center Message. No separate "button" type — card-btn vs card-sm is a presentation choice on Menu Item favorites, not data-driven.Impact: New member-scoped endpoints, delivered. Each list reconciles its items against the member's favorites set client-side; the affected item view-models expose IsFavorite + a toggle command. The individual content models needed no change (they already carry stable IDs).
Mobile model: IFavoritesQueries (GetFavoritesAsync() / AddFavoriteAsync(Favorite) / RemoveFavoriteAsync(itemTypeId, itemId)) + IsFavorite/ShowFavorite/OnFavoriteToggle parameters on Card/CardSmall/HeroCard/ButtonCard (D31). IsFavorite now sources from the live API, not local storage.
Dedicated consumer: the Favorites screen (5287:15227, see favorites-screen-data-gap.html) renders the member's favorites by native control — card-lg / card-sm; Notification favorites render as a title-only row (no card component for that type). The condensed 5287:15454 list variant was not built this pass. Design point resolved: the read returns (itemType, itemId) + title only, not a hydrated payload — the client hydrates from IDocumentsQueries/IMenuQueries by itemId. sortOrder exists as the ordering field.
Status (2026-06-24): This requirement has been abandoned. The "Did You Know?" feature is no longer planned. No endpoint will be built; remove from all planning.
Why (historical): The "Did You Know?" screen was a new feature — it does not exist in the app today (no UI, no endpoint, not hard-coded). Unlike Changes 1–3 (small additions to existing models/capabilities), this is a net-new backend build, so it is called out separately.
Change: Add a new endpoint returning the DYK/FAQ feed — a list of items carrying a date, headline, and body/answer (plus a nav target). The shape is already modeled by the unwired KCMFAQItem { Question, Answer } and KCMAlertItem { headline, story, publishDate } classes in KCM.cs — a reasonable contract starting point.
Impact: New endpoint + content store. Blocking — the "Did You Know?" feature cannot be built until this endpoint exists. The client builds the list (listy) against it. Favorites on these rows reuse the shared capability from Change 3.
Mobile model: a DYK/FAQ item model (Q/A + date/headline) and a service method (e.g. GetDidYouKnowAsync()); the KCMFAQItem/KCMAlertItem shapes can seed it.
Why: Figma's banner component (D43, PromoBanner) is a solid-navy-fill (#05273e) card with a title ("Survey Now Open"), a subtitle/expiry line ("Closes June 10 at 11:59PM PT"), a small logo image, and a dismiss (X) control — four distinct pieces of structured content, not one flat image. Today's implementation renders this as a single full-bleed image (FeaturedImageLink) with the title/subtitle text baked into the image pixels — confirmed 2026-07-06 against the live app: it's just an image plus a click-through target, no separate text fields.
Domain source identified: this is not a new Dynamic Feed ItemDto or a dedicated announcements service (the two candidates D43 originally floated) — it's the richer version of the existing, already-shipped PageBanner model (ALPAMobile.Domain/Data/Models/PageBanner.cs), fetched via GET /api/pagebanner/list (IRestService.GetPageBannersAsync → RestService.cs:1194) and resolved per page in BannerPageViewModel via PageBanner.GetBannerForPage(PageName, banners). This is 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 — see the Key_*PageBanner constants in PageBanner.cs), not a Home-only or one-off feature. That also resolves D43's "is this even a library component" question per D40: it's a recurring, reusable pattern, not static one-off content — it correctly stays a catalogued domain control.
Change: Add to the PageBanner API response:
Title (string, nullable) — headline text, e.g. "Survey Now Open".Subtitle (string, nullable) — secondary line, e.g. "Closes June 10 at 11:59PM PT". Open sub-question: a pre-formatted display string from the backend, or a structured ExpiresAt (DateTimeOffset) the client formats itself (better for timezone/localization consistency, matches the pattern already used elsewhere in FTDT)? Needs a decision before implementation.LogoImageLink (string, nullable) — the small logo asset shown in the Figma design. Since the design's background is a solid fill (not a photo), this is likely a rename/repurpose of the existing FeaturedImageLink rather than an additional field — needs a decision on whether to reuse or add a second image field./api/pagebanner/list still returns the banner as active, it shows again. If a given banner ever needs a true "dismiss once, never show again" behavior, that's a per-device local-storage flag (the app's existing local Settings/Preferences mechanism), keyed by banner Id — not a member-scoped API call. Neither mode touches the backend; no favorites-style storage/endpoint is needed here.Impact: Title/Subtitle/LogoImageLink are additive/nullable — non-breaking, PageBanner API only, benefits every page's banner slot (not just Home). Dismiss is entirely client-side (session in-memory, or per-device local storage for permanent dismiss) — no backend involvement.
Mobile model: PageBanner.cs — add Title, Subtitle (or ExpiresAt), LogoImageLink. Navigation (Path, DocType, DisplayMode, PageBannerNavigationExtensions.PageBannerClicked) is unaffected — click-through behavior stays as-is. Dismiss state is tracked client-side only: per-session in-memory by default, or a per-device local-storage flag keyed by Id if permanent dismiss is ever needed for a given banner — no new model field required on PageBanner itself.
Proposed diff — ALPAMobile.Domain/Data/Models/PageBanner.cs (mobile-side model; the backend response shape should mirror this 1:1):
public class PageBanner
{
public const string Key_HomePageBanner = "Home Page";
// ...existing Key_*PageBanner constants unchanged (~30 more)...
public PageBanner() { }
public int Id { get; set; }
public string Page { get; set; } = string.Empty;
public string ContentType { get; set; } = string.Empty;
public string DocType { get; set; } = string.Empty;
public string Source { get; set; } = string.Empty;
public string FileId { get; set; } = string.Empty;
public string Path { get; set; } = string.Empty;
public string FeaturedImageLink { get; set; } = string.Empty;
public string DisplayMode { get; set; } = string.Empty;
+
+ // -- New fields requested for the PromoBanner design (D43 / Change 5) --
+ public string? Title { get; set; }
+ public string? Subtitle { get; set; }
+ // Alternative to Subtitle -- pick ONE, do not add both:
+ // public DateTimeOffset? ExpiresAt { get; set; }
+ public string? LogoImageLink { get; set; }
public static PageBanner? GetBannerForPage(string page, ObservableCollection<PageBanner> banners)
{ /* unchanged */ }
}
Sample GET /api/pagebanner/list response — one entry, before/after (all three new fields nullable/omittable, so existing rows for the other ~20 pages need no changes):
// Before (today)
{
"id": 42,
"page": "Home Page",
"contentType": "webpage",
"docType": "weblink",
"source": "",
"fileId": "",
"path": "https://ual.alpa.org/upa27-survey",
"featuredImageLink": "https://cdn.alpa.org/banners/upa27-survey-full.png",
"displayMode": "Normal"
}
// After (Change 5)
{
"id": 42,
"page": "Home Page",
"contentType": "webpage",
"docType": "weblink",
"source": "",
"fileId": "",
"path": "https://ual.alpa.org/upa27-survey",
"featuredImageLink": "https://cdn.alpa.org/banners/upa27-survey-full.png",
"displayMode": "Normal",
"title": "Survey Now Open",
"subtitle": "Closes June 10 at 11:59PM PT",
"logoImageLink": "https://cdn.alpa.org/logos/upa27-logo.png"
}
featuredImageLink is left in place deliberately — it's still used as-is by every other page's banner (a full-bleed image, no separate text fields needed there). logoImageLink is additive so those existing banners keep working unchanged; only rows that want the richer PromoBanner treatment populate the three new fields.
Status: ON HOLD — pending design-intent confirmation (DQ-26, 2026-07-07): if a supplied flat image per campaign is adequate, Change 5 is withdrawn; if structured content is required, Change 5 proceeds as documented. When it proceeds, this section is the handoff; no separate email/Teams send (decision 2026-07-07) — the backend team is pointed here via the AB#1821 work item (see the Backend Handoff Tracker). The cover text below is the handoff summary; the technical payload is the Proposed diff and Sample response blocks above — reference those verbatim rather than forking a copy.
Subject: PageBanner API — request for 3 additive fields (mobile UI Refresh, AB#1821)
Ask: add Title, Subtitle, LogoImageLink (all nullable) to the PageBanner response from GET /api/pagebanner/list. The UI Refresh banner design (Figma PromoBanner, decision D43) is structured content — a solid-navy card with a headline ("Survey Now Open"), a secondary/expiry line ("Closes June 10 at 11:59PM PT"), a small logo, and a dismiss control. Today the same content ships as one full-bleed image (FeaturedImageLink) with the text baked into the pixels, which blocks the new design, accessibility, and text scaling.
Non-breaking: all three fields are additive and nullable. Existing banner rows for the other ~20 pages need no changes and keep rendering via FeaturedImageLink exactly as today. Dismiss behavior is entirely client-side — no API change, no per-member storage.
Technical payload: the proposed C# model fields are the Proposed diff block above (the backend response shape should mirror ALPAMobile.Domain/Data/Models/PageBanner.cs 1:1), and the before/after JSON is the Sample GET /api/pagebanner/list response block above.
Two decisions we'd like backend input on before implementation:
Subtitle (pre-formatted display string) vs ExpiresAt (DateTimeOffset the client formats)? We lean ExpiresAt for timezone/localization consistency — it matches the pattern already used elsewhere in the app — but a display string is fine if banner copy won't always be an expiry.LogoImageLink as a new field vs repurposing FeaturedImageLink? The new design's background is a solid fill, not a photo, so the row could reuse the existing field for the logo — but a separate field keeps existing full-bleed banners untouched. We lean separate/additive (as drafted).The package closes by pointing the backend team back to this section for the full analysis and domain-source rationale (branch imp/blazor-hybrid).
| Component | Backed By | Rationale | Extend? |
|---|---|---|---|
| card-sm (DART, Directories, etc.) | MenuItem |
Navigation tiles. Title, Path, ImageSource, ShowOnHomeScreen, SortOrder all exist. Ordering and visibility controlled by backend. | No — existing fields sufficient for card-sm |
| ButtonCard (Emergency Hotline) | MenuItem |
Action button with Glyph + Title + Path. Identified by SpecialCode. | No |
| TextCard (Eyebrows/Simple variants) | MenuItem |
Navigation tile with richer display — needs Description. One nullable field addition. | Yes — add Description |
| card-lg / HeroCard (feeds & standalone) | DocumentItem |
One component (CardHeroViewModel) — the same card-lg whether shown standalone (e.g. Hotel Request) or inside a carousel/feed (CarouselViewModel.Items is a List<CardHeroViewModel>). Image, eyebrow, title; Scope + Category determine which documents appear. DocumentItem already has all fields. |
No |
Welcome / Pilot Card (domain control → Card) |
IAuthentication + DocumentItem |
Greeting from UserInfo, contract link from DocumentItem. Hybrid — no single model extension needed. | UserInfo: add Rank |
Do not merge MenuItem and DocumentItem, or make one carry the other's fields. They model fundamentally different entities — navigation entries vs. content documents. Merging them would couple the navigation API to the content API and make both harder to evolve independently. Adding a small nullable field to a single model (Rank on UserInfo, Description on MenuItem) is a fine, backward-compatible extension — that is different from merging two models. Those two additions, the shared Favorites capability, and the new DYK/FAQ endpoint (all in §1) are the backend changes required to ship the app-wide design.
This section rolls up the data-gap analysis of every non-Home screen set (Home itself is detailed in Part 2) —
other screen sets — each has a full *-screen-data-gap.html report, and the at-a-glance rollup also lives in
screen-mapping.html → Consolidated Gaps. Bottom line: most sets are
already data-ready; the cross-cutting backend ask is Favorites (Change 3), and the one net-new build is the DYK/FAQ endpoint (Change 4).
| Screen set | Backing endpoint(s) / model(s) | Backend gap |
|---|---|---|
| MEC | GetDocuments (Scope/Category) · MenuItem | Favorites card-md Description |
| KCM | GetKCMAirlineItemsAsync → KCMAirlineItem · GetKCMAirportItemsAsync → KCMAirportItem | None |
| Jumpseat Flight Finder | FlightSearchAsync → Flight/Leg · GetFlightSearchAirportsAsync · FindFlightInfoAsync · GetAirlineJumpseatPoliciesAsync | None |
| FTDT | DutyPeriod CRUD+sync · GetDutyPeriodsUpdatedAtAsync · client FAR-117 / Canadian calculators | None |
| Member Resources | MenuItem nav · GetMemberAsync → Member (PDR prefill) | PDR = web-scoped |
| Notifications | GetNotificationListAsync → NotificationCenterMessage · GetAllSubscriptionsForUserAsync · ToggleSubscriptionAsync | None |
| Comms (Advocacy + Internal Comms) | GetPACMemberInfoAsync → PACMember · GetCallToActionLinksAsync · GetDocumentListAsync | Meeting Report = web-scoped |
| Search | client-side over loaded data (no search endpoint) | None |
| FAQ / Did You Know | NEW feature — not in the existing app; needs a new API endpoint (no existing source). The Q/A + date/headline shape is already modeled by the unwired KCMFAQItem { Question, Answer } / KCMAlertItem { headline, story, publishDate } classes in KCM.cs — a contract starting point. | New endpoint Favorites |
Two forms in the mocks are web-app features accidentally surfaced in the mobile designs — no mobile endpoint needed; remove/hide on mobile: Submit a Pilot Data Request (Member Resources) and Submit Meeting Report (Advocacy — links to an online web form).
Member carries memberNumber (not an employee number); pilot identity is split across UserInfo (auth) and Member — see the consolidation note above.CallToAction.isActedUpon (bool) is maintained by the API — the item returned by GetCallToActionLinksAsync carries the updated value after the CTA is completed. Client just reads it; no change.GetAllSubscriptionsForUserAsync / ToggleSubscriptionAsync) already ship. No gap.KCMFAQItem { Question, Answer } and KCMAlertItem { headline, story, publishDate } classes in KCM.cs — a reasonable contract starting point. (Corrects an earlier, incorrect "sourced from GetNotificationListAsync" note.)CalculatorRest_CAN et al.). The backend only persists/syncs DutyPeriod; there is no backend rule config.airportName == "NOTICE" (existing sentinel), rendered from its checkinNotes; eligibility flags = KCMAirlineItem.pilots / attendants bools; Usage Procedures = a bundled PDF (kcm_usage_procedures.pdf) gated by a MenuItem. No backend change.DocumentItem already has PublishDate (date) and Description (summary). No change.HomeFeedConfig endpoint needed.MenuItem; the client identifies it and renders the primary button. Confirm the identifying value (e.g. SpecialCode) on that MenuItem.#f7bb0d) on some card-lg instances is a design question — waiting on Figma annotations before we can gauge whether it's data-driven (a DocumentItem field) or static styling. Tracked as DQ-2 in the Component Design Questions.FavoritesService to the shape and calls in the delivered API client — do not pre-design the contract. FAQ / "Did You Know" is abandoned (Change 4 dropped), so the favorites dependency there is moot.
The original Home deep dive (Figma node 4099:5230), kept for reference. Every cross-cutting ask it
surfaced is already rolled up into Part 1; this appendix shows the per-component derivation.
The home screen feed is a vertical scroll of heterogeneous card types. The order and visibility of
items is controlled by MenuItem.ShowOnHomeScreen and MenuItem.SortOrder.
| # | Figma Component | MAUI Component | Data Source | Status |
|---|---|---|---|---|
| 1 | welcome / Welcome thing |
Pilot Card (domain control) → CardViewModel |
IAuthentication + DocumentDatabase | Gap: Rank |
| 2 | horizontal scrolling feed (UAL MEC) |
CarouselViewModel → card-lg (HeroCard items) | DocumentItem (scope = pilot MEC) | Mapped |
| 3 | Primary button (Emergency Hotline) |
ButtonViewModel | MenuItem (SpecialCode = "emergency") | Mapped |
| 4 | card-sm ×3–6 (DART, ALPA Directories, etc.) |
CardSmallViewModel : CardViewModel | MenuItem (ShowOnHomeScreen = true) | MenuItem-backed |
| 5 | horizontal scrolling feed (New & Notable) |
CarouselViewModel → card-lg (HeroCard items) | DocumentItem (recent, sorted by PublishDate) | Query TBD |
| 6 | card-lg / HeroCard (standalone on Home, e.g. Hotel Request) |
CardHeroViewModel | DocumentItem | Mapped |
| 7 | horizontal scrolling feed (Feed) |
CarouselViewModel → card-lg (HeroCard items) | DocumentItem (general, scope-filtered) | Query TBD |
Background: #dfedf9 (--blue/100)
Figma node: I4099:5230;4038:893;5042:18877
| Property | Figma Text | API Source | Status | Notes |
|---|---|---|---|---|
| Greeting line | "Welcome Captain Johnson" | IAuthentication.GetUserInfo() |
Partial | FirstName + LastName available. Rank ("Captain") has no API source. |
| Pilot rank | "Captain" | None currently | Blocked | No rank field on the API. Member.classification carries a membership classification (e.g. "Active Member", "Retired Member", "Staff", "FOA") — not a pilot rank like Captain / First Officer. |
| Contract link text | "View Contract" | DocumentItem.Title via DataManager |
Mapped | First contract doc for pilot MEC. Fallback: "View Contract". |
| Contract navigation | Tap → document detail | DocumentItem.FileID via SharedActionsService |
Mapped | Resolved at presentation layer. |
| Background fill | — | Design token --blue/100 |
Token | #dfedf9. Note: our spec previously had #e8f4fd — corrected from Figma source. |
| Chevron (Group 568) | Right arrow | Static asset | Static | Visual affordance only — the chevron has no navigation of its own. Selecting the card navigates the same as View Contract (→ document detail, see Contract navigation above). |
The Figma design shows "Welcome Captain Johnson". The current API has no field for pilot rank. Three options:
Rank (string, nullable) to the UserInfo response from the auth/profile API. Possible values: "Captain", "First Officer", "Second Officer". No model extension needed on the mobile side.Member data if rank is available on the member profile endpoint — needs backend investigation.
Layout: 80px image left + flex title center
Image fallback bg: #dfedf9 (--blue/100) or #05273e (--blue/900) for dark tiles
card-sm directly represents a MenuItem
row on the home screen. Instances seen in Figma: DART, ALPA Directories, Committee Calendar,
Flight Pay Loss, My Payments, Infor Expense Management, Finance Reports, Meeting Request, MPS.
All are standard app menu items with ShowOnHomeScreen = true.
| Property | Figma Layer | API Source | Model Field | Status |
|---|---|---|---|---|
| Title | body / p — Lora SemiBold 16px |
MenuItem | MenuItem.Title |
Mapped |
| Image / Icon | img — 80×full-height |
MenuItem | MenuItem.ImageSource |
Mapped |
| Image fallback bg | Blue tint #dfedf9 or navy #05273e |
Design token | Client-side fallback | Client |
| Navigation target | Card tap → page | MenuItem | MenuItem.Path |
Mapped |
| IsFavorite | i-heart icon 18×18 |
Favorites API (Change 3) — Local Settings is the fallback | Client-persisted state used only until the backend favorites API is delivered | Client (fallback) |
| ShowOnHomeScreen | Presence in feed | MenuItem | MenuItem.ShowOnHomeScreen |
Mapped |
| Sort order | Position in list | MenuItem | MenuItem.SortOrder |
Mapped |
Why no Description row: card-sm renders image + title only — it has no description.
MenuItem.Description does not exist on the API today, but that missing field is a
card-md / TextCard concern (see A5 below), not a card-sm gap.
Add CardSmallViewModel backed entirely by MenuItem.
No DocumentItem involvement. Properties: Title, ImageSource,
Link (= Path), IsFavorite, ShowOnHomeScreen, SortOrder.
This is the simplest component to implement — all properties exist on the current model
except Description (needed only for the TextCard variant, not card-sm itself).
Three horizontal scroll feeds appear on the home screen: UAL MEC,
New & Notable, and Feed. Each contains card-lg
instances backed by DocumentItem. Card-lg structure confirmed from Figma:
200px image with 6px left border accent, eyebrow label, title.
Horizontal-feed card — 200px image, eyebrow + title, and a 6px left accent shown here in
yellow (#f7bb0d) to flag the open gap: the
accent can also be blue (#007bc2) and the variant logic that picks the
colour is still TBD (see table below).
Backed by DocumentItem; appears in the UAL MEC / New & Notable / Feed scrollers.
| Feed Section | Section Title | Link Label | Query Pattern | Status |
|---|---|---|---|---|
| UAL MEC | "UAL MEC" (dynamic — pilot's MEC) | "View My MEC" | GetDocumentsForScopeCategoryAsync(userInfo.MEC, category) |
Category TBD |
| New & Notable | "New & Notable" | "View All" | DocumentItem collection via GetDocuments — Scope/Category/Grouping configured to match "New & Notable" so the proper items surface |
Resolved |
| Feed | "Feed" | "View All" | General content feed — scope and category to be defined with content team | Content config TBD |
| card-lg Property | Figma Layer | Model Field | Status |
|---|---|---|---|
| Image | img 200px height, 6px left border |
DocumentItem.Image |
Mapped |
| Border accent color | Blue #007bc2 or Yellow #f7bb0d |
Design token / category variant | Variant logic TBD |
| Eyebrow | Inter Bold 12px, uppercase, opacity 50% | DocumentItem.Category or DocumentItem.Grouping |
Mapped |
| Title | Lora SemiBold 20px, truncated with ellipsis | DocumentItem.Title |
Mapped |
| IsFavorite | i-heart 18×18 |
Favorites API (Change 3) — Client-persisted (Settings) is the fallback until the backend favorites API is delivered | Client (fallback) |
| Navigation | Card tap → document | DocumentItem.FileID via SharedActionsService |
Mapped |
The three feed section titles ("UAL MEC", "New & Notable", "Feed") and their query filters are content configuration decisions. The mobile client should receive feed configuration (title, scope, category, sort).
A full-width blue action button at the top of the feed. Figma shows "EMERGENCY HOTLINE" label
with a phone icon. This maps to a MenuItem with a known SpecialCode.
Full-width primary action — blue fill (#007bc2), white phone icon + label.
Identified by a known MenuItem.SpecialCode (legacy buffer, see below).
| Property | Source | Status |
|---|---|---|
| Label | MenuItem.Title |
Mapped |
| Icon | MenuItem.Glyph + MenuItem.GlyphFontFamily |
Mapped |
| Action | MenuItem.Path (tel: URI or in-app screen) |
Mapped |
| Identification | MenuItem.SpecialCode = "emergency" (or similar). Legacy — this SpecialCode-based identification is a buffer used only until dynamic (server-driven) UI is delivered, which will supersede it. |
SpecialCode value TBD (legacy buffer) |
Shown: the simplest Description variant — description + link only (no label or
title set). The description is the card, so the MenuItem.Description gap is
front and centre. The two richer variants add a title (Title) then an eyebrow
label (Label); the full property set is listed below.
The CardText (card-md) composes a generic scaffold via a factory. Its
Menu Text variant is backed by MenuItem and shows a description.
The other text source, DocumentItem, already carries
DocumentItem.Description — so only the MenuItem-backed variant has the gap.
| Property | Figma Layer | API Source | Model Field | Status |
|---|---|---|---|---|
| Label (optional) | Eyebrow — Inter Bold 12px, uppercase | Parent MenuItem | MenuItem.Title of parent category |
Mapped |
| Title | Lora SemiBold 18px | MenuItem | MenuItem.Title |
Mapped |
| Description | Body text — rendered on card-md | MenuItem | MenuItem.Description — missing (DocumentItem has one; MenuItem does not) |
API Change |
| Image | img — 250×200 |
MenuItem | MenuItem.ImageSource |
Mapped |
| Navigation | Card tap → destination | MenuItem | MenuItem.Path |
Mapped |
card-md renders a description line, but MenuItem has no
Description field today (confirmed against the model: DocumentItem.Description
exists, MenuItem has none). Add a nullable Description string to the
MenuItem API response — a backward-compatible field addition. Null/empty collapses to
the Title variant, so existing menu items render unchanged until content is supplied. This is the
gap previously surfaced (misleadingly) under A2 card-sm — it belongs here, on the
component that actually shows a description.
Captured 2026-07-10 from the production /v2/api/doc/getdocuments response (20 scope nodes,
20 categories, 383 documents), while wiring the Blazor document surfaces. The client now works around each of
these, but they belong to the payload — raising as API data-sanitation work so every consumer stops
re-implementing the same workarounds.
| # | Inconsistency | Evidence (production response) | Suggested alignment |
|---|---|---|---|
| 1 | Friendly-name field is inconsistent — no single field reliably carries the display name | 15/20 categories: title == description == raw key (all three duplicated) · 2/20: friendly name in title (PRESIDENT → "From The President") · 1/20 (ALP MAG): title echoes the key and the friendly name appears only in description, as a sentence ("ALPA Magazine.") · 2/20: description empty |
One canonical displayName per category, always populated; description reserved for actual descriptions. (Client meanwhile resolves via CategoryItem.DisplayTitle: title-if-distinct → description → key.) |
| 2 | Category keys mix naming styles | ALLCAPS codes (KCM, FASTREAD, PRESIDENT) alongside display-style keys ("Canada Limits", "ALP MAG", "Press Release"); one key contains a slash (IRS/PBGC Limits) — a URL-encoding hazard since keys travel as query parameters |
Stable machine codes for keys (no spaces/slashes), display text in the name field |
| 3 | Duplicate scope nodes — the same scope name appears as multiple sibling nodes | ALPA ×6, MEC ×11, LEC ×2 (20 nodes for ~3 logical scopes); ALPA's categories are scattered across its 6 nodes. Root cause of the 5.0.13 documents-missing regression (BUG-1995 dedup dropped 41/42 docs) |
One node per scope with all categories inside, or an explicit disambiguating id if the duplication is intentional |
| 4 | Document imagery absent | image null/empty on 383/383 documents; category iconUrl present on only 14/20 |
Populate document imagery (the card-lg design renders a 200px photo; the app currently substitutes placeholder art). Already tracked as the imagery gap — folded here for the sanitation conversation |
| 5 | path uses four different conventions (plus casing and encoding drift) |
233 bare site-relative ("Documents/…" — incl. lowercase "documents/kcm/…" variants) · 115 absolute https · 32 DNN-relative ("~/media/…") · several paths with unencoded spaces ("documents/kcm/KCM FAQ.pdf") | Canonical absolute URLs (or one documented relative convention + base), URL-encoded, consistent casing |
| 6 | fileName never populated |
Empty on 383/383 documents — clients derive cache/display names from date + fileID | Populate with the actual file name incl. extension |
| 7 | docType vocabulary drift |
Both htm (10) and html (10) exist alongside pdf (248) and webpage (115) |
Collapse htm/html into one value; publish the closed vocabulary |
Related: MobileMenu/mobilecontent gateway rollout (Part 1 probe matrix / Bug AB#2237) and MenuItem.Description population (backend Change 2) are the same class of alignment work on the menu side.