Build Your Own JSON-Render Frontend
@devframes/json-render-ui is the reference frontend, not the protocol; any
implementation of the renderer contract replaces it. The Next hub witness ships a React one at
src/client/json-render/.
The contract
@devframes/json-render/hub owns the types:
import type { JsonRenderDockRenderer } from '@devframes/json-render/hub'
// a hub DockRenderer narrowed to the json-render dock entry
const renderer: JsonRenderDockRenderer = async ({ entry, container, context }) => {
// mount your framework's root into `container`, render `entry.view`
return { dispose() { /* unmount, unsubscribe */ } }
}Resolve the entry's view:
{ stateKey }: subscribe viacontext.rpc.sharedState.get(stateKey), render it as the live spec, re-render on'updated'. Unsubscribe indispose.{ spec }: render the embedded spec directly.
Detect static output via context.rpc.connectionMeta.backend === 'static',
disabling action dispatch there.
Behavior expectations
- Actions: a spec action name dispatches the same-named RPC call. Never
bridge the reserved built-ins (
setState,pushState,removeState,validateForm) or promise probes (then/catch/finally); surface failures to the view. - Validation: validate element props against
basePropSchemasfrom@devframes/json-render; swap an invalid element for an error placeholder. - Unknown components: a component your registry lacks renders as a
placeholder (type + prop-key gist) with a
console.warn; the rest renders. - State reset: reseed spec state only when the view identity changes, not on every update.
Plugging it in
- Local registration: a hub UI provider bundling its own client runtime passes
createDevframeClientRuntime({ renderers: { 'json-render': myRenderer } }); local registrations win over the manifest. A prebuilt renderer module: bundle your renderer as one self-contained browser ES module (framework and styles included), default-exporting the renderer, plus a node helper returning the registration:
import type { DockRendererRegistration } from '@devframes/hub/initiate' export function myRenderer(): DockRendererRegistration { return { type: 'json-render', file: myPrebuiltModulePath } }Compose it with
initHub({ renderers: [myRenderer()] }); the hub serves the module and hub UI providers import it lazily (see renderer modules).
A prebuilt module must be self-styling and shadow-root-safe: deliver your
stylesheet into the mount subtree, which may be a shadow root. Read the theme
from the container's live dark class, brand color from the inherited
--devframe-primary.
Deep Linking
Send a user to a view inside a devframe (from another dock, a coding agent, or a copied URL) two ways: the hub relays a dock activation to focus a dock in place; a standalone SPA reads its URL hash to restore the view.
Build Your Own Hub UI
A hub UI provider implements two contracts, the node-side ui slot and the browser-side context. @devframes/hub-ui is the reference.