MEC Customization Mock Guide

For the backend dev mapping MEC customization onto the theme and content endpoints: real screens broken into small chunks, each paired with the JSON that would produce it.

Sources: UAL — CLE172 LEC Detail · DAL — Home · theme-endpoint-contract.html · content-endpoint-contract.html · Last updated: 2026-07-15

What this guide is. The theme endpoint contract and content endpoint contract define the shape of the two APIs a MEC-customized screen depends on. This guide shows what that shape looks like filled in with real content, one visual chunk at a time, using screens that are already fully documented and screenshot-verified in the page-composition docs. It is a companion, not a replacement — always check the two contract docs for the authoritative field list; this guide shows worked instances of that shape.

Why chunked, not full-page. A full-page screenshot next to a full-page JSON payload is hard to cross-reference. Each chunk below is one visually distinct section — matching the numbered rows already used in the page-composition slot tables — paired with just the JSON that drives that section.

Two Data Sources, One Screen

Every MEC-customized screen is built from exactly two upstream sources — never hardcoded per-airline in client code:

SourceEndpointDrives
ThemeGET /api/theme/{mecId}Colors, fonts, spacing, corner radii — the 45-token map documented in theme-endpoint-contract.html. Resolved values only, never semantic references.
ContentMobileContent APINavigation tiles (MenuItem) and content cards (DocumentItem) — documented in content-endpoint-contract.html. Not every component on a MEC screen is MobileContent-driven — see the Representatives chunk below for a real counter-example.

Some components aren't documented yet. The content contract's component↔type mapping (§4) covers card-sm, ButtonCard, TextCard, card-lg/HeroCard, and Carousels. It does not yet cover every component visible on a real MEC screen — e.g. the "listy card generic" Representatives block. Where a chunk below uses an undocumented component, this guide says so explicitly rather than inventing a schema. Treat those as open items, not gaps in this guide.

Live Data Samples — Captured via MauiDevFlow

Everything below is real traffic captured off a live, authenticated UAL pilot session (production gateway, 2026-07-15) using MauiDevFlow's network monitor and its native ILogger log stream (maui-devflow MAUI network / MAUI logs) — not reconstructed from screenshots. Names/emails/phones shown are published MEC/LEC officer directory info, the same category of data already documented for DAL's Council 44 representatives elsewhere in this doc set. Auth tokens and session identifiers are excluded.

Correction to this guide's earlier framing. The Representatives, Events, and Committees content on the current, live MEC section is not MobileContent-driven at all — confirmed by capture, not inferred. The client-side entry points are IMECQueries.GetMECLECRepsAsync(scope) and GetMECLECEventsAsync(scope) (scope = MEC or LEC — confirmed via ALPADocs.Services.RestService log lines [GetMECLECRepsAsync] Scope:MEC / Scope:LEC), which call a separate, previously-undocumented carrier/* HTTP API:

This is the legacy native page's actual live data source (MyPilotGroupPageViewModel), not a hypothetical. The content contract's MenuItem/DocumentItem model remains the target shape for the new Blazor MEC pages this doc set otherwise describes — but until those pages are wired up, carrier/* is the ground truth for this content category. A backend dev extending MEC customization should decide whether the new pages call these same endpoints directly or go through a MobileContent migration — that decision isn't made in this repo yet.

Real confirmed DocumentItem usage — MEC Documents → Roster

Unlike Representatives/Events/Committees, the "MEC Documents" section of the same live page is genuinely DocumentItem-backed — confirmed via a real Shell navigation log line, not a guess:

Navigating to route: DocumentsListPage?Scope=MEC&Category=Roster&Grouping=MEC%20Documents

This is a real 3-parameter query — Scope, Category, and Grouping — matching DataManager.GetDocumentsForScopeCategoryGroupingAsync(scope, category, grouping), one level more specific than the 2-parameter Scope+Category selection described in the content contract §5. Navigating this route rendered one real document:

TitleDate
MEC Roster06/03/2026

The raw JSON for this specific document wasn't captured this session — the underlying bulk document fetch (GetDocumentListAsyncGET /api/doc/getdocuments) happens once and is cached in a local LiteDB store (_documentDatabase); it had already fired and been persisted before this capture window started, so no matching network request appeared in the live monitor. The route, the real category structure (Contract / Did You Know? / Roster / Policy Manual / Treasurer Reports, each with its own document count), and the rendered title+date are confirmed; the exact field-level JSON for this document is not.

GET /api/carrier/getmecreps — real UAL MEC representatives

[ { "office_level": "MEC", "mec_cd": "UAL", "lec_cd": null, "name": "Brian Noyes", "status": "Captain", "office": "Chair", "termenddate": "Feb 29 2028 12:00AM", "phone": "847-292-1700", "email": "Brian.Noyes@alpa.org", "sortorder": 1, "block": null }, { "office_level": "MEC", "mec_cd": "UAL", "lec_cd": null, "name": "PHIL DiCostanzo", "status": "Captain", "office": "Vice Chair", "termenddate": "Feb 29 2028 12:00AM", "phone": "973-801-1733", "email": "Phil.DiCostanzo@alpa.org", "sortorder": 2, "block": null }, { "office_level": "MEC", "mec_cd": "UAL", "lec_cd": null, "name": "Brian Bunkers", "status": "Captain", "office": "Secretary", "termenddate": "Feb 29 2028 12:00AM", "phone": "No Preferred Phone #", "email": "brian.bunkers@alpa.org", "sortorder": 5, "block": null } // + 3 more (Treasurer, 2× Executive Administrator) — 6 total ]

Note the office_level/mec_cd/lec_cd discriminator — this exact shape is reused for LEC-level reps below, just with office_level: "LEC" and a populated lec_cd. Brian Noyes is the same real MEC Chair whose name appears in the confirmed UAL Media Hub screenshot's "Master Chair Messages" video card — independent cross-confirmation this is real production data, not a stub.

GET /api/carrier/getlecreps — real UAL LEC-012 representatives

[ { "office_level": "LEC", "mec_cd": "UAL", "lec_cd": "012", "name": "Phil Anderson", "status": "Captain", "office": "Chair", "termenddate": "Feb 28 2029 12:00AM", "phone": "219-306-2664", "email": "phil.anderson@alpa.org", "sortorder": 1, "block": null }, { "office_level": "LEC", "mec_cd": "UAL", "lec_cd": "012", "name": "Tom Cross", "status": "Captain", "office": "Vice Chair", "termenddate": "Feb 28 2029 12:00AM", "phone": "616-633-7099", "email": "thomas.cross@alpa.org", "sortorder": 2, "block": null }, { "office_level": "LEC", "mec_cd": "UAL", "lec_cd": "012", "name": "Shanon Jamison", "status": "Captain", "office": "Secretary-Treasurer", "termenddate": "Feb 28 2029 12:00AM", "phone": "515-865-9955", "email": "Shanon.Jamison@alpa.org", "sortorder": 5, "block": null } ]

This session's LEC is 012, not CLE172 — the shape is identical to what CLE172's Representatives block (chunk 3 below) renders; only the specific roster differs per LEC.

GET /api/carrier/getmecevents — real events, exact CLE172 match

36 events returned MEC-wide. Two of them are the exact same real events already documented from the CLE172 Figma screenshot (chunk 2 below) — confirming that screenshot's content is live, current data, not placeholder:

{ "title": "Family Awareness – CLE – Guardians vs. Pirates", "location": "", "description": "<html>...<b>Guardians vs. Pirates – CLE</b>...<b>Save the Date</b>...<b>Sunday, July 19<br>1:40 p.m.</b>...<a href=\"https://www.eventbrite.com/e/ual-mec-family-awareness-cle-guardians-vs-pit-pirates-registration-1989096310610\">...</a></html>", "startDate": "7/19/2026", "endDate": "7/20/2026", "startTime": "12:00 AM", "endTime": "12:00 AM", "contactEmail": "", "contactPhone": "", "id": "AAMkADg1ZDFlNWZmLWQ3ZWMtNDRiZi04YmI0LTBlNjkxZDZhM2E2ZAB..." }

This is a materially different shape than the DocumentItem guess this guide originally made for CLE172's events chunk. The real event record is calendar/Exchange-backed (title/startDate/endDate/startTime/endTime/a raw HTML description body/an Exchange item id) — not a DocumentItem's Scope/Category/publishDate/image shape at all. The client strips the HTML down to the "SAVE THE DATE" / title / date display seen in the screenshot. Chunk 2 below is corrected accordingly.

GET /api/carrier/getlecevents — real empty state

[]

This LEC (012) currently has zero LEC-scoped events — a genuine empty array, not an error. Useful as the real "no events" reference rather than guessing what an empty state should look like.

GET /api/carrier/GetCommittees — real committee + volunteer directory (282 committees)

{ "comm_cd": "UALAREO00001", "comm_name": "Aeromedical", "mec_cd": "UAL", "email": "", "phone": "", "committeeVolunteers": [ { "alpa_id": "2213155", "comm_cd": "UALAREO00001", "ofc_cd": "CHR", "title": "Chair", "name": "Marion Ruff", "frst_name": "Marion", "lst_name": "Ruff", "mec_cd": "UAL", "lec_cd": "012", "email": "Sonny.Ruff@alpa.org", "phone": "4144599860", "sortorder": 1 }, { "alpa_id": "1506492", "comm_cd": "UALAREO00001", "ofc_cd": "VCH", "title": "Vice Chair", "name": "Joshua Thompson", "frst_name": "Joshua", "lst_name": "Thompson", "mec_cd": "UAL", "lec_cd": "033", "email": "Joshua.Thompson@alpa.org", "phone": "4055355250", "sortorder": 2 } ] }

This is the full UAL committee tree (national + per-LEC committees) in one call, not paginated or scoped by category — the client presumably filters client-side for whatever subset a given screen needs. Volunteers nest one level deep per committee.

Worked Example — UAL, CLE172 (LEC Detail)

Read this before the chunks below. CLE172 is a Figma mockup for a per-LEC detail screen that does not exist in the live app yet — the live app's actual MEC section (MyPilotGroupPage, confirmed via live capture — see Live Data Samples) is a flatter, non-per-LEC list (MEC Communications, MEC Documents, MEC Committee Updates, MEC Committees, MEC Events, MEC Reps, MEC Hotels). The screenshots below are real design captures; the specific field values in chunks 1 and 4 (item ids, exact path strings) were reconstructed to match the confirmed general MenuItem shape, not independently captured for these exact items — no "CLE 172 Roster" or "Meeting Minutes Archive" MenuItem was found in any live capture this session. Chunks 2 and 3 (Events, Representatives) are independently confirmed real, since the same underlying carrier/* data drives both this mockup and the live app regardless of which screen renders it.

Chosen because it is the richest content example captured from Figma: a fully-resolved theme (no open design questions), and comms content for every slot. Full slot table: ual-mec-page-compositions.html#lec-detail.

1 Title + Roster button Illustrative — not independently captured
CLE172 title and Roster button

CLE172 (20826:1699), top slice

Corrected 2026-07-15. An earlier version of this guide labeled the JSON below "confirmed live production shape ... captured off gateway.alpa.org" — that overstated it. What's actually confirmed live is the general MenuItem field shape (from unrelated top-level items — see Live Data Samples); this specific "CLE 172 Roster" item was never observed in any capture. Worse, the userFilterType: "MEC" / userFilterValue: "UAL" values below were a guess about how MEC-scoping works — the one real MEC-related item actually captured (top-level "My Pilot Group") has both fields empty, contradicting that guess. Treat the shape below as illustrative only, not a confirmed contract.

More useful real finding: a live-navigated capture found the actual app has a "MEC Documents → Roster" category containing one real document titled "MEC Roster" (dated 06/03/2026) — i.e. in the live app, "Roster" is DocumentItem content, not a MenuItem route at all. This is a real, independently-confirmed alternative to the guess below — worth checking with design/backend before assuming the new Blazor CLE172 page's Roster button should behave like this speculative MenuItem.

{ "id": 4201, "parentId": 0, "replacedId": 0, "title": "CLE 172 Roster", "description": null, "path": "LECs/CLE172/Roster", "imageSource": "", "glyph": "", "glyphFontFamily": "", "sortOrder": 1, "enabled": true, "visible": true, "specialCode": "", "default": false, "showOnHomeScreen": false, "showToAdmin": false, "userFilterType": "", "userFilterValue": "", "parentFilterType": "", "parentFilterValue": "", "createdUser": "system", "createdDateTime": "2026-06-01T00:00:00", "lastUpdatedUser": "system", "lastUpdatedDateTime": "2026-06-01T00:00:00" }

Fixed userFilterType/userFilterValue to empty strings to match the one real example on file, rather than the unverified "MEC"/"UAL" guess. How MenuItem-level MEC-scoping actually works (if it exists at all, distinct from the carrier/* mec_cd/lec_cd pattern) remains an open question — do not build against this field until it's confirmed.

2 Upcoming Events feed NOT MobileContent — carrier/getmecevents
CLE172 Upcoming Events feed

CLE172, Upcoming Events slice

Corrected 2026-07-15 — this is not a DocumentItem feed. A live capture confirmed these two exact events come from GET /api/carrier/getmecevents (see Live Data Samples above), a calendar/Exchange-backed feed, not MobileContent. An earlier version of this guide modeled it as DocumentItem — that was an unverified guess made before this endpoint was known; the shape below is the real captured record.

Real captured record for the "GUARDIANS VS PIRATES" card (client renders the HTML description down to the "SAVE THE DATE" / date line shown in the screenshot):

{ "title": "Family Awareness – CLE – Guardians vs. Pirates", "location": "", "description": "<html>...<b>Save the Date</b>...<b>Sunday, July 19<br>1:40 p.m.</b>...eventbrite.com link...</html>", "startDate": "7/19/2026", "endDate": "7/20/2026", "startTime": "12:00 AM", "endTime": "12:00 AM", "contactEmail": "", "contactPhone": "", "id": "AAMkADg1ZDFlNWZmLWQ3ZWMtNDRiZi04YmI0LTBlNjkxZDZhM2E2ZAB..." }

The "CLEVELAND AIRSHOW" card is the same shape — real title "Family Awareness – CLE – Airshow", startDate: "9/7/2026". Both were confirmed present in the live 36-event MEC-wide feed, matching the Figma screenshot exactly.

3 Representatives NOT MobileContent — carrier/getlecreps
CLE172 Representatives list

CLE172, Representatives slice

Endpoint confirmed 2026-07-15 (this guide originally flagged this as undocumented — it no longer is). The three rows (Ed Higgins, Josh Burke, Mark Wapenaar) come from GET /api/carrier/getlecreps, keyed by LEC code (CLE172 = lec_cd: "012"... except CLE172's real code wasn't captured this session — see note below). Same shape as the real UAL LEC-012 capture in Live Data Samples above: office_level, mec_cd, lec_cd, name, status, office, termenddate, phone, email, sortorder, block.

This is not a MenuItem or DocumentItem — do not force it into that shape. The captured session's test pilot belongs to LEC-012, not CLE172, so the exact rep names in the live sample above (Phil Anderson, Tom Cross, Shanon Jamison) differ from CLE172's own roster (Ed Higgins, Josh Burke, Mark Wapenaar) — but the endpoint and field shape are confirmed identical regardless of which LEC is queried.

4 Meeting Minutes Archive button Illustrative — not independently captured
CLE172 Meeting Minutes Archive button

CLE172, bottom slice

Corrected 2026-07-15 — same issue as chunk 1. This "Meeting Minutes Archive" item was never observed in any live capture; it's a plausible reconstruction of the confirmed general MenuItem shape, not confirmed data. userFilterType/userFilterValue corrected to empty strings to match the one real example on file (see chunk 1's note).

Same shape as chunk 1, but an external link — path is a full URL rather than a client route:

{ "id": 4204, "parentId": 0, "replacedId": 0, "title": "Meeting Minutes Archive", "description": null, "path": "https://ual.alpa.org/LECs/CLE-172/Meeting-Minutes", "imageSource": "", "glyph": "", "glyphFontFamily": "", "sortOrder": 4, "enabled": true, "visible": true, "specialCode": "", "default": false, "showOnHomeScreen": false, "showToAdmin": false, "userFilterType": "", "userFilterValue": "", "parentFilterType": "", "parentFilterValue": "", "createdUser": "system", "createdDateTime": "2026-06-01T00:00:00", "lastUpdatedUser": "system", "lastUpdatedDateTime": "2026-06-01T00:00:00" }

If confirmed a MenuItem, an external path (starts with http) is expected to open in the in-app web view rather than the Shell router, per the general external-link pattern elsewhere in the app — not independently verified for this specific item.

Worked Example — DAL, Home (a second MEC, and what "not populated yet" looks like)

DAL's theme is not shown here — DQ-15 (which surface gets which of the two candidate navy tones) is still open, and fabricating a resolved token map would misrepresent an unresolved design decision. What DAL's Home screen usefully demonstrates instead is the mock/placeholder convention already live on real screens — several feeds here are not yet populated with real content, and they render with an explicit, consistent stub shape rather than being empty. Full slot table: dal-mec-page-compositions.html#home-dal.

1 Welcome banner + Council 44 nav tile Illustrative — not independently captured
DAL Home welcome banner and Council 44 tile

DAL Home, top slice

Same caveat as the CLE172 chunks above. This session's live capture was a UAL pilot session — nothing DAL-specific was independently captured. The JSON below is a plausible reconstruction matching the confirmed general MenuItem field shape, not confirmed data; userFilterType/userFilterValue are left empty to match the one real example on file (see Live Data Samples) rather than guessing "MEC"/"DAL".

The Welcome banner reads from IAuthentication/UserInfo, not MobileContent (see content contract §4, "Welcome / Pilot Card" row) — no JSON needed for that part. The Council 44 row below it, if it's a MenuItem, would plausibly look like this:

{ "id": 5301, "parentId": 0, "replacedId": 0, "title": "Local Council 44 (ATL)", "description": null, "path": "MEC/DAL/Council44", "imageSource": "council44-thumb.jpg", "glyph": "", "glyphFontFamily": "", "sortOrder": 1, "enabled": true, "visible": true, "specialCode": "", "default": false, "showOnHomeScreen": true, "showToAdmin": false, "userFilterType": "", "userFilterValue": "", "parentFilterType": "", "parentFilterValue": "", "createdUser": "system", "createdDateTime": "2026-06-01T00:00:00", "lastUpdatedUser": "system", "lastUpdatedDateTime": "2026-06-01T00:00:00" }
3 Publications feed — not yet populated Placeholder / stub state
DAL Home Publications feed, placeholder content

DAL Home, Publications slice

This is the same DocumentItem feed shape as CLE172's Events — this is literally what "not populated" looks like at the JSON level, not just visually. Every not-yet-content-team-populated feed across every MEC screen documented so far (DAL Publications, DAL Council Updates, DAL Notepads) uses this exact stub pattern:

[ { "id": 9001, "scope": "MEC", "category": "DAL-Publications", "grouping": null, "docType": "Article", "title": "SUBJECT LINE", "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.", "publishDate": "2026-06-16T00:00:00", "image": null, "fileID": null, "source": "stub — no comms-content.html source for DAL yet", "path": null, "read": false } // × 3, identical shape, same publishDate ]

If you're authoring mock data for an unbuilt MEC section, matching this exact stub shape (title: "SUBJECT LINE", lorem-ipsum description, a real but generic publishDate, image: null) is the established convention — it's what every other in-flight MEC screen already does, and it reads clearly as "structure confirmed, content pending" rather than looking like a bug.

4 Contract 2026 feed — populated, contrast with chunk 3 Title text confirmed — JSON structure illustrative
DAL Home Contract 2026 feed, real content

DAL Home, Contract 2026 slice

Partial confidence, not fully captured. The card title/subtitle text ("CONTRACT COMPARISON" / "View the Contract Comparison Guide") is genuinely confirmed — it's real, screenshot-verified text from dal-mec-page-compositions.html#contract-2026, matching this session's UAL finding that not-yet-populated feeds use a distinct "SUBJECT LINE" stub pattern (chunk 3) — this one clearly isn't that. But the surrounding JSON shape (id, exact scope/category strings, path) was not independently captured this session; it's a plausible reconstruction, not confirmed data. Do not treat the field values below as confirmed — only the card's displayed text.

Same DocumentItem shape as chunk 3, illustrating the "populated" contrast against the stub pattern above:

{ "id": 9101, "scope": "MEC", "category": "DAL-Contract2026", "grouping": null, "docType": "Article", "title": "CONTRACT COMPARISON", "description": "View the Contract Comparison Guide", "publishDate": "2026-06-01T00:00:00", "image": "dal-contract-comparison.jpg", "fileID": null, "source": null, "path": "MEC/DAL/ContractComparison", "read": false }
5 Navigate row (icon tiles) Illustrative — not independently captured
DAL Home Navigate row and Tools feed

DAL Home, Navigate + Tools slice

Same caveat as chunk 1. "PILOT DASHBOARD" is real, screenshot-confirmed tile text, but the JSON below was not independently captured this session — plausible reconstruction only, userFilterType/userFilterValue left empty to match the one real example on file rather than guessing.

"NAVIGATE" is a 3-up card-btn row — same MenuItem shape again, this time with a glyph (icon tiles use the app's icon system, not an image):

{ "id": 5310, "parentId": 0, "replacedId": 0, "title": "PILOT DASHBOARD", "description": null, "path": "MEC/DAL/Dashboard", "imageSource": "", "glyph": "ViewDashboard", "glyphFontFamily": "MaterialIcons", "sortOrder": 1, "enabled": true, "visible": true, "specialCode": "", "default": false, "showOnHomeScreen": true, "showToAdmin": false, "userFilterType": "", "userFilterValue": "", "parentFilterType": "", "parentFilterValue": "", "createdUser": "system", "createdDateTime": "2026-06-01T00:00:00", "lastUpdatedUser": "system", "lastUpdatedDateTime": "2026-06-01T00:00:00" }

The "TOOLS" feed below it ("SCHEDULING TICKETING SYSTEM", "DART") has real, screenshot-confirmed title/description text — same caveat as chunk 4 applies to its exact JSON shape.

Full Worked Payload — CLE172, End to End

One complete, literal starting point: the resolved UAL theme (fully confirmed, from theme-endpoint-contract.html §4) plus the content items from the CLE172 chunks above, combined as a backend dev would actually receive/author them across the two endpoints.

GET /api/theme/UAL

{ "mecId": "UAL", "themeVersion": "2026-06-26T00:00:00Z", "tokens": { "Surface/Brand": "#002243", "Text/On-Brand": "#bed6fb", "Action/Primary": "#0008ce", "Nav/Background": "#002243", "Font/Heading": "LeagueSpartan", "Font/Body": "LeagueSpartan" // ...full 45-token set — see theme-endpoint-contract.html §4 for every field } }

MobileContent items for CLE172 (menu items + documents combined)

{ "menuItems": [ { "id": 4201, "title": "CLE 172 Roster", "path": "LECs/CLE172/Roster", "sortOrder": 1, "userFilterValue": "UAL" }, { "id": 4204, "title": "Meeting Minutes Archive", "path": "https://ual.alpa.org/LECs/CLE-172/Meeting-Minutes", "sortOrder": 4, "userFilterValue": "UAL" } ], "documentItems": [ { "id": 8801, "scope": "MEC", "category": "LEC-CLE172-Events", "title": "GUARDIANS VS PIRATES", "description": "SAVE THE DATE", "publishDate": "2026-07-19T00:00:00", "image": "Slider-CLE172-FamAwareness.jpg" }, { "id": 8802, "scope": "MEC", "category": "LEC-CLE172-Events", "title": "CLEVELAND AIRSHOW", "description": "SAVE THE DATE", "publishDate": "2026-09-07T00:00:00", "image": "Slider-CLE172-Airshow.jpg" } ] }

Trimmed to the fields that vary per item for readability — every field from the chunk examples above is present in the real payload; this isn't a smaller schema, just a smaller listing. Events (chunk 2) and Representatives (chunk 3) are intentionally absent from this payload — both are confirmed sourced from carrier/*, not MobileContent; see Live Data Samples for their real captured shape.

Rules Recap