Renderer implementation
DynUI renders a validated UITree. The renderer should be deliberately boring:
component ids map to known app components, slot children resolve recursively, and
unknown or failing components degrade safely.
The model never emits markup or code. The app renderer is the only thing that turns a tree into UI.
Registry
Section titled “Registry”Create a registry from manifest component ids to renderer functions:
type Renderer = ( props: Record<string, unknown>, children: React.ReactNode) => React.ReactNode;
const registry: Record<string, Renderer> = { "recovery-score-card": RecoveryScoreCard, "readiness-panel": ReadinessPanel,};Keep this registry close to the app components it renders. It is app code, not a generic DynUI package.
Renderer specs
Section titled “Renderer specs”Keep a RendererSpec beside the registry so CI can prove a manifest is
renderable before the app receives a tree.
The spec should describe:
- supported component ids;
- supported variants;
- required and optional data bindings the renderer consumes;
- supported slots;
- compatible component version ranges;
- design tokens the renderer can map to native styles;
- minimum renderer version, if applicable.
Run the compatibility check whenever a manifest changes. A valid manifest is not enough; the renderer must also prove it can render that manifest.
Render gate
Section titled “Render gate”Only render trees that came from generateScreen with validation.ok === true,
or trees you explicitly validate before render:
import { validateRenderableTree } from "@dynui/validate";
const result = validateRenderableTree(tree, manifest, { surface, profile, data, experiments,});
if (!result.ok) { showFallback(); return;}
render(result.tree);validateTreeStructure is structural only. It is not sufficient for rendering
because it cannot see consent, data, experiments, or the current surface.
Slot resolution
Section titled “Slot resolution”If a component declares slots, resolve children recursively and pass them into the parent component’s own layout. Do not flatten the tree unless the manifest explicitly models a flat layout.
This distinction matters. Slot composition is where a model can improve on a deterministic ranking engine: it can group known components into known containers without inventing new UI.
Renderer behavior should be deterministic for a given tree and data payload.
Failure behavior
Section titled “Failure behavior”Define visible, boring failure behavior:
| Failure | Recommended behavior |
|---|---|
| Unknown component | Show a safe fallback or suppress with telemetry, depending on surface risk |
| Invalid tree | Do not render; show the app-owned fallback state |
| Invalid slot child | Show the parent without the invalid child or show a safe fallback |
| Component throw | Isolate with a per-component error boundary |
| Missing optional data | Render the component’s designed empty state |
| Missing required data | Suppress the component unless backed by safe fallbackData |
Explicit unrenderable |
Show an app-owned empty or error state |
Record render errors separately from exposures. A component should count as an exposure only after it actually rendered.
CI checks
Section titled “CI checks”At minimum, CI should run:
- manifest schema validation;
- manifest lint;
- manifest diff against the last accepted manifest;
- renderer compatibility;
- unit tests for slot resolution;
- no-consent and neutral-output tests;
- visual or browser checks for representative valid and invalid fixtures.
The reference React Native app implements these ideas in
apps/fitness-app/src/renderer/: registry mapping, registry compatibility,
nested-slot resolution, and per-component error boundaries.
Rollback
Section titled “Rollback”Have a manifest rollback path. The fastest safe rollback is usually restoring the previous accepted manifest or disabling the DynUI-powered surface behind your existing release mechanism.
Avoid deploying a manifest before the renderer that supports it.