Dynamic UI Hub — How It All Fits Together

Updated: 2026-07-17 11:04 ET · Audited: 2026-07-15 23:22 ET

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.

1. The Pipeline in One Table

Every dynamic screen is the same five stages. Each stage has exactly one owner and one deep-dive doc:

StageWhat happensCode (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.

2. Worked Example — /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.

2.1 What you see

Per-slot skeletons during progressive hydration
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.
Fully hydrated home feed
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.
Home preview hydrated from live GetDocuments data
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).

2.2 Stage 1–2: the feed builds ItemDtos

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.

2.3 Stage 3: adapters map DTOs → ViewModels

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.

2.4 Stage 4: ComponentView dispatches, skeletons hold the layout

@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.

2.5 The layers around the feed

3. What's Real Today vs. What's Coming

PieceTodayNext
Feed endpointClient 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 upAB#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 libraryDone — 25 components in the Presentation RCL (WI-2254/D61)Grows per Figma sync (D-record governs naming)
Page bannerImage-only live contract; structured treatment on mock previewBackend Change 5 = AB#2269 (title/subtitle/logo, additive)
Theme endpointContract documented; base tokens in CSSAB#2185–2187 delivery → per-MEC resolution at login
FavoritesMock-pinned (FavoritesUseLive=false) pending gateway rolloutAB#2237 gateway rollout → one-line flip

4. Where to Go Next

ALPA Mobile Team · Dynamic UI Hub · Screenshots captured 2026-07-15 on iPhone 17 Pro Max simulator (post-WI-2254 RCL build, authenticated UAL session, production GetDocuments data)