Pattern
intent and mechanism
Use Web/Components/* as the named fragment API inside an
IHP Web/* layer. A component is not just "some HSX". It is
a reusable Web-layer fragment that a full route page can embed and/or a
controller can return for an HTMX response.
The boundary separates two axes that often get confused:
- Web responsibility — controller boundary, complete
route page, component fragment, authorization policy.
- Reusable vocabulary — page-local presentation
versus shared view helper primitives below
Web/*.
This is a Web request/presentation boundary, not a complete
application module architecture. Domain operations and integrations may
live behind their own named APIs; see the Composition
scope.
The core rule is ownership:
- controllers parse params, enforce method and authorization gates,
orchestrate data access and provider calls directly or through
application APIs, and choose the response at the active IHP
request/response boundary;
- route views compose complete pages and keep page-local helpers
private;
- Web components render named fragments, form groups, controls,
section swap targets, and component-local presentation/workflow
configuration;
- reusable view helpers render repeated presentation primitives, even
when they are entity-specific;
- authorization modules define policy language and decisions.
When a controller needs to return a reusable HTMX fragment, move that
fragment behind a named component entrypoint. The full page delegates to
the same component. This prevents Web/View/* modules from
becoming accidental partial APIs and keeps fragment call sites
grepable.
This is a Composition pattern because the mechanism is code
placement, not a single rendering trick. It composes with HTMX
target/swap patterns, controller gates, authorization policies, reusable
view helpers, and external-provider boundaries.
Search aliases: component boundary, fragment API, partial response
boundary, view-to-component split, controller-owned endpoint,
presentation helper placement, Web layer placement.
Project-specific
notes and rollout guidance
Use component modules when the fragment owns Web workflow vocabulary:
a named component boundary, a domain-specific HTMX swap target,
authorization salience, enum ordering, domain workflow semantics, or
controller-rendered partial responses. Use reusable view helpers when
the code is repeated presentation vocabulary without workflow
ownership.
Generic HTMX helpers belong below Web/* when they
parameterize route/action vocabulary instead of owning it. A helper such
as a select, listing, button, or field renderer may accept a
HasPath action, render hx-post, or receive an
hx-target; that does not make it a component when the
caller supplies the workflow boundary.
A helper can be entity-specific and still belong below
Web/* when it renders pure presentation reused across
unrelated views or components. It may even link to a route when that
link is ordinary presentation vocabulary. Conversely, a fragment can
look visually generic and still belong in Web/Components/*
when it owns route/action/HTMX/workflow semantics.
Use export lists according to the layer. Component modules and
reusable helper modules should have explicit export lists because their
public surface is the API. Ordinary IHP route view modules may stay
idiomatic and omit export lists when they only expose the
View instance and page-local helpers. Add a selective view
export list only when the view module deliberately exposes a named
fragment API for controller use or another reviewed public entry
point.
For component modules, the export list is the fragment API: expose
form groups, controller-facing section renderers, configuration types,
and stable ids that callers genuinely need. Keep DOM-id derivation, row
renderers, OOB helpers, validation internals, and endpoint-local helpers
private unless a concrete consumer needs them as part of the intended
fragment API. A second caller alone does not justify exposing
implementation details.
Do not state a hard rule that every partial-looking helper must leave
a view. A large singular route-local section may stay in its route view
when it is not a shared fragment API, when moving it would only create
ceremony, and when the exception is documented locally. If such a
section is still rendered by a controller, name the controller-facing
renderer clearly, keep low-level DOM ids and plumbing helpers internal,
and use a small config record when the context is dense. But a
controller-used, reusable HTMX fragment normally belongs in
Web/Components/*.
Components may consume authorization decisions or small pure
classification helpers. They must not duplicate policy matrices or
enforce gates. The controller enforces; the component renders the
allowed, disabled, or unavailable state using the decision it was
given.
Components may render forms and controls, but endpoint bodies stay in
controllers. Request parsing, gates, and HTTP response selection remain
at that boundary; database work and external-service calls may be
delegated to named application operations or integrations. Their
implementations need not live in controller files. Small controller
helpers may perform delegated response mechanics, but components remain
renderers rather than endpoint handlers. IHP 1.6 no longer requires
capturing and reinstalling ControllerContext before a
partial response. If a component appears to need response machinery,
split the function into controller orchestration and component
rendering.
Preserve pattern identity in host code through names. Local modules
and helpers should combine domain vocabulary with component vocabulary,
for example itemsIndexSection,
projectMemberRoleDropdown, or
entityPickerComponent. Do not silently inline the component
boundary into a full-page view or controller action.
Treat config records as API hygiene, not architecture dogma. They are
useful for controller-facing fragments with dense context because they
make call sites readable and reduce positional-argument mistakes. They
are unnecessary ceremony for small page-local helpers with one or two
obvious arguments.
Supporting
snippets
Typical component module export shape:
module Web.Components.Items
( ItemsIndexConfig(..)
, itemsIndexSectionId
, itemsIndexSection
, itemFormGroupComponent
) where
Typical full-page view delegation. The explicit view export list is
optional for an ordinary IHP route view; use it when the module
intentionally exposes a reviewed API beyond the framework's view
instance.
module Web.View.Items.Index where
import Web.Components.Items (ItemsIndexConfig(..), itemsIndexSection)
instance View IndexView where
html IndexView { items = items, filterValue = filterValue } = [hsx|
{itemsIndexSection config}
|]
where
config = ItemsIndexConfig { items = items, filterValue = filterValue }
Typical controller fragment response shape:
action ItemsAction = do
let filterValue = fromMaybe "all" (paramOrNothing @Text "filter")
items <- fetchItemsForFilter filterValue
let config = ItemsIndexConfig { items = items, filterValue = filterValue }
if isHtmxRequestFor itemsIndexSectionId
then respondHtml (itemsIndexSection config)
else render IndexView { items = items, filterValue = filterValue }
Typical route-local controller-facing exception. Keep the public
renderer named and narrow; keep DOM-id derivation and small row helpers
private.
module Web.View.Items.Index
( IndexView(..)
, ItemsIndexSectionConfig(..)
, itemsIndexSection
) where
Typical reusable presentation helper counter-shape:
module Application.Helper.View.Entities
( renderEntityLinked
, renderEntitiesLinked
) where
The helper may be entity-specific and may link to routes, but it is
not a component when it has no route-page, HTMX, action, authorization,
or workflow ownership.
Typical parameterized HTMX helper counter-shape:
renderSelectPrimitive :: HasPath action => (value -> action) -> Maybe Text -> [value] -> Html
This helper renders route and HTMX attributes, but the caller owns
the concrete workflow, domain vocabulary, and swap boundary.
Core
reusable pattern infrastructure
The ADT below is deliberately small. It gives agents and maintainers
a typed placement vocabulary without turning the pattern into a
generator or registry.
{-# LANGUAGE OverloadedStrings #-}
module Patterns.Composition.WebComponentBoundary
( WebPlacement(..)
, PlacementSignal(..)
, SplitReason(..)
, PlacementDecision(..)
, PlacementConflict(..)
, classifyWebPlacement
, placementModuleHint
, requiresSplit
) where
import Prelude
import Data.Text (Text)
-- | The allowed homes for Web-layer code under this pattern.
data WebPlacement
= ControllerBoundary
| RoutePageView
| WebFragmentComponent
| ReusableViewHelper
| AuthorizationPolicyModule
deriving (Eq, Show)
-- | Evidence observed while deciding where code belongs.
-- Signals are intentionally mechanism-level, not domain-specific.
data PlacementSignal
= ParsesRequestParams
| PerformsAuthorizationGate
| ReadsOrWritesDatabase
| CallsExternalProvider
| ChoosesResponse
| RendersCompleteRoutePage
| RendersControllerUsedHtmxFragment
| RendersReusableWebFragment
| RendersPureReusablePresentation
| RendersParameterizedHtmxPrimitive
| DefinesPolicyDecision
| ConsumesPolicyDecision
-- | Use only for deliberate, locally documented exceptions that should
-- remain in a route view instead of becoming a component API.
| DocumentedPageLocalSingularSection
| ParameterizesRouteOrActionVocabulary
-- | Concrete route/action use is a weak signal; ordinary links and helper
-- parameters do not imply component ownership.
| UsesConcreteRouteOrActionVocabulary
| OwnsHtmxSwapBoundary
| CarriesDomainWorkflowSemantics
| DuplicatedAcrossViewsOrComponents
deriving (Eq, Show)
-- | Why one observed unit should be split across two homes.
data SplitReason
= ControllerRendersComponent
| ControllerSelectsRoutePage
| ControllerRendersRouteLocalPageSection
| PolicyFeedsController
| PolicyFeedsPresentation
| FullPageDelegatesComponent
deriving (Eq, Show)
-- | A placement decision is either one home or an explicit split.
data PlacementDecision
= Single WebPlacement
| Split WebPlacement WebPlacement SplitReason
deriving (Eq, Show)
-- | Cases where the signals are insufficient for an automatic placement.
data PlacementConflict
= NoPlacementSignal
| AmbiguousHelperVsComponent
deriving (Eq, Show)
The classifier encodes precedence, not full automation. Apply it to
Web-layer request/presentation code, not to an arbitrary IO helper,
domain module, or provider implementation.
ReadsOrWritesDatabase and
CallsExternalProvider identify endpoint orchestration in
that context; they do not require delegated implementations to move into
controllers. It is conservative: when the evidence does not distinguish
helper from component, it asks the caller to decide or split.
classifyWebPlacement :: [PlacementSignal] -> Either PlacementConflict PlacementDecision
classifyWebPlacement signals
| null signals = Left NoPlacementSignal
| has DocumentedPageLocalSingularSection signals && has RendersReusableWebFragment signals =
Left AmbiguousHelperVsComponent
| has RendersPureReusablePresentation signals && has RendersReusableWebFragment signals && not (hasAny strongComponentSignals signals) =
Left AmbiguousHelperVsComponent
| hasAny helperPrimitiveSignals signals && has RendersReusableWebFragment signals && not (hasAny strongComponentSignals signals) =
Left AmbiguousHelperVsComponent
| has DefinesPolicyDecision signals && hasAny controllerSignals signals =
Right (Split AuthorizationPolicyModule ControllerBoundary PolicyFeedsController)
| has DefinesPolicyDecision signals && hasAny presentationSignals signals =
Right (Split AuthorizationPolicyModule (preferredPresentationPlacement signals) PolicyFeedsPresentation)
| hasAny controllerSignals signals && has DocumentedPageLocalSingularSection signals && hasAny renderSignals signals =
Right (Split ControllerBoundary RoutePageView ControllerRendersRouteLocalPageSection)
| hasAny controllerSignals signals && has RendersCompleteRoutePage signals && not (hasAny fragmentSignals signals) =
Right (Split ControllerBoundary RoutePageView ControllerSelectsRoutePage)
| hasAny controllerSignals signals && hasAny renderSignals signals =
Right (Split ControllerBoundary (preferredPresentationPlacement signals) ControllerRendersComponent)
| hasAny controllerSignals signals =
Right (Single ControllerBoundary)
| has DefinesPolicyDecision signals =
Right (Single AuthorizationPolicyModule)
| has RendersCompleteRoutePage signals && hasAny fragmentSignals signals && not (has DocumentedPageLocalSingularSection signals) =
Right (Split RoutePageView WebFragmentComponent FullPageDelegatesComponent)
| has RendersCompleteRoutePage signals =
Right (Single RoutePageView)
| hasAny strongComponentSignals signals =
Right (Single WebFragmentComponent)
| hasAny helperPrimitiveSignals signals && not (hasAny strongComponentSignals signals) =
Right (Single ReusableViewHelper)
| has RendersPureReusablePresentation signals || has DuplicatedAcrossViewsOrComponents signals =
Right (Single ReusableViewHelper)
| hasAny fragmentSignals signals =
Right (Single WebFragmentComponent)
| otherwise = Left NoPlacementSignal
Precedence helpers stay private. They make the classifier readable
and keep the exported API small.
controllerSignals :: [PlacementSignal]
controllerSignals =
[ ParsesRequestParams
, PerformsAuthorizationGate
, ReadsOrWritesDatabase
, CallsExternalProvider
, ChoosesResponse
]
renderSignals :: [PlacementSignal]
renderSignals =
[ RendersCompleteRoutePage
, RendersControllerUsedHtmxFragment
, RendersReusableWebFragment
, RendersPureReusablePresentation
, RendersParameterizedHtmxPrimitive
]
presentationSignals :: [PlacementSignal]
presentationSignals =
renderSignals <> [ConsumesPolicyDecision]
helperPrimitiveSignals :: [PlacementSignal]
helperPrimitiveSignals =
[ RendersParameterizedHtmxPrimitive
, ParameterizesRouteOrActionVocabulary
]
fragmentSignals :: [PlacementSignal]
fragmentSignals =
[ RendersControllerUsedHtmxFragment
, RendersReusableWebFragment
, OwnsHtmxSwapBoundary
, CarriesDomainWorkflowSemantics
]
strongComponentSignals :: [PlacementSignal]
strongComponentSignals =
[ RendersControllerUsedHtmxFragment
, OwnsHtmxSwapBoundary
, CarriesDomainWorkflowSemantics
]
preferredPresentationPlacement :: [PlacementSignal] -> WebPlacement
preferredPresentationPlacement signals
| has DocumentedPageLocalSingularSection signals = RoutePageView
| has RendersCompleteRoutePage signals && not (hasAny fragmentSignals signals) = RoutePageView
| hasAny helperPrimitiveSignals signals && not (hasAny strongComponentSignals signals) = ReusableViewHelper
| has RendersPureReusablePresentation signals && not (hasAny strongComponentSignals signals) = ReusableViewHelper
| otherwise = WebFragmentComponent
has :: PlacementSignal -> [PlacementSignal] -> Bool
has signal = elem signal
hasAny :: [PlacementSignal] -> [PlacementSignal] -> Bool
hasAny candidates signals = any (`elem` signals) candidates
Module hints are examples, not globally required names.
placementModuleHint :: WebPlacement -> Text
placementModuleHint ControllerBoundary = "Web/Controller/*"
placementModuleHint RoutePageView = "Web/View/<Entity>/<Action>.hs"
placementModuleHint WebFragmentComponent = "Web/Components/*"
placementModuleHint ReusableViewHelper = "Application/Helper/View/*"
placementModuleHint AuthorizationPolicyModule = "Web/Authorization/*"
requiresSplit :: PlacementDecision -> Bool
requiresSplit (Single _) = False
requiresSplit (Split _ _ _) = True
Helper
implementation examples
A controller-rendered HTMX section is a split: the controller owns
request and response orchestration; the component owns the stable
fragment renderer.
controllerRenderedSectionDecision :: Either PlacementConflict PlacementDecision
controllerRenderedSectionDecision = classifyWebPlacement
[ ParsesRequestParams
, ReadsOrWritesDatabase
, ChoosesResponse
, RendersControllerUsedHtmxFragment
, OwnsHtmxSwapBoundary
]
A complete route page that delegates to a reusable fragment is also a
split: the route view remains the page, while the component is the
reusable fragment API.
routePageDelegatesToComponentDecision :: Either PlacementConflict PlacementDecision
routePageDelegatesToComponentDecision = classifyWebPlacement
[ RendersCompleteRoutePage
, RendersReusableWebFragment
, OwnsHtmxSwapBoundary
]
A repeated label/list renderer is a reusable view helper when it is
pure presentation vocabulary and carries no HTMX or workflow ownership.
Ordinary route links do not make it a component.
reusablePresentationHelperDecision :: Either PlacementConflict PlacementDecision
reusablePresentationHelperDecision = classifyWebPlacement
[ RendersPureReusablePresentation
, UsesConcreteRouteOrActionVocabulary
, DuplicatedAcrossViewsOrComponents
]
A generic parameterized HTMX primitive is a reusable view helper. It
may render hx-post or hx-target, but the
caller supplies the concrete action and owns the swap boundary.
genericParameterizedHtmxHelperDecision :: Either PlacementConflict PlacementDecision
genericParameterizedHtmxHelperDecision = classifyWebPlacement
[ RendersParameterizedHtmxPrimitive
, ParameterizesRouteOrActionVocabulary
]
A controller-rendered route-local section is an explicit documented
exception: split the request/response work from the route view, but do
not force a component when the section is singular, page-local, and not
a reusable Web fragment.
routeLocalControllerSectionDecision :: Either PlacementConflict PlacementDecision
routeLocalControllerSectionDecision = classifyWebPlacement
[ ParsesRequestParams
, ChoosesResponse
, RendersCompleteRoutePage
, DocumentedPageLocalSingularSection
]
A documented route-local exception cannot also claim to be a reusable
Web fragment. That contradiction must stop instead of silently keeping
the fragment in the route view.
ambiguousRouteLocalReusableFragmentDecision :: Either PlacementConflict PlacementDecision
ambiguousRouteLocalReusableFragmentDecision = classifyWebPlacement
[ ParsesRequestParams
, ChoosesResponse
, RendersReusableWebFragment
, DocumentedPageLocalSingularSection
]
The same contradiction must stop even without controller signals;
otherwise a complete route-page render could hide a reusable fragment
inside the documented exception.
ambiguousRoutePageReusableFragmentDecision :: Either PlacementConflict PlacementDecision
ambiguousRoutePageReusableFragmentDecision = classifyWebPlacement
[ RendersCompleteRoutePage
, RendersReusableWebFragment
, DocumentedPageLocalSingularSection
]
A provider-backed autocomplete endpoint is split three ways in
practice. This classifier identifies the Web-layer split; compose it
with ExternalProviderModuleBoundary for the provider module
itself.
providerAutocompleteEndpointDecision :: Either PlacementConflict PlacementDecision
providerAutocompleteEndpointDecision = classifyWebPlacement
[ ParsesRequestParams
, PerformsAuthorizationGate
, CallsExternalProvider
, ChoosesResponse
, RendersControllerUsedHtmxFragment
]
Usage
examples
Use the decision values as executable documentation in host-project
planning or review. The expected shapes below are intentionally small
and grepable.
expectedControllerRenderedSection :: PlacementDecision
expectedControllerRenderedSection =
Split ControllerBoundary WebFragmentComponent ControllerRendersComponent
controllerRenderedSectionIsPlaced :: Bool
controllerRenderedSectionIsPlaced =
controllerRenderedSectionDecision == Right expectedControllerRenderedSection
A full route page delegates its reusable fragment to the component
layer.
expectedRoutePageDelegation :: PlacementDecision
expectedRoutePageDelegation =
Split RoutePageView WebFragmentComponent FullPageDelegatesComponent
routePageDelegationIsPlaced :: Bool
routePageDelegationIsPlaced =
routePageDelegatesToComponentDecision == Right expectedRoutePageDelegation
Repeated presentation without workflow ownership remains a reusable
helper.
expectedReusablePresentationHelper :: PlacementDecision
expectedReusablePresentationHelper =
Single ReusableViewHelper
reusablePresentationHelperIsPlaced :: Bool
reusablePresentationHelperIsPlaced =
reusablePresentationHelperDecision == Right expectedReusablePresentationHelper
A parameterized HTMX primitive follows that same helper
placement.
genericParameterizedHtmxHelperIsPlaced :: Bool
genericParameterizedHtmxHelperIsPlaced =
genericParameterizedHtmxHelperDecision == Right expectedReusablePresentationHelper
A documented route-local exception keeps its renderer in the route
view while separating controller orchestration.
expectedRouteLocalControllerSection :: PlacementDecision
expectedRouteLocalControllerSection =
Split ControllerBoundary RoutePageView ControllerRendersRouteLocalPageSection
routeLocalControllerSectionIsPlaced :: Bool
routeLocalControllerSectionIsPlaced =
routeLocalControllerSectionDecision == Right expectedRouteLocalControllerSection
A section cannot simultaneously claim the route-local exception and
reusable fragment ownership.
ambiguousRouteLocalReusableFragmentStops :: Bool
ambiguousRouteLocalReusableFragmentStops =
ambiguousRouteLocalReusableFragmentDecision == Left AmbiguousHelperVsComponent
Adding a complete-page signal must not hide that same
contradiction.
ambiguousRoutePageReusableFragmentStops :: Bool
ambiguousRoutePageReusableFragmentStops =
ambiguousRoutePageReusableFragmentDecision == Left AmbiguousHelperVsComponent
A helper/component ambiguity is a stop point. If the code is merely
repeated presentation or a parameterized HTMX primitive, keep or move it
to the helper layer. If it owns a concrete HTMX boundary or domain
workflow semantics, place it as a component.
ambiguousHelperOrComponentDecision :: Either PlacementConflict PlacementDecision
ambiguousHelperOrComponentDecision = classifyWebPlacement
[ RendersPureReusablePresentation
, RendersReusableWebFragment
]
ambiguousHelperOrComponentStops :: Bool
ambiguousHelperOrComponentStops =
ambiguousHelperOrComponentDecision == Left AmbiguousHelperVsComponent
A pure-presentation fragment with concrete HTMX or workflow ownership
is no longer ambiguous; the ownership signal makes it
component-owned.
ownedPurePresentationFragmentDecision :: Either PlacementConflict PlacementDecision
ownedPurePresentationFragmentDecision = classifyWebPlacement
[ RendersPureReusablePresentation
, RendersReusableWebFragment
, OwnsHtmxSwapBoundary
]
ownedPurePresentationFragmentIsComponent :: Bool
ownedPurePresentationFragmentIsComponent =
ownedPurePresentationFragmentDecision == Right (Single WebFragmentComponent)
Duplication does not override concrete controller-fragment ownership.
Repeated controller-facing fragments remain components, including when
they also render pure presentation or parameterize route vocabulary.
duplicatedControllerFragmentIsComponent :: Bool
duplicatedControllerFragmentIsComponent = all placedAsComponent
[ [RendersControllerUsedHtmxFragment, DuplicatedAcrossViewsOrComponents]
, [OwnsHtmxSwapBoundary, DuplicatedAcrossViewsOrComponents]
, [RendersControllerUsedHtmxFragment, RendersPureReusablePresentation]
, [RendersControllerUsedHtmxFragment, RendersParameterizedHtmxPrimitive]
]
where
placedAsComponent signals =
classifyWebPlacement signals == Right (Single WebFragmentComponent)
A parameterized HTMX primitive that is also marked as a reusable Web
fragment is ambiguous unless it owns a concrete HTMX/workflow boundary.
Do not silently push that mixed signal to the helper layer.
ambiguousParameterizedFragmentDecision :: Either PlacementConflict PlacementDecision
ambiguousParameterizedFragmentDecision = classifyWebPlacement
[ RendersParameterizedHtmxPrimitive
, RendersReusableWebFragment
]
ambiguousParameterizedFragmentStops :: Bool
ambiguousParameterizedFragmentStops =
ambiguousParameterizedFragmentDecision == Left AmbiguousHelperVsComponent
Those ambiguity contracts also win before policy splitting.
Policy-aware fragments still need a coherent component/helper/page
boundary.
ambiguousPolicyRouteLocalFragmentDecision :: Either PlacementConflict PlacementDecision
ambiguousPolicyRouteLocalFragmentDecision = classifyWebPlacement
[ DefinesPolicyDecision
, ConsumesPolicyDecision
, RendersReusableWebFragment
, DocumentedPageLocalSingularSection
]
ambiguousPolicyParameterizedFragmentDecision :: Either PlacementConflict PlacementDecision
ambiguousPolicyParameterizedFragmentDecision = classifyWebPlacement
[ DefinesPolicyDecision
, ConsumesPolicyDecision
, RendersParameterizedHtmxPrimitive
, RendersReusableWebFragment
]
ambiguousPolicyFragmentsStop :: Bool
ambiguousPolicyFragmentsStop =
ambiguousPolicyRouteLocalFragmentDecision == Left AmbiguousHelperVsComponent
&& ambiguousPolicyParameterizedFragmentDecision == Left AmbiguousHelperVsComponent
Policy plus rendering is also a split. Define the policy in
authorization code; pass the decision into the component or view.
policyAwareComponentDecision :: Either PlacementConflict PlacementDecision
policyAwareComponentDecision = classifyWebPlacement
[ DefinesPolicyDecision
, RendersReusableWebFragment
, ConsumesPolicyDecision
]
expectedPolicyAwareComponent :: PlacementDecision
expectedPolicyAwareComponent =
Split AuthorizationPolicyModule WebFragmentComponent PolicyFeedsPresentation
Standalone
checks
The example decisions should keep matching the intended placement
contract.
standalonePlacementChecks :: Bool
standalonePlacementChecks = and
[ controllerRenderedSectionIsPlaced
, routePageDelegationIsPlaced
, reusablePresentationHelperIsPlaced
, genericParameterizedHtmxHelperIsPlaced
, routeLocalControllerSectionIsPlaced
, ambiguousRouteLocalReusableFragmentStops
, ambiguousRoutePageReusableFragmentStops
, ambiguousHelperOrComponentStops
, ownedPurePresentationFragmentIsComponent
, duplicatedControllerFragmentIsComponent
, ambiguousParameterizedFragmentStops
, ambiguousPolicyFragmentsStop
, policyAwareComponentDecision == Right expectedPolicyAwareComponent
, requiresSplit expectedControllerRenderedSection
, not (requiresSplit expectedReusablePresentationHelper)
]