Client Scripts & Client Context
A dock client script runs a plugin's code inside the host page; the client context is how client surfaces reach the hub.
The client host runtime
createDevframeClientHost() (@devframes/hub/client) boots the host page: it connects (or adopts) an RPC client, publishes the DevframeClientContext, and imports each dock's client script:
// main.ts — the host app / hub page's browser entry
import { connectDevframe, createDevframeClientHost } from '@devframes/hub/client'
const rpc = await connectDevframe({ baseURL: '/__hub/' })
const { context, dispose } = await createDevframeClientHost({ rpc })Options
| Option | Description |
|---|---|
rpc | An already-connected DevframeRpcClient; when omitted, created via connectDevframe(connect). |
connect | Forwarded to connectDevframe when rpc is omitted (e.g. baseURL). |
clientType | 'standalone' (default) — owns the page; 'embedded' — inside a user app alongside a panel. |
loadClientScripts | Import and run dock client scripts (default true). |
renderers | Dock renderers registered at boot, keyed by dock type; local wins over the hub's renderer manifest. |
A second boot replaces the context and warns; dispose() tears down listeners and unpublishes it.
The client context
| Property | Description |
|---|---|
rpc | The RPC client — server/client functions, shared state. |
clientType | 'embedded' (inside your app) or 'standalone' (independent hub page). |
docks | entries, selected, groupedEntries, switchEntry(), toggleEntry(), getStateById(), register() / update() for client-only docks. |
panel | Dock panel state: position, size, drag/resize. |
commands | Command palette: register(), execute(), getKeybindings(). |
renderers | Dock-renderer registry — register(), get(), has(), mount(entry, container). Routes a dock type to a renderer (local boot or the hub's manifest; local wins). mount() resolves a status: mounted (with dispose), missing-renderer, or load-error (with error). |
when | The when-clause context. |
connection | Live connection status — status, error, events. |
Accessing the context
getDevframeClientContext() returns the context anywhere; undefined before boot.
Client-only docks
A client host can register a dock local to this page (unlike node hub context docks synced via devframe:docks). ctx.docks.register(entry) — e.g. type: 'custom-render' with renderer: { importFrom } — returns a handle whose update({ badge }) patches in place (id immutable) and dispose() removes it. One sharing a server dock's id overrides it locally; re-registering an owned id throws unless you pass register(entry, true).
A client-only dock can also carry type: 'json-render' with an inline JSON-render view: { spec } (a DevframeJsonRenderSpec built in-browser), rendered when a json-render renderer is registered at boot. view also accepts { stateKey } for live shared state (from createJsonRenderView).
Dock client scripts
A client script is a ClientScriptEntry — { importFrom, importName? } (importName defaults 'default'); the field varies by entry kind:
| Entry kind | Field | Runs |
|---|---|---|
action | action | when the dock button is activated |
custom-render | renderer | to render the entry's panel |
iframe | clientScript (optional) | alongside the iframe panel, inside the host page |
The exported function (DockClientScriptContext) receives the client context and two dock-scoped extras:
current— this entry's state:entryMeta,isActive,domElements,events(entry:activated,entry:deactivated,entry:updated,dom:panel:mounted,dom:iframe:mounted).messages— an entry-scoped messages client (categorydefaults to the entry id;info/warn/error/success/debugshortcuts foradd()).
A failed import retries on the next dock update.
Shipping a client script
importFrom accepts two shapes:
- A host-served URL — a self-contained ES module; works on every host.
- A bare npm specifier (
'vite-plugin-vue-tracer/client/vite-devtools') — resolved through the host runtime.
For a URL, attach it via ctx.install(myDevframe, { dock: { clientScript: { importFrom } } }). Under Vite /@fs/<absolute path> serves it; other hosts mount the directory statically.
Bare npm specifiers
Bare specifiers are a host-runtime capability: a host advertises a resolution template at ConnectionMeta.configs.dock.clientModuleResolution (loaders replace {specifier} before import). A Vite host declares this by default (@devframes/vite/hub) as initHub({ clientModuleResolution: '/@id/{specifier}' }); then use the specifier alone as importFrom. A host with no template (Next.js) supports the URL shape only, warning DF8111 on a specifier. A viewer can override with createDevframeClientHost({ resolveClientModule }).
Client scripts execute in the inspected page's realm (window); anchor shared state on globalThis.
Dual boots
One bundle can serve as both a client script (default export) and, via a globally-guarded self-boot, a standalone in-page agent (a11y inspector).
Iframe panels
Dock iframes are their own documents: the panel SPA calls connectDevframe(), discovering ./__connection.json from its base. Host script and iframe share the server via RPC and shared state, or a same-origin BroadcastChannel for static builds.
Shared-iframe soft navigation
A tool with many internal views (Nuxt DevTools' tabs) can surface each as a hub dock sharing one live iframe — the anchor owns a frameId and opts in via ctx.install(…, { dock: { frameId, subTabs: { protocol: 'postmessage' } } }). On mount, the host attaches a frame-nav adapter speaking an origin-locked postMessage protocol on devframe:frame-nav.
| Message | Direction | Meaning |
|---|---|---|
ready / manifest | frame → host | tab list ({ tabs, current }), on load and change |
navigate | host → frame | show a view ({ tabId, navTarget }); app routes client-side |
navigated | frame → host | app navigated internally; host highlights the dock |
It materializes a client-only dock per tab (id <frameId>:<tabId>) sharing the anchor's frameId and a navTarget, independent of groupId.
The viewer's part
A viewer keeps one iframe alive per frameId (shown/hidden); on mount, it sets the element on the anchor's docks.getStateById(anchorId) state (domElements.iframe) and emits dom:iframe:mounted. See the "Tabbed Tool" in examples/hub-vite / hub-next.
Hub
@devframes/hub orchestrates many devtools sharing a UI: a dock registry, terminal aggregation, message/toast queue, and command palette. It ships no UI — each framework kit provides its own atop the hub's RPC + shared-state protocol.
Serve a Hub Anywhere
initHub() from @devframes/hub/initiate serves a whole multi-devframe devtools install from one web-standard handler on a catch-all route.