Skip to content

Commit d73fe67

Browse files
committed
feat(devframe,hub,hub-ui): static connection-meta configs + plugin-declared dock layout
Add ConnectionMeta.configs — an augmentable, boot-time, read-only-from-the- browser config channel, distinct from the mutable/shared-state-synced ctx.settings and devframe:user-settings. Each key is contributed via declaration merging against DevframeConnectionConfigsRegistry. Ports vitejs/devtools#515 (plugin-declared dock ordering, layout, and window defaults) on top of it: DevframeDockDefaults gains categoryOrder, maxVisibleItems, defaultMode, and defaultPosition. The hub aggregates them across every installed devframe (last-installed wins per scalar, categoryOrder shallow-merged) into ConnectionMeta.configs.dock, folded into the dock bar's category sort, the floating dock's inline-item capacity, and a first-run visitor's mode/position seed. hub-ui's branding moves off the ad-hoc branding.json asset route onto configs.ui.branding, delivered through the one connection handshake the dock already performs instead of a separate fetch. createUi({ branding }) keeps its existing signature. Built with the help of an agent.
1 parent 50f20f8 commit d73fe67

32 files changed

Lines changed: 423 additions & 74 deletions

File tree

docs/guide/build-your-own-hub-ui.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface DevframeHubUi {
1616
viewer?: { distDir: string } // a standalone SPA served at the hub base
1717
embedded?: { entry: string } // a self-contained bootstrap at <base>embedded.js
1818
assets?: Record<string, () => string | Uint8Array> // extra UI-owned files
19+
settings?: () => Record<string, unknown> // static config, published as ConnectionMeta.configs.ui
1920
}
2021
```
2122

@@ -24,6 +25,15 @@ prebuilt assets: the viewer SPA is built with relative asset paths, and the
2425
embedded entry is one self-contained ES module that mounts your dock into any
2526
host page.
2627

28+
`settings` publishes whatever you return verbatim as
29+
`ConnectionMeta.configs.ui` — the reference UI's `createUi({ branding })` uses
30+
it to deliver `{ branding }`, read by the dock from the one connection
31+
handshake it already performs, rather than a separate fetched file. The hub
32+
never interprets this object; it's a policy-free pass-through to your own
33+
client code. It's the read-only counterpart to `assets`: reach for `settings`
34+
for small, structured, boot-time config, and `assets` for arbitrary served
35+
files.
36+
2737
## The client contracts
2838

2939
A viewer renders from the hub's shared state and drives it through

docs/guide/hub-initiate.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ The hub is headless — `DevframeHubUi` is pure data, and whoever fills it decid
5656
interface DevframeHubUi {
5757
viewer?: { distDir: string } // a standalone SPA served at the namespace root
5858
embedded?: { entry: string } // a prebuilt bootstrap served at <base>embedded.js
59+
assets?: Record<string, () => string | Uint8Array> // extra UI-owned files
60+
settings?: () => Record<string, unknown> // static config, published as ConnectionMeta.configs.ui
5961
}
6062
```
6163

examples/hub-hono-minimal/src/app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ export const hub: HubInstance = globalRef.__hubHonoMinimal ??= initHub({
3939
createAssetsDevframe({ watch: false }),
4040
],
4141
// Rebrand the reference UI to Hono's own orange — one field, no CSS:
42-
// `createUi`'s `branding` option publishes `branding.json`, which the dock
43-
// fetches at boot and feeds into `--devframe-primary` (see
44-
// `@devframes/hub-ui`'s `primary-ramp.css`).
42+
// `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
43+
// which the dock reads at connect time and feeds into `--devframe-primary`
44+
// (see `@devframes/hub-ui`'s `primary-ramp.css`).
4545
ui: createUi({ branding: { primaryColor: '#e36002', productName: 'Devframes on Hono' } }),
4646
// Gate with devframe's interactive OTP (the default). The hub prints a
4747
// 6-digit code + magic link on startup, and the reference UI's authorization

examples/hub-next-minimal/src/client/hub.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ async function loadHub(): Promise<HubInstance> {
7373
// `@devframes/next/hub` runs the socket on a side-car (Next routes can't
7474
// accept WS upgrades). This host overrides the default UI slot to rebrand
7575
// the reference viewer to Next.js/Vercel's monochrome black — one field, no
76-
// CSS: `createUi`'s `branding` option publishes `branding.json`, which the
77-
// dock fetches at boot and feeds into `--devframe-primary` (see
78-
// `@devframes/hub-ui`'s `primary-ramp.css`).
76+
// CSS: `createUi`'s `branding` option publishes
77+
// `ConnectionMeta.configs.ui.branding`, which the dock reads at connect
78+
// time and feeds into `--devframe-primary` (see `@devframes/hub-ui`'s
79+
// `primary-ramp.css`).
7980
return createNextDevframeHub({
8081
devframes,
8182
ui: (hubUi.createUi as typeof CreateUi)({ branding: { primaryColor: '#000000', productName: 'Devframes on Next.js' } }),

examples/hub-nitro-minimal/hub.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ export const hub: HubInstance = globalRef.__hubNitroMinimal ??= initHub({
4242
createAssetsDevframe({ watch: false }),
4343
],
4444
// Rebrand the reference UI to Nitro's own pink/red — one field, no CSS:
45-
// `createUi`'s `branding` option publishes `branding.json`, which the dock
46-
// fetches at boot and feeds into `--devframe-primary` (see
47-
// `@devframes/hub-ui`'s `primary-ramp.css`).
45+
// `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
46+
// which the dock reads at connect time and feeds into `--devframe-primary`
47+
// (see `@devframes/hub-ui`'s `primary-ramp.css`).
4848
ui: createUi({ branding: { primaryColor: '#ff2056', productName: 'Devframes on Nitro' } }),
4949
// Gate with devframe's interactive OTP (the default). The hub prints a
5050
// 6-digit code + magic link on startup, and the reference UI's authorization

examples/hub-rsbuild-minimal/rsbuild.config.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ export default defineConfig({
7777
base,
7878
devframes: builtinDevframes,
7979
// Rebrand the reference UI to Rsbuild's own orange — one field, no
80-
// CSS: `createUi`'s `branding` option publishes `branding.json`,
81-
// which the dock fetches at boot and feeds into `--devframe-primary`
82-
// (see `@devframes/hub-ui`'s `primary-ramp.css`).
80+
// CSS: `createUi`'s `branding` option publishes
81+
// `ConnectionMeta.configs.ui.branding`, which the dock reads at
82+
// connect time and feeds into `--devframe-primary` (see
83+
// `@devframes/hub-ui`'s `primary-ramp.css`).
8384
ui: createUi({ branding: { primaryColor: '#ff5e00', productName: 'Devframes on Rsbuild' } }),
8485
// Serve the reference json-render frontend as a prebuilt renderer
8586
// module — the one-liner that makes `'json-render'` docks render in

examples/hub-vite-minimal/vite.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export default defineConfig({
6767
quiet: true,
6868
devframes: builtinDevframes,
6969
// Rebrand the reference UI to Vite's own purple — one field, no CSS:
70-
// `createUi`'s `branding` option publishes `branding.json`, which the
71-
// dock fetches at boot and feeds into `--devframe-primary` (see
72-
// `@devframes/hub-ui`'s `primary-ramp.css`). Passing `ui` overrides the
73-
// default `createUi()` the plugin would otherwise use.
70+
// `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
71+
// which the dock reads at connect time and feeds into `--devframe-primary`
72+
// (see `@devframes/hub-ui`'s `primary-ramp.css`). Passing `ui` overrides
73+
// the default `createUi()` the plugin would otherwise use.
7474
ui: createUi({ branding: { primaryColor: '#646cff', productName: 'Devframes on Vite' } }),
7575
// Serve the reference json-render frontend as a prebuilt renderer
7676
// module — the one-liner that makes `'json-render'` docks render in

packages/devframe/src/types/context.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,36 @@ export interface ConnectionMeta {
205205
* token same-origin until the requesting origin has been verified.
206206
*/
207207
viewerOriginToken?: string
208+
/**
209+
* Static, host-declared configuration — baked in once at connect time and
210+
* fixed for the life of the server (e.g. a hub's UI rebrand, or its
211+
* aggregated dock-bar layout preferences). Read-only from the browser: a
212+
* client only ever reads `rpc.connectionMeta.configs`, never writes to it.
213+
*
214+
* Contrast this with {@link DevframeSettingsRegistry} (`ctx.scope(ns).settings`)
215+
* and a hub's `devframe:user-settings` shared-state key — both are
216+
* mutable, user-editable, and synced bidirectionally over RPC for the
217+
* life of the session. `configs` is the opposite: one-way, immutable,
218+
* decided by whoever assembled the server.
219+
*
220+
* Each key is owned by one package, contributed via declaration merging:
221+
*
222+
* ```ts
223+
* declare module 'devframe/types' {
224+
* interface DevframeConnectionConfigsRegistry {
225+
* 'my-key': { some: 'shape' }
226+
* }
227+
* }
228+
* ```
229+
*/
230+
configs?: Partial<DevframeConnectionConfigsRegistry>
208231
}
232+
233+
/**
234+
* Augmentation point for {@link ConnectionMeta.configs}. Empty by default —
235+
* a package that wants to publish static, boot-time config through the
236+
* connection handshake augments this interface with its own key (see
237+
* {@link ConnectionMeta.configs} for the pattern). `@devframes/hub`
238+
* augments it with `dock`; `@devframes/hub-ui` augments it with `ui`.
239+
*/
240+
export interface DevframeConnectionConfigsRegistry {}

packages/devframe/src/types/devframe.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,36 @@ export interface DevframeDockDefaults {
262262
badge?: string
263263
/** Id of the dock group this entry collapses under, if any. */
264264
groupId?: string
265+
/**
266+
* This devframe's opinion about the top-level dock-bar **category**
267+
* ordering, hub-wide — not an attribute of this entry itself. Every
268+
* installed devframe's `categoryOrder` is shallow-merged into one
269+
* aggregate (last-installed wins per key), delivered once via
270+
* `ConnectionMeta.configs.dock.categoryOrder` and merged beneath
271+
* `DEFAULT_CATEGORIES_ORDER`. A host page's own
272+
* `createDevframeClientHost({ categoryOrder })` still overrides it.
273+
*/
274+
categoryOrder?: Record<string, number>
275+
/**
276+
* This devframe's preferred inline-item capacity for the floating dock
277+
* bar before entries overflow — hub-wide, not an attribute of this entry.
278+
* The last installed devframe declaring it wins; an explicit `layout`
279+
* prop passed to the dock UI still overrides it. Edge mode ignores this
280+
* by design — it shows every entry with no capacity cutoff.
281+
*/
282+
maxVisibleItems?: number
283+
/**
284+
* Seeds a first-run visitor's dock mode — hub-wide, not an attribute of
285+
* this entry. Only applies when the visitor has no stored dock
286+
* preference yet; never overwrites one who already moved their dock. The
287+
* last installed devframe declaring it wins.
288+
*/
289+
defaultMode?: 'float' | 'edge'
290+
/**
291+
* Seeds a first-run visitor's dock position, same override semantics as
292+
* {@link defaultMode}.
293+
*/
294+
defaultPosition?: 'left' | 'right' | 'top' | 'bottom'
265295
}
266296

267297
export interface DevframeSpaOptions {

packages/hub-ui/src/client/components/DockEmbedded.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { DocksContext } from '@devframes/hub/client'
22
import type { VueElementConstructor } from 'vue'
3+
import type { DockLayout } from './dock/dock-layout'
34
import { defineCustomElement } from 'vue'
45
import css from '../.generated/css'
56
import Component from './dock/DockEmbedded.vue'
@@ -12,6 +13,7 @@ export const DockEmbedded = defineCustomElement(
1213
},
1314
) as VueElementConstructor<{
1415
context: DocksContext
16+
layout?: Partial<DockLayout>
1517
}>
1618

1719
customElements.define('devframes-dock-embedded', DockEmbedded)

0 commit comments

Comments
 (0)