Dynamic Feed Refactor — Phase 0 Baseline and Inventory

Status: Complete

Date: 2026-09-22

Parent plan: Dynamic Feed Orchestrator Refactor Plan §5 Phase 0

This is the inventory Phase 0 asks for: current route-to-page mappings, IFeedTemplateContentFactory registrations, search/favorites item-to-ViewModel paths, and existing unit-test coverage — captured before any contract changes so later phases can diff against a known baseline.

1. Route-to-page mappings

Page Route(s) Page key source CMS-driven?
DynamicFeedOrchestratorPage /dynamic-feed-orchestrator (?page={key}) Query param, normalized, passed to IHomeFeedQueries.GetPageAsync directly — no LocalizedDynamicPageLoader Yes
MecHomePage /mec "{englishSiteKey}-home" → LocalizedDynamicPageLoader.LoadAsync Yes
MecCommitteesPage /mec/committees "{englishSiteKey}-committees" → LocalizedDynamicPageLoader.LoadAsync Yes
MecCommitteeDetailPage /mec/committee/{CommCd}, /mec/committees/{CommCd} pageKey query override, else "{englishSiteKey}-committees-{Slugify(comm_name)}" → LocalizedDynamicPageLoader.LoadAsync. The "Committee Information" block is a direct IMECQueries lookup, not feed content — mixed page. Partially
LecPage /mec/{Mec}/lec/{LecId}, /mec/{Mec}/lec/{LecId}/{Page} "{mecSiteKey}-lec-{lecId}[-{page}]" → LocalizedDynamicPageLoader.LoadAsync Yes
LecRosterPage /mec/{Mec}/lec/{LecId}/roster Not feed content — live IMECQueries calls. Borrows the LEC home page's CMS title for display only. No
AlpaIHomePage /alpa Signed-in: const "alpa-home" via IHomeFeedQueries. Signed-out: const "global-home-loggedout" via IDynamicFeedQueries. No LocalizedDynamicPageLoader. Yes
HydrationOrchestratorPage (dev/preview) /HydrationOrchestratorPage "{englishSiteKey}-home" / "{englishSiteKey}-fr-home" pair via IScaffoldContentQueries, side-by-side EN/FR preview Dev tool only
DocumentsHub (embedded component, not routed) n/a DocumentsPageKey property → IHomeFeedQueries.GetPageAsync Yes

No other @page-routed file references PageDto/GetPageAsync/IDynamicFeedQueries/IHomeFeedQueries — confirmed by repo-wide search (KCM, Jumpseat, Documents list, Advocacy, Notifications, Member Resources, etc. are all non-CMS or bespoke-query pages).

Loader split: DynamicFeedOrchestratorPage and AlpaIHomePage call IHomeFeedQueries/ IDynamicFeedQueries + DynamicPageOrchestrator directly. MecHomePage, MecCommitteesPage, MecCommitteeDetailPage, and LecPage all go through LocalizedDynamicPageLoader. This is the existing split Phase 1's IDynamicPageLoader extraction needs to unify — the first two are already effectively using what Phase 1 wants to formalize as the "generic" path.

2. IFeedTemplateContentFactory registrations

Factory ContentType Registered in DI?
LecLinkFactory "LecLink" Yes
PilotCardFactory "PilotCard" Yes
CommitteeListFactory "Committee" Yes
CommitteeMembersFactory "ContactItem" Yes
DocumentHeroFactory "DocumentItem" Yes
PodcastEpisodeFactory "PodcastEpisode" Yes
MecHotelDirectoryFactory "MecHotelDirectory" Yes
DynamicListFactory "DynamicList" No — blocked on IItemsQueries having no production implementation. Only implementation repo-wide is a test double (FakeItemsQueries in DynamicListFactoryTests). This is the only unregistered factory; matches the plan's §8 risk row unchanged.

Registrations live solely in ALPAMobile/HeadServiceCollectionExtensions.cs — no other *ServiceCollectionExtensions.cs registers an IFeedTemplateContentFactory.

3. Search item-to-ViewModel path

Search does not uniformly reuse the feed pipeline:

This is a dual path, not a single shared one — Phase 7 ("Unify search rendering") has real work to do; it isn't starting from zero, but the "reuse" today is a narrow, JSON-round-trip special case, not the general pipeline.

4. Favorites item-to-ViewModel path

Favorites has its own independent hydration/rendering pipeline, entirely separate from ItemComponentFactory/ComponentView/ItemDto:

Favorites reuses the same card-factory classes the feed pipeline uses internally (DocumentHeroFactory, MenuItemCardFactory, CategoryCardFactory, LinkCardFactory) but calls them directly against domain models, not through ItemDto. Contact and Hotel re-implement matching/mapping logic from scratch rather than reusing CommitteeMembersFactory/ MecHotelDirectoryFactory. Phase 8 should prioritize Contact and Hotel — they're the only two with duplicated logic rather than just a different entry point into an already-shared factory.

5. Existing unit-test coverage

Test file Cases Covers
DynamicPageOrchestratorTests 47 Orchestrator resolve/progressive-resolve behavior
ItemComponentFactoryTests 67 Component dispatch across discriminators, including DocumentItem
FavoritesViewModelTests 34 Favorites hydration across all ItemTypeId branches
CommitteeListFactoryTests 22 "Committee" resolver
DynamicFeedRenderHelpersTests 12 Progressive-loading apply/cancel/stuck-container behavior (§2.1 of parent plan)
PilotCardFactoryTests 10 "PilotCard" resolver
CommitteeMembersFactoryTests 7 "ContactItem" resolver
DynamicListFactoryTests 6 "DynamicList" resolver (against FakeItemsQueries; not exercised in production since it's unregistered)
Architecture/RazorComponentResolutionTests — Architecture-level check that every registered RCL tag resolves (lives under UnitTest/Architecture/, not the flat UnitTest/ root the parent plan's file list implies)

Gap found: no dedicated test file exists for PodcastEpisodeFactory ("PodcastEpisode"). It has zero references anywhere under UnitTest/. Every other registered factory has either a dedicated test file or indirect coverage through ItemComponentFactoryTests/DynamicPageOrchestratorTests. Recommend adding PodcastEpisodeFactoryTests before Phase 6 touches shared item-resolution behavior, since there's currently no regression signal for that resolver at all.

Fixture coverage: there's no separate fixture-file directory (no fixtures/*.json anywhere in the repo) — "fixtures" today means inline ItemDto/TemplateDto construction inside each test. Coverage by content type: home (MecHomePageRefreshTests, AlpaIHomePageRefreshTests), committee (CommitteeListFactoryTests), document (ItemComponentFactoryTests, DynamicPageOrchestratorTests), contact (CommitteeMembersFactoryTests), link (LecLinkFactoryTests). All five content types the Phase 0 checklist names already have representative inline fixtures; no new fixture files are needed to satisfy that checklist item, only the PodcastEpisodeFactoryTests gap above.

6. Baseline test run

dotnet test UnitTest/UnitTest.csproj --logger "trx;LogFileName=phase0-baseline.trx" run against this baseline before any contract changes:

Passed!  - Failed: 0, Passed: 2167, Skipped: 0, Total: 2167, Duration: 1m 35s — UnitTest.dll (net10.0)

Results: UnitTest/TestResults/phase0-baseline.trx. Clean baseline — 0 failures, 0 skips — to diff future phase runs against.

Phase 0 exit criteria — status

Phase 0 exit criteria met. Ready to start Phase 1 (extract the common page loader).