From ef831b93769f744ad83358aa7c55c54e59ade47b Mon Sep 17 00:00:00 2001 From: "Anthony Fu (via agent)" Date: Thu, 13 Aug 2026 02:38:18 +0000 Subject: [PATCH 01/11] =?UTF-8?q?feat!:=20swappable=20UI=20packages=20?= =?UTF-8?q?=E2=80=94=20renderer-module=20manifest=20+=20missing-renderer?= =?UTF-8?q?=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The headless/UI split becomes fully swappable, both ways, independently (plan 033): - @devframes/hub: initHub({ renderers }) serves prebuilt renderer modules at __renderers/.mjs and publishes a renderer manifest over shared state; the client registry lazy-imports manifest modules (local registrations win) and mount() resolves a typed result (mounted / missing-renderer / load-error). DF8108–DF8110 diagnostics. - @devframes/json-render: owns the renderer contract (JsonRenderDockRenderer / JsonRenderDockMountOptions on ./hub). - @devframes/json-render-ui: ships a self-contained, self-styling, shadow-root-safe renderer module plus the jsonRenderUiRenderer() registration helper on the new ./hub entry. - @devframes/hub-ui: drops its bundled json-render components; every non-native dock type routes through the registry, with a generic missing-renderer fallback view (load-error variant with retry). - Examples: minimal hosts compose json-render-ui via the manifest one-liner; hub-vite consumes the manifest, hub-next overrides it with a local React renderer, and both witness the fallback with an unrendered dock type. - Docs: renderer-modules guide section, build-your-own-hub-ui and build-your-own-json-render-frontend guides, error pages, 0.9 migration notes. BREAKING CHANGE: renderers.mount() resolves a DockRendererMountResult instead of a bare disposer, and @devframes/hub-ui no longer bundles a json-render renderer — compose one via initHub({ renderers: [jsonRenderUiRenderer()] }). --- alias.ts | 1 + docs/.vitepress/config.ts | 2 + docs/errors/DF8108.md | 33 +++ docs/errors/DF8109.md | 32 +++ docs/errors/DF8110.md | 31 +++ docs/guide/build-your-own-hub-ui.md | 100 +++++++ .../build-your-own-json-render-frontend.md | 79 ++++++ docs/guide/client-context.md | 4 +- docs/guide/hub-initiate.md | 20 ++ docs/guide/json-render.md | 61 +++- docs/guide/migration-0.9.md | 36 ++- examples/hub-next-minimal/package.json | 2 + examples/hub-next-minimal/src/client/hub.ts | 35 ++- examples/hub-next/README.md | 5 +- examples/hub-next/package.json | 1 + examples/hub-next/src/client/app/page.tsx | 72 ++++- .../src/client/devframe/next-devframe-hub.ts | 12 + .../src/client/devframe/unrendered-dock.ts | 28 ++ .../src/client/json-render/dock-renderer.tsx | 23 +- examples/hub-rsbuild-minimal/package.json | 2 + .../hub-rsbuild-minimal/rsbuild.config.ts | 33 +++ examples/hub-vite-minimal/package.json | 2 + examples/hub-vite-minimal/vite.config.ts | 33 +++ examples/hub-vite/README.md | 5 +- examples/hub-vite/package.json | 4 +- examples/hub-vite/src/client/env.d.ts | 10 - examples/hub-vite/src/client/main.ts | 57 ++-- examples/hub-vite/src/unrendered-dock.ts | 28 ++ examples/hub-vite/src/vite-devframe-hub.ts | 11 +- examples/hub-vite/vite.config.ts | 20 +- knip.jsonc | 6 +- packages/hub-ui/package.json | 2 - packages/hub-ui/src/client/.generated/css.ts | 2 +- .../views/ViewDockRenderer.stories.ts | 70 +++++ .../components/views/ViewDockRenderer.vue | 116 ++++++++ .../src/client/components/views/ViewEntry.vue | 18 +- .../components/views/ViewJsonRender.vue | 113 -------- .../client/json-render/JsonRender.stories.ts | 261 ------------------ .../client/json-render/components/Badge.ts | 38 --- .../client/json-render/components/Button.ts | 52 ---- .../src/client/json-render/components/Card.ts | 71 ----- .../json-render/components/CodeBlock.ts | 51 ---- .../json-render/components/DataTable.ts | 81 ------ .../client/json-render/components/Divider.ts | 32 --- .../src/client/json-render/components/Icon.ts | 23 -- .../json-render/components/KeyValueTable.ts | 34 --- .../src/client/json-render/components/Link.ts | 69 ----- .../client/json-render/components/Progress.ts | 46 --- .../client/json-render/components/Select.ts | 244 ---------------- .../client/json-render/components/Stack.ts | 59 ---- .../client/json-render/components/Switch.ts | 78 ------ .../src/client/json-render/components/Tabs.ts | 175 ------------ .../src/client/json-render/components/Text.ts | 32 --- .../json-render/components/TextInput.ts | 67 ----- .../src/client/json-render/components/Tree.ts | 54 ---- .../components/UnsupportedComponent.ts | 41 --- .../client/json-render/components/tokens.ts | 80 ------ .../client/json-render/components/types.ts | 44 --- .../hub-ui/src/client/json-render/registry.ts | 46 --- packages/hub-ui/src/client/primary-ramp.css | 11 - packages/hub-ui/src/client/state/context.ts | 15 +- packages/hub-ui/src/client/state/renderers.ts | 62 ++--- .../hub-ui/src/client/stories/mock-context.ts | 3 + .../src/client/__tests__/renderers.test.ts | 78 +++++- packages/hub/src/client/host.ts | 53 ++-- packages/hub/src/client/renderers.ts | 192 +++++++++++-- packages/hub/src/constants.ts | 8 + packages/hub/src/node/diagnostics.ts | 12 + packages/hub/src/node/initiate.ts | 99 +++++++ packages/json-render-ui/package.json | 14 +- packages/json-render-ui/scripts/build-css.ts | 74 +++++ packages/json-render-ui/src/.generated/css.ts | 3 + packages/json-render-ui/src/dock-renderer.ts | 25 +- packages/json-render-ui/src/hub.ts | 44 +++ .../src/renderer-module/index.ts | 76 +++++ .../src/renderer-module/primary-ramp.css | 29 ++ .../src/renderer-module/vite.config.ts | 64 +++++ packages/json-render-ui/tsdown.config.ts | 3 + packages/json-render/src/hub.ts | 20 ++ ...-swappable-hub-ui-and-renderer-manifest.md | 185 +++++++++++++ plans/README.md | 1 + pnpm-lock.yaml | 51 ++-- .../@devframes/hub/client.snapshot.d.ts | 25 +- .../tsnapi/@devframes/hub/client.snapshot.js | 1 + .../@devframes/hub/constants.snapshot.d.ts | 1 + .../@devframes/hub/constants.snapshot.js | 1 + .../@devframes/hub/initiate.snapshot.d.ts | 6 + .../json-render-ui/hub.snapshot.d.ts | 6 + .../@devframes/json-render-ui/hub.snapshot.js | 6 + .../json-render-ui/index.snapshot.d.ts | 17 +- .../@devframes/json-render/hub.snapshot.d.ts | 5 + tsconfig.base.json | 3 + 92 files changed, 1918 insertions(+), 2062 deletions(-) create mode 100644 docs/errors/DF8108.md create mode 100644 docs/errors/DF8109.md create mode 100644 docs/errors/DF8110.md create mode 100644 docs/guide/build-your-own-hub-ui.md create mode 100644 docs/guide/build-your-own-json-render-frontend.md create mode 100644 examples/hub-next/src/client/devframe/unrendered-dock.ts create mode 100644 examples/hub-vite/src/unrendered-dock.ts create mode 100644 packages/hub-ui/src/client/components/views/ViewDockRenderer.stories.ts create mode 100644 packages/hub-ui/src/client/components/views/ViewDockRenderer.vue delete mode 100644 packages/hub-ui/src/client/components/views/ViewJsonRender.vue delete mode 100644 packages/hub-ui/src/client/json-render/JsonRender.stories.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Badge.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Button.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Card.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/CodeBlock.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/DataTable.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Divider.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Icon.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/KeyValueTable.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Link.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Progress.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Select.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Stack.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Switch.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Tabs.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Text.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/TextInput.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/Tree.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/UnsupportedComponent.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/tokens.ts delete mode 100644 packages/hub-ui/src/client/json-render/components/types.ts delete mode 100644 packages/hub-ui/src/client/json-render/registry.ts create mode 100644 packages/json-render-ui/scripts/build-css.ts create mode 100644 packages/json-render-ui/src/.generated/css.ts create mode 100644 packages/json-render-ui/src/hub.ts create mode 100644 packages/json-render-ui/src/renderer-module/index.ts create mode 100644 packages/json-render-ui/src/renderer-module/primary-ramp.css create mode 100644 packages/json-render-ui/src/renderer-module/vite.config.ts create mode 100644 plans/033-swappable-hub-ui-and-renderer-manifest.md create mode 100644 tests/__snapshots__/tsnapi/@devframes/json-render-ui/hub.snapshot.d.ts create mode 100644 tests/__snapshots__/tsnapi/@devframes/json-render-ui/hub.snapshot.js diff --git a/alias.ts b/alias.ts index 659cabda..f3500fc6 100644 --- a/alias.ts +++ b/alias.ts @@ -57,6 +57,7 @@ export const alias = { '@devframes/json-render/node': r('json-render/src/node/index.ts'), '@devframes/json-render': r('json-render/src/index.ts'), '@devframes/json-render-ui/components': r('json-render-ui/src/components/index.ts'), + '@devframes/json-render-ui/hub': r('json-render-ui/src/hub.ts'), '@devframes/json-render-ui/spa': r('json-render-ui/src/spa.ts'), '@devframes/json-render-ui': r('json-render-ui/src/index.ts'), 'json-render/dashboard': fileURLToPath(new URL('./examples/json-render/src/dashboard.ts', import.meta.url)), diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 2c37dd44..c2c1feb1 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -35,6 +35,8 @@ function guideItems(prefix: string) { { text: 'Serve a Hub Anywhere', link: `${prefix}/guide/hub-initiate` }, { text: 'Deep Linking', link: `${prefix}/guide/deep-linking` }, { text: 'Client Scripts & Context', link: `${prefix}/guide/client-context` }, + { text: 'Build Your Own Hub UI', link: `${prefix}/guide/build-your-own-hub-ui` }, + { text: 'Build Your Own JSON-Render Frontend', link: `${prefix}/guide/build-your-own-json-render-frontend` }, { text: 'Agent-Native (experimental)', link: `${prefix}/guide/agent-native` }, ] satisfies DefaultTheme.NavItemWithLink[] } diff --git a/docs/errors/DF8108.md b/docs/errors/DF8108.md new file mode 100644 index 00000000..12429f32 --- /dev/null +++ b/docs/errors/DF8108.md @@ -0,0 +1,33 @@ +--- +outline: deep +--- + +# DF8108: Duplicate Renderer Module Type + +## Message + +> A renderer module is already registered for dock type "`{type}`" + +## Cause + +`initHub({ renderers })` received two registrations carrying the same `type`. Each dock type resolves to exactly one renderer module in the hub's renderer manifest — the module served at `__renderers/.mjs` — so a second registration for the same type would be unreachable. + +## Example + +```ts +initHub({ + renderers: [ + jsonRenderUiRenderer(), + { type: 'json-render', file: myOtherRenderer }, // ✗ duplicate type + ], +}) +``` + +## Fix + +- Keep one registration per dock type — pick the implementation you want the manifest to serve. +- To override a manifest module for one specific client, register a renderer locally instead (`createDevframeClientHost({ renderers })`); local registrations take precedence. + +## Source + +- [`packages/hub/src/node/initiate.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/initiate.ts) — `resolveRendererRegistrations()` throws when a `type` repeats. diff --git a/docs/errors/DF8109.md b/docs/errors/DF8109.md new file mode 100644 index 00000000..4369edd2 --- /dev/null +++ b/docs/errors/DF8109.md @@ -0,0 +1,32 @@ +--- +outline: deep +--- + +# DF8109: Renderer Module File Missing + +## Message + +> The renderer module registered for dock type "`{type}`" does not exist at "`{file}`" + +## Cause + +An `initHub({ renderers })` registration points at a file that isn't on disk. Renderer modules are prebuilt, self-contained browser ES modules the hub serves verbatim at `__renderers/.mjs` — a missing bundle would make every client's lazy import 404 at mount time, so the hub fails fast at startup instead. + +## Example + +```ts +initHub({ + renderers: [ + { type: 'json-render', file: '/path/that/was/never/built.mjs' }, // ✗ + ], +}) +``` + +## Fix + +- Build the renderer package first — the bundle is a build artifact (e.g. `@devframes/json-render-ui`'s `dist/renderer/json-render.mjs`). +- Prefer the package's registration helper over a hand-written path — `jsonRenderUiRenderer()` from `@devframes/json-render-ui/hub` resolves the shipped bundle for you. + +## Source + +- [`packages/hub/src/node/initiate.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/initiate.ts) — `resolveRendererRegistrations()` throws when the resolved `file` fails the existence probe. diff --git a/docs/errors/DF8110.md b/docs/errors/DF8110.md new file mode 100644 index 00000000..a448bca7 --- /dev/null +++ b/docs/errors/DF8110.md @@ -0,0 +1,31 @@ +--- +outline: deep +--- + +# DF8110: Renderer Type Is Not URL-Safe + +## Message + +> Dock type "`{type}`" is not a servable renderer-module name — the hub serves each module at `__renderers/.mjs` + +## Cause + +An `initHub({ renderers })` registration carries a `type` that can't become a URL segment. The hub derives each module's serving path — and the manifest's `importFrom` — from the type, so `:` and `*` (route-pattern markers to the underlying router) or separators like `/` would break the route. + +## Example + +```ts +initHub({ + renderers: [ + { type: 'my:renderer', file: bundle }, // ✗ `:` is a route-param marker + ], +}) +``` + +## Fix + +Use a route-safe dock type: letters, digits, `_`, `-`, and `.` only (e.g. `json-render`, `my-renderer`). The dock entries' `type` discriminator must match, so pick the safe name once, in the integration that declares the type. + +## Source + +- [`packages/hub/src/node/initiate.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/initiate.ts) — `resolveRendererRegistrations()` rejects a `type` failing the `[\w.-]+` segment check. diff --git a/docs/guide/build-your-own-hub-ui.md b/docs/guide/build-your-own-hub-ui.md new file mode 100644 index 00000000..83258683 --- /dev/null +++ b/docs/guide/build-your-own-hub-ui.md @@ -0,0 +1,100 @@ +# Build Your Own Hub UI + +A hub viewer is a replaceable implementation of two contracts — the node-side +`ui` slot and the client-side context — so you can ship a completely custom +devtools surface (your framework, your design system) on top of the hub's +infrastructure. `@devframes/hub-ui` is the reference implementation of both; +this page is the map for writing another. + +## The node seam: `DevframeHubUi` + +`initHub({ ui })` takes pure data (see [the `ui` +slot](./hub-initiate#the-ui-slot)): + +```ts +interface DevframeHubUi { + viewer?: { distDir: string } // a standalone SPA served at the hub base + embedded?: { entry: string } // a self-contained bootstrap at embedded.js + assets?: Record string | Uint8Array> // extra UI-owned files +} +``` + +Ship a function returning this object (the reference is `createUi()`), with +prebuilt assets: the viewer SPA is built with relative asset paths, and the +embedded entry is one self-contained ES module that mounts your dock into any +host page. + +## The client contracts + +A viewer renders from the hub's shared state and drives it through +`@devframes/hub/client`. The simplest boot is +[`createDevframeClientHost()`](./client-context) — it assembles the whole +`DevframeClientContext` (docks, commands, renderers, when-clauses, connection) +and loads dock client scripts for you; the reference UI assembles the same +context shape with its own reactive machinery instead. Either way, honor these +contracts: + +### Dock entry types + +Render the built-in variants of the open dock union +(`DevframeDockEntryRegistry` from `@devframes/hub/types`): + +| Type | The viewer renders | +|---|---| +| `iframe` | the entry's `url` in a kept-alive iframe (per `frameId` for shared frames); honor `subTabs` soft navigation | +| `action` | a bar button only — activating it runs the entry's client script | +| `custom-render` | a container the entry's client script mounts into | +| `launcher` | a launch call-to-action reflecting `launcher.status` | +| `group` | one bar button collapsing its member entries | +| `~builtin` | your own native views (settings, feeds) for reserved ids | + +Honor `when` / `visibility` clauses, `category` grouping (order from +`DEFAULT_CATEGORIES_ORDER` in `@devframes/hub/constants`), and the +`hub:docks:activate` broadcast. + +### The renderer registry and its fallback + +**Every other dock type routes through the dock-renderer registry** — build it +with `createDockRenderersContext()` from `@devframes/hub/client` so local +registrations, the hub's [renderer +manifest](./hub-initiate#renderer-modules), and the typed mount result behave +like every other viewer: + +```ts +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) +``` + +The mount result is the fallback contract. A viewer shows a visible state for +each variant instead of a dead panel: + +- `{ status: 'mounted', dispose }` — the renderer owns the container; call + `dispose` when the view unmounts. +- `{ status: 'missing-renderer' }` — render a fallback view: *No renderer for + "``" in the current environment*. `renderers.has(type)` answers up + front, so you can render this declaratively without a mount attempt. +- `{ status: 'load-error', error }` — the module failed to import or the + renderer threw; render the error with a retry affordance (a failed import is + not cached, so retrying re-imports). + +### The theme contract for renderers + +Renderer modules style themselves (they may attach a shadow root inside your +container). Your part: keep a live `dark` class on the mount container +reflecting your color mode, and let CSS custom properties inherit — a +`--devframe-primary` set on an ancestor rebrands rendered content too. + +## Reference points + +- `packages/hub-ui` — the full reference viewer (Vue, `@antfu/design`). +- [`examples/hub-vite`](/examples/hub-vite) and + [`examples/hub-next`](/examples/hub-next) — protocol witnesses: complete + hand-rolled viewers in ~500 lines of vanilla DOM and React respectively, + covering docks, the drawer subsystems, the renderer registry, and the + missing-renderer fallback. diff --git a/docs/guide/build-your-own-json-render-frontend.md b/docs/guide/build-your-own-json-render-frontend.md new file mode 100644 index 00000000..aa56e7b0 --- /dev/null +++ b/docs/guide/build-your-own-json-render-frontend.md @@ -0,0 +1,79 @@ +# 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, in any framework. The +[Next hub witness](/examples/hub-next) ships a complete React one in two files +(`src/client/json-render/`); this page is the contract it implements. + +## The contract + +`@devframes/json-render/hub` owns the types: + +```ts +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 serializable `view` reference: + +- `{ stateKey }` — subscribe to that shared state via + `context.rpc.sharedState.get(stateKey)`, render its value as the live spec, + and re-render on `'updated'`. **Unsubscribe in `dispose`.** +- `{ spec }` — render the embedded spec directly; no shared state involved. + +Detect static output via `context.rpc.connectionMeta.backend === 'static'` and +disable action dispatch there. + +## Behavior expectations + +Match the reference frontend's semantics so specs behave identically across +frontends: + +- **Actions** — a spec action name dispatches an RPC call of the same name. + Never bridge the reserved built-ins (`setState`, `pushState`, `removeState`, + `validateForm` — handled by the upstream renderer) or promise probes + (`then`/`catch`/`finally`). Surface failures to the view rather than + swallowing them. +- **Validation** — validate element props against `basePropSchemas` from + `@devframes/json-render`; swap an invalid element for an error placeholder so + one bad element doesn't break the view. +- **Unknown components** — a component your registry lacks renders as a + placeholder (type + prop-key gist) with a `console.warn`; the rest of the + view renders. +- **State reset** — reseed spec state only when the view identity changes, not + on every spec update. + +## Plugging it in + +Two seams, one contract: + +- **Local registration** — a host page that bundles its own client passes + `createDevframeClientHost({ 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) whose default export is the + renderer, and ship a node helper returning the hub registration: + + ```ts + import type { DockRendererRegistration } from '@devframes/hub/initiate' + + export function myRenderer(): DockRendererRegistration { + return { type: 'json-render', file: myPrebuiltModulePath } + } + ``` + + Hosts compose it with `initHub({ renderers: [myRenderer()] })` — the hub + serves the module and every viewer imports it lazily (see [renderer + modules](./hub-initiate#renderer-modules)). + +A prebuilt module must be **self-styling and shadow-root-safe**: the viewer's +container may live inside a shadow root, so deliver your stylesheet into the +mount subtree (the reference module attaches its own shadow root inside the +container and injects its compiled CSS there). Read the theme from the live +`dark` class the viewer keeps on the container, and derive brand color from the +inherited `--devframe-primary` custom property when present. diff --git a/docs/guide/client-context.md b/docs/guide/client-context.md index 9a9f8c93..902acece 100644 --- a/docs/guide/client-context.md +++ b/docs/guide/client-context.md @@ -38,7 +38,7 @@ Viewers with an HTML pipeline layer injection on top: `@vitejs/devtools` wraps t | `connect` | Options forwarded to `connectDevframe` when `rpc` is not supplied — pass `baseURL` to point at the hub's connection-meta mount (e.g. `/__hub/`). | | `clientType` | `'standalone'` (default) — the runtime owns the whole page (a hub UI). `'embedded'` — the runtime lives inside a user app alongside a panel. | | `loadClientScripts` | Import and run dock entries' client scripts. Default `true`. | -| `renderers` | Dock renderers to register at boot, keyed by dock `type` (e.g. `{ 'json-render': createJsonRenderDockRenderer() }` from `@devframes/json-render-ui`). The hub ships none. | +| `renderers` | Dock renderers to register at boot, keyed by dock `type` (e.g. `{ 'json-render': createJsonRenderDockRenderer() }` from `@devframes/json-render-ui`). Local registrations take precedence over the hub's [renderer manifest](./hub-initiate#renderer-modules). | Boot the host once per page: a second boot replaces the published context and logs a warning. `dispose()` tears down its listeners and unpublishes the context it owns. @@ -53,7 +53,7 @@ Boot the host once per page: a second boot replaces the published context and lo | `docks` | Dock entries and selection — `entries`, `selected`, `groupedEntries`, `switchEntry()`, `toggleEntry()`, `getStateById()`, plus `register()` / `update()` for [client-only docks](#client-only-docks). | | `panel` | Dock panel state: position, size, drag/resize flags. | | `commands` | The command palette: `register()`, `execute()`, `getKeybindings()`. | -| `renderers` | Dock-renderer registry — `register()`, `get()`, `has()`, `mount(entry, container)`. Routes a dock `type` to a host-registered renderer (e.g. [JSON-Render](./json-render)); the hub ships none. | +| `renderers` | Dock-renderer registry — `register()`, `get()`, `has()`, `mount(entry, container)`. Routes a dock `type` to a renderer: one registered locally at boot, or a prebuilt module lazy-imported from the hub's [renderer manifest](./hub-initiate#renderer-modules) (local wins). `mount()` resolves a typed result — `{ status: 'mounted', dispose }`, `{ status: 'missing-renderer' }`, or `{ status: 'load-error', error }` — so a viewer renders a visible fallback for a type nothing covers instead of a dead panel; `has()` answers for both sources so the fallback can render without a mount attempt. | | `when` | The [when-clause](./when-clauses) evaluation context. | | `connection` | The client's live [connection status](./client#handling-connection-and-auth-errors) — `status`, `error`, and `events` — so a viewer can render one central connection indicator for every docked plugin. | diff --git a/docs/guide/hub-initiate.md b/docs/guide/hub-initiate.md index 3c4e1275..2ae4ab67 100644 --- a/docs/guide/hub-initiate.md +++ b/docs/guide/hub-initiate.md @@ -61,6 +61,26 @@ interface DevframeHubUi { `@devframes/hub-ui`'s `createUi()` is the reference implementation: a standalone viewer plus the floating dock — one ` + + diff --git a/packages/hub-ui/src/client/components/views/ViewEntry.vue b/packages/hub-ui/src/client/components/views/ViewEntry.vue index 200d94ad..841d7812 100644 --- a/packages/hub-ui/src/client/components/views/ViewEntry.vue +++ b/packages/hub-ui/src/client/components/views/ViewEntry.vue @@ -3,10 +3,10 @@ import type { DevframeDockEntry } from '@devframes/hub' import type { DocksContext } from '@devframes/hub/client' import type { IframePanes } from 'iframe-pane' import type { CSSProperties } from 'vue' -import { defineAsyncComponent } from 'vue' import ViewBuiltinClientAuthNotice from '../views-builtin/ViewBuiltinClientAuthNotice.vue' import ViewBuiltinSettings from '../views-builtin/ViewBuiltinSettings.vue' import ViewCustomRenderer from './ViewCustomRenderer.vue' +import ViewDockRenderer from './ViewDockRenderer.vue' import ViewIframe from './ViewIframe.vue' import ViewLauncher from './ViewLauncher.vue' @@ -17,9 +17,6 @@ defineProps<{ iframeStyle?: CSSProperties divStyle?: CSSProperties }>() - -// Lazy load some less frequently used builtin views -const ViewJsonRender = defineAsyncComponent(() => import('./ViewJsonRender.vue'))