React UI
- What it receives
- Typed surfaces, actions, forms, views, and data bindings.
- Why it helps
- Frontend code starts from product semantics instead of duplicated route and field strings.
Building Blocks
Cohesive.Presentation declares user-facing surfaces from backend-owned semantics: pages, workspaces, views, forms, actions, flows, bindings, and generated frontend contracts.
Presentation turns semantic system definitions into inspectable product surfaces. It is not a replacement for a frontend framework; it is the layer that preserves meaning while projecting into one.
Many systems duplicate the same facts across backend handlers, generated clients, frontend routes, form schemas, table columns, action enablement checks, and tests.
Cohesive.Presentation keeps those facts attached to the semantic model. A form can know which shape it edits, an action can know which API operation it calls, and a view can know which relation or repository query supplies its data.
Presentation definitions can describe:
Forms and actions are strongest when they remain connected to backend semantics. A create action can bind to an API command, a transition action can expose preconditions and expected results, and a query form can lower into a Cohesive.Relations query.
This lets presentation surfaces participate in the same identity, validation, API, storage, and process model as the backend.
The Ari admin UI uses backend-authored presentation definitions as the source of truth, then projects them through a React runtime that supplies route state, data bindings, renderer choices, and design-system interpretation.
public static class AdminPresentationModule
{
public const string ModuleId = "ari-admin-ui";
public const string ModuleVersion = "0.1.0";
public static PresentationModuleDefinition Create() =>
PresentationModuleComposer.Compose(
id: ModuleId,
name: "Ari Admin UI",
version: ModuleVersion,
contributions:
[
AdminShellArea.CreateContribution(),
ShapeGraphAuthoringArea.CreateContribution(),
RelationsArea.CreateContribution(),
ProcessesArea.CreateContribution()
]);
}
public static class AdminShellArea
{
public static PresentationModuleContribution CreateContribution() => new()
{
Navigation = [AdminNavigation.Create()],
Views = CreateViews(),
DataSources = CreateDataSources(),
Actions = CreateActions()
};
static ViewDefinition[] CreateViews() =>
[
AdminPresentationModule.Page(
id: AdminPresentationViewIds.RelationsWorkspacePage,
name: "Relations Workspace",
routeId: AdminNavigationRouteIds.RelationsWorkspace,
regions:
[
AdminPresentationModule.Region(
id: "body",
name: "Relations",
kind: ViewRegionKind.Surface,
viewIds: [AdminPresentationViewIds.RelationsWorkspaceBody])
],
actions:
[
AdminPresentationModule.ActionPlacement(
actionId: AdminPresentationActionIds.RefreshRelations,
region: "toolbar",
label: "Refresh",
icon: AdminPresentationIconIds.RefreshCw)
]),
AdminPresentationModule.SurfaceView(
id: AdminPresentationViewIds.RelationsWorkspaceBody,
name: "Relations",
regions:
[
AdminPresentationModule.Region(
id: "definitions",
name: "Relation Definitions",
kind: ViewRegionKind.Collection,
viewIds: [AdminPresentationViewIds.RelationDefinitionTable])
]),
AdminPresentationModule.Collection(
id: AdminPresentationViewIds.RelationDefinitionTable,
name: "Relation Definitions",
dataSourceId: AdminPresentationDataSourceIds.RelationDefinitions,
fieldIds:
[
AdminPresentationFieldIds.RelationSummaryName,
AdminPresentationFieldIds.RelationSummarySource,
AdminPresentationFieldIds.RelationSummaryTarget,
AdminPresentationFieldIds.RelationSummaryLifecycle
],
rowActions:
[
AdminPresentationModule.CollectionRowAction(
actionId: AdminPresentationActionIds.OpenRelationDefinition,
label: "Open",
icon: AdminPresentationIconIds.ExternalLink,
parameters:
[
AdminPresentationModule.CollectionRowActionParameter(
name: "id",
valuePath: nameof(RelationDefinitionSummary.Id))
])
],
rowIdentityPath: nameof(RelationDefinitionSummary.Id),
rowLabelPath: nameof(RelationDefinitionSummary.Name)),
AdminPresentationModule.Dashboard(
id: AdminPresentationViewIds.RelationDefinitionHeader,
name: "Relation Summary",
dataSourceIds: [AdminPresentationDataSourceIds.RelationDefinition],
fieldIds:
[
AdminPresentationFieldIds.RelationDefinitionName,
AdminPresentationFieldIds.RelationDefinitionVersion,
AdminPresentationFieldIds.RelationDefinitionLifecycle
],
chrome: AdminPresentationModule.DocumentHeaderChrome(
titleField: "RelationDefinition.Name",
subtitleField: "RelationDefinition.Id",
metadataFieldIds:
[
AdminPresentationFieldIds.RelationDefinitionVersion,
AdminPresentationFieldIds.RelationDefinitionLifecycle
]))
];
static DataSourceDefinition[] CreateDataSources() =>
[
AdminPresentationModule.RemoteDataSource(
id: AdminPresentationDataSourceIds.PresentationNavigation,
name: "Presentation Navigation",
kind: DataSourceKind.Navigation,
resultShape: "NavigationDefinition",
endpointId: AdminApiDefinition.GetPresentationNavigation.Id.Value),
AdminPresentationModule.RemoteDataSource(
id: AdminPresentationDataSourceIds.PresentationModule,
name: "Presentation Module",
kind: DataSourceKind.Module,
resultShape: "PresentationModuleDefinition",
endpointId: AdminApiDefinition.GetPresentationModule.Id.Value),
AdminPresentationModule.LocalDataSource(
id: AdminPresentationDataSourceIds.AuthContext,
name: "Authenticated User Context",
kind: DataSourceKind.LocalState,
resultShape: "AdminAuthContext")
];
static ActionDefinition[] CreateActions() =>
[
AdminPresentationModule.NavigationAction(
id: AdminPresentationActionIds.OpenRelationDefinition,
name: "Open Relation Definition",
routeId: AdminNavigationRouteIds.RelationDefinitionEditor,
parameters: [new(Name: "id", Type: "string", IsRequired: true)]),
AdminPresentationModule.LocalAction(
id: AdminPresentationActionIds.RefreshRelations,
name: "Refresh Relations",
scope: ActionScopeKind.Page,
resultInvalidateDataSourceIds:
[
AdminPresentationDataSourceIds.RelationDefinitions
])
];
}
public static class AdminNavigation
{
public static NavigationDefinition Create() => new(
Id: "ari-admin",
Label: "Ari Admin",
Nodes:
[
new(
Id: AdminNavigationNodeIds.RelationsWorkspace,
Label: "Relations",
Kind: NavigationNodeKind.Page,
RouteId: AdminNavigationRouteIds.RelationsWorkspace,
IsPrimary: true,
ActiveRouteIds: [AdminNavigationRouteIds.RelationDefinitionEditor],
Icon: AdminPresentationIconIds.GitBranch,
ActionId: AdminNavigationActionIds.OpenRelationsWorkspace),
new(
Id: AdminNavigationNodeIds.RelationDefinitionEditor,
Label: "Relation Definition Editor",
Kind: NavigationNodeKind.EntityDetail,
RouteId: AdminNavigationRouteIds.RelationDefinitionEditor,
IsPrimary: false,
ActiveRouteIds: [],
Icon: AdminPresentationIconIds.ExternalLink,
ActionId: AdminPresentationActionIds.OpenRelationDefinition)
],
Edges:
[
new(
Id: "relations-to-relation-definition",
FromNodeId: AdminNavigationNodeIds.RelationsWorkspace,
ToNodeId: AdminNavigationNodeIds.RelationDefinitionEditor,
Kind: NavigationEdgeKind.RelatedEntityRoute,
Label: "Open relation definition")
]);
}export function AdminRoutedSurfaceRouteHost({
componentSystem = shadcnPresentationComponentSystem,
designSystem = tailwindPresentationDesignSystem,
navigationTarget,
routeParameters,
}: AdminRoutedSurfaceRouteHostProps) {
const { activeAccount, canLoadApiData, querySuffix } = useTrainingApiAuthState()
const { tenants } = useTrainingApiTenantContext()
const { activeTasks } = useProcessTasks()
const dataSourceTargetInterpretation = useMemo(
() => createTrainingDataSourceTargetInterpretation({
isApiAuthorized: canLoadApiData,
}),
[canLoadApiData],
)
const createDataSourceBindingRegistry = useCallback(
({ paginationRequestsByDataSourceId }) =>
createTrainingDataSourceBindingRegistry({
activeProcessTasks: activeTasks,
currentUserLabel: activeAccount?.name ?? activeAccount?.username ?? 'No account',
isApiAuthorized: canLoadApiData,
paginationRequestsByDataSourceId,
querySuffix,
tenants,
targetInterpretation: dataSourceTargetInterpretation,
}),
[activeAccount, activeTasks, canLoadApiData, dataSourceTargetInterpretation, querySuffix, tenants],
)
return (
<ProjectedRoutedSurfaceRuntime<AdminPresentationRouteContext>
componentSet="ari-admin-ui"
componentSystem={componentSystem}
createDataSourceBindingRegistry={createDataSourceBindingRegistry}
dataSourceTargetInterpretation={dataSourceTargetInterpretation}
designSystem={designSystem}
navigationTarget={navigationTarget}
routeParameters={routeParameters}
/>
)
}Presentation becomes another interpretation of the semantic system model rather than a separate frontend reinvention. Product surfaces can evolve while preserving identifiers, contracts, actions, and state behavior across the stack.