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/.

@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 via context.rpc.sharedState.get(stateKey), render it as the live spec, re-render on 'updated'. Unsubscribe in dispose.
  • { 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 basePropSchemas from @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.