The seven Figma screens covering KCM and Airline Policies, specified from the working Figma file and reconciled against the current Blazor implementation. Divergences are logged as numbered findings; fixes are separate work items.
grep for "filter" matched the identifier FilteredAirlines. Class surfaces
and service injection are not evidence of behaviour.
| Figma node | Screen | Route | Verdict |
|---|---|---|---|
4562:7572 | KCM home | /kcm |
DIVERGES KCM-1 |
4562:7754 | KCM-Airlines | /kcm/airlines |
DIVERGES KCM-2, KCM-3, KCM-4 |
4562:8545 | overlay-kcm filter | — none — | NOT BUILT KCM-5 |
4562:8249 | KCM-Airports (list) | — none — | NOT BUILT KCM-6 |
4562:7954 | Airline Policies — search state | /kcm/policies |
PURGED 2026-08-13 — the screen had no backing data source and no
legacy-XAML counterpart; the search need is served by /jumpseat/airline-policies's
title-bar search state (4713:25303). |
4713:25321 | Airline Policies — default state | /jumpseat/airline-policies |
DIVERGES KCM-7 |
4713:25339 | Airline Policies — detail | /jumpseat/airline-policy |
DIVERGES KCM-7 |
| — no node — | KCM airport detail | /kcm/airport/{Code} |
NO DESIGN KCM-8 |
/kcm/policies and /jumpseat/airline-policies
look alike and share the alpa-jsff-* form classes and the JumpseatQueriesRouter
gateway, but they are different screens. KCM's is a search form (airline
select + airport field + submit) over KCMAirlineItem, badging jumpseat eligibility by
crew role. Jumpseat's is a browse list with type-ahead over PolicyViewModel,
badging carrier affiliation (ALPA / CASS). Different data, different facts, different
destinations. A shared gateway is not evidence of shared purpose./kcm/policies was purged: the "different data"
above never materialized (no backing data source), and the legacy XAML app has no such screen. The one
airline-policy search surface is /jumpseat/airline-policies.
The Airlines screen's two columns are an independently maintained list of carriers that support jumpseating by employee role. That is modelled and available:
| Model | Fields | Used by |
|---|---|---|
KCMAirlineItem |
airlineName · pilots (bool) · attendants (bool) |
KCM Airlines, KCM Policies search |
JumpseatAirlineItem |
AirlineName · IsALPACarrier · IsCASSCarrier · Policy · Website |
Jumpseat Airline Policies |
These are different lists carrying different facts and must not be conflated —
ALPA/CASS is carrier affiliation, pilots/attendants is jumpseat eligibility
by crew role. Source today is MockKcmApiService, pending the live endpoint; eligibility
semantics are recorded in kcm-screen-data-gap.html.
| Token | Value | Used by |
|---|---|---|
Page Margins / Page Columns | 16 / 8 | all KCM screens |
bg-no-img / bg-03 | #F5F7FA | page background |
Surface/Subtle | #dfedf9 | Airlines rows, Policies detail |
Border/Default · Border/Subtle | #d2d4d6 · #e3e3e3 | form fields · separators |
Border Radius/S | 0 | form fields — square, not rounded |
Drop-subtle | drop shadow #0000001A, radius 7 | Airlines rows |
Buttons and Form Fields | padding 16/16, icon-text gap 12 | all form fields |
UI/form-text labels · UI/Primary | #5d6471 · #007bc2 | filter overlay · Airlines |
| Type style | Family | Style | Size | Line height | Found on |
|---|---|---|---|---|---|
Display/XS | Lora | SemiBold 600 | 16 | 1.2 | KCM home, filter overlay, Policies detail |
UI/forms/input text | Futura | Medium 500 | 18 | 1.0 | Airlines, Airports, Policies search |
Other/Form Field | Inter | Regular 400 | 14 | 1.5 | filter overlay |
At least 11 type styles, not 8. All three map onto the two licensed Futura PT files by the existing rule (≤ 500 → Book 400, ≥ 600 → Bold 700), so no mapping change is required — only the count.
Other/Form Field and Other/Caption separate.
They carry identical values today (Inter Regular 14px, lh 1.5) under distinct family and weight
tokens. Two variables cost almost nothing; merging them destroys information, so a later decision to
restyle form fields would silently move every caption too. Distinct roles that happen to share values
are cheap insurance. Extending for new design styles is likewise cheap: our scale steps are keyed by
size, not role, so a new style at an existing size needs no new variable at all.
--font-title-lg-size (28px) is a scaffold artifact, not a missing design style.
The Display ramp found in Figma is XS 16 / S 20 / M 24 — no 28. Tracing the three unaccounted sizes in
our stylesheet: 28px is used by exactly one rule (.alpa-kcm-hero-status), 36px by one
(.alpa-kcm-hero-iata), and 48px by an icon glyph. The first two belong to the bespoke KCM
hero that Figma does not specify (KCM-1), so both should retire with that component. Confirmed 2026-07-31: this is a scaffold artifact, not a missing
design style — no design input required.
Figma 4562:7572 specifies the title "Known Crewmember" over four
card-sm rows (51px each). KcmHomePage.razor renders something else
entirely:
KCM STATUS /
ACTIVE / "United Airlines · Employee #482910". A fabricated employee number
and a status that is never computed..alpa-policy-row.None of this is in the design, and the hardcoded status is the most pressing part — it presents fabricated eligibility as fact.
card-sm rows to draw in place of a hero and
some tiles. Reading the legacy XAML on 2026-07-31 showed the rows are not a fixed list at
all. KCMHomePageViewModel builds them from the KCM
MenuItem's children — rendering each only when Visible, navigating only
when Enabled — and then appends Usage Procedures from its own separate
MenuItem only when that is Enabled.
KCMAirlinesPage, KCMAirportsPage and
WebViewPage?SourceString=https://www.tsa.gov/…. Rendered into an anchor unchanged they
resolve as relative Blazor routes and navigate nowhere, so the Blazor page has to map them.img_kcm_white.png at 0.2 opacity in XAML),
but that file exists only under native Resources/Images — there is no copy in
wwwroot. The page background colour itself is already correct
(--alpa-page #f5f7fa matches the frame).4562:7572: title over a
card-sm stack at the design's 16px gap (cards at y 0/67/134/201 against a 51px row), on
the existing page background. Visible and Enabled are both honoured, and an
unmapped destination is presented as unavailable rather than linked into a dead end. The hero, the
emoji tiles and the invented "Recent airports" list are gone.
jsff is a misnomer for a shared form subsystem NAMINGKCM's screens are built from .alpa-jsff-* classes. That prefix reads as Jumpseat Flight
Finder private, but the subsystem is used by six pages — Jumpseat search and policies,
both KCM screens, MEC hotels and global Search. It is a de-facto shared form system, so the reuse is
legitimate; the name misleads and invites exactly the cross-subsystem-borrowing suspicion this
audit initially raised. Worth renaming to a neutral form prefix.
Figma 4562:7754 specifies a table: a persistent header row —
Airline · Pilots · Flight Attendants — with each row carrying two check-mark
columns. The 188px head frame is taller than the standard template to accommodate it.
The implementation renders the same information as StatusBadge pills shown only when true, stacked under the airline name, with no header. The data is correct and present; the presentation differs in a way that matters:
The legacy XAML (KCMAirlinesPage.xaml) does implement the header and columns, binding
pilots and attendants to check-mark visibility. It is the closer reference.
min-height containers so rows grow rather than clip
(WCAG F69), and the --fs-* non-linear text-scale steps. A grid built with fixed row
heights or hard-coded pixel type would undo all three. Build the columns with
min-height rows and the existing scale steps, and the check-mark glyphs as fixed
geometry (they are icons, not text, so they legitimately do not scale).
4562:7754 (row order matches the design's sample). New
alpa-kcmal-* prefix, verified unused so no later selector can silently override it.
Constraints held: min-height rows, --fs-* type steps, check glyph fixed
at 22px.
4562:8918 is a single symbol with no variant set, and the page
mock shows every row fully ticked — so false is never drawn. Live data has it
constantly: 125 eligible / 27 not. Rendering nothing for false would
reproduce the very ambiguity this rebuild exists to remove, so absence is stated explicitly — same
22px circle, tick omitted, muted, with an aria-label of "not eligible" so assistive
tech states the value rather than inferring it from a missing element. Our reading, not
design intent; an explicit ×, or a third "unknown" state, are equally reasonable. Raised
for design in new-components-2026-06-10.html.
<a href="/kcm/airport"> — an airport route, with no Code
parameter, reached from an airline list — so every airline opened the same parameterless
airport detail. The design has no chevron and no drill-in, so rows are no longer links at all.
StatusBadgeKind.Cass mislabels flight attendants BUGBoth KCM screens badge crew-role eligibility with:
Label = "PILOTS", Kind = StatusBadgeKind.Kcm and
Label = "FLIGHT ATTENDANTS", Kind = StatusBadgeKind.Cass
CASS is the Cockpit Access Security System — it governs cockpit access for pilots and has
nothing to do with flight attendants. The Jumpseat policies page uses the same
Kind = Cass to badge an actual CASS carrier, so two screens style semantically unrelated
facts identically. The enum value is being used as a colour token rather than a meaning.
Cass went with
them, so no new enum value was introduced for a component the design deletes.
ALPA / CASS badges on JumpseatAirlinePoliciesPage are
deliberately left in place: those are carrier type, where Kind = Cass is the
correct meaning. Note they are still a divergence from 4713:25321, which shows no
badges — carrier type already appears on the detail screen. Open, pending a call.
Figma 4562:8545 reuses the flight finder filter and sort component (357px):
two radio buttons, a primary button and an underline Clear, with the tab group hidden. No KCM page
contains any filter or sort UI. Its two radio options are presumably the crew-type split, which the
current pill presentation could not express as a filter anyway.
Figma 4562:8249 specifies a Secondary button above a list of 10 form-field rows (56px),
with back + search in the nav. No /kcm/airports route exists —
/kcm/airport/{Code} is the detail page. Legacy XAML exists at
ALPAMobile/Pages/KCMAirportsPage.xaml and should be consulted for behaviour.
KCMAirportItem carries airportName, airportCode,
checkinNotes, mapUrl and no crew-role booleans.
KCMAirlineItem is the only model with pilots/attendants, so a
check-column table here would have nothing to check — and the mock 4562:8249 is a plain
drill-in list anyway. Build it as a list. The erroneous note is corrected at source; no design input
needed.
JumpseatAirlinePolicyPage.xaml were rendering
incorrectly when this document was written: the Track B FormFieldListRowView migration
dropped the <Border> that supplied each row's white card, so rows sat directly on
BannerPage's blue gradient, and TrailingText was set to »
(a double angle quote) instead of a single chevron. Fixed under
AB#2397 — the pages now render white rounded cards with 15pt gaps and one chevron,
matching what ships on DotNet10.
JumpseatAirlinePolicyDetailPage.razor exists at
@@page "/jumpseat/airline-policy", the list's
/jumpseat/airline-policy?airline=… link resolves to it, and it was built on
2026-07-18/20 under AB#2301 (7654d893, c25d0d01,
dd9f3f1a) — eleven days before this document was written, with all three commits
ancestors of the tip it claims to audit.
Figma 4713:25339 specifies a heading ("21 AIR" over "CASS Carrier"), an
MEC-scoped callout ("UAL MEC JUMPSEAT DETAILS / UAL does not have a reciprocal
agreement with 21 Air"), an "*Information below last updated 2/10/2026" freshness line, and then
four discrete labelled fields — Airline Summary, Airline Jumpseat
Reciprocal Agreement, Jumpseat Dress Code, Ability to Cockpit Jumpseat
International — each a small label above its value.
What is built. The page renders the name heading, the ALPA/CASS carrier line, the
MEC callout (with credentials, MEC links and sign-in gating), the CMS body, and a feedback line. The
MEC callout already resolves the viewer's MEC rather than using a static string, and treats the
"---" MecId sentinel as GENERAL.
The real divergence is the body, and it is a data-shape gap — not a UI gap.
PolicyViewModel carries a single Description holding CMS HTML, rendered as one
block via SanitizeCmsHtml. It has no field for any of the four labelled
values and no last-updated field. Rendering the specified structure therefore cannot be
done in the component alone; it needs either four typed fields plus a timestamp from the source, or
parsing of the CMS HTML — and parsing prose into structure is brittle enough to be a poor trade.
4713:25339, the policy payload needs AirlineSummary,
ReciprocalAgreement, DressCode and InternationalCockpitJumpseat
as discrete values, plus a LastUpdated date for the freshness line. Until then the single
CMS body is the only faithful rendering of the data that exists. Raise with the backend team alongside
the other data-shape asks rather than absorbing it as UI work.
The legacy list page for this screen, JumpseatAirlinePolicyPage.xaml, carried the same
rendering regression described under KCM-6 and is fixed under AB#2397.
It matters less here than for Airports, since this screen does have Figma coverage
(4713:25321 / 4713:25339) — the design, not the legacy page, is the
reference.
/kcm/airport/{Code} (KcmAirportDetailPage.razor) is implemented but has
no Figma node — the inverse of the usual gap. It also uses two prefixes
(.alpa-kcm-* and .alpa-kcmad-*), so its styling is not cleanly owned.
Implemented 2026-08-13 — the XAML-parity alignment proposed below is now the shipped
Blazor page: airport name · inline cached map (IMapCacheService, cache hit inlined as a
data: URI for the WebView) with loading/offline states and a full-screen viewer · check-in
notes · advisory, all on app tokens. The scaffold's IATA hero and the per-airport KCM AIRLINES reference
list are gone, and with them the .alpa-kcmad-* prefix. The screen still has no Figma node —
the design-status question for the team below stands.
ALPAMobile/Pages/KCMAirportDetailsPage.xaml), which is the shipped behaviour this screen
replaces. Offered as our sensible alignment for the design team to accept, amend or replace — not as
an assertion of design intent.
| Region | Content | Proposed treatment |
|---|---|---|
| Heading | Full airport name | Display/XS (Lora SemiBold 16) — matches the other KCM headings |
| Map | Schematic terminal diagram with KCM checkpoint pins, tappable to full screen | Full-bleed within page margins; "Tap to view full screen" hint below |
| Map — full screen | Same diagram enlarged, plus an unlabelled floating action button | Undesigned second view — needs a designed affordance for the FAB, or its removal |
| Map — offline | Icon + "Map unavailable offline" + "Connect to WiFi to download and cache this map for offline use" | Same treatment as the Off the Radar empty state, on bg-no-img #F5F7FA |
| Check-in notes | checkinNotes — multi-paragraph, one entry per checkpoint with hours and station count | Body/Body M (18) with paragraph spacing between entries |
| Notice | Advisory text, conditional | Body/Body S (12), secondary colour |
The screen is two views, not one — the detail and the full-screen map it opens. Both are captured in §6.
/kcm/airport/{Code} is live in the app but has no Figma node — it was
carried over from the legacy app without a design pass. It is the only KCM screen in that position.
Our proposed alignment, derived from the shipped screen's content model and mapped onto real design tokens. Offered so the gap is not simply left open — please accept, amend or replace:
| Region | Content | Proposed treatment |
|---|---|---|
| Heading | Full airport name ("Hartsfield-Jackson Atlanta International Airport") | Display/XS (Lora SemiBold 16) — matches other KCM headings; wraps to 2 lines at the long end |
| Map | Schematic terminal diagram with KCM checkpoint pins, tappable to full screen | Full-bleed within page margins; "Tap to view full screen" hint below |
| Map — full screen | Same diagram enlarged, with a floating action button | Undesigned. The FAB carries no label or icon meaning in the current build — needs a designed affordance, or removal |
| Map — offline | Icon + "Map unavailable offline" + "Connect to WiFi to download and cache this map for offline use" | Same treatment as the Off the Radar empty state, on bg-no-img #F5F7FA |
| Check-in notes | Multi-paragraph operational text — checkpoint name, hours, and station count per entry (e.g. "South Security Checkpoint: 0400-2100 (2 KCM Stations)") | Body/Body M (18); needs paragraph spacing — these are discrete entries, not prose |
| Notice | Advisory text, conditional (absent on ATL) | Body/Body S (12), secondary colour |
What we need: accept the proposal, amend it, or design the screen — note this is two views, the detail and the full-screen map. If this is a legacy surface being retired, say so — we will stop investing in it rather than design it twice. The two parts most in need of a designed treatment are the offline map state (currently a bespoke fallback) and the full-screen map's floating action button (currently unlabelled).
Other/Form Field vs Other/Caption — identical values
today, kept as separate tokens so a future change to one does not silently move the other.4562:7754),
replacing the current pills. No design input needed.This document is specification and audit only — no code was changed. The implementation work it implies:
card-sm layout, and remove the
hardcoded status (KCM-1). Retire the 28px/36px hero sizes with it.min-height rows and the --fs-* scale steps — do not reintroduce fixed row heights or hard-coded type, which would regress the AB#2358 / AB#2391 work.StatusBadgeKind.Cass misuse — flight attendants need their own kind (KCM-4).jsff form prefix to something neutral (KCM-2).typography-font-mapping.html (§ 4).