diff --git a/docs/guide/migration-0.7.md b/docs/guide/migration-0.7.md index d0bd7d8..9e6f386 100644 --- a/docs/guide/migration-0.7.md +++ b/docs/guide/migration-0.7.md @@ -72,6 +72,6 @@ const view = createJsonRenderView(ctx, { }) ``` -`@devframes/hub` still exports `defineJsonRenderSpec` as a deprecated identity function (same for the `JsonRenderSpec` / `JsonRenderElement` / `JsonRenderer` types), so existing imports keep compiling — but it no longer registers anything with the hub on its own. Move call sites over to `createJsonRenderView` now rather than waiting for the alias's removal in a future major release. +`@devframes/hub` still exports `defineJsonRenderSpec` as a deprecated identity function (same for the `JsonRenderSpec` / `JsonRenderElement` / `JsonRenderer` types) and still runs `ctx.createJsonRenderer` (against its own pre-0.7 shared-state implementation, not `@devframes/json-render`), so existing call sites keep working through 0.7 unmodified — but it no longer registers anything with the hub's dock union on its own, and won't gain the new dock projection or `registerRenderer()` support that `createJsonRenderView` gets. Move call sites over to `createJsonRenderView` now; `ctx.createJsonRenderer` and the other aliases above are removed in 0.8. See [JSON-Render](./json-render) for the full integration reference. diff --git a/packages/hub/src/define.ts b/packages/hub/src/define.ts index 39a0931..fe68081 100644 --- a/packages/hub/src/define.ts +++ b/packages/hub/src/define.ts @@ -26,8 +26,8 @@ export function defineDockEntry< * @deprecated json-render moved out of the hub into the opt-in * `@devframes/json-render` integration in 0.7. This identity helper is kept * so existing imports keep compiling — pass your spec directly to - * `createJsonRenderView` (from `@devframes/json-render/node`) instead. Will - * be removed in a future major release. + * `createJsonRenderView` (from `@devframes/json-render/node`) instead. + * Removed in 0.8. */ export function defineJsonRenderSpec(spec: JsonRenderSpec): JsonRenderSpec { return spec diff --git a/packages/hub/src/node/__tests__/context.test.ts b/packages/hub/src/node/__tests__/context.test.ts index 919e24a..3774322 100644 --- a/packages/hub/src/node/__tests__/context.test.ts +++ b/packages/hub/src/node/__tests__/context.test.ts @@ -59,6 +59,38 @@ describe('createHubContext dock activation', () => { }) }) +describe('createHubContext createJsonRenderer (deprecated 0.7 compatibility factory)', () => { + it('seeds shared state with the initial spec and applies updateSpec/updateState', async () => { + const context = await createHubContext({ + cwd: process.cwd(), + mode: 'build', + host: createHost(), + }) + + const renderer = context.createJsonRenderer({ root: 'panel', elements: {} }) + const state = await context.rpc.sharedState.get(renderer._stateKey) + expect(state.value()).toEqual({ root: 'panel', elements: {} }) + + await renderer.updateSpec({ root: 'panel', elements: {}, state: { count: 1 } }) + expect(state.value()).toEqual({ root: 'panel', elements: {}, state: { count: 1 } }) + + await renderer.updateState({ count: 2, label: 'hi' }) + expect(state.value()).toEqual({ root: 'panel', elements: {}, state: { count: 2, label: 'hi' } }) + }) + + it('gives each renderer its own stable, distinct state key', async () => { + const context = await createHubContext({ + cwd: process.cwd(), + mode: 'build', + host: createHost(), + }) + + const first = context.createJsonRenderer({ root: 'a', elements: {} }) + const second = context.createJsonRenderer({ root: 'b', elements: {} }) + expect(first._stateKey).not.toEqual(second._stateKey) + }) +}) + describe('startHttpAndWs remote endpoint metadata', () => { it('sets and clears the internal websocket endpoint', async () => { const context = await createHostContext({ diff --git a/packages/hub/src/node/context.ts b/packages/hub/src/node/context.ts index 8489af4..dc8984f 100644 --- a/packages/hub/src/node/context.ts +++ b/packages/hub/src/node/context.ts @@ -2,6 +2,7 @@ import type { CreateHostContextOptions } from 'devframe/node' import type { DevframeHost, DevframeNodeContext } from 'devframe/types' import type { DevframeCommandsHost } from '../types/commands' import type { DevframeDockActivation, DevframeDocksActiveState, DevframeDocksHost } from '../types/docks' +import type { JsonRenderer, JsonRenderSpec } from '../types/json-render' import type { DevframeMessageEntry, DevframeMessageEntryInput, DevframeMessagesHost } from '../types/messages' import type { DevframeTerminalsHost } from '../types/terminals' import { createHostContext } from 'devframe/node' @@ -84,14 +85,17 @@ declare module 'devframe/types' { /** * Hub-augmented node context — extends devframe's framework-neutral * `DevframeNodeContext` with the hub-level subsystems (`docks`, - * `terminals`, `messages`, `commands`). + * `terminals`, `messages`, `commands`) and the deprecated + * `createJsonRenderer` compatibility factory. * * Framework kits further extend this with their own slots (e.g. * `viteConfig`, `viteServer`). Host-specific capabilities (editor open, * filesystem reveal, etc.) ship as kit-registered RPC functions rather - * than as part of this surface. JSON-render is not part of the hub: it is - * an opt-in integration (`@devframes/json-render`) that augments any - * devframe context and contributes its own dock type. + * than as part of this surface. JSON-render itself is not part of the hub: + * it is an opt-in integration (`@devframes/json-render`) that augments any + * devframe context and contributes its own dock type — prefer + * `createJsonRenderView` from `@devframes/json-render/node` over the + * deprecated factory below. */ export interface DevframeHubContext extends DevframeNodeContext { readonly host: DevframeHost @@ -99,6 +103,16 @@ export interface DevframeHubContext extends DevframeNodeContext { terminals: DevframeTerminalsHost messages: DevframeMessagesHost commands: DevframeCommandsHost + /** + * Create a `JsonRenderer` handle for building json-render powered UIs. + * + * @deprecated json-render moved out of the hub into the opt-in + * `@devframes/json-render` integration in 0.7. This factory is kept + * working (not just type-compatible) for the 0.7 series so existing call + * sites don't break — use `createJsonRenderView(ctx, { id, spec })` from + * `@devframes/json-render/node` instead. Will be removed in 0.8. + */ + createJsonRenderer: (spec: JsonRenderSpec) => JsonRenderer } /** @@ -136,6 +150,36 @@ export async function createHubContext(options: CreateHubContextOptions): Promis await docks.init() + // Deprecated pre-0.7 compatibility factory — restored as a working (not + // merely type-compatible) shim so 0.7 only *deprecates* it rather than + // breaking it outright; see the `createJsonRenderer` JSDoc above. Kept + // self-contained against `context.rpc.sharedState` (as it was pre-0.7) + // rather than delegating to `@devframes/json-render`'s + // `createJsonRenderView`, so the hub still carries no dependency — direct + // or peer — on that opt-in package. Removed in 0.8: migrate call sites to + // `createJsonRenderView` before then. + let jsonRenderCounter = 0 + context.createJsonRenderer = (initialSpec: JsonRenderSpec): JsonRenderer => { + const stateKey = `devframe:json-render:${jsonRenderCounter++}` + const statePromise = context.rpc.sharedState.get(stateKey as any, { + initialValue: initialSpec as any, + }) + + return { + _stateKey: stateKey, + async updateSpec(spec) { + const state = await statePromise + state.mutate(() => spec as any) + }, + async updateState(newState) { + const state = await statePromise + state.mutate((draft: any) => { + draft.state = { ...draft.state, ...newState } + }) + }, + } + } + const debounceMs = options.mode === 'build' ? 0 : 10 const docksSharedState = await context.rpc.sharedState.get('devframe:docks', { initialValue: [] }) diff --git a/packages/hub/src/types/docks.ts b/packages/hub/src/types/docks.ts index 49b5c7e..fba10e0 100644 --- a/packages/hub/src/types/docks.ts +++ b/packages/hub/src/types/docks.ts @@ -259,10 +259,11 @@ export interface DevframeViewBuiltin extends DevframeDockEntryBase { * `JsonRenderer` handle) to {@link DevframeDockEntryRegistry} via declaration * merging. This type is kept for compatibility but is no longer a member of * {@link DevframeDockUserEntry} — use `@devframes/json-render/hub` instead. + * Removed in 0.8. */ export interface DevframeViewJsonRender extends DevframeDockEntryBase { type: 'json-render' - /** JsonRenderer handle created by ctx.createJsonRenderer() */ + /** JsonRenderer handle created by the deprecated ctx.createJsonRenderer() */ ui: JsonRenderer } diff --git a/packages/hub/src/types/json-render.ts b/packages/hub/src/types/json-render.ts index 6fdfc8c..5c583f3 100644 --- a/packages/hub/src/types/json-render.ts +++ b/packages/hub/src/types/json-render.ts @@ -3,10 +3,10 @@ // 0.7 moved json-render out of the hub into the opt-in `@devframes/json-render` // integration — use its `DevframeJsonRenderSpec` (from `@devframes/json-render`) // and `createJsonRenderView` (from `@devframes/json-render/node`) instead. These -// types are kept so existing imports keep compiling; they will be removed in a -// future major release. +// types are kept so existing imports keep compiling through the 0.7 series; +// they are removed in 0.8. -/** @deprecated Use `DevframeJsonRenderSpec`'s element shape from `@devframes/json-render` instead. */ +/** @deprecated Use `DevframeJsonRenderSpec`'s element shape from `@devframes/json-render` instead. Removed in 0.8. */ export interface JsonRenderElement { type: string props?: Record @@ -21,7 +21,7 @@ export interface JsonRenderElement { [key: string]: unknown } -/** @deprecated Use `DevframeJsonRenderSpec` from `@devframes/json-render` instead. */ +/** @deprecated Use `DevframeJsonRenderSpec` from `@devframes/json-render` instead. Removed in 0.8. */ export interface JsonRenderSpec { root: string elements: Record @@ -29,7 +29,7 @@ export interface JsonRenderSpec { state?: Record } -/** @deprecated Use `JsonRenderView` from `@devframes/json-render` instead. */ +/** @deprecated Use `JsonRenderView` from `@devframes/json-render` instead. Removed in 0.8. */ export interface JsonRenderer { /** Replace the entire spec */ updateSpec: (spec: JsonRenderSpec) => void | Promise