Skip to content

Commit 64128e5

Browse files
committed
fix(hub): always publish renderer manifest
1 parent e1d491d commit 64128e5

2 files changed

Lines changed: 32 additions & 16 deletions

File tree

packages/hub/src/node/__tests__/initiate.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createRpcClient } from 'devframe/rpc/client'
77
import { createWsRpcChannel } from 'devframe/rpc/transports/ws-client'
88
import { getPort } from 'get-port-please'
99
import { describe, expect, it } from 'vitest'
10+
import { DOCK_RENDERERS_STATE_KEY } from '../../constants'
1011
import { DEVFRAMES_HUB_BASE, initHub } from '../initiate'
1112

1213
function makeDist(html: string): string {
@@ -44,6 +45,23 @@ function connectWsClient(url: string) {
4445
}
4546

4647
describe('initHub', () => {
48+
it('publishes an empty renderer manifest when no renderers are registered', async () => {
49+
expect.assertions(2)
50+
const hub = initHub({ base: DEVFRAMES_HUB_BASE, auth: false })
51+
52+
try {
53+
await hub.ready
54+
const context = await hub.context
55+
expect(context.rpc.sharedState.keys()).toContain(DOCK_RENDERERS_STATE_KEY)
56+
57+
const rendererManifest = await context.rpc.sharedState.get(DOCK_RENDERERS_STATE_KEY)
58+
expect(rendererManifest.value()).toEqual({})
59+
}
60+
finally {
61+
await hub.close()
62+
}
63+
})
64+
4765
it('connectionMeta() before ready throws DF8003', () => {
4866
const hub = initHub({ base: DEVFRAMES_HUB_BASE, auth: false })
4967
expect(() => hub.connectionMeta()).toThrow(/DF8003|finished initializing/)

packages/hub/src/node/initiate.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -546,24 +546,22 @@ export function initHub(options: InitHubOptions): HubInstance {
546546
// into the connection meta right after this `init` returns.
547547
await options.ui?.setup?.(ctx)
548548

549-
// Publish the renderer manifest — one `ClientScriptEntry` per dock
550-
// `type`, `importFrom` base-absolute so it resolves to the served module
551-
// from any page depth. Clients read it from shared state and import a
552-
// module lazily the first time a dock of that type mounts.
553-
if (rendererRegistrations.length > 0) {
554-
const manifest: Record<string, ClientScriptEntry> = {}
555-
for (const registration of rendererRegistrations) {
556-
manifest[registration.type] = {
557-
importFrom: joinURL(base, '__renderers', `${registration.type}.mjs`),
558-
...(registration.importName ? { importName: registration.importName } : {}),
559-
}
549+
// Publish the authoritative renderer manifest, including an empty one.
550+
// Each `importFrom` is base-absolute so it resolves to the served module
551+
// from any page depth. Clients import a module lazily the first time a
552+
// dock of that type mounts.
553+
const manifest: Record<string, ClientScriptEntry> = {}
554+
for (const registration of rendererRegistrations) {
555+
manifest[registration.type] = {
556+
importFrom: joinURL(base, '__renderers', `${registration.type}.mjs`),
557+
...(registration.importName ? { importName: registration.importName } : {}),
560558
}
561-
const manifestState = await ctx.rpc.sharedState.get<Record<string, ClientScriptEntry>>(
562-
DOCK_RENDERERS_STATE_KEY,
563-
{ initialValue: {} },
564-
)
565-
manifestState.mutate(() => manifest)
566559
}
560+
const manifestState = await ctx.rpc.sharedState.get<Record<string, ClientScriptEntry>>(
561+
DOCK_RENDERERS_STATE_KEY,
562+
{ initialValue: {} },
563+
)
564+
manifestState.mutate(() => manifest)
567565

568566
// Aggregate MCP — one Streamable-HTTP endpoint over the shared
569567
// context's whole registry (tool ids are namespaced per plugin, and the

0 commit comments

Comments
 (0)