Build Your Own Hub UI
A hub viewer implements two contracts — the node-side ui slot and the client-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 client at handshake.
The client contracts
A viewer renders from the hub's shared state via createDevframeClientHost(), 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):
| Type | The viewer renders |
|---|---|
iframe | the entry's url in a kept-alive iframe (per frameId when shared); honor subTabs soft nav |
action | a bar button; activating runs its client script |
custom-render | a container its client script mounts into |
launcher | a launch call-to-action reflecting launcher.status |
group | one bar button collapsing its member entries |
~builtin | your native views (settings, feeds) for reserved ids |
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 frame'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; calldisposeon 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
packages/hub-ui— the full reference viewer (Vue,@antfu/design).examples/hub-viteandexamples/hub-next— hand-rolled viewers in vanilla DOM and React.