@ethisyscore/extension-runtime 1.9.2 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge-client-CIThO7jZ.d.cts +102 -0
- package/dist/bridge-client-UK3qcGoi.d.ts +102 -0
- package/dist/bridge-envelopes-BRKGSiSC.d.cts +63 -0
- package/dist/bridge-envelopes-BRKGSiSC.d.ts +63 -0
- package/dist/host/index.cjs +299 -0
- package/dist/host/index.cjs.map +1 -1
- package/dist/host/index.d.cts +94 -2
- package/dist/host/index.d.ts +94 -2
- package/dist/host/index.js +298 -1
- package/dist/host/index.js.map +1 -1
- package/dist/mock-host/cli.cjs +49 -0
- package/dist/mock-host/cli.cjs.map +1 -1
- package/dist/mock-host/cli.d.cts +2 -1
- package/dist/mock-host/cli.d.ts +2 -1
- package/dist/mock-host/cli.js +49 -0
- package/dist/mock-host/cli.js.map +1 -1
- package/dist/mock-host/index.cjs +84 -0
- package/dist/mock-host/index.cjs.map +1 -1
- package/dist/mock-host/index.d.cts +94 -2
- package/dist/mock-host/index.d.ts +94 -2
- package/dist/mock-host/index.js +83 -1
- package/dist/mock-host/index.js.map +1 -1
- package/dist/plugin/index.cjs +204 -0
- package/dist/plugin/index.cjs.map +1 -1
- package/dist/plugin/index.d.cts +50 -2
- package/dist/plugin/index.d.ts +50 -2
- package/dist/plugin/index.js +199 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/{transport-73otePiw.d.ts → transport-BN9Mzn_m.d.cts} +24 -0
- package/dist/{transport-73otePiw.d.cts → transport-Jfd9KXAh.d.ts} +24 -0
- package/package.json +3 -2
- package/dist/transport-DVn2GVZh.d.cts +0 -32
- package/dist/transport-DVn2GVZh.d.ts +0 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/host/declarative/interpreter.ts","../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/mock-host/InMemoryMcpTransport.ts","../../src/mock-host/DeclarativeMockHost.tsx"],"names":["createElement","createContext","useMemo","jsx","jsxs"],"mappings":";;;;;;AA0BO,SAAS,SAAA,CAAU,MAAgB,QAAA,EAC1C;AACI,EAAA,MAAM,SAAA,GAAY,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AACpC,EAAA,IAAI,CAAC,SAAA,EACL;AACI,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,OAAO,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,QAAA,GAAoC,KAAK,QAAA,EAAU,GAAA;AAAA,IACrD,CAAC,KAAA,EAAO,KAAA,KAAU,cAAA,CAAe,KAAA,EAAO,UAAU,KAAK;AAAA,GAC3D;AAEA,EAAA,OAAOA,oBAAc,SAAA,EAAW,EAAE,OAAO,IAAA,CAAK,KAAA,IAAS,QAAQ,CAAA;AACnE;AAEA,SAAS,cAAA,CAAe,IAAA,EAAgB,QAAA,EAA6B,KAAA,EACrE;AACI,EAAA,MAAM,SAAA,GAAY,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AACpC,EAAA,IAAI,CAAC,SAAA,EACL;AACI,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,OAAO,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,EAClE;AAEA,EAAA,MAAM,QAAA,GAAoC,KAAK,QAAA,EAAU,GAAA;AAAA,IACrD,CAAC,KAAA,EAAO,UAAA,KAAe,cAAA,CAAe,KAAA,EAAO,UAAU,UAAU;AAAA,GACrE;AAEA,EAAA,OAAOA,mBAAA,CAAc,WAAW,EAAE,KAAA,EAAO,KAAK,KAAA,EAAO,GAAA,EAAK,KAAA,EAAM,EAAG,QAAQ,CAAA;AAC/E;AC7CA,IAAM,uBAAA,GAA0BC,oBAAmC,IAAI,CAAA;AAiBhE,SAAS,wBAAA,CAAyB,EAAE,SAAA,EAAW,QAAA,EAAS,EAC/D;AAEI,EAAA,MAAM,QAAQC,aAAA,CAAQ,MAAM,SAAA,EAAW,CAAC,SAAS,CAAC,CAAA;AAClD,EAAA,uBACIC,cAAA,CAAC,uBAAA,CAAwB,QAAA,EAAxB,EAAiC,OAC7B,QAAA,EACL,CAAA;AAER;;;ACPO,IAAM,uBAAN,MACP;AAAA,EACqB,SAAA;AAAA,EACA,KAAA;AAAA,EAEV,WAAA,CACH,SAAA,EACA,KAAA,GAAyC,EAAC,EAE9C;AACI,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AACjB,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACjB;AAAA,EAEA,MAAa,YAAe,GAAA,EAC5B;AACI,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AAC/B,IAAA,IAAI,SAAS,MAAA,EACb;AACI,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,GAAG,CAAA,CAAE,CAAA;AAAA,IACrD;AACA,IAAA,OAAO,EAAE,KAAK,IAAA,EAA2B;AAAA,EAC7C;AAAA,EAEA,MAAa,UAAA,CAAuB,IAAA,EAAc,IAAA,EAClD;AACI,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC5B,IAAA,IAAI,CAAC,IAAA,EACL;AACI,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qBAAA,EAAwB,IAAI,CAAA,CAAE,CAAA;AAAA,IAClD;AACA,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,IAAI,CAAA;AAC9B,IAAA,OAAO,MAAA;AAAA,EACX;AACJ;ACGO,SAAS,oBAAoB,KAAA,EACpC;AACI,EAAA,MAAM,EAAE,SAAA,EAAW,KAAA,EAAO,kBAAA,EAAoB,QAAA,EAAU,UAAS,GAAI,KAAA;AAKrE,EAAA,MAAM,SAAA,GAAYD,aAAAA;AAAA,IACd,MAAM,IAAI,oBAAA,CAAqB,SAAA,EAAW,KAAA,IAAS,EAAE,CAAA;AAAA,IACrD,CAAC,WAAW,KAAK;AAAA,GACrB;AAEA,EAAA,MAAM,IAAA,GAAO,UAAU,kBAAkB,CAAA;AACzC,EAAA,MAAM,YAAA,GAA0B,IAAA,GAAO,SAAA,CAAU,IAAA,EAAM,QAAQ,CAAA,GAAI,QAAA;AAEnE,EAAA,uBACIE,eAAA,CAAC,4BAAyB,SAAA,EACrB,QAAA,EAAA;AAAA,IAAA,YAAA;AAAA,IACA,OAAO,QAAA,GAAW;AAAA,GAAA,EACvB,CAAA;AAER","file":"index.cjs","sourcesContent":["import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"./registry\";\nimport type { SduiNode } from \"./types\";\n\n/**\n * Walk a parsed SDUI tree and render it as a React element by looking up each\n * node's `type` in the supplied {@link ComponentRegistry}.\n *\n * The interpreter is purely structural:\n *\n * - It owns no UI styling, layout, or data fetching.\n * - It never reads `node.props` — props are forwarded opaquely to the host\n * component, which owns interpretation per primitive.\n * - It does not evaluate `node.bindings` — reactive rules are handled in a\n * separate task (E2.S2). For v1 the interpreter passes through the static\n * tree only.\n *\n * Each child is given a stable React `key` derived from its position so that\n * React's reconciler can identify list items across renders. The key is a\n * sibling-local index; the registry consumer is responsible for opting into a\n * stable identity if it has a domain-meaningful `props.key`.\n *\n * @throws Error when `node.type` is not present in the registry. This is the\n * fail-loud behaviour required by the closed v1 vocabulary — unknown\n * primitives must not silently degrade.\n */\nexport function interpret(node: SduiNode, registry: ComponentRegistry): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, index) => interpretChild(child, registry, index),\n );\n\n return createElement(Component, { props: node.props }, children);\n}\n\nfunction interpretChild(node: SduiNode, registry: ComponentRegistry, index: number): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, childIndex) => interpretChild(child, registry, childIndex),\n );\n\n return createElement(Component, { props: node.props, key: index }, children);\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import type { McpTransport } from \"../plugin/transport\";\nimport type { SduiNode } from \"../host/declarative/types\";\n\n/**\n * Handler for a mocked tool invocation. The handler receives the request\n * payload supplied by the caller and may return synchronously or\n * asynchronously. The result is forwarded verbatim through\n * {@link InMemoryMcpTransport.invokeTool}.\n */\nexport type MockToolHandler = (args: unknown) => Promise<unknown> | unknown;\n\n/**\n * An in-memory {@link McpTransport} backed by a `{ resources, tools }` map.\n *\n * Used by {@link DeclarativeMockHost} so plugin authors can run their app\n * standalone for local development without a real host. The transport mirrors\n * the runtime contract exactly:\n *\n * - `getResource(uri)` resolves a `SduiNode` keyed by URI, or rejects with a\n * descriptive error if the URI is not registered.\n * - `invokeTool(name, args)` dispatches to a synchronous or async handler,\n * or rejects if the tool name is unknown.\n *\n * The transport intentionally does NOT honour the supplied `AbortSignal` —\n * mock handlers are synchronous from the caller's perspective and there is\n * no in-flight network call to abort. Hooks still work correctly because\n * they treat the `AbortSignal` as a one-way notification, not a contract.\n */\nexport class InMemoryMcpTransport implements McpTransport\n{\n private readonly resources: Record<string, SduiNode>;\n private readonly tools: Record<string, MockToolHandler>;\n\n public constructor(\n resources: Record<string, SduiNode>,\n tools: Record<string, MockToolHandler> = {},\n )\n {\n this.resources = resources;\n this.tools = tools;\n }\n\n public async getResource<T>(uri: string): Promise<{ uri: string; data: T }>\n {\n const data = this.resources[uri];\n if (data === undefined)\n {\n throw new Error(`Mock resource not found: ${uri}`);\n }\n return { uri, data: data as unknown as T };\n }\n\n public async invokeTool<TReq, TRes>(name: string, args: TReq): Promise<TRes>\n {\n const tool = this.tools[name];\n if (!tool)\n {\n throw new Error(`Mock tool not found: ${name}`);\n }\n const result = await tool(args);\n return result as TRes;\n }\n}\n","import { useMemo, type ReactElement, type ReactNode } from \"react\";\nimport { interpret } from \"../host/declarative/interpreter\";\nimport type { ComponentRegistry } from \"../host/declarative/registry\";\nimport type { SduiNode } from \"../host/declarative/types\";\nimport { ExtensionRuntimeProvider } from \"../plugin/ExtensionRuntimeProvider\";\nimport { InMemoryMcpTransport, type MockToolHandler } from \"./InMemoryMcpTransport\";\n\n/**\n * Props for {@link DeclarativeMockHost}.\n *\n * Plugin authors `npm link` the runtime and render `<DeclarativeMockHost>` in\n * their local dev app to exercise the same declarative pipeline the real host\n * uses, but backed by in-memory fakes instead of the platform.\n */\nexport interface DeclarativeMockHostProps\n{\n /**\n * In-memory resource map. Keys are MCP resource URIs; values are SDUI trees\n * that {@link interpret} will render against the supplied registry.\n */\n resources: Record<string, SduiNode>;\n\n /**\n * In-memory tool map. Keys are MCP tool names; values are handlers invoked\n * when a child component calls `useMcpTool(name).invoke(args)`.\n *\n * Handlers may be sync or async — the transport awaits the result before\n * forwarding it to the caller.\n */\n tools?: Record<string, MockToolHandler>;\n\n /**\n * URI of the resource rendered as the host's default tree. If the URI is\n * not present in `resources` the host renders the supplied `children`\n * instead — useful for stubs that exercise only tool invocations.\n */\n defaultResourceUri: string;\n\n /**\n * The same primitive → component registry the real host uses. Passed\n * verbatim to {@link interpret}; the mock host owns no UI of its own.\n */\n registry: ComponentRegistry;\n\n /**\n * Optional fallback content rendered when `defaultResourceUri` does not\n * resolve to a registered resource. Children also have access to the wired\n * transport via {@link ExtensionRuntimeProvider}, so they can invoke\n * mocked tools and resources directly through the React hooks.\n */\n children?: ReactNode;\n}\n\n/**\n * In-memory host for declarative (Contract A) plugin local-dev.\n *\n * Renders a plugin's SDUI resource against the supplied registry and wires an\n * {@link InMemoryMcpTransport} into context so descendant components that use\n * `useMcpResource` / `useMcpTool` resolve against the same fakes.\n *\n * The host is intentionally minimal: it does not simulate permissions, theme\n * propagation, or capability tokens. Its purpose is to exercise the\n * declarative pipeline end-to-end against deterministic in-memory data so\n * plugin authors can iterate without standing up the real platform.\n */\nexport function DeclarativeMockHost(props: DeclarativeMockHostProps): ReactElement\n{\n const { resources, tools, defaultResourceUri, registry, children } = props;\n\n // Memoise the transport so React doesn't churn the context identity every\n // render — re-rendering this host with stable inputs must not abort\n // in-flight hook calls.\n const transport = useMemo(\n () => new InMemoryMcpTransport(resources, tools ?? {}),\n [resources, tools],\n );\n\n const tree = resources[defaultResourceUri];\n const renderedTree: ReactNode = tree ? interpret(tree, registry) : children;\n\n return (\n <ExtensionRuntimeProvider transport={transport}>\n {renderedTree}\n {tree ? children : null}\n </ExtensionRuntimeProvider>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/host/declarative/interpreter.ts","../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/mock-host/InMemoryMcpTransport.ts","../../src/mock-host/DeclarativeMockHost.tsx","../../src/mock-host/InMemoryBridgeTransport.ts","../../src/plugin/BridgeClientContext.ts","../../src/mock-host/WorkerMockHost.tsx"],"names":["createElement","createContext","useMemo","jsx","jsxs"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"./registry\";\nimport type { SduiNode } from \"./types\";\n\n/**\n * Walk a parsed SDUI tree and render it as a React element by looking up each\n * node's `type` in the supplied {@link ComponentRegistry}.\n *\n * The interpreter is purely structural:\n *\n * - It owns no UI styling, layout, or data fetching.\n * - It never reads `node.props` — props are forwarded opaquely to the host\n * component, which owns interpretation per primitive.\n * - It does not evaluate `node.bindings` — reactive rules are handled in a\n * separate task (E2.S2). For v1 the interpreter passes through the static\n * tree only.\n *\n * Each child is given a stable React `key` derived from its position so that\n * React's reconciler can identify list items across renders. The key is a\n * sibling-local index; the registry consumer is responsible for opting into a\n * stable identity if it has a domain-meaningful `props.key`.\n *\n * @throws Error when `node.type` is not present in the registry. This is the\n * fail-loud behaviour required by the closed v1 vocabulary — unknown\n * primitives must not silently degrade.\n */\nexport function interpret(node: SduiNode, registry: ComponentRegistry): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, index) => interpretChild(child, registry, index),\n );\n\n return createElement(Component, { props: node.props }, children);\n}\n\nfunction interpretChild(node: SduiNode, registry: ComponentRegistry, index: number): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, childIndex) => interpretChild(child, registry, childIndex),\n );\n\n return createElement(Component, { props: node.props, key: index }, children);\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import type { McpTransport } from \"../plugin/transport\";\nimport type { SduiNode } from \"../host/declarative/types\";\n\n/**\n * Handler for a mocked tool invocation. The handler receives the request\n * payload supplied by the caller and may return synchronously or\n * asynchronously. The result is forwarded verbatim through\n * {@link InMemoryMcpTransport.invokeTool}.\n */\nexport type MockToolHandler = (args: unknown) => Promise<unknown> | unknown;\n\n/**\n * An in-memory {@link McpTransport} backed by a `{ resources, tools }` map.\n *\n * Used by {@link DeclarativeMockHost} so plugin authors can run their app\n * standalone for local development without a real host. The transport mirrors\n * the runtime contract exactly:\n *\n * - `getResource(uri)` resolves a `SduiNode` keyed by URI, or rejects with a\n * descriptive error if the URI is not registered.\n * - `invokeTool(name, args)` dispatches to a synchronous or async handler,\n * or rejects if the tool name is unknown.\n *\n * The transport intentionally does NOT honour the supplied `AbortSignal` —\n * mock handlers are synchronous from the caller's perspective and there is\n * no in-flight network call to abort. Hooks still work correctly because\n * they treat the `AbortSignal` as a one-way notification, not a contract.\n */\nexport class InMemoryMcpTransport implements McpTransport\n{\n private readonly resources: Record<string, SduiNode>;\n private readonly tools: Record<string, MockToolHandler>;\n\n public constructor(\n resources: Record<string, SduiNode>,\n tools: Record<string, MockToolHandler> = {},\n )\n {\n this.resources = resources;\n this.tools = tools;\n }\n\n public async getResource<T>(uri: string): Promise<{ uri: string; data: T }>\n {\n const data = this.resources[uri];\n if (data === undefined)\n {\n throw new Error(`Mock resource not found: ${uri}`);\n }\n return { uri, data: data as unknown as T };\n }\n\n public async invokeTool<TReq, TRes>(name: string, args: TReq): Promise<TRes>\n {\n const tool = this.tools[name];\n if (!tool)\n {\n throw new Error(`Mock tool not found: ${name}`);\n }\n const result = await tool(args);\n return result as TRes;\n }\n}\n","import { useMemo, type ReactElement, type ReactNode } from \"react\";\nimport { interpret } from \"../host/declarative/interpreter\";\nimport type { ComponentRegistry } from \"../host/declarative/registry\";\nimport type { SduiNode } from \"../host/declarative/types\";\nimport { ExtensionRuntimeProvider } from \"../plugin/ExtensionRuntimeProvider\";\nimport { InMemoryMcpTransport, type MockToolHandler } from \"./InMemoryMcpTransport\";\n\n/**\n * Props for {@link DeclarativeMockHost}.\n *\n * Plugin authors `npm link` the runtime and render `<DeclarativeMockHost>` in\n * their local dev app to exercise the same declarative pipeline the real host\n * uses, but backed by in-memory fakes instead of the platform.\n */\nexport interface DeclarativeMockHostProps\n{\n /**\n * In-memory resource map. Keys are MCP resource URIs; values are SDUI trees\n * that {@link interpret} will render against the supplied registry.\n */\n resources: Record<string, SduiNode>;\n\n /**\n * In-memory tool map. Keys are MCP tool names; values are handlers invoked\n * when a child component calls `useMcpTool(name).invoke(args)`.\n *\n * Handlers may be sync or async — the transport awaits the result before\n * forwarding it to the caller.\n */\n tools?: Record<string, MockToolHandler>;\n\n /**\n * URI of the resource rendered as the host's default tree. If the URI is\n * not present in `resources` the host renders the supplied `children`\n * instead — useful for stubs that exercise only tool invocations.\n */\n defaultResourceUri: string;\n\n /**\n * The same primitive → component registry the real host uses. Passed\n * verbatim to {@link interpret}; the mock host owns no UI of its own.\n */\n registry: ComponentRegistry;\n\n /**\n * Optional fallback content rendered when `defaultResourceUri` does not\n * resolve to a registered resource. Children also have access to the wired\n * transport via {@link ExtensionRuntimeProvider}, so they can invoke\n * mocked tools and resources directly through the React hooks.\n */\n children?: ReactNode;\n}\n\n/**\n * In-memory host for declarative (Contract A) plugin local-dev.\n *\n * Renders a plugin's SDUI resource against the supplied registry and wires an\n * {@link InMemoryMcpTransport} into context so descendant components that use\n * `useMcpResource` / `useMcpTool` resolve against the same fakes.\n *\n * The host is intentionally minimal: it does not simulate permissions, theme\n * propagation, or capability tokens. Its purpose is to exercise the\n * declarative pipeline end-to-end against deterministic in-memory data so\n * plugin authors can iterate without standing up the real platform.\n */\nexport function DeclarativeMockHost(props: DeclarativeMockHostProps): ReactElement\n{\n const { resources, tools, defaultResourceUri, registry, children } = props;\n\n // Memoise the transport so React doesn't churn the context identity every\n // render — re-rendering this host with stable inputs must not abort\n // in-flight hook calls.\n const transport = useMemo(\n () => new InMemoryMcpTransport(resources, tools ?? {}),\n [resources, tools],\n );\n\n const tree = resources[defaultResourceUri];\n const renderedTree: ReactNode = tree ? interpret(tree, registry) : children;\n\n return (\n <ExtensionRuntimeProvider transport={transport}>\n {renderedTree}\n {tree ? children : null}\n </ExtensionRuntimeProvider>\n );\n}\n","/**\n * In-realm bridge transport for plugin local-dev and contract testing.\n *\n * Calling `pushTheme(...)`, `pushLocale(...)`, etc. invokes the registered\n * subscriber callbacks **synchronously** — no serialisation, no port. This\n * lets Vitest + React Testing Library drive bridge state changes with `act()`\n * without a real MessageChannel.\n *\n * In production, the bridge client is `createPortBridgeClient` backed by a\n * real MessagePort. `InMemoryBridgeTransport` is the dev/test equivalent:\n * both expose the same `PortBridgeClient`-compatible subscriber API on the\n * consumer side, but `InMemoryBridgeTransport` also exposes the push-side\n * and the `onChromeRequest` handler for test assertions.\n */\nimport type { PortBridgeClient, ThemePayload, LocalePayload, DensityPayload, A11yPayload, NavPayload, SessionTokenPayload } from \"../plugin/bridge-client\";\n\ntype ChromeRequestHandler = (\n action: string,\n payload: Record<string, unknown>,\n) => Promise<unknown> | unknown;\n\nexport class InMemoryBridgeTransport implements PortBridgeClient {\n private _themeCb: ((p: ThemePayload) => void) | undefined;\n private _localeCb: ((p: LocalePayload) => void) | undefined;\n private _densityCb: ((p: DensityPayload) => void) | undefined;\n private _a11yCb: ((p: A11yPayload) => void) | undefined;\n private _navCb: ((p: NavPayload) => void) | undefined;\n private _tokenCb: ((p: SessionTokenPayload) => void) | undefined;\n private _chromeHandler: ChromeRequestHandler | undefined;\n\n // ── PortBridgeClient subscriber interface ──────────────────────────────────\n\n onTheme(cb: (p: ThemePayload) => void): void { this._themeCb = cb; }\n onLocale(cb: (p: LocalePayload) => void): void { this._localeCb = cb; }\n onDensity(cb: (p: DensityPayload) => void): void { this._densityCb = cb; }\n onA11y(cb: (p: A11yPayload) => void): void { this._a11yCb = cb; }\n onNav(cb: (p: NavPayload) => void): void { this._navCb = cb; }\n onSessionToken(cb: (p: SessionTokenPayload) => void): void { this._tokenCb = cb; }\n\n requestChrome(action: string, payload: Record<string, unknown>): Promise<unknown> {\n return this.simulateChromeRequest(action, payload);\n }\n\n announceA11y(_message: string, _politeness: \"polite\" | \"assertive\"): void {\n // No-op in the mock — tests assert via `onChromeRequest` or inspect DOM.\n }\n\n // ── Test / dev control surface ─────────────────────────────────────────────\n\n /** Push a theme update to the registered subscriber (synchronous). */\n pushTheme(payload: ThemePayload): void { this._themeCb?.(payload); }\n /** Push a locale update to the registered subscriber. */\n pushLocale(payload: LocalePayload): void { this._localeCb?.(payload); }\n /** Push a density update. */\n pushDensity(payload: DensityPayload): void { this._densityCb?.(payload); }\n /** Push a11y preference changes. */\n pushA11y(payload: A11yPayload): void { this._a11yCb?.(payload); }\n /** Push a nav state update. */\n pushNav(payload: NavPayload): void { this._navCb?.(payload); }\n /** Push a frontend-session token. */\n pushSessionToken(payload: SessionTokenPayload): void { this._tokenCb?.(payload); }\n\n /**\n * Register a handler for plugin→host chrome requests (toast, confirm, etc).\n * Called by `requestChrome` and by `simulateChromeRequest`.\n */\n onChromeRequest(handler: ChromeRequestHandler): void {\n this._chromeHandler = handler;\n }\n\n /**\n * Programmatically send a chrome request as if a plugin component called\n * `PortBridgeClient.requestChrome(...)`. Useful for test assertions.\n */\n async simulateChromeRequest(action: string, payload: Record<string, unknown>): Promise<unknown> {\n if (this._chromeHandler === undefined) {\n return null;\n }\n return this._chromeHandler(action, payload);\n }\n}\n","import { createContext, useContext } from \"react\";\nimport type { PortBridgeClient } from \"./bridge-client\";\n\n/**\n * React context carrying the plugin's active {@link PortBridgeClient}.\n * Provided by the host mount (WorkerMockHost in dev, real bridge in production)\n * and consumed by `useBridgeTheme`, `useBridgeLocale`, and the `plugin-ui` hooks.\n */\nexport const BridgeClientContext = createContext<PortBridgeClient | null>(null);\n\n/**\n * Returns the bridge client from context, throwing a clear error when missing.\n * Used by the `useBridge*` hooks to fail loudly on misconfiguration.\n */\nexport function useBridgeClient(): PortBridgeClient {\n const client = useContext(BridgeClientContext);\n if (client === null) {\n throw new Error(\n \"No PortBridgeClient available. Wrap your plugin in <BridgeClientProvider> \"\n + \"or ensure the host mount wires a BridgeClientContext.Provider.\",\n );\n }\n return client;\n}\n","/**\n * Mock host for plugins that use bridge hooks (`useBridgeTheme`,\n * `useBridgeLocale`, `useBridgeA11y`, etc.) during local-dev or contract\n * testing.\n *\n * Wraps {@link DeclarativeMockHost} and wires a {@link BridgeClientContext}\n * provider so any descendant bridge hook resolves against the supplied\n * `bridgeTransport` instead of throwing \"no bridge client available\".\n *\n * For tests where bridge state must be driven externally (push a new theme,\n * assert that a component re-renders), pass an {@link InMemoryBridgeTransport}\n * instance and call `transport.pushTheme(...)` wrapped in `act()`.\n */\nimport { type ReactElement } from \"react\";\nimport { DeclarativeMockHost, type DeclarativeMockHostProps } from \"./DeclarativeMockHost\";\nimport { BridgeClientContext } from \"../plugin/BridgeClientContext\";\nimport type { PortBridgeClient } from \"../plugin/bridge-client\";\n\nexport interface WorkerMockHostProps extends DeclarativeMockHostProps {\n /**\n * The bridge transport to wire into context. Pass an\n * {@link InMemoryBridgeTransport} for tests; pass a\n * `createPortBridgeClient(port)` instance for postMessage integration tests.\n */\n bridgeTransport: PortBridgeClient;\n}\n\n/**\n * Mock host that combines the SDUI declarative pipeline with bridge context.\n *\n * Rendering contract (same as DeclarativeMockHost):\n * - `defaultResourceUri` present in `resources` → renders the SDUI tree.\n * - URI absent → renders `children` instead (tool-invocation stubs, etc).\n *\n * Bridge contract:\n * - All `useBridgeTheme`, `useBridgeLocale`, etc. hooks in the subtree resolve\n * against `bridgeTransport`.\n */\nexport function WorkerMockHost(props: WorkerMockHostProps): ReactElement {\n const { bridgeTransport, ...declarativeProps } = props;\n\n return (\n <BridgeClientContext.Provider value={bridgeTransport}>\n <DeclarativeMockHost {...declarativeProps} />\n </BridgeClientContext.Provider>\n );\n}\n"]}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ReactNode, ReactElement } from 'react';
|
|
2
2
|
import { C as ComponentRegistry } from '../registry-DpCx_LxF.cjs';
|
|
3
3
|
import { SduiNode } from '@ethisyscore/protocol';
|
|
4
|
-
import { M as McpTransport } from '../
|
|
4
|
+
import { M as McpTransport, P as PortBridgeClient, T as ThemePayload, L as LocalePayload, D as DensityPayload, A as A11yPayload, N as NavPayload, S as SessionTokenPayload } from '../bridge-client-CIThO7jZ.cjs';
|
|
5
|
+
import '../bridge-envelopes-BRKGSiSC.cjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Handler for a mocked tool invocation. The handler receives the request
|
|
@@ -92,4 +93,95 @@ interface DeclarativeMockHostProps {
|
|
|
92
93
|
*/
|
|
93
94
|
declare function DeclarativeMockHost(props: DeclarativeMockHostProps): ReactElement;
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
/**
|
|
97
|
+
* In-realm bridge transport for plugin local-dev and contract testing.
|
|
98
|
+
*
|
|
99
|
+
* Calling `pushTheme(...)`, `pushLocale(...)`, etc. invokes the registered
|
|
100
|
+
* subscriber callbacks **synchronously** — no serialisation, no port. This
|
|
101
|
+
* lets Vitest + React Testing Library drive bridge state changes with `act()`
|
|
102
|
+
* without a real MessageChannel.
|
|
103
|
+
*
|
|
104
|
+
* In production, the bridge client is `createPortBridgeClient` backed by a
|
|
105
|
+
* real MessagePort. `InMemoryBridgeTransport` is the dev/test equivalent:
|
|
106
|
+
* both expose the same `PortBridgeClient`-compatible subscriber API on the
|
|
107
|
+
* consumer side, but `InMemoryBridgeTransport` also exposes the push-side
|
|
108
|
+
* and the `onChromeRequest` handler for test assertions.
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
type ChromeRequestHandler = (action: string, payload: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
112
|
+
declare class InMemoryBridgeTransport implements PortBridgeClient {
|
|
113
|
+
private _themeCb;
|
|
114
|
+
private _localeCb;
|
|
115
|
+
private _densityCb;
|
|
116
|
+
private _a11yCb;
|
|
117
|
+
private _navCb;
|
|
118
|
+
private _tokenCb;
|
|
119
|
+
private _chromeHandler;
|
|
120
|
+
onTheme(cb: (p: ThemePayload) => void): void;
|
|
121
|
+
onLocale(cb: (p: LocalePayload) => void): void;
|
|
122
|
+
onDensity(cb: (p: DensityPayload) => void): void;
|
|
123
|
+
onA11y(cb: (p: A11yPayload) => void): void;
|
|
124
|
+
onNav(cb: (p: NavPayload) => void): void;
|
|
125
|
+
onSessionToken(cb: (p: SessionTokenPayload) => void): void;
|
|
126
|
+
requestChrome(action: string, payload: Record<string, unknown>): Promise<unknown>;
|
|
127
|
+
announceA11y(_message: string, _politeness: "polite" | "assertive"): void;
|
|
128
|
+
/** Push a theme update to the registered subscriber (synchronous). */
|
|
129
|
+
pushTheme(payload: ThemePayload): void;
|
|
130
|
+
/** Push a locale update to the registered subscriber. */
|
|
131
|
+
pushLocale(payload: LocalePayload): void;
|
|
132
|
+
/** Push a density update. */
|
|
133
|
+
pushDensity(payload: DensityPayload): void;
|
|
134
|
+
/** Push a11y preference changes. */
|
|
135
|
+
pushA11y(payload: A11yPayload): void;
|
|
136
|
+
/** Push a nav state update. */
|
|
137
|
+
pushNav(payload: NavPayload): void;
|
|
138
|
+
/** Push a frontend-session token. */
|
|
139
|
+
pushSessionToken(payload: SessionTokenPayload): void;
|
|
140
|
+
/**
|
|
141
|
+
* Register a handler for plugin→host chrome requests (toast, confirm, etc).
|
|
142
|
+
* Called by `requestChrome` and by `simulateChromeRequest`.
|
|
143
|
+
*/
|
|
144
|
+
onChromeRequest(handler: ChromeRequestHandler): void;
|
|
145
|
+
/**
|
|
146
|
+
* Programmatically send a chrome request as if a plugin component called
|
|
147
|
+
* `PortBridgeClient.requestChrome(...)`. Useful for test assertions.
|
|
148
|
+
*/
|
|
149
|
+
simulateChromeRequest(action: string, payload: Record<string, unknown>): Promise<unknown>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Mock host for plugins that use bridge hooks (`useBridgeTheme`,
|
|
154
|
+
* `useBridgeLocale`, `useBridgeA11y`, etc.) during local-dev or contract
|
|
155
|
+
* testing.
|
|
156
|
+
*
|
|
157
|
+
* Wraps {@link DeclarativeMockHost} and wires a {@link BridgeClientContext}
|
|
158
|
+
* provider so any descendant bridge hook resolves against the supplied
|
|
159
|
+
* `bridgeTransport` instead of throwing "no bridge client available".
|
|
160
|
+
*
|
|
161
|
+
* For tests where bridge state must be driven externally (push a new theme,
|
|
162
|
+
* assert that a component re-renders), pass an {@link InMemoryBridgeTransport}
|
|
163
|
+
* instance and call `transport.pushTheme(...)` wrapped in `act()`.
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
interface WorkerMockHostProps extends DeclarativeMockHostProps {
|
|
167
|
+
/**
|
|
168
|
+
* The bridge transport to wire into context. Pass an
|
|
169
|
+
* {@link InMemoryBridgeTransport} for tests; pass a
|
|
170
|
+
* `createPortBridgeClient(port)` instance for postMessage integration tests.
|
|
171
|
+
*/
|
|
172
|
+
bridgeTransport: PortBridgeClient;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Mock host that combines the SDUI declarative pipeline with bridge context.
|
|
176
|
+
*
|
|
177
|
+
* Rendering contract (same as DeclarativeMockHost):
|
|
178
|
+
* - `defaultResourceUri` present in `resources` → renders the SDUI tree.
|
|
179
|
+
* - URI absent → renders `children` instead (tool-invocation stubs, etc).
|
|
180
|
+
*
|
|
181
|
+
* Bridge contract:
|
|
182
|
+
* - All `useBridgeTheme`, `useBridgeLocale`, etc. hooks in the subtree resolve
|
|
183
|
+
* against `bridgeTransport`.
|
|
184
|
+
*/
|
|
185
|
+
declare function WorkerMockHost(props: WorkerMockHostProps): ReactElement;
|
|
186
|
+
|
|
187
|
+
export { DeclarativeMockHost, type DeclarativeMockHostProps, InMemoryBridgeTransport, InMemoryMcpTransport, type MockToolHandler, WorkerMockHost, type WorkerMockHostProps };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ReactNode, ReactElement } from 'react';
|
|
2
2
|
import { C as ComponentRegistry } from '../registry-DpCx_LxF.js';
|
|
3
3
|
import { SduiNode } from '@ethisyscore/protocol';
|
|
4
|
-
import { M as McpTransport } from '../
|
|
4
|
+
import { M as McpTransport, P as PortBridgeClient, T as ThemePayload, L as LocalePayload, D as DensityPayload, A as A11yPayload, N as NavPayload, S as SessionTokenPayload } from '../bridge-client-UK3qcGoi.js';
|
|
5
|
+
import '../bridge-envelopes-BRKGSiSC.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Handler for a mocked tool invocation. The handler receives the request
|
|
@@ -92,4 +93,95 @@ interface DeclarativeMockHostProps {
|
|
|
92
93
|
*/
|
|
93
94
|
declare function DeclarativeMockHost(props: DeclarativeMockHostProps): ReactElement;
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
/**
|
|
97
|
+
* In-realm bridge transport for plugin local-dev and contract testing.
|
|
98
|
+
*
|
|
99
|
+
* Calling `pushTheme(...)`, `pushLocale(...)`, etc. invokes the registered
|
|
100
|
+
* subscriber callbacks **synchronously** — no serialisation, no port. This
|
|
101
|
+
* lets Vitest + React Testing Library drive bridge state changes with `act()`
|
|
102
|
+
* without a real MessageChannel.
|
|
103
|
+
*
|
|
104
|
+
* In production, the bridge client is `createPortBridgeClient` backed by a
|
|
105
|
+
* real MessagePort. `InMemoryBridgeTransport` is the dev/test equivalent:
|
|
106
|
+
* both expose the same `PortBridgeClient`-compatible subscriber API on the
|
|
107
|
+
* consumer side, but `InMemoryBridgeTransport` also exposes the push-side
|
|
108
|
+
* and the `onChromeRequest` handler for test assertions.
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
type ChromeRequestHandler = (action: string, payload: Record<string, unknown>) => Promise<unknown> | unknown;
|
|
112
|
+
declare class InMemoryBridgeTransport implements PortBridgeClient {
|
|
113
|
+
private _themeCb;
|
|
114
|
+
private _localeCb;
|
|
115
|
+
private _densityCb;
|
|
116
|
+
private _a11yCb;
|
|
117
|
+
private _navCb;
|
|
118
|
+
private _tokenCb;
|
|
119
|
+
private _chromeHandler;
|
|
120
|
+
onTheme(cb: (p: ThemePayload) => void): void;
|
|
121
|
+
onLocale(cb: (p: LocalePayload) => void): void;
|
|
122
|
+
onDensity(cb: (p: DensityPayload) => void): void;
|
|
123
|
+
onA11y(cb: (p: A11yPayload) => void): void;
|
|
124
|
+
onNav(cb: (p: NavPayload) => void): void;
|
|
125
|
+
onSessionToken(cb: (p: SessionTokenPayload) => void): void;
|
|
126
|
+
requestChrome(action: string, payload: Record<string, unknown>): Promise<unknown>;
|
|
127
|
+
announceA11y(_message: string, _politeness: "polite" | "assertive"): void;
|
|
128
|
+
/** Push a theme update to the registered subscriber (synchronous). */
|
|
129
|
+
pushTheme(payload: ThemePayload): void;
|
|
130
|
+
/** Push a locale update to the registered subscriber. */
|
|
131
|
+
pushLocale(payload: LocalePayload): void;
|
|
132
|
+
/** Push a density update. */
|
|
133
|
+
pushDensity(payload: DensityPayload): void;
|
|
134
|
+
/** Push a11y preference changes. */
|
|
135
|
+
pushA11y(payload: A11yPayload): void;
|
|
136
|
+
/** Push a nav state update. */
|
|
137
|
+
pushNav(payload: NavPayload): void;
|
|
138
|
+
/** Push a frontend-session token. */
|
|
139
|
+
pushSessionToken(payload: SessionTokenPayload): void;
|
|
140
|
+
/**
|
|
141
|
+
* Register a handler for plugin→host chrome requests (toast, confirm, etc).
|
|
142
|
+
* Called by `requestChrome` and by `simulateChromeRequest`.
|
|
143
|
+
*/
|
|
144
|
+
onChromeRequest(handler: ChromeRequestHandler): void;
|
|
145
|
+
/**
|
|
146
|
+
* Programmatically send a chrome request as if a plugin component called
|
|
147
|
+
* `PortBridgeClient.requestChrome(...)`. Useful for test assertions.
|
|
148
|
+
*/
|
|
149
|
+
simulateChromeRequest(action: string, payload: Record<string, unknown>): Promise<unknown>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Mock host for plugins that use bridge hooks (`useBridgeTheme`,
|
|
154
|
+
* `useBridgeLocale`, `useBridgeA11y`, etc.) during local-dev or contract
|
|
155
|
+
* testing.
|
|
156
|
+
*
|
|
157
|
+
* Wraps {@link DeclarativeMockHost} and wires a {@link BridgeClientContext}
|
|
158
|
+
* provider so any descendant bridge hook resolves against the supplied
|
|
159
|
+
* `bridgeTransport` instead of throwing "no bridge client available".
|
|
160
|
+
*
|
|
161
|
+
* For tests where bridge state must be driven externally (push a new theme,
|
|
162
|
+
* assert that a component re-renders), pass an {@link InMemoryBridgeTransport}
|
|
163
|
+
* instance and call `transport.pushTheme(...)` wrapped in `act()`.
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
interface WorkerMockHostProps extends DeclarativeMockHostProps {
|
|
167
|
+
/**
|
|
168
|
+
* The bridge transport to wire into context. Pass an
|
|
169
|
+
* {@link InMemoryBridgeTransport} for tests; pass a
|
|
170
|
+
* `createPortBridgeClient(port)` instance for postMessage integration tests.
|
|
171
|
+
*/
|
|
172
|
+
bridgeTransport: PortBridgeClient;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Mock host that combines the SDUI declarative pipeline with bridge context.
|
|
176
|
+
*
|
|
177
|
+
* Rendering contract (same as DeclarativeMockHost):
|
|
178
|
+
* - `defaultResourceUri` present in `resources` → renders the SDUI tree.
|
|
179
|
+
* - URI absent → renders `children` instead (tool-invocation stubs, etc).
|
|
180
|
+
*
|
|
181
|
+
* Bridge contract:
|
|
182
|
+
* - All `useBridgeTheme`, `useBridgeLocale`, etc. hooks in the subtree resolve
|
|
183
|
+
* against `bridgeTransport`.
|
|
184
|
+
*/
|
|
185
|
+
declare function WorkerMockHost(props: WorkerMockHostProps): ReactElement;
|
|
186
|
+
|
|
187
|
+
export { DeclarativeMockHost, type DeclarativeMockHostProps, InMemoryBridgeTransport, InMemoryMcpTransport, type MockToolHandler, WorkerMockHost, type WorkerMockHostProps };
|
package/dist/mock-host/index.js
CHANGED
|
@@ -66,6 +66,88 @@ function DeclarativeMockHost(props) {
|
|
|
66
66
|
] });
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// src/mock-host/InMemoryBridgeTransport.ts
|
|
70
|
+
var InMemoryBridgeTransport = class {
|
|
71
|
+
_themeCb;
|
|
72
|
+
_localeCb;
|
|
73
|
+
_densityCb;
|
|
74
|
+
_a11yCb;
|
|
75
|
+
_navCb;
|
|
76
|
+
_tokenCb;
|
|
77
|
+
_chromeHandler;
|
|
78
|
+
// ── PortBridgeClient subscriber interface ──────────────────────────────────
|
|
79
|
+
onTheme(cb) {
|
|
80
|
+
this._themeCb = cb;
|
|
81
|
+
}
|
|
82
|
+
onLocale(cb) {
|
|
83
|
+
this._localeCb = cb;
|
|
84
|
+
}
|
|
85
|
+
onDensity(cb) {
|
|
86
|
+
this._densityCb = cb;
|
|
87
|
+
}
|
|
88
|
+
onA11y(cb) {
|
|
89
|
+
this._a11yCb = cb;
|
|
90
|
+
}
|
|
91
|
+
onNav(cb) {
|
|
92
|
+
this._navCb = cb;
|
|
93
|
+
}
|
|
94
|
+
onSessionToken(cb) {
|
|
95
|
+
this._tokenCb = cb;
|
|
96
|
+
}
|
|
97
|
+
requestChrome(action, payload) {
|
|
98
|
+
return this.simulateChromeRequest(action, payload);
|
|
99
|
+
}
|
|
100
|
+
announceA11y(_message, _politeness) {
|
|
101
|
+
}
|
|
102
|
+
// ── Test / dev control surface ─────────────────────────────────────────────
|
|
103
|
+
/** Push a theme update to the registered subscriber (synchronous). */
|
|
104
|
+
pushTheme(payload) {
|
|
105
|
+
this._themeCb?.(payload);
|
|
106
|
+
}
|
|
107
|
+
/** Push a locale update to the registered subscriber. */
|
|
108
|
+
pushLocale(payload) {
|
|
109
|
+
this._localeCb?.(payload);
|
|
110
|
+
}
|
|
111
|
+
/** Push a density update. */
|
|
112
|
+
pushDensity(payload) {
|
|
113
|
+
this._densityCb?.(payload);
|
|
114
|
+
}
|
|
115
|
+
/** Push a11y preference changes. */
|
|
116
|
+
pushA11y(payload) {
|
|
117
|
+
this._a11yCb?.(payload);
|
|
118
|
+
}
|
|
119
|
+
/** Push a nav state update. */
|
|
120
|
+
pushNav(payload) {
|
|
121
|
+
this._navCb?.(payload);
|
|
122
|
+
}
|
|
123
|
+
/** Push a frontend-session token. */
|
|
124
|
+
pushSessionToken(payload) {
|
|
125
|
+
this._tokenCb?.(payload);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Register a handler for plugin→host chrome requests (toast, confirm, etc).
|
|
129
|
+
* Called by `requestChrome` and by `simulateChromeRequest`.
|
|
130
|
+
*/
|
|
131
|
+
onChromeRequest(handler) {
|
|
132
|
+
this._chromeHandler = handler;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Programmatically send a chrome request as if a plugin component called
|
|
136
|
+
* `PortBridgeClient.requestChrome(...)`. Useful for test assertions.
|
|
137
|
+
*/
|
|
138
|
+
async simulateChromeRequest(action, payload) {
|
|
139
|
+
if (this._chromeHandler === void 0) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return this._chromeHandler(action, payload);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var BridgeClientContext = createContext(null);
|
|
146
|
+
function WorkerMockHost(props) {
|
|
147
|
+
const { bridgeTransport, ...declarativeProps } = props;
|
|
148
|
+
return /* @__PURE__ */ jsx(BridgeClientContext.Provider, { value: bridgeTransport, children: /* @__PURE__ */ jsx(DeclarativeMockHost, { ...declarativeProps }) });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { DeclarativeMockHost, InMemoryBridgeTransport, InMemoryMcpTransport, WorkerMockHost };
|
|
70
152
|
//# sourceMappingURL=index.js.map
|
|
71
153
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/host/declarative/interpreter.ts","../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/mock-host/InMemoryMcpTransport.ts","../../src/mock-host/DeclarativeMockHost.tsx"],"names":["useMemo"],"mappings":"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","file":"index.js","sourcesContent":["import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"./registry\";\nimport type { SduiNode } from \"./types\";\n\n/**\n * Walk a parsed SDUI tree and render it as a React element by looking up each\n * node's `type` in the supplied {@link ComponentRegistry}.\n *\n * The interpreter is purely structural:\n *\n * - It owns no UI styling, layout, or data fetching.\n * - It never reads `node.props` — props are forwarded opaquely to the host\n * component, which owns interpretation per primitive.\n * - It does not evaluate `node.bindings` — reactive rules are handled in a\n * separate task (E2.S2). For v1 the interpreter passes through the static\n * tree only.\n *\n * Each child is given a stable React `key` derived from its position so that\n * React's reconciler can identify list items across renders. The key is a\n * sibling-local index; the registry consumer is responsible for opting into a\n * stable identity if it has a domain-meaningful `props.key`.\n *\n * @throws Error when `node.type` is not present in the registry. This is the\n * fail-loud behaviour required by the closed v1 vocabulary — unknown\n * primitives must not silently degrade.\n */\nexport function interpret(node: SduiNode, registry: ComponentRegistry): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, index) => interpretChild(child, registry, index),\n );\n\n return createElement(Component, { props: node.props }, children);\n}\n\nfunction interpretChild(node: SduiNode, registry: ComponentRegistry, index: number): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, childIndex) => interpretChild(child, registry, childIndex),\n );\n\n return createElement(Component, { props: node.props, key: index }, children);\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import type { McpTransport } from \"../plugin/transport\";\nimport type { SduiNode } from \"../host/declarative/types\";\n\n/**\n * Handler for a mocked tool invocation. The handler receives the request\n * payload supplied by the caller and may return synchronously or\n * asynchronously. The result is forwarded verbatim through\n * {@link InMemoryMcpTransport.invokeTool}.\n */\nexport type MockToolHandler = (args: unknown) => Promise<unknown> | unknown;\n\n/**\n * An in-memory {@link McpTransport} backed by a `{ resources, tools }` map.\n *\n * Used by {@link DeclarativeMockHost} so plugin authors can run their app\n * standalone for local development without a real host. The transport mirrors\n * the runtime contract exactly:\n *\n * - `getResource(uri)` resolves a `SduiNode` keyed by URI, or rejects with a\n * descriptive error if the URI is not registered.\n * - `invokeTool(name, args)` dispatches to a synchronous or async handler,\n * or rejects if the tool name is unknown.\n *\n * The transport intentionally does NOT honour the supplied `AbortSignal` —\n * mock handlers are synchronous from the caller's perspective and there is\n * no in-flight network call to abort. Hooks still work correctly because\n * they treat the `AbortSignal` as a one-way notification, not a contract.\n */\nexport class InMemoryMcpTransport implements McpTransport\n{\n private readonly resources: Record<string, SduiNode>;\n private readonly tools: Record<string, MockToolHandler>;\n\n public constructor(\n resources: Record<string, SduiNode>,\n tools: Record<string, MockToolHandler> = {},\n )\n {\n this.resources = resources;\n this.tools = tools;\n }\n\n public async getResource<T>(uri: string): Promise<{ uri: string; data: T }>\n {\n const data = this.resources[uri];\n if (data === undefined)\n {\n throw new Error(`Mock resource not found: ${uri}`);\n }\n return { uri, data: data as unknown as T };\n }\n\n public async invokeTool<TReq, TRes>(name: string, args: TReq): Promise<TRes>\n {\n const tool = this.tools[name];\n if (!tool)\n {\n throw new Error(`Mock tool not found: ${name}`);\n }\n const result = await tool(args);\n return result as TRes;\n }\n}\n","import { useMemo, type ReactElement, type ReactNode } from \"react\";\nimport { interpret } from \"../host/declarative/interpreter\";\nimport type { ComponentRegistry } from \"../host/declarative/registry\";\nimport type { SduiNode } from \"../host/declarative/types\";\nimport { ExtensionRuntimeProvider } from \"../plugin/ExtensionRuntimeProvider\";\nimport { InMemoryMcpTransport, type MockToolHandler } from \"./InMemoryMcpTransport\";\n\n/**\n * Props for {@link DeclarativeMockHost}.\n *\n * Plugin authors `npm link` the runtime and render `<DeclarativeMockHost>` in\n * their local dev app to exercise the same declarative pipeline the real host\n * uses, but backed by in-memory fakes instead of the platform.\n */\nexport interface DeclarativeMockHostProps\n{\n /**\n * In-memory resource map. Keys are MCP resource URIs; values are SDUI trees\n * that {@link interpret} will render against the supplied registry.\n */\n resources: Record<string, SduiNode>;\n\n /**\n * In-memory tool map. Keys are MCP tool names; values are handlers invoked\n * when a child component calls `useMcpTool(name).invoke(args)`.\n *\n * Handlers may be sync or async — the transport awaits the result before\n * forwarding it to the caller.\n */\n tools?: Record<string, MockToolHandler>;\n\n /**\n * URI of the resource rendered as the host's default tree. If the URI is\n * not present in `resources` the host renders the supplied `children`\n * instead — useful for stubs that exercise only tool invocations.\n */\n defaultResourceUri: string;\n\n /**\n * The same primitive → component registry the real host uses. Passed\n * verbatim to {@link interpret}; the mock host owns no UI of its own.\n */\n registry: ComponentRegistry;\n\n /**\n * Optional fallback content rendered when `defaultResourceUri` does not\n * resolve to a registered resource. Children also have access to the wired\n * transport via {@link ExtensionRuntimeProvider}, so they can invoke\n * mocked tools and resources directly through the React hooks.\n */\n children?: ReactNode;\n}\n\n/**\n * In-memory host for declarative (Contract A) plugin local-dev.\n *\n * Renders a plugin's SDUI resource against the supplied registry and wires an\n * {@link InMemoryMcpTransport} into context so descendant components that use\n * `useMcpResource` / `useMcpTool` resolve against the same fakes.\n *\n * The host is intentionally minimal: it does not simulate permissions, theme\n * propagation, or capability tokens. Its purpose is to exercise the\n * declarative pipeline end-to-end against deterministic in-memory data so\n * plugin authors can iterate without standing up the real platform.\n */\nexport function DeclarativeMockHost(props: DeclarativeMockHostProps): ReactElement\n{\n const { resources, tools, defaultResourceUri, registry, children } = props;\n\n // Memoise the transport so React doesn't churn the context identity every\n // render — re-rendering this host with stable inputs must not abort\n // in-flight hook calls.\n const transport = useMemo(\n () => new InMemoryMcpTransport(resources, tools ?? {}),\n [resources, tools],\n );\n\n const tree = resources[defaultResourceUri];\n const renderedTree: ReactNode = tree ? interpret(tree, registry) : children;\n\n return (\n <ExtensionRuntimeProvider transport={transport}>\n {renderedTree}\n {tree ? children : null}\n </ExtensionRuntimeProvider>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/host/declarative/interpreter.ts","../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/mock-host/InMemoryMcpTransport.ts","../../src/mock-host/DeclarativeMockHost.tsx","../../src/mock-host/InMemoryBridgeTransport.ts","../../src/plugin/BridgeClientContext.ts","../../src/mock-host/WorkerMockHost.tsx"],"names":["useMemo","createContext","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"./registry\";\nimport type { SduiNode } from \"./types\";\n\n/**\n * Walk a parsed SDUI tree and render it as a React element by looking up each\n * node's `type` in the supplied {@link ComponentRegistry}.\n *\n * The interpreter is purely structural:\n *\n * - It owns no UI styling, layout, or data fetching.\n * - It never reads `node.props` — props are forwarded opaquely to the host\n * component, which owns interpretation per primitive.\n * - It does not evaluate `node.bindings` — reactive rules are handled in a\n * separate task (E2.S2). For v1 the interpreter passes through the static\n * tree only.\n *\n * Each child is given a stable React `key` derived from its position so that\n * React's reconciler can identify list items across renders. The key is a\n * sibling-local index; the registry consumer is responsible for opting into a\n * stable identity if it has a domain-meaningful `props.key`.\n *\n * @throws Error when `node.type` is not present in the registry. This is the\n * fail-loud behaviour required by the closed v1 vocabulary — unknown\n * primitives must not silently degrade.\n */\nexport function interpret(node: SduiNode, registry: ComponentRegistry): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, index) => interpretChild(child, registry, index),\n );\n\n return createElement(Component, { props: node.props }, children);\n}\n\nfunction interpretChild(node: SduiNode, registry: ComponentRegistry, index: number): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, childIndex) => interpretChild(child, registry, childIndex),\n );\n\n return createElement(Component, { props: node.props, key: index }, children);\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import type { McpTransport } from \"../plugin/transport\";\nimport type { SduiNode } from \"../host/declarative/types\";\n\n/**\n * Handler for a mocked tool invocation. The handler receives the request\n * payload supplied by the caller and may return synchronously or\n * asynchronously. The result is forwarded verbatim through\n * {@link InMemoryMcpTransport.invokeTool}.\n */\nexport type MockToolHandler = (args: unknown) => Promise<unknown> | unknown;\n\n/**\n * An in-memory {@link McpTransport} backed by a `{ resources, tools }` map.\n *\n * Used by {@link DeclarativeMockHost} so plugin authors can run their app\n * standalone for local development without a real host. The transport mirrors\n * the runtime contract exactly:\n *\n * - `getResource(uri)` resolves a `SduiNode` keyed by URI, or rejects with a\n * descriptive error if the URI is not registered.\n * - `invokeTool(name, args)` dispatches to a synchronous or async handler,\n * or rejects if the tool name is unknown.\n *\n * The transport intentionally does NOT honour the supplied `AbortSignal` —\n * mock handlers are synchronous from the caller's perspective and there is\n * no in-flight network call to abort. Hooks still work correctly because\n * they treat the `AbortSignal` as a one-way notification, not a contract.\n */\nexport class InMemoryMcpTransport implements McpTransport\n{\n private readonly resources: Record<string, SduiNode>;\n private readonly tools: Record<string, MockToolHandler>;\n\n public constructor(\n resources: Record<string, SduiNode>,\n tools: Record<string, MockToolHandler> = {},\n )\n {\n this.resources = resources;\n this.tools = tools;\n }\n\n public async getResource<T>(uri: string): Promise<{ uri: string; data: T }>\n {\n const data = this.resources[uri];\n if (data === undefined)\n {\n throw new Error(`Mock resource not found: ${uri}`);\n }\n return { uri, data: data as unknown as T };\n }\n\n public async invokeTool<TReq, TRes>(name: string, args: TReq): Promise<TRes>\n {\n const tool = this.tools[name];\n if (!tool)\n {\n throw new Error(`Mock tool not found: ${name}`);\n }\n const result = await tool(args);\n return result as TRes;\n }\n}\n","import { useMemo, type ReactElement, type ReactNode } from \"react\";\nimport { interpret } from \"../host/declarative/interpreter\";\nimport type { ComponentRegistry } from \"../host/declarative/registry\";\nimport type { SduiNode } from \"../host/declarative/types\";\nimport { ExtensionRuntimeProvider } from \"../plugin/ExtensionRuntimeProvider\";\nimport { InMemoryMcpTransport, type MockToolHandler } from \"./InMemoryMcpTransport\";\n\n/**\n * Props for {@link DeclarativeMockHost}.\n *\n * Plugin authors `npm link` the runtime and render `<DeclarativeMockHost>` in\n * their local dev app to exercise the same declarative pipeline the real host\n * uses, but backed by in-memory fakes instead of the platform.\n */\nexport interface DeclarativeMockHostProps\n{\n /**\n * In-memory resource map. Keys are MCP resource URIs; values are SDUI trees\n * that {@link interpret} will render against the supplied registry.\n */\n resources: Record<string, SduiNode>;\n\n /**\n * In-memory tool map. Keys are MCP tool names; values are handlers invoked\n * when a child component calls `useMcpTool(name).invoke(args)`.\n *\n * Handlers may be sync or async — the transport awaits the result before\n * forwarding it to the caller.\n */\n tools?: Record<string, MockToolHandler>;\n\n /**\n * URI of the resource rendered as the host's default tree. If the URI is\n * not present in `resources` the host renders the supplied `children`\n * instead — useful for stubs that exercise only tool invocations.\n */\n defaultResourceUri: string;\n\n /**\n * The same primitive → component registry the real host uses. Passed\n * verbatim to {@link interpret}; the mock host owns no UI of its own.\n */\n registry: ComponentRegistry;\n\n /**\n * Optional fallback content rendered when `defaultResourceUri` does not\n * resolve to a registered resource. Children also have access to the wired\n * transport via {@link ExtensionRuntimeProvider}, so they can invoke\n * mocked tools and resources directly through the React hooks.\n */\n children?: ReactNode;\n}\n\n/**\n * In-memory host for declarative (Contract A) plugin local-dev.\n *\n * Renders a plugin's SDUI resource against the supplied registry and wires an\n * {@link InMemoryMcpTransport} into context so descendant components that use\n * `useMcpResource` / `useMcpTool` resolve against the same fakes.\n *\n * The host is intentionally minimal: it does not simulate permissions, theme\n * propagation, or capability tokens. Its purpose is to exercise the\n * declarative pipeline end-to-end against deterministic in-memory data so\n * plugin authors can iterate without standing up the real platform.\n */\nexport function DeclarativeMockHost(props: DeclarativeMockHostProps): ReactElement\n{\n const { resources, tools, defaultResourceUri, registry, children } = props;\n\n // Memoise the transport so React doesn't churn the context identity every\n // render — re-rendering this host with stable inputs must not abort\n // in-flight hook calls.\n const transport = useMemo(\n () => new InMemoryMcpTransport(resources, tools ?? {}),\n [resources, tools],\n );\n\n const tree = resources[defaultResourceUri];\n const renderedTree: ReactNode = tree ? interpret(tree, registry) : children;\n\n return (\n <ExtensionRuntimeProvider transport={transport}>\n {renderedTree}\n {tree ? children : null}\n </ExtensionRuntimeProvider>\n );\n}\n","/**\n * In-realm bridge transport for plugin local-dev and contract testing.\n *\n * Calling `pushTheme(...)`, `pushLocale(...)`, etc. invokes the registered\n * subscriber callbacks **synchronously** — no serialisation, no port. This\n * lets Vitest + React Testing Library drive bridge state changes with `act()`\n * without a real MessageChannel.\n *\n * In production, the bridge client is `createPortBridgeClient` backed by a\n * real MessagePort. `InMemoryBridgeTransport` is the dev/test equivalent:\n * both expose the same `PortBridgeClient`-compatible subscriber API on the\n * consumer side, but `InMemoryBridgeTransport` also exposes the push-side\n * and the `onChromeRequest` handler for test assertions.\n */\nimport type { PortBridgeClient, ThemePayload, LocalePayload, DensityPayload, A11yPayload, NavPayload, SessionTokenPayload } from \"../plugin/bridge-client\";\n\ntype ChromeRequestHandler = (\n action: string,\n payload: Record<string, unknown>,\n) => Promise<unknown> | unknown;\n\nexport class InMemoryBridgeTransport implements PortBridgeClient {\n private _themeCb: ((p: ThemePayload) => void) | undefined;\n private _localeCb: ((p: LocalePayload) => void) | undefined;\n private _densityCb: ((p: DensityPayload) => void) | undefined;\n private _a11yCb: ((p: A11yPayload) => void) | undefined;\n private _navCb: ((p: NavPayload) => void) | undefined;\n private _tokenCb: ((p: SessionTokenPayload) => void) | undefined;\n private _chromeHandler: ChromeRequestHandler | undefined;\n\n // ── PortBridgeClient subscriber interface ──────────────────────────────────\n\n onTheme(cb: (p: ThemePayload) => void): void { this._themeCb = cb; }\n onLocale(cb: (p: LocalePayload) => void): void { this._localeCb = cb; }\n onDensity(cb: (p: DensityPayload) => void): void { this._densityCb = cb; }\n onA11y(cb: (p: A11yPayload) => void): void { this._a11yCb = cb; }\n onNav(cb: (p: NavPayload) => void): void { this._navCb = cb; }\n onSessionToken(cb: (p: SessionTokenPayload) => void): void { this._tokenCb = cb; }\n\n requestChrome(action: string, payload: Record<string, unknown>): Promise<unknown> {\n return this.simulateChromeRequest(action, payload);\n }\n\n announceA11y(_message: string, _politeness: \"polite\" | \"assertive\"): void {\n // No-op in the mock — tests assert via `onChromeRequest` or inspect DOM.\n }\n\n // ── Test / dev control surface ─────────────────────────────────────────────\n\n /** Push a theme update to the registered subscriber (synchronous). */\n pushTheme(payload: ThemePayload): void { this._themeCb?.(payload); }\n /** Push a locale update to the registered subscriber. */\n pushLocale(payload: LocalePayload): void { this._localeCb?.(payload); }\n /** Push a density update. */\n pushDensity(payload: DensityPayload): void { this._densityCb?.(payload); }\n /** Push a11y preference changes. */\n pushA11y(payload: A11yPayload): void { this._a11yCb?.(payload); }\n /** Push a nav state update. */\n pushNav(payload: NavPayload): void { this._navCb?.(payload); }\n /** Push a frontend-session token. */\n pushSessionToken(payload: SessionTokenPayload): void { this._tokenCb?.(payload); }\n\n /**\n * Register a handler for plugin→host chrome requests (toast, confirm, etc).\n * Called by `requestChrome` and by `simulateChromeRequest`.\n */\n onChromeRequest(handler: ChromeRequestHandler): void {\n this._chromeHandler = handler;\n }\n\n /**\n * Programmatically send a chrome request as if a plugin component called\n * `PortBridgeClient.requestChrome(...)`. Useful for test assertions.\n */\n async simulateChromeRequest(action: string, payload: Record<string, unknown>): Promise<unknown> {\n if (this._chromeHandler === undefined) {\n return null;\n }\n return this._chromeHandler(action, payload);\n }\n}\n","import { createContext, useContext } from \"react\";\nimport type { PortBridgeClient } from \"./bridge-client\";\n\n/**\n * React context carrying the plugin's active {@link PortBridgeClient}.\n * Provided by the host mount (WorkerMockHost in dev, real bridge in production)\n * and consumed by `useBridgeTheme`, `useBridgeLocale`, and the `plugin-ui` hooks.\n */\nexport const BridgeClientContext = createContext<PortBridgeClient | null>(null);\n\n/**\n * Returns the bridge client from context, throwing a clear error when missing.\n * Used by the `useBridge*` hooks to fail loudly on misconfiguration.\n */\nexport function useBridgeClient(): PortBridgeClient {\n const client = useContext(BridgeClientContext);\n if (client === null) {\n throw new Error(\n \"No PortBridgeClient available. Wrap your plugin in <BridgeClientProvider> \"\n + \"or ensure the host mount wires a BridgeClientContext.Provider.\",\n );\n }\n return client;\n}\n","/**\n * Mock host for plugins that use bridge hooks (`useBridgeTheme`,\n * `useBridgeLocale`, `useBridgeA11y`, etc.) during local-dev or contract\n * testing.\n *\n * Wraps {@link DeclarativeMockHost} and wires a {@link BridgeClientContext}\n * provider so any descendant bridge hook resolves against the supplied\n * `bridgeTransport` instead of throwing \"no bridge client available\".\n *\n * For tests where bridge state must be driven externally (push a new theme,\n * assert that a component re-renders), pass an {@link InMemoryBridgeTransport}\n * instance and call `transport.pushTheme(...)` wrapped in `act()`.\n */\nimport { type ReactElement } from \"react\";\nimport { DeclarativeMockHost, type DeclarativeMockHostProps } from \"./DeclarativeMockHost\";\nimport { BridgeClientContext } from \"../plugin/BridgeClientContext\";\nimport type { PortBridgeClient } from \"../plugin/bridge-client\";\n\nexport interface WorkerMockHostProps extends DeclarativeMockHostProps {\n /**\n * The bridge transport to wire into context. Pass an\n * {@link InMemoryBridgeTransport} for tests; pass a\n * `createPortBridgeClient(port)` instance for postMessage integration tests.\n */\n bridgeTransport: PortBridgeClient;\n}\n\n/**\n * Mock host that combines the SDUI declarative pipeline with bridge context.\n *\n * Rendering contract (same as DeclarativeMockHost):\n * - `defaultResourceUri` present in `resources` → renders the SDUI tree.\n * - URI absent → renders `children` instead (tool-invocation stubs, etc).\n *\n * Bridge contract:\n * - All `useBridgeTheme`, `useBridgeLocale`, etc. hooks in the subtree resolve\n * against `bridgeTransport`.\n */\nexport function WorkerMockHost(props: WorkerMockHostProps): ReactElement {\n const { bridgeTransport, ...declarativeProps } = props;\n\n return (\n <BridgeClientContext.Provider value={bridgeTransport}>\n <DeclarativeMockHost {...declarativeProps} />\n </BridgeClientContext.Provider>\n );\n}\n"]}
|