Scoping of the Favorites screens — the "My Favorites" list and its condensed variant. Documentation only.
MobileMenu.ApiClient 1.0.7, WI 2182) 2026-07-08; the mobile side (heterogeneous card-lg/card-sm
rendering, remove, empty state, heart-toggle across the card library per D31, and the
Off the Radar not-found fallback) shipped the same day.
See §5 below for what shipped and how the open questions resolved. The original data-gap analysis (§1–§4) is
kept as-is for historical context.


card-lg (favorited document/content), a card-sm (favorited nav tile, e.g. DART), a card-btn (favorited button, e.g. Committees). Heterogeneous list.Master Form Field row list (title + » → item).| Element | Data | Source | Status |
|---|---|---|---|
| Favorites list (both views) | the member's favorited items | — (no favorites API) | gap |
| Per-item render | full item to draw card-lg / card-sm / card-btn | per-type sources once identity is known (DocumentItem · MenuItem · button) | depends |
| Unfavorite / remove | toggle off | — (favorites toggle, Change 3) | gap |
| Ordering | display order | client or API order field | confirm |
This set confirms and refines the Favorites capability ask:
card-lg), a nav tile (card-sm), or a button (card-btn). So the favorites store keys on (itemType, itemId), exactly as proposed.(itemType, itemId) and the client hydrates from the per-type sources (DocumentItem, MenuItem, …). Confirm which approach (hydrated payload vs ID-only + client hydrate).(itemType, itemId). This feature is blocked on it.GetFavorites response returns id/userId/itemTypeId/itemTypeDescription/itemId/sortOrder/title/
audit fields only — not a hydrated payload. The client (FavoritesViewModel.HydrateAsync) joins
itemId against IDocumentsQueries/IMenuQueries to build the card-lg/card-sm
view models.sortOrder is a real field on the delivered
Favorite record.1=Menu Item, 2=Document Item, 3=Notification Center Message. There is no
distinct backend "button" type — card-btn vs card-sm for a Menu Item favorite is a
Figma-curated presentation choice, not data-driven; v1 renders all Menu Item favorites as card-sm uniformly
(flagged as a follow-up once a concrete signal exists to key the split off of).
(Extended 2026-07-16: type 4=Document Category added client-first —
D62, backend handoff Task AB#2271; raw id
{scope}|{categoryKey}, hydrated from the GetDocuments tree. The live API must accept
type 4 before the mock→live flip. The wire ItemId convention is now codified as
FavoriteKey's composite {typePrefix}-{rawId} — menu-/doc-/
msg-/cat-, matching the backend's production rows; parsing splits on the
first dash only (GUID raw ids contain dashes) and legacy un-prefixed values pass
through unchanged, degrading to a failed lookup rather than a crash.)RemoveFavoriteAsync(itemTypeId, itemId); the
heart on the Favorites screen itself is the remove control (no separate confirm step), matching "this screen
removes."HeroCard (Document → card-lg) and
CardSmall (Menu Item → card-sm); Notification Center Message favorites render as a title-only row
(no card component exists for that type). The condensed Master Form Field list view (5287:15454) was
not built in this pass — only the rich view shipped.Card/CardSmall/HeroCard/ButtonCard) only renders when
IAuthentication.IsLoggedIn — not shown-disabled, genuinely absent from the DOM when logged out — and
/favorites itself redirects to /login if visited unauthenticated./favorites → /login redirect, document-favorite add / persist / remove, empty state,
and the Off the Radar page (route, blank-fill copy, back-to-/favorites chevron). Live mode
is not yet exercisable: GET /v2/api/mobilecontent/favorites (and
…/itemtypes, POST) return 404 on the production gateway — the
MobileMenu.ApiClient 1.0.7 package is delivered but the service isn't reachable there yet, and the
resulting ApiException propagates unhandled out of FavoriteToggleContext.LoadAsync,
killing the Home Preview page (hardening fix queued). Superseded 2026-09-18: the 404 is a
2026-07-09 observation against gateway.alpa.org; on gatewayapi.alpa.org — the host this
branch reads in Production mode — favorites and favorites/itemtypes are live (matched on
both hosts 2026-08-28 per wiki/GATEWAY-ENDPOINT-COMPARISON-2026-08-28.md; 200 with 14+ item
types 2026-09-18), so Live mode is exercisable. Also found: menu-item hearts silently no-op when the
rendered menu items carry no SpecialCode (offline-fallback menu list; fixed same day).
Later that day Home Preview's menu quick-list rows were replaced by document category
rows (2026-07-09 direction) — menu-item favorites still render on the Favorites screen, but the app
currently has no surface to add one; the walkthrough's Check 4 is parked until a screen with
saveable menu items ships.FavoriteItemTypes.Contact = 12
for committee volunteers and MEC/LEC representatives, same "shipped ahead of the backend, gated by
FavoriteToggleContext.Supports" precedent as DocumentSet above.
Verified live 2026-09-11/12: the backend already registers {"id":12,"description":"Contact"} on
GET /favorites/itemtypes in production, and a committee-volunteer row round-trips (POST 201 / DELETE 204)
end to end. Representative rows (LEC Chair/Vice Chair/Secretary-Treasurer) carry no stable id on the client yet and
correctly render no heart — see the Tester Walkthrough
Check 10.ContactList.razor) skips
FavCtx.ShowHearts/FavCtx.Supports. Every other card type's heart renders through
ComponentView.CanFavorite, which requires both the member to be logged in with a successful favorites
load (ShowHearts) and the backend to currently register the item type (Supports).
ContactList.razor — the shared component every contact-favoriting caller (LecRosterPage,
Stack.razor.cs) renders through — only checks that the row has a delegate and a non-empty id. Currently
harmless (the backend does register type 12), but if a favorites load fails or the backend ever drops the type,
contact hearts would keep rendering and failing on every tap instead of disappearing like every other card.FavoriteToggleContext caches a member's favorites for the life of the app process and
is never invalidated — its own Invalidate() method has zero call sites anywhere in the repo, and
Authentication.RunLogOutTaskAsync clears other session-lifetime singleton caches
(IThemeCoordinator, IDynamicFeedQueries) on logout but not this one. Live-reproduced: Member
A logs in, favorites the My MEC tile; Member A logs out via Profile → Log Out (no force-quit); Member B logs in
on the same running app — Member B's own My MEC heart renders filled immediately (confirmed via DOM
class inspection, zero new /favorites network calls on Member B's login), and tapping it to "remove" it
fires a DELETE against Member B's account for an item that was never favorited there, which 404s and
leaves the heart stuck filled until the app is force-quit. Root cause is unrelated to the Contact type itself — any
favoritable content is affected — but was found while validating this PR. Fixed 2026-09-12,
AB#2734 — FavoriteToggleContext is now registered AddSingleton (was
AddScoped, which is why the first fix attempt — adding Invalidate() to
RunLogOutTaskAsync alone — had no effect until this correction) and is invalidated on logout
alongside IThemeCoordinator/IDynamicFeedQueries. See
Tester Walkthrough Check 11 (now a regression
check, confirmed passing) and the multi-dimension code review this finding came out of for the other
related gating/lifecycle findings from the same review pass (PR 2426).ItemComponentFactory copies a
feed-declared FavoriteItemTypeId onto a ViewModel unfiltered. The backend's item-type registry already
lists types (5, 6, 9, 10, 11, 13, 14, 15 — KCM Airport/Airline, Jumpseat Policy/Airline, Event, Hotel, Committee,
Country) that FavoriteKey.Prefixes does not cover, so FavCtx.Supports would allow a heart
to render for one, but tapping it throws an uncaught NotSupportedException out of
FavoriteToggleContext.ToggleAsync — which kills the Blazor circuit per that class's own documented
failure policy. Not reachable today (no current adapter declares those types), but reachable the moment a CMS
author publishes feed content that does, with no code change needed on the client.DocumentsListPage.razor's category-drill-down branch rendered each document through the plain
List component, which per D31's own 2026-07-08
implementation note was explicitly never wired for favorites ("flagged as a remaining gap, not silently
dropped"). A complete fix already existed on an unopened, stale branch
(fix/AB2721-doc-favorites-hearts-01, authored 2026-09-10) — migrates the branch onto
DocumentsBrowser (Mode=DocumentList, Presentation=DatedRows), the same
building block a MEC hub's own "Latest Documents" section already rides, so the row picks up
DocumentsBrowser's already-working cascaded FavoriteToggleContext wiring for free.
Rebased onto the AB#2734 fix (22 commits behind, no conflicts) and opened as a PR for the first time.
Live-verified: heart renders, POST 201 itemTypeId 2 "Document Item", appears on Favorites,
removes via DELETE 204, row-body tap still opens the real document.DocumentsListPage.razor's category-picker branch (Documents reached with no MEC
context, e.g. plain scope ALPA) built a bare CardSmallViewModel with only
Title/Link set — no favorite identity at all — even though
CategoryCardFactory (the adapter Home Preview's own
category rows already use) existed and was directly reusable. The AB#2721 branch's own commit explicitly
disclaimed this level ("these rows are categories, not documents"). Fixed by wiring
CategoryCardFactory.Create(cat) plus ComponentView.CanFavorite (reused directly,
not re-implemented) into that branch. DocumentsBrowser's own CategoryGrid mode
was considered and rejected for this branch specifically — it requires a single Scope, and this
list spans every scope when none is given. Live-verified: heart renders, POST 201 itemTypeId 4
"DocumentSet", appears on Favorites, removes via DELETE 204.