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.

A hub UI provider implements two contracts, the node-side ui slot and the browser-side context. @devframes/hub-ui is the reference.

The node seam: DevframeHubUi

initHub({ ui }) takes pure data (see the ui slot):

interface DevframeHubUi {
  viewer?: { distDir: string } // a standalone SPA served at the hub base
  embedded?: { entry: string } // a self-contained bootstrap at <base>embedded.js
  assets?: Record<string, () => string | Uint8Array> // extra UI-owned files
  setup?: (ctx) => void | Promise<void> // publish static config via ctx.staticConfig
}

Ship a function returning this object (createUi()) with prebuilt assets using relative paths.

setup(ctx) runs once at hub init: config in ctx.staticConfig is serialized into ConnectionMeta.configs and read by the RPC client at handshake.

The browser-side contracts

A hub UI provider renders from the hub's shared state via createDevframeClientRuntime(), which assembles the DevframeClientContext (docks, commands, renderers, when-clauses, connection) and loads dock client scripts. Honor:

Dock entry types

Render the built-in variants of the open dock union (DevframeDockEntryRegistry, @devframes/hub/types): iframe (the entry's url in a kept-alive iframe, honoring subTabs soft nav), action (a dock-rail button running its client script), custom-render (a container its client script mounts into), launcher (a launch call-to-action reflecting launcher.status), group (one dock-rail button collapsing its members), and ~builtin (your native views for reserved ids). What to render per type is in the Hub API reference.

Honor when / visibility, category grouping (order from DEFAULT_CATEGORIES_ORDER, @devframes/hub/constants), and the hub:docks:activate broadcast.

An iframe entry serving a remote assets package can report it unreachable: its fallback page posts a RemoteAssetsErrorMessage (DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, @devframes/hub/constants) to window.parent. Match it against the iframe's contentWindow to offer install + retry.

The renderer registry and its fallback

Every other dock type routes through the dock-renderer registry: build it with createDockRenderersContext() (@devframes/hub/client), wiring local registrations and the hub's renderer manifest:

import { createDockRenderersContext } from '@devframes/hub/client'

const renderers = createDockRenderersContext({
  context: () => context,
  manifest: () => manifestState.value(), // the devframe:dock-renderers slot
})

const result = await renderers.mount(entry, container)

Show a state per mount-result variant:

  • { status: 'mounted', dispose }: the renderer owns the container; call dispose on unmount.
  • { status: 'missing-renderer' }: render a fallback (renderers.has(type) answers up front).
  • { status: 'load-error', error }: import failed or the renderer threw; render the error with retry.

The theme contract for renderers

Renderer modules self-style (sometimes via a shadow root). Keep a live dark class on the mount container and let CSS custom properties inherit; a --devframe-primary ancestor rebrands rendered content.

Reference points