TL;DR: This is the first stop for exploring the dynamic UI system: server-composed pages arrive as wire DTOs, adapter factories map them onto ViewModels, a fixed Razor component library renders them, and theme tokens brand the result per MEC. This page walks the whole pipeline through one real screen — /home-preview, the ALPA Magazine feed test surface — with live screenshots, then routes you to the deep-dive doc for each layer. If you read only one other doc, read the Dynamic UI Architecture.
Every dynamic screen is the same five stages. Each stage has exactly one owner and one deep-dive doc:
| Stage | What happens | Code (post-D61 locations) | Deep dive |
|---|---|---|---|
| 1. Content source | A feed service returns the page as a list of wire DTOs. Today: mock services (some hydrating from live production data). Future: the AB#2133 dynamic endpoint. | ALPAMobile/Services/MockHomeFeedService.cs · MockHomePreviewFeedService.cs · Mock/Live pins in ScaffoldDataSourceRouters.cs |
Dynamic Feed API contract |
| 2. Wire shape | ItemDto — one flat "bag" per item, discriminated by ItemType. Canonical fields (contract §3.3) with documented legacy fallbacks. |
ALPAMobile.Application/ApiModels/ItemDto.cs |
contract §3 · content-endpoint contract |
| 3. Adapters | Pure-mapper factories turn DTOs/domain models into ViewModels. ItemComponentFactory dispatches on the ItemType discriminator; MenuItemCardFactory/DocumentHeroFactory adapt MobileContent models. Route policy (e.g. /document-open) is owned structurally by the factory. |
ALPAMobile.Presentation/Components/ (all three factories) |
N-Tier § Content-to-Component Mapping · architecture §3.1 |
| 4. Render | ComponentView switches on the ViewModel type and renders the matching library component; ComponentSkeleton renders a shape-matched placeholder until each slot's data is ready (no layout shift). |
ALPAMobile.Presentation/Components/Library/ (25 razors + dispatcher) |
component library · naming decisions (D-record) |
| 5. Theme | Semantic tokens (Surface/*, Spacing/*, Font/*) resolve to per-MEC values. Today: CSS custom properties in alpa-components.css; the theme endpoint (AB#2185–2187) delivers resolved values per MEC at login. |
ALPAMobile/wwwroot/css/alpa-components.css |
theme-endpoint contract · design tokens |
Who owns what after WI-2254/D61: the component library, ViewModels, and adapter factories live in the ALPAMobile.Presentation RCL (MAUI-free, consumable by a future ASP.NET Core host); wire DTOs live in ALPAMobile.Application beside the query ports; pages, navigation chrome (AlpaScreen/TopNav/BottomNav), and the Mock/Live routers stay in the ALPAMobile head. Full layering rules: N-TIER-ARCHITECTURE.md.
/home-preview, End to End/home-preview (ALPAMobile/Components/Pages/HomePreviewPage.razor) is the reference screen for the whole system: it is NOT the future ALPA-brand home page (that is gated on AB#2133 + the theme endpoints), but it is that page's template — and unlike /home's canned JSON, it hydrates from real production data (the ALPA-scope GetDocuments feed) so the pipeline is exercised with live content. When the real endpoint lands, the page swaps its feed service and nothing else changes.
![]() 1 — Progressive hydration: every slot renders its shape-matched ComponentSkeleton first (pilot card, hero, button strip, list rows), and each swaps to the real component as its data comes online. The PromoBanner is already live here. |
![]() 2 — Fully hydrated /home: PromoBanner (AlpaScreen chrome), PilotCard, Carousel of CardHeros, EmergencyButton, CardSmall rows — every one produced by ItemComponentFactory.CreateAll from the feed's ItemDto list. |
![]() 3 — /home-preview on live data: the ALP MAG carousel (real magazine issues, real fileIds behind each link) plus the scope's categories as CardSmall quick-list rows. Note the favorite heart — the auth-gated favorites layer (§2.5). |
MockHomePreviewFeedService mocks the AB#2133 endpoint's response shape while sourcing real content: it asks DocumentsQueriesRouter (live when authenticated, mock otherwise) for the ALPA scope, takes the ALP MAG category's four newest articles for the carousel, and every category becomes a quick-list row:
ItemType = "Feed", // carousel discriminator (contract §5)
SliderTitle = feedCategoryTitle,
SliderViewAllLink = "/documents?scope=ALPA&category=ALP%20MAG",
SliderItems = articles.Select(d => new ItemSliderItemDto {
Id = d.FileID, // server reconciliation key (D39)
Title = d.Title,
// Route policy is owned by the factory — never a raw Path href (P1):
Link = DocumentHeroFactory.OpenRoute(d.FileID),
})
The wire fields and every valid ItemType string are contract-pinned: dynamic-feed-api-contract.html §3 (shapes) and §5 (discriminator table). The discriminators are also pinned by unit tests (UnitTest/ItemComponentFactoryTests.cs) — a test breaking on one is a backend-coordination event, not a refactor.
The page hands the whole list to the factory — unknown item types degrade to "that one widget missing", never a dead page:
var widgets = await Feed.GetWidgetsAsync();
components = Factory.CreateAll(widgets); // ItemComponentFactory, DI singleton
Inside, each arm maps canonical-first with legacy fallback (w.Description ?? w.Blurb, w.Image ?? w.Icon) and encodes render invariants — e.g. a ButtonCard's card-level link and its labeled CTA are mutually exclusive so the renderer can never nest anchors. The three factories and their rules are catalogued in N-Tier § Content-to-Component Mapping; the decisions behind them are D57–D61 in the naming-decisions record.
@foreach (var item in components) {
<ComponentView Vm="item" /> // switch on ViewModel type → CardHero / CardSmall / …
}
Every library component binds one ViewModel via [Parameter, EditorRequired] Vm — that ViewModel is the single binding contract for all surfaces (D20). Until a slot's data arrives, ComponentSkeleton renders the same footprint (screenshot 1) so hydration causes no layout shift. All 25 components + the dispatcher: ALPAMobile.Presentation/Components/Library/; specs per component: component-specifications index.
AlpaScreen (head project) wraps all 28 scaffold pages — top/bottom nav plus the per-page PageBanner slot ("Survey Now Open" in the screenshots). The banner's structured treatment is backend Change 5 / AB#2269; its mapping will become PageBannerPromoFactory at integration time. See the backend API mapping report and the promo-banner walkthrough.FavoriteToggleContext (Presentation, on the IFavoritesQueries Application port) is an opt-in cascading value — pages that supply it get live favorite hearts on their cards (screenshot 3); pages that don't render identically without them. Degrades to "no hearts" if favorites can't load — never a dead page. See the favorites walkthrough.BackgroundToken etc. arrive on the wire as token names and resolve client-side. Per-MEC brand values come from the theme endpoint at login (contract, AB#2185–2187); MEC-level composition examples: MEC page compositions and the MEC customization mock guide.| Piece | Today | Next |
|---|---|---|
| Feed endpoint | Client three-level model (PageDto → containers → items) DELIVERED for /home-preview (WI-2270/D63, 2026-07-17) — PageFactory/ContainerFactory over the mock-pinned IHomeFeedQueries seam, hydrating live GetDocuments data; /home deliberately stays on the legacy flat path (canned JSON) for other devs to roll up | AB#2133 live endpoint (route/auth still open) → router pin flip; /home roll-up on its own schedule |
| Item level (leaf) | Done — canonical ItemDto + adapter factories + pinned tests (WI-2262) | — |
| Component library | Done — 25 components in the Presentation RCL (WI-2254/D61) | Grows per Figma sync (D-record governs naming) |
| Page banner | Image-only live contract; structured treatment on mock preview | Backend Change 5 = AB#2269 (title/subtitle/logo, additive) |
| Theme endpoint | Contract documented; base tokens in CSS | AB#2185–2187 delivery → per-MEC resolution at login |
| Favorites | Mock-pinned (FavoritesUseLive=false) pending gateway rollout | AB#2237 gateway rollout → one-line flip |
/home-preview (Blazor Dev tile → route); live-debugging commands are in development.html · behavior walkthroughs: favorites · promo-banner