Status: Proposed
Date: 2026-09-15
Related plan: Dynamic Feed Orchestrator Refactor Plan
This document defines the boundary between the web-based CMS/admin tool and the ALPA Mobile application for authoring, previewing, publishing, resolving, and rendering dynamic content.
The central decision is:
The admin tool authors, previews, and compiles content contracts. The mobile app consumes the published contract, executes its existing runtime behavior, and renders content through its RCL component library. The mobile app does not know about the admin placeholder package or syntax.
The systems share schemas, component metadata, resolver capabilities, theme tokens, and compatibility information. They do not share mobile authentication code, member-specific runtime logic, or compiled mobile RCL implementations.
The admin tool and mobile app are two clients of the same published content contract:
Admin tool
-> author content
-> preview content
-> validate compatibility
-> publish content
Mobile app
-> load published content
-> resolve runtime data
-> apply member context
-> map to ViewModels
-> render RCL components
The admin tool owns:
The admin tool does not own:
The admin tool needs adapter logic to retrieve data for authoring and preview. Those adapters must run behind an admin backend or controlled gateway.
The gateway owns:
Browser code must not call arbitrary administrator-supplied URLs or receive mobile/API credentials.
The mobile app owns:
DynamicPageLoader;LocalizedDynamicPageLoader policy;DynamicContentHost — the shared page-hosting shell (loading skeleton, not-found/error states, favorite context, component loop) that non-localized and localized dynamic pages render through;DynamicPageOrchestrator, including the ContentResolutionContext (source item, current user, page id, container type) it passes into content resolvers alongside the raw template;ItemDto and ViewModel mapping;ItemComponentFactory;ComponentView;The admin tool should consume a versioned component catalog package. The package is a public authoring contract, not a copy of the mobile source code.
The package contains:
The package does not contain:
The package also includes an admin-only placeholder module. This module is an authoring, preview, and publication dependency for the CMS. It is not a mobile dependency and is not shipped to the mobile application.
Each component or resolver should have an explicit definition similar to:
{
"key": "HeroCard",
"packageVersion": "1.0.0",
"wireType": "HeroCard",
"category": "Content",
"propertiesSchema": "schemas/components/hero-card.json",
"allowedContainers": ["Stack", "Grid", "Carousel"],
"themeTokens": ["HeroEyebrow", "HeroTitle", "HeroParagraph"],
"dataCapabilities": ["documents"],
"favoriteSupport": "optional",
"previewFixture": "fixtures/hero-card-document.json",
"deprecatedAliases": ["DocumentHero"]
}
The registry is the source of truth for the admin authoring surface. It must not be inferred from Razor filenames because filenames do not describe schemas, capabilities, compatibility, or security requirements.
The catalog separates four concepts:
| Concept | Responsibility | Example |
|---|---|---|
| Container | Arranges child content | Stack, Grid, Carousel |
| Visual component | Defines the rendered presentation shape | HeroCard, CardSmall, ButtonCard |
| Data resolver | Obtains or hydrates content | documents, podcasts, pilotProfile |
| Content template | Connects a resolver to a visual component | Document resolver rendered as HeroCard |
A committee or document adapter is therefore not a visual component. It is a data capability that can supply data to a reusable visual component.
The wire contract uses contentType for the data resolver and renderType for the visual
component. These are separate fields:
contentType: DocumentItem -> data resolver
renderType: HeroCard -> visual component
Example:
Carousel
-> contentType: DocumentItem
-> parameters: category = "MEC Updates and Weekly", limit = 3
-> renderType: HeroCard
This allows the same HeroCard to be used by:
There are two adapter layers with separate implementations.
Admin adapters provide safe data for the CMS editor and preview canvas.
An admin adapter definition includes:
contentType value;Initial candidates include:
Mobile resolvers execute when the app consumes published content. They may use:
The mobile resolver maps its result into ItemDto, which then follows the normal component pipeline:
Mobile resolver
-> ItemDto
-> ItemComponentFactory
-> ComponentView
-> RCL component
The implementations share the contentType value and normalized schema, but remain separate where
their security, caching, or member-context behavior differs.
The same contentType can be used by both systems, but each system uses its own adapter and
context. There is no separate resolver field, execution, or executionOwner field in the
published content contract.
contentType + parameters + normalized schema
|
+--> Admin adapter + synthetic preview context -> CMS result
|
+--> Mobile resolver + member/device context -> Mobile result
Examples:
contentType |
Admin preview | Mobile result |
|---|---|---|
documents |
Documents returned by the admin API adapter | Documents returned through mobile document queries/cache |
podcasts |
Public feed preview | Mobile feed result and mobile navigation |
pilotProfile |
Synthetic pilot fixture | Signed-in pilot profile |
lecLink |
Synthetic LEC fixture | Current member's LEC |
favorites |
Synthetic favorite state, if needed for preview | Current member's favorite state |
committeeList |
Neutral or synthetic committee data | MEC/member-filtered committee data |
contractAvailability |
Synthetic capability fixture | Current member's actual document availability |
The contentType and parameters identify the requested content. The adapter implementation and
context determine the result. The CMS must use synthetic or approved admin data; the mobile app
uses its normal authenticated and device-aware services.
The existing persisted format stores template as an escaped JSON string:
{
"itemType": "CardSmall",
"template": "{\"renderType\":\"CardSmall\",\"contentType\":\"LecLink\",\"filterValue\":\"SELF\"}"
}
This storage format remains supported. It is a wire/storage concern, not the internal authoring or runtime model.
The processing boundary is:
Persisted escaped string
-> deserialize once
-> TemplateDto
-> orchestrator and resolver pipeline
The CMS may expose structured fields such as:
renderType: CardSmall
contentType: LecLink
parameters:
filterValue: SELF
When publishing, the admin backend serializes that structured model back into the existing escaped string format. The mobile app continues to deserialize the string into TemplateDto.
Required compatibility behavior:
TemplateDto values;The admin tool should not force a storage migration merely to provide a structured editing experience.
Profile-aware placeholders should be handled by a dedicated package inside the admin system rather than by copying mobile profile or authentication logic into the CMS. The mobile app does not reference this package and does not parse its placeholder syntax.
The package should provide:
Its responsibility is:
Admin placeholder package
-> reads SELF/{mec}/{lec}/profile placeholders
-> validates them against the component catalog
-> substitutes synthetic preview values in the canvas
-> compiles them into the existing mobile runtime contract
It must not:
The package maintains separate representations:
Authored value:
{lec}
Preview value:
005
Published runtime contract:
existing mobile resolver instruction
The preview value is a rendering input only. It must never be serialized as the administrator's value. The admin package emits whatever existing runtime contract the mobile app already supports.
Profile-dependent values need to be authorable without placing a real member account in the CMS. The admin package provides a separate, typed preview context for profile-aware authoring. The placeholder expression is an admin authoring value; it is compiled away before publication. The mobile app receives its existing typed runtime contract, not the admin expression.
SELF is an admin authoring value that means "use the member-aware runtime behavior associated
with this resolver." The admin package validates and compiles it into the existing mobile resolver
contract. The mobile app does not need to know that the author used SELF.
For example:
{
"contentType": "LecLink",
"parameters": {
"filterValue": "SELF"
}
}
The published content must contain the existing mobile resolver contract. The admin package must
not replace SELF with the administrator's profile or a single concrete member value.
The CMS should offer a named, non-production preview context for member-aware content. A preview context is a bundle of safe representative values, not a member identity:
{
"previewContext": {
"profile": {
"firstName": "Preview",
"lastName": "Pilot",
"userType": "Member"
},
"site": {
"mec": "UAL",
"lec": "005"
},
"capabilities": {
"hasContract": true,
"isFavorited": false
}
}
}
The preview context may be selected by label, such as UAL member with LEC 005, but it must not
contain an ALPA ID, email address, access token, favorite record, or any other real member
identifier.
The catalog should declare which placeholders a resolver accepts:
| Placeholder | Meaning | Preview behavior | Runtime behavior |
|---|---|---|---|
SELF |
Authoring instruction for member-aware content | Use selected synthetic preview context | Mobile consumes its existing member-aware resolver contract |
{mec} |
Authoring token for current MEC/site | Use preview context MEC | Mobile consumes its existing site/context contract |
{lec} |
Authoring token for current LEC | Use preview context LEC | Mobile consumes its existing member-aware resolver contract |
{profile.firstName} |
Authoring display value | Use synthetic name | Mobile receives its existing profile-aware content contract |
{profile.hasContract} |
Authoring capability value | Use declared preview capability | Mobile receives its existing authenticated capability result |
{favorite.isFavorited} |
Authoring preview state | Use preview boolean only | Mobile handles favorites through its existing runtime state |
Each placeholder must have a declared type, allowed locations, and fallback behavior. The CMS should not support arbitrary reflection expressions or arbitrary object paths.
Admin placeholder processing and mobile rendering are separate phases:
CMS preview
-> validate placeholder against catalog
-> apply synthetic preview context
-> render preview only
Publish
-> validate placeholder and required runtime capability
-> compile placeholders into the existing mobile runtime contract
Mobile runtime
-> load published JSON
-> consume its normal typed values and resolver contracts
-> apply member visibility and data policy
-> render final RCL component
The CMS must never replace SELF with the administrator's profile. The administrator's identity
and organization membership are not a valid representation of an end user's mobile experience.
The mobile app must not need to know whether a value originated from an admin placeholder; it only
consumes the published contract it already understands.
Preview must visibly distinguish synthetic values from live values. When a capability cannot be represented safely, the CMS should show a controlled placeholder state, for example:
This content is resolved for the signed-in member in the mobile app.
Preview uses the selected synthetic member context.
This is preferable to silently showing an administrator's documents, LEC, favorites, or contract availability and presenting that result as universal content.
PageDto
-> DynamicPageLoader
-> optional localized page selection
-> DynamicPageOrchestrator
-> template visibility and token resolution (ContentResolutionContext-aware where a resolver needs it)
-> mobile content resolver
-> ItemDto
-> ContainerFactory / ItemComponentFactory
-> ComponentView
-> RCL component
Most dynamic pages render the result through the shared DynamicContentHost component rather than owning their own loading/error/favorite-context markup; a page keeps its own hosting shell only when it has real page-specific behavior the shared host doesn't model (see the Dynamic Feed Orchestrator Refactor Plan, Phase 4, for current examples).
The shared rendering boundary is the RCL/ViewModel pipeline, not the feature adapter.
For example, a document carousel can be rendered as:
DocumentItem resolver
-> document rows
-> ItemDto.ItemType = HeroCard
-> CardHeroViewModel
-> CardHero.razor
A search result for the same document should produce the same CardHeroViewModel where the content contract is compatible.
The admin tool should not receive the complete alpa-components.css file. The style contract should be layered:
The CMS may choose:
Surface/Brand;Text/Primary;Font/Heading;BorderRadius/M;IconSize/L;The CMS may not inject arbitrary selectors, positioning rules, or CSS that can break component layout and interaction.
The mobile app continues to own:
The admin preview should implement equivalent component styles using the same token values and component metadata, but it does not need to consume the mobile stylesheet wholesale.
Version independently:
Published content should record the catalog version it was authored against. The mobile runtime should advertise supported catalog versions and capabilities.
Publishing must fail when content requires an unsupported:
Compatibility metadata should define:
A dynamic UAL page can contain:
Site: ual
Page: ual-home
Stack
PilotCard
contentType: PilotCard
Stack
CardSmall
contentType: LecLink
parameters: filterValue = SELF
Grid
ButtonCard: PDR
ButtonCard: Documents
ButtonCard: Committees
Carousel: LATEST UPDATES
HeroCard
contentType: DocumentItem
renderType: HeroCard
parameters: category = MEC Updates and Weekly, sort = date descending, limit = 3
admin preview: documents adapter
mobile runtime: documents resolver
The CMS can author the composition, preview the document carousel through its admin adapter, and publish the site. At runtime, mobile resolves the pilot and LEC content using the current member, while the document resolver produces items for the same reusable HeroCard component.