React Element Rendering preview
Render nested elements in the order described by a specification, including elements that depend on local state. A parent view owns its markup; the renderer supplies its ordered children.
The native React APIs on this page are experimental, private source-build packages. Run the repository example to use them; they are not an npm installation recipe.
Pass presentation state explicitly
RenderSpec resolves visibility from the caller’s state and supplies rendered children to container views. The example’s Show detail control changes a boolean without changing the JSON or requesting an agent. During partial playback, it waits for the required visibility expression so hidden content does not flash before the condition arrives.
The React conversation walkthrough connects this capability to requests, tools, UI state, and saved history.
Try children, nesting and visibility
Choose Run, select Parent + Children, Deep Nesting or Visibility, then press Finish. Parent + Children displays two ordered Text children beneath a Heading. Deep Nesting displays one Text leaf inside two Card wrappers. Visibility includes one always-visible Text and a conditional detail.
Show detail updates explicit host state. Uncheck it to hide the conditional element, then check it to show the element. Changing the checkbox does not rewrite JSON or move playback. Your choice survives Play, Pause, Playback position, Finish, Reset and sample changes. Reloading shows detail. No agent requests or saved browser state are used.
export const ELEMENT_RENDERING_SAMPLES: readonly {
readonly label: string;
readonly json: string;
}[] = [
{
label: 'Parent + Children',
json: JSON.stringify(
{
root: 'root',
elements: {
root: {
type: 'Heading',
props: { content: 'Parent Heading' },
children: ['child1', 'child2'],
},
child1: {
type: 'Text',
props: {
content:
'First child text element rendered beneath the parent heading.',
},
},
child2: {
type: 'Text',
props: {
content:
'Second child text element demonstrating sibling rendering.',
},
},
},
},
null,
2
),
},
{
label: 'Deep Nesting',
json: JSON.stringify(
{
root: 'root',
elements: {
root: {
type: 'Card',
props: { title: 'Outer Card' },
children: ['inner'],
},
inner: {
type: 'Card',
props: { title: 'Inner Card' },
children: ['leaf'],
},
leaf: {
type: 'Text',
props: {
content: 'Deeply nested text inside two levels of card wrappers.',
},
},
},
},
null,
2
),
},
{
label: 'Visibility',
json: JSON.stringify(
{
root: 'root',
elements: {
root: {
type: 'Heading',
props: { content: 'Visibility Demo' },
children: ['always', 'conditional'],
},
always: {
type: 'Text',
props: { content: 'This element is always visible.' },
},
conditional: {
type: 'Text',
props: {
content:
'This element is conditionally visible based on /showDetail.',
},
visible: { $state: '/showDetail' },
},
},
},
null,
2
),
},
];Use native rendered children
A stable readonly registry maps Heading, Text and Card to ordinary React components. RenderSpec supplies resolved props, elementKey, loading and children. Heading and Card display the supplied children exactly once; they do not walk the source graph themselves. Text is a leaf. The native tree owns sibling ordering and nesting.
import type {
ReactRenderRegistry,
RenderViewProps,
} from '@threadplane/react/render';
function literal(value: unknown): string {
return typeof value === 'string' ? value : '';
}
function Heading({ props, children, loading }: RenderViewProps) {
return (
<div className="render-heading">
<h3 aria-busy={loading}>
{literal(props.content) || (loading ? '…' : '')}
</h3>
{children}
</div>
);
}
function Text({ props, loading, elementKey }: RenderViewProps) {
return (
<p data-element-key={elementKey} aria-busy={loading}>
{literal(props.content) || (loading ? '…' : '')}
</p>
);
}
function Card({ props, children, loading }: RenderViewProps) {
return (
<article className="render-card">
<h4 aria-busy={loading}>
{literal(props.title) || (loading ? '…' : '')}
</h4>
{children}
</article>
);
}
export const localRegistry: ReactRenderRegistry = Object.freeze({
Heading,
Text,
Card,
});The host supplies an immutable state object with a boolean showDetail value. The conditional Text uses the authored visibility binding to /showDetail. Native RenderSpec evaluates the binding; the host does not remove or rewrite source elements.
// eslint-disable-next-line @nx/enforce-module-boundaries -- The isolated consumer copies this internal example layout.
import { ExampleLayout } from '../../../../shared/react-example';
import { useEffect, useSyncExternalStore } from 'react';
import type { RenderStateSnapshot } from '@threadplane/content/render-state';
import { useAgent } from '@threadplane/react';
import { RenderSpec } from '@threadplane/react/render';
import type { Playback } from './playback';
import { localRegistry } from './views';
export function ElementRenderingDemo({
playback,
renderState,
onShowDetailChange,
samples,
onReady,
}: {
readonly playback: Playback;
readonly renderState: {
readonly getSnapshot: () => RenderStateSnapshot;
readonly subscribe: (notify: () => void) => () => void;
};
readonly onShowDetailChange: (value: boolean) => void;
readonly samples: readonly string[];
readonly onReady: () => void;
}) {
const renderSnapshot = useAgent(renderState);
const state = useSyncExternalStore(
playback.subscribe,
playback.getSnapshot,
playback.getSnapshot
);
useEffect(onReady, [onReady]);
const phase =
state.phase === 'error'
? 'Unavailable'
: state.phase[0].toUpperCase() + state.phase.slice(1);
return (
<ExampleLayout kind="render" className="spec-demo">
<header>
<h1>Element Rendering</h1>
</header>
<p>
Render ordered children and nested views. Choose whether the native
renderer shows the conditional detail.
</p>
<section aria-label="Visibility controls" className="controls">
<label className="visibility-toggle">
<input
type="checkbox"
checked={renderSnapshot.showDetail === true}
onChange={(event) => onShowDetailChange(event.target.checked)}
/>
Show detail
</label>
<p>
Your choice survives playback and sample changes. Reloading shows
detail.
</p>
</section>
<section aria-label="Playback controls" className="controls">
<label>
Sample
<select
value={state.selected}
onChange={(event) => playback.select(Number(event.target.value))}
>
{samples.map((label, index) => (
<option key={index} value={index}>
{label}
</option>
))}
</select>
</label>
<div className="buttons">
<button
type="button"
disabled={state.phase === 'error'}
onClick={state.playing ? playback.pause : playback.play}
>
{state.playing ? 'Pause' : 'Play'}
</button>
<button
type="button"
disabled={state.phase === 'error'}
onClick={playback.finish}
>
Finish
</button>
<button type="button" onClick={playback.reset}>
Reset
</button>
</div>
<label>
Playback position
<input
type="range"
min={0}
max={state.total}
step={1}
value={state.position}
disabled={state.phase === 'error'}
onChange={(event) => playback.seek(Number(event.target.value))}
/>
</label>
<p role="status">
{phase} · {state.position} characters
</p>
{state.phase === 'error' && (
<p role="alert">
This sample could not be displayed. Choose another sample to
continue.
</p>
)}
</section>
<div className="panels">
<section aria-label="Render output">
<h2>Render output</h2>
{state.spec ? (
<RenderSpec
spec={state.spec}
registry={localRegistry}
state={renderSnapshot}
loading={state.position < state.total}
/>
) : (
<p className="empty">Play a sample to see its view.</p>
)}
</section>
<section aria-label="Streaming JSON">
<h2>Streaming JSON</h2>
<pre>{state.rawJson}</pre>
</section>
</div>
</ExampleLayout>
);
}Keep partial visibility faithful
This bounded local projection admits only three literal display types and the one authored conditional visibility path. The conditional node is withheld until its exact visibility metadata is available, preventing hidden text from flashing while JSON streams. Complete samples are admitted before playback; owned frozen copies leave the parser's mutable tree intact.
import type {
RenderElementData,
RenderSpecData,
} from '@threadplane/react/render';
const TYPES = ['Heading', 'Text', 'Card'];
const ID = /^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/;
const MAX_NODES = 32;
const MAX_TEXT = 16 * 1024;
const VISIBILITY_PATH = '/showDetail';
function invalid(): never {
throw new TypeError('Invalid local render data.');
}
function record(input: unknown): Record<string, unknown> {
if (
input === null ||
typeof input !== 'object' ||
Array.isArray(input) ||
![null, Object.prototype].includes(Object.getPrototypeOf(input)) ||
Object.getOwnPropertySymbols(input).length
)
invalid();
const entries = Object.entries(Object.getOwnPropertyDescriptors(input));
if (entries.length > MAX_NODES) invalid();
const result = Object.create(null) as Record<string, unknown>;
for (const [key, descriptor] of entries) {
if (!descriptor.enumerable || !('value' in descriptor)) invalid();
Object.defineProperty(result, key, {
value: descriptor.value,
enumerable: true,
});
}
return result;
}
function fields(
input: Record<string, unknown>,
allowed: readonly string[]
): void {
if (Object.keys(input).some((key) => !allowed.includes(key))) invalid();
}
function references(input: unknown, complete: boolean): readonly string[] {
if (input === undefined && !complete) return [];
if (
!Array.isArray(input) ||
Object.getPrototypeOf(input) !== Array.prototype ||
input.length > MAX_NODES ||
Object.getOwnPropertySymbols(input).length
)
invalid();
const descriptors = Object.getOwnPropertyDescriptors(input);
for (const [key, descriptor] of Object.entries(descriptors))
if (
!('value' in descriptor) ||
(key !== 'length' && !/^(0|[1-9][0-9]*)$/.test(key))
)
invalid();
const result: string[] = [];
for (let index = 0; index < input.length; index++) {
const value = descriptors[String(index)]?.value;
if ((value === undefined || value === '') && !complete) continue;
if (typeof value !== 'string' || !ID.test(value) || result.includes(value))
invalid();
result.push(value);
}
return result;
}
function project(input: unknown, complete: boolean): RenderSpecData | null {
if (input === null && !complete) return null;
const source = record(input);
fields(source, ['root', 'elements']);
if (
source.root === undefined ||
source.root === '' ||
source.elements === undefined
) {
if (complete) invalid();
return null;
}
if (typeof source.root !== 'string' || !ID.test(source.root)) invalid();
const sourceElements = record(source.elements);
const elements = Object.create(null) as Record<string, RenderElementData>;
const childLists = new Map<string, readonly string[]>();
let textSize = 0;
for (const [key, inputElement] of Object.entries(sourceElements)) {
if (!ID.test(key)) invalid();
if (inputElement === undefined && !complete) continue;
const element = record(inputElement);
fields(element, ['type', 'props', 'children', 'visible']);
if (key !== 'conditional' && Object.hasOwn(element, 'visible')) invalid();
if (element.type === undefined && !complete) continue;
if (typeof element.type !== 'string') invalid();
if (!TYPES.includes(element.type)) {
if (complete) invalid();
continue;
}
const rawProps = element.props === undefined ? {} : record(element.props);
fields(rawProps, [element.type === 'Card' ? 'title' : 'content']);
const props = Object.create(null) as Record<string, string>;
for (const [prop, value] of Object.entries(rawProps)) {
if (value === undefined && !complete) continue;
if (typeof value !== 'string' || (textSize += value.length) > MAX_TEXT)
invalid();
Object.defineProperty(props, prop, { value, enumerable: true });
}
const children = Object.hasOwn(element, 'children')
? references(element.children, complete)
: undefined;
let visible: Readonly<{ $state: string }> | undefined;
if (key === 'conditional') {
if (element.type !== 'Text') invalid();
if (element.visible === undefined) {
if (complete) invalid();
continue;
}
const condition = record(element.visible);
fields(condition, ['$state']);
const path = condition.$state;
if (path !== VISIBILITY_PATH) {
if (
!complete &&
(path === undefined ||
(typeof path === 'string' && VISIBILITY_PATH.startsWith(path)))
)
continue;
invalid();
}
visible = Object.freeze({ $state: VISIBILITY_PATH });
}
if (children) childLists.set(key, children);
Object.defineProperty(elements, key, {
value: {
type: element.type,
props: Object.freeze(props),
...(visible ? { visible } : {}),
},
enumerable: true,
});
}
for (const [key, children] of childLists) {
if (complete && children.some((child) => !Object.hasOwn(elements, child)))
invalid();
Object.defineProperty(elements[key], 'children', {
value: Object.freeze(
children.filter((child) => Object.hasOwn(elements, child))
),
enumerable: true,
});
}
const heights = new Map<string, number>();
const active = new Set<string>();
function height(key: string): number {
const known = heights.get(key);
if (known !== undefined) return known;
if (active.has(key)) invalid();
active.add(key);
let result = 1;
for (const child of elements[key].children ?? [])
result = Math.max(result, 1 + height(child));
active.delete(key);
if (result > 16) invalid();
heights.set(key, result);
return result;
}
for (const key of Object.keys(elements)) {
height(key);
Object.freeze(elements[key]);
}
if (!Object.hasOwn(elements, source.root)) {
if (complete) invalid();
return null;
}
return Object.freeze({
root: source.root,
elements: Object.freeze(elements),
});
}
/** Copy the known display subset; the incremental parser retains its own mutable tree. */
export function projectPartialSpec(input: unknown): RenderSpecData | null {
return project(input, false);
}
/** Admit a complete authored local sample before any playback begins. */
export function validateCompleteSpec(input: unknown): RenderSpecData {
return project(input, true) ?? invalid();
}The private playback owner rebuilds the parser on seek and revokes obsolete callbacks on Pause, Reset, sample changes and page exit. Complete requires finished parser output to match the admitted source. Host visibility state remains independent of this owner.
import type { RenderSpecData } from '@threadplane/react/render';
import { createPartialJsonParser, materialize } from '@cacheplane/partial-json';
import { projectPartialSpec, validateCompleteSpec } from './projection';
export interface PlaybackSample {
readonly label: string;
readonly json: string;
}
export interface FrameScheduler {
request(callback: FrameRequestCallback): number;
cancel(id: number): void;
}
export interface PlaybackSnapshot {
readonly selected: number;
readonly label: string;
readonly position: number;
readonly total: number;
readonly phase: 'paused' | 'playing' | 'complete' | 'error';
readonly playing: boolean;
readonly rawJson: string;
readonly spec: RenderSpecData | null;
}
export interface Playback {
getSnapshot(): PlaybackSnapshot;
subscribe(listener: () => void): () => void;
play(): void;
pause(): void;
seek(position: number): void;
select(index: number): void;
finish(): void;
reset(): void;
dispose(): void;
}
export function createPlayback(
samples: readonly PlaybackSample[],
scheduler: FrameScheduler = {
request: (callback) => requestAnimationFrame(callback),
cancel: (id) => cancelAnimationFrame(id),
}
): Playback {
const owned = samples.map((sample) => ({
label: sample.label,
json: sample.json,
}));
const listeners = new Set<() => void>();
let live = true;
let generation = 0;
let frame: number | undefined;
let parser = createPartialJsonParser();
let admitted: RenderSpecData | null = null;
let snapshot: PlaybackSnapshot;
function revoke(): number {
generation++;
if (frame !== undefined) scheduler.cancel(frame);
frame = undefined;
return generation;
}
function publish(next: PlaybackSnapshot): void {
snapshot = Object.freeze(next);
const current = generation;
for (const listener of [...listeners]) {
if (!live || current !== generation) break;
if (!listeners.has(listener)) continue;
try {
listener();
} catch {
/* A subscriber cannot take ownership of playback. */
}
}
}
function initialize(selected: number, notify: boolean): void {
parser = createPartialJsonParser();
admitted = null;
const sample = owned[selected];
try {
if (!sample || sample.json.length > 16 * 1024)
throw Error('Invalid sample');
// Incremental parsers can retain malformed input as streaming forever.
admitted = validateCompleteSpec(JSON.parse(sample.json));
} catch {
/* Only authored, bounded complete JSON may be played. */
}
const next: PlaybackSnapshot = {
selected,
label: sample?.label ?? 'Unavailable sample',
position: 0,
total: sample?.json.length ?? 0,
phase: admitted ? 'paused' : 'error',
playing: false,
rawJson: '',
spec: null,
};
if (notify) publish(next);
else snapshot = Object.freeze(next);
}
function advance(position: number, playing: boolean): void {
const source = owned[snapshot.selected].json;
try {
parser.push(source.slice(snapshot.position, position));
const complete = position === source.length;
if (complete) parser.finish();
const value = parser.root ? materialize(parser.root) : null;
const spec = complete
? validateCompleteSpec(value)
: projectPartialSpec(value);
if (
complete &&
(parser.root?.status !== 'complete' ||
JSON.stringify(spec) !== JSON.stringify(admitted))
)
throw Error('Incomplete sample');
publish({
...snapshot,
position,
rawJson: source.slice(0, position),
spec,
playing: playing && !complete,
phase: complete ? 'complete' : playing ? 'playing' : 'paused',
});
} catch {
revoke();
publish({
...snapshot,
phase: 'error',
playing: false,
rawJson: '',
spec: null,
});
}
}
function schedule(current: number): void {
if (
!live ||
generation !== current ||
!snapshot.playing ||
frame !== undefined
)
return;
frame = scheduler.request(() => {
if (!live || generation !== current || !snapshot.playing) return;
frame = undefined;
advance(Math.min(snapshot.total, snapshot.position + 4), true);
schedule(current);
});
}
function seek(position: number): void {
if (!live || !Number.isFinite(position) || !admitted) return;
revoke();
parser = createPartialJsonParser();
const target = Math.max(0, Math.min(snapshot.total, Math.floor(position)));
// Rebuild privately, then publish once so a subscriber cannot interrupt a reset.
snapshot = Object.freeze({
...snapshot,
position: 0,
rawJson: '',
spec: null,
playing: false,
phase: 'paused',
});
advance(target, false);
}
initialize(0, false);
return {
getSnapshot: () => snapshot,
subscribe(listener) {
if (live) listeners.add(listener);
return () => {
listeners.delete(listener);
};
},
play() {
if (!live || !admitted || snapshot.playing) return;
const current = revoke();
if (snapshot.phase === 'complete') {
parser = createPartialJsonParser();
snapshot = Object.freeze({
...snapshot,
position: 0,
rawJson: '',
spec: null,
});
}
publish({ ...snapshot, playing: true, phase: 'playing' });
schedule(current);
},
pause() {
if (!live || !snapshot.playing) return;
revoke();
publish({ ...snapshot, playing: false, phase: 'paused' });
},
seek,
select(index) {
if (
!live ||
!Number.isInteger(index) ||
index < 0 ||
index >= owned.length
)
return;
revoke();
initialize(index, true);
},
finish() {
seek(snapshot.total);
},
reset() {
seek(0);
},
dispose() {
if (!live) return;
live = false;
revoke();
listeners.clear();
snapshot = Object.freeze({
...snapshot,
playing: false,
phase: snapshot.phase === 'playing' ? 'paused' : snapshot.phase,
});
},
};
}Source build and verification
From a Threadplane checkout with dependencies installed:
npx nx build cockpit-render-element-rendering-react
npx nx e2e cockpit-render-element-rendering-reactThe isolated static build installs local React, core and content artifacts with root-locked presentation dependencies. Browser coverage exercises native children and visibility, partial playback, retained host state, cleanup and narrow screens. Select Angular to read and run the existing Angular API example.