@seed-ship/mcp-ui-solid 6.4.0 → 6.5.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/CHANGELOG.md +100 -0
- package/dist/components/UIResourceRenderer.cjs +40 -10
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts +20 -0
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +42 -12
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/duplicate-mount-registry.cjs +27 -0
- package/dist/utils/duplicate-mount-registry.cjs.map +1 -0
- package/dist/utils/duplicate-mount-registry.d.ts +84 -0
- package/dist/utils/duplicate-mount-registry.d.ts.map +1 -0
- package/dist/utils/duplicate-mount-registry.js +27 -0
- package/dist/utils/duplicate-mount-registry.js.map +1 -0
- package/dist/utils/stable-key.cjs +41 -0
- package/dist/utils/stable-key.cjs.map +1 -0
- package/dist/utils/stable-key.d.ts +33 -0
- package/dist/utils/stable-key.d.ts.map +1 -0
- package/dist/utils/stable-key.js +41 -0
- package/dist/utils/stable-key.js.map +1 -0
- package/package.json +1 -1
- package/src/components/UIResourceRenderer.identity.test.tsx +161 -0
- package/src/components/UIResourceRenderer.tsx +63 -2
- package/src/index.ts +8 -0
- package/src/utils/duplicate-mount-registry.test.ts +82 -0
- package/src/utils/duplicate-mount-registry.ts +113 -0
- package/src/utils/stable-key.test.ts +96 -0
- package/src/utils/stable-key.ts +91 -0
- package/tsconfig.tsbuildinfo +1 -1
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,8 @@ const AutocompleteFormField = require("./components/AutocompleteFormField.cjs");
|
|
|
41
41
|
const GraphRenderer = require("./components/GraphRenderer.cjs");
|
|
42
42
|
const logger = require("./utils/logger.cjs");
|
|
43
43
|
const perf = require("./utils/perf.cjs");
|
|
44
|
+
const stableKey = require("./utils/stable-key.cjs");
|
|
45
|
+
const duplicateMountRegistry = require("./utils/duplicate-mount-registry.cjs");
|
|
44
46
|
const MCPUITelemetryContext = require("./context/MCPUITelemetryContext.cjs");
|
|
45
47
|
const telemetry = require("./services/telemetry.cjs");
|
|
46
48
|
const useStreamingUI = require("./hooks/useStreamingUI.cjs");
|
|
@@ -110,6 +112,8 @@ exports.setDebugMode = logger.setDebugMode;
|
|
|
110
112
|
exports.PERF_PREFIX = perf.PERF_PREFIX;
|
|
111
113
|
exports.markRenderEnd = perf.markRenderEnd;
|
|
112
114
|
exports.markRenderStart = perf.markRenderStart;
|
|
115
|
+
exports.getUiResourceStableKey = stableKey.getUiResourceStableKey;
|
|
116
|
+
exports.setDuplicateMountReporter = duplicateMountRegistry.setDuplicateMountReporter;
|
|
113
117
|
exports.MCPUITelemetryContext = MCPUITelemetryContext.MCPUITelemetryContext;
|
|
114
118
|
exports.MCPUITelemetryProvider = MCPUITelemetryContext.MCPUITelemetryProvider;
|
|
115
119
|
exports.useTelemetry = MCPUITelemetryContext.useTelemetry;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.d.cts
CHANGED
|
@@ -61,6 +61,9 @@ export type { CitationEntry } from './types';
|
|
|
61
61
|
export { GraphRenderer, isG6Available, graphToMermaid, graphToJSON } from './components/GraphRenderer';
|
|
62
62
|
export { setDebugMode, isDebugEnabled } from './utils/logger';
|
|
63
63
|
export { markRenderStart, markRenderEnd, PERF_PREFIX } from './utils/perf';
|
|
64
|
+
export { getUiResourceStableKey } from './utils/stable-key';
|
|
65
|
+
export { setDuplicateMountReporter } from './utils/duplicate-mount-registry';
|
|
66
|
+
export type { DuplicateMountInfo, DuplicateMountReporter, } from './utils/duplicate-mount-registry';
|
|
64
67
|
export { MCPUITelemetryProvider, MCPUITelemetryContext, useTelemetry, } from './context/MCPUITelemetryContext';
|
|
65
68
|
export type { MCPUITelemetryProviderProps } from './context/MCPUITelemetryContext';
|
|
66
69
|
export { createTelemetryDispatcher } from './services/telemetry';
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,9 @@ export type { CitationEntry } from './types';
|
|
|
61
61
|
export { GraphRenderer, isG6Available, graphToMermaid, graphToJSON } from './components/GraphRenderer';
|
|
62
62
|
export { setDebugMode, isDebugEnabled } from './utils/logger';
|
|
63
63
|
export { markRenderStart, markRenderEnd, PERF_PREFIX } from './utils/perf';
|
|
64
|
+
export { getUiResourceStableKey } from './utils/stable-key';
|
|
65
|
+
export { setDuplicateMountReporter } from './utils/duplicate-mount-registry';
|
|
66
|
+
export type { DuplicateMountInfo, DuplicateMountReporter, } from './utils/duplicate-mount-registry';
|
|
64
67
|
export { MCPUITelemetryProvider, MCPUITelemetryContext, useTelemetry, } from './context/MCPUITelemetryContext';
|
|
65
68
|
export type { MCPUITelemetryProviderProps } from './context/MCPUITelemetryContext';
|
|
66
69
|
export { createTelemetryDispatcher } from './services/telemetry';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,8 @@ import { AutocompleteFormField } from "./components/AutocompleteFormField.js";
|
|
|
39
39
|
import { GraphRenderer, graphToJSON, graphToMermaid, isG6Available } from "./components/GraphRenderer.js";
|
|
40
40
|
import { isDebugEnabled, setDebugMode } from "./utils/logger.js";
|
|
41
41
|
import { PERF_PREFIX, markRenderEnd, markRenderStart } from "./utils/perf.js";
|
|
42
|
+
import { getUiResourceStableKey } from "./utils/stable-key.js";
|
|
43
|
+
import { setDuplicateMountReporter } from "./utils/duplicate-mount-registry.js";
|
|
42
44
|
import { MCPUITelemetryContext, MCPUITelemetryProvider, useTelemetry } from "./context/MCPUITelemetryContext.js";
|
|
43
45
|
import { createTelemetryDispatcher } from "./services/telemetry.js";
|
|
44
46
|
import { useStreamingUI } from "./hooks/useStreamingUI.js";
|
|
@@ -119,6 +121,7 @@ export {
|
|
|
119
121
|
elicitationToPromptConfig,
|
|
120
122
|
evaluateCondition,
|
|
121
123
|
getIframeSandbox,
|
|
124
|
+
getUiResourceStableKey,
|
|
122
125
|
graphToJSON,
|
|
123
126
|
graphToMermaid,
|
|
124
127
|
isDebugEnabled,
|
|
@@ -128,6 +131,7 @@ export {
|
|
|
128
131
|
mergeScratchpadSections,
|
|
129
132
|
renderCellValue,
|
|
130
133
|
setDebugMode,
|
|
134
|
+
setDuplicateMountReporter,
|
|
131
135
|
setServerCapabilities,
|
|
132
136
|
useAction,
|
|
133
137
|
useAutocomplete,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const registry = /* @__PURE__ */ new Map();
|
|
4
|
+
let moduleReporter = null;
|
|
5
|
+
function setDuplicateMountReporter(reporter) {
|
|
6
|
+
moduleReporter = reporter;
|
|
7
|
+
}
|
|
8
|
+
function getDuplicateMountReporter() {
|
|
9
|
+
return moduleReporter;
|
|
10
|
+
}
|
|
11
|
+
function _registerMount(key) {
|
|
12
|
+
const entry = registry.get(key) ?? { count: 0, firstMountedAt: Date.now() };
|
|
13
|
+
entry.count += 1;
|
|
14
|
+
registry.set(key, entry);
|
|
15
|
+
return { key, count: entry.count, firstMountedAt: entry.firstMountedAt };
|
|
16
|
+
}
|
|
17
|
+
function _unregisterMount(key) {
|
|
18
|
+
const entry = registry.get(key);
|
|
19
|
+
if (!entry) return;
|
|
20
|
+
entry.count -= 1;
|
|
21
|
+
if (entry.count <= 0) registry.delete(key);
|
|
22
|
+
}
|
|
23
|
+
exports._registerMount = _registerMount;
|
|
24
|
+
exports._unregisterMount = _unregisterMount;
|
|
25
|
+
exports.getDuplicateMountReporter = getDuplicateMountReporter;
|
|
26
|
+
exports.setDuplicateMountReporter = setDuplicateMountReporter;
|
|
27
|
+
//# sourceMappingURL=duplicate-mount-registry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-mount-registry.cjs","sources":["../../src/utils/duplicate-mount-registry.ts"],"sourcesContent":["/**\n * Opt-in duplicate-mount registry (v6.5.0).\n *\n * Tracks how many times each `getUiResourceStableKey()` has been mounted\n * concurrently across all `<UIResourceRenderer>` instances. When the same\n * key is mounted more than once, registered reporters fire so consumers\n * can detect double-render bugs in their parent framework.\n *\n * **Opt-in by design** : the registry is always populated (cheap), but\n * notifications only fire when a consumer has wired one of the two opt-in\n * paths :\n * - module-level `setDuplicateMountReporter(fn)` (app-wide telemetry)\n * - per-instance `<UIResourceRenderer onMountDuplicate={fn}>` prop\n *\n * **What this does NOT do** : visual deduplication. The renderer never\n * hides or replaces a duplicate mount automatically — that would mask\n * parent-framework bugs and could remove legitimate co-mounts (e.g. drawer\n * + main panel showing the same card). Consumers who want dedup implement\n * it on top of the reported events.\n */\n\nexport interface DuplicateMountInfo {\n /** Stable key from `getUiResourceStableKey(content)`. */\n key: string\n /**\n * Current concurrent mount count. The reporter fires whenever this\n * crosses 2 (i.e. on the 2nd, 3rd, etc. mount of the same key while\n * earlier mounts are still alive).\n */\n count: number\n /** `Date.now()` of the FIRST mount of this key (telemetry, not identity). */\n firstMountedAt: number\n}\n\nexport type DuplicateMountReporter = (info: DuplicateMountInfo) => void\n\nconst registry = new Map<string, { count: number; firstMountedAt: number }>()\nlet moduleReporter: DuplicateMountReporter | null = null\n\n/**\n * Wire a module-level reporter for duplicate mount events. Pass `null` to\n * unwire. Only one module reporter at a time (replaces any previous one).\n *\n * @example\n * ```ts\n * import { setDuplicateMountReporter } from '@seed-ship/mcp-ui-solid'\n *\n * setDuplicateMountReporter(({ key, count }) => {\n * telemetry.warn('mcp-ui.duplicate-mount', { key, count })\n * })\n * ```\n */\nexport function setDuplicateMountReporter(reporter: DuplicateMountReporter | null): void {\n moduleReporter = reporter\n}\n\n/**\n * Internal — read by `<UIResourceRenderer>` to dispatch on mount. Not part\n * of the public API.\n *\n * @internal\n */\nexport function getDuplicateMountReporter(): DuplicateMountReporter | null {\n return moduleReporter\n}\n\n/**\n * Internal — registers a mount for `key` and returns the resulting state.\n * The caller decides whether to surface a notification based on `count > 1`.\n *\n * @internal\n */\nexport function _registerMount(key: string): DuplicateMountInfo {\n const entry = registry.get(key) ?? { count: 0, firstMountedAt: Date.now() }\n entry.count += 1\n registry.set(key, entry)\n return { key, count: entry.count, firstMountedAt: entry.firstMountedAt }\n}\n\n/**\n * Internal — undoes a prior `_registerMount(key)`. Removes the entry when\n * the count reaches zero so the registry never leaks across mount/unmount\n * cycles of unique keys.\n *\n * @internal\n */\nexport function _unregisterMount(key: string): void {\n const entry = registry.get(key)\n if (!entry) return\n entry.count -= 1\n if (entry.count <= 0) registry.delete(key)\n}\n\n/**\n * Internal — clears the registry and unwires any module reporter. Used by\n * tests to ensure isolation between cases.\n *\n * @internal\n */\nexport function _resetRegistry(): void {\n registry.clear()\n moduleReporter = null\n}\n\n/**\n * Internal — read the current count for a key (0 if not mounted). Useful\n * for tests and for consumers building their own debug overlays.\n *\n * @internal\n */\nexport function _getMountCount(key: string): number {\n return registry.get(key)?.count ?? 0\n}\n"],"names":[],"mappings":";;AAoCA,MAAM,+BAAe,IAAA;AACrB,IAAI,iBAAgD;AAe7C,SAAS,0BAA0B,UAA+C;AACvF,mBAAiB;AACnB;AAQO,SAAS,4BAA2D;AACzE,SAAO;AACT;AAQO,SAAS,eAAe,KAAiC;AAC9D,QAAM,QAAQ,SAAS,IAAI,GAAG,KAAK,EAAE,OAAO,GAAG,gBAAgB,KAAK,IAAA,EAAI;AACxE,QAAM,SAAS;AACf,WAAS,IAAI,KAAK,KAAK;AACvB,SAAO,EAAE,KAAK,OAAO,MAAM,OAAO,gBAAgB,MAAM,eAAA;AAC1D;AASO,SAAS,iBAAiB,KAAmB;AAClD,QAAM,QAAQ,SAAS,IAAI,GAAG;AAC9B,MAAI,CAAC,MAAO;AACZ,QAAM,SAAS;AACf,MAAI,MAAM,SAAS,EAAG,UAAS,OAAO,GAAG;AAC3C;;;;;"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in duplicate-mount registry (v6.5.0).
|
|
3
|
+
*
|
|
4
|
+
* Tracks how many times each `getUiResourceStableKey()` has been mounted
|
|
5
|
+
* concurrently across all `<UIResourceRenderer>` instances. When the same
|
|
6
|
+
* key is mounted more than once, registered reporters fire so consumers
|
|
7
|
+
* can detect double-render bugs in their parent framework.
|
|
8
|
+
*
|
|
9
|
+
* **Opt-in by design** : the registry is always populated (cheap), but
|
|
10
|
+
* notifications only fire when a consumer has wired one of the two opt-in
|
|
11
|
+
* paths :
|
|
12
|
+
* - module-level `setDuplicateMountReporter(fn)` (app-wide telemetry)
|
|
13
|
+
* - per-instance `<UIResourceRenderer onMountDuplicate={fn}>` prop
|
|
14
|
+
*
|
|
15
|
+
* **What this does NOT do** : visual deduplication. The renderer never
|
|
16
|
+
* hides or replaces a duplicate mount automatically — that would mask
|
|
17
|
+
* parent-framework bugs and could remove legitimate co-mounts (e.g. drawer
|
|
18
|
+
* + main panel showing the same card). Consumers who want dedup implement
|
|
19
|
+
* it on top of the reported events.
|
|
20
|
+
*/
|
|
21
|
+
export interface DuplicateMountInfo {
|
|
22
|
+
/** Stable key from `getUiResourceStableKey(content)`. */
|
|
23
|
+
key: string;
|
|
24
|
+
/**
|
|
25
|
+
* Current concurrent mount count. The reporter fires whenever this
|
|
26
|
+
* crosses 2 (i.e. on the 2nd, 3rd, etc. mount of the same key while
|
|
27
|
+
* earlier mounts are still alive).
|
|
28
|
+
*/
|
|
29
|
+
count: number;
|
|
30
|
+
/** `Date.now()` of the FIRST mount of this key (telemetry, not identity). */
|
|
31
|
+
firstMountedAt: number;
|
|
32
|
+
}
|
|
33
|
+
export type DuplicateMountReporter = (info: DuplicateMountInfo) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Wire a module-level reporter for duplicate mount events. Pass `null` to
|
|
36
|
+
* unwire. Only one module reporter at a time (replaces any previous one).
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { setDuplicateMountReporter } from '@seed-ship/mcp-ui-solid'
|
|
41
|
+
*
|
|
42
|
+
* setDuplicateMountReporter(({ key, count }) => {
|
|
43
|
+
* telemetry.warn('mcp-ui.duplicate-mount', { key, count })
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function setDuplicateMountReporter(reporter: DuplicateMountReporter | null): void;
|
|
48
|
+
/**
|
|
49
|
+
* Internal — read by `<UIResourceRenderer>` to dispatch on mount. Not part
|
|
50
|
+
* of the public API.
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export declare function getDuplicateMountReporter(): DuplicateMountReporter | null;
|
|
55
|
+
/**
|
|
56
|
+
* Internal — registers a mount for `key` and returns the resulting state.
|
|
57
|
+
* The caller decides whether to surface a notification based on `count > 1`.
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export declare function _registerMount(key: string): DuplicateMountInfo;
|
|
62
|
+
/**
|
|
63
|
+
* Internal — undoes a prior `_registerMount(key)`. Removes the entry when
|
|
64
|
+
* the count reaches zero so the registry never leaks across mount/unmount
|
|
65
|
+
* cycles of unique keys.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export declare function _unregisterMount(key: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Internal — clears the registry and unwires any module reporter. Used by
|
|
72
|
+
* tests to ensure isolation between cases.
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export declare function _resetRegistry(): void;
|
|
77
|
+
/**
|
|
78
|
+
* Internal — read the current count for a key (0 if not mounted). Useful
|
|
79
|
+
* for tests and for consumers building their own debug overlays.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
export declare function _getMountCount(key: string): number;
|
|
84
|
+
//# sourceMappingURL=duplicate-mount-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-mount-registry.d.ts","sourceRoot":"","sources":["../../src/utils/duplicate-mount-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;AAKvE;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAEvF;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,IAAI,sBAAsB,GAAG,IAAI,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAK9D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAKlD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAGrC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2
|
+
let moduleReporter = null;
|
|
3
|
+
function setDuplicateMountReporter(reporter) {
|
|
4
|
+
moduleReporter = reporter;
|
|
5
|
+
}
|
|
6
|
+
function getDuplicateMountReporter() {
|
|
7
|
+
return moduleReporter;
|
|
8
|
+
}
|
|
9
|
+
function _registerMount(key) {
|
|
10
|
+
const entry = registry.get(key) ?? { count: 0, firstMountedAt: Date.now() };
|
|
11
|
+
entry.count += 1;
|
|
12
|
+
registry.set(key, entry);
|
|
13
|
+
return { key, count: entry.count, firstMountedAt: entry.firstMountedAt };
|
|
14
|
+
}
|
|
15
|
+
function _unregisterMount(key) {
|
|
16
|
+
const entry = registry.get(key);
|
|
17
|
+
if (!entry) return;
|
|
18
|
+
entry.count -= 1;
|
|
19
|
+
if (entry.count <= 0) registry.delete(key);
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
_registerMount,
|
|
23
|
+
_unregisterMount,
|
|
24
|
+
getDuplicateMountReporter,
|
|
25
|
+
setDuplicateMountReporter
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=duplicate-mount-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-mount-registry.js","sources":["../../src/utils/duplicate-mount-registry.ts"],"sourcesContent":["/**\n * Opt-in duplicate-mount registry (v6.5.0).\n *\n * Tracks how many times each `getUiResourceStableKey()` has been mounted\n * concurrently across all `<UIResourceRenderer>` instances. When the same\n * key is mounted more than once, registered reporters fire so consumers\n * can detect double-render bugs in their parent framework.\n *\n * **Opt-in by design** : the registry is always populated (cheap), but\n * notifications only fire when a consumer has wired one of the two opt-in\n * paths :\n * - module-level `setDuplicateMountReporter(fn)` (app-wide telemetry)\n * - per-instance `<UIResourceRenderer onMountDuplicate={fn}>` prop\n *\n * **What this does NOT do** : visual deduplication. The renderer never\n * hides or replaces a duplicate mount automatically — that would mask\n * parent-framework bugs and could remove legitimate co-mounts (e.g. drawer\n * + main panel showing the same card). Consumers who want dedup implement\n * it on top of the reported events.\n */\n\nexport interface DuplicateMountInfo {\n /** Stable key from `getUiResourceStableKey(content)`. */\n key: string\n /**\n * Current concurrent mount count. The reporter fires whenever this\n * crosses 2 (i.e. on the 2nd, 3rd, etc. mount of the same key while\n * earlier mounts are still alive).\n */\n count: number\n /** `Date.now()` of the FIRST mount of this key (telemetry, not identity). */\n firstMountedAt: number\n}\n\nexport type DuplicateMountReporter = (info: DuplicateMountInfo) => void\n\nconst registry = new Map<string, { count: number; firstMountedAt: number }>()\nlet moduleReporter: DuplicateMountReporter | null = null\n\n/**\n * Wire a module-level reporter for duplicate mount events. Pass `null` to\n * unwire. Only one module reporter at a time (replaces any previous one).\n *\n * @example\n * ```ts\n * import { setDuplicateMountReporter } from '@seed-ship/mcp-ui-solid'\n *\n * setDuplicateMountReporter(({ key, count }) => {\n * telemetry.warn('mcp-ui.duplicate-mount', { key, count })\n * })\n * ```\n */\nexport function setDuplicateMountReporter(reporter: DuplicateMountReporter | null): void {\n moduleReporter = reporter\n}\n\n/**\n * Internal — read by `<UIResourceRenderer>` to dispatch on mount. Not part\n * of the public API.\n *\n * @internal\n */\nexport function getDuplicateMountReporter(): DuplicateMountReporter | null {\n return moduleReporter\n}\n\n/**\n * Internal — registers a mount for `key` and returns the resulting state.\n * The caller decides whether to surface a notification based on `count > 1`.\n *\n * @internal\n */\nexport function _registerMount(key: string): DuplicateMountInfo {\n const entry = registry.get(key) ?? { count: 0, firstMountedAt: Date.now() }\n entry.count += 1\n registry.set(key, entry)\n return { key, count: entry.count, firstMountedAt: entry.firstMountedAt }\n}\n\n/**\n * Internal — undoes a prior `_registerMount(key)`. Removes the entry when\n * the count reaches zero so the registry never leaks across mount/unmount\n * cycles of unique keys.\n *\n * @internal\n */\nexport function _unregisterMount(key: string): void {\n const entry = registry.get(key)\n if (!entry) return\n entry.count -= 1\n if (entry.count <= 0) registry.delete(key)\n}\n\n/**\n * Internal — clears the registry and unwires any module reporter. Used by\n * tests to ensure isolation between cases.\n *\n * @internal\n */\nexport function _resetRegistry(): void {\n registry.clear()\n moduleReporter = null\n}\n\n/**\n * Internal — read the current count for a key (0 if not mounted). Useful\n * for tests and for consumers building their own debug overlays.\n *\n * @internal\n */\nexport function _getMountCount(key: string): number {\n return registry.get(key)?.count ?? 0\n}\n"],"names":[],"mappings":"AAoCA,MAAM,+BAAe,IAAA;AACrB,IAAI,iBAAgD;AAe7C,SAAS,0BAA0B,UAA+C;AACvF,mBAAiB;AACnB;AAQO,SAAS,4BAA2D;AACzE,SAAO;AACT;AAQO,SAAS,eAAe,KAAiC;AAC9D,QAAM,QAAQ,SAAS,IAAI,GAAG,KAAK,EAAE,OAAO,GAAG,gBAAgB,KAAK,IAAA,EAAI;AACxE,QAAM,SAAS;AACf,WAAS,IAAI,KAAK,KAAK;AACvB,SAAO,EAAE,KAAK,OAAO,MAAM,OAAO,gBAAgB,MAAM,eAAA;AAC1D;AASO,SAAS,iBAAiB,KAAmB;AAClD,QAAM,QAAQ,SAAS,IAAI,GAAG;AAC9B,MAAI,CAAC,MAAO;AACZ,QAAM,SAAS;AACf,MAAI,MAAM,SAAS,EAAG,UAAS,OAAO,GAAG;AAC3C;"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const FNV_OFFSET_BASIS = 2166136261;
|
|
4
|
+
const FNV_PRIME = 16777619;
|
|
5
|
+
function fnv1a(str) {
|
|
6
|
+
let hash = FNV_OFFSET_BASIS;
|
|
7
|
+
for (let i = 0; i < str.length; i++) {
|
|
8
|
+
hash ^= str.charCodeAt(i);
|
|
9
|
+
hash = Math.imul(hash, FNV_PRIME);
|
|
10
|
+
}
|
|
11
|
+
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
12
|
+
}
|
|
13
|
+
function stableStringify(value) {
|
|
14
|
+
if (value === void 0) return "undefined";
|
|
15
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
18
|
+
}
|
|
19
|
+
const obj = value;
|
|
20
|
+
const keys = Object.keys(obj).sort().filter((k) => obj[k] !== void 0);
|
|
21
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
22
|
+
}
|
|
23
|
+
function normalizeForHash(input) {
|
|
24
|
+
if (!input || typeof input !== "object") return input;
|
|
25
|
+
const { id: _id, ...rest } = input;
|
|
26
|
+
if (rest.metadata && typeof rest.metadata === "object" && !Array.isArray(rest.metadata)) {
|
|
27
|
+
const meta = rest.metadata;
|
|
28
|
+
const { generatedAt: _t, ...metaRest } = meta;
|
|
29
|
+
rest.metadata = Object.keys(metaRest).length > 0 ? metaRest : void 0;
|
|
30
|
+
}
|
|
31
|
+
return rest;
|
|
32
|
+
}
|
|
33
|
+
function getUiResourceStableKey(input) {
|
|
34
|
+
if (input && typeof input === "object") {
|
|
35
|
+
const id = input.id;
|
|
36
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
37
|
+
}
|
|
38
|
+
return fnv1a(stableStringify(normalizeForHash(input)));
|
|
39
|
+
}
|
|
40
|
+
exports.getUiResourceStableKey = getUiResourceStableKey;
|
|
41
|
+
//# sourceMappingURL=stable-key.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stable-key.cjs","sources":["../../src/utils/stable-key.ts"],"sourcesContent":["/**\n * Stable identity key for UIResource payloads (v6.5.0).\n *\n * Consumers need a way to derive a deterministic key from a layout/component\n * payload — for `<For>` keys, dedup detection, telemetry correlation, etc.\n *\n * Spec semantics : `UILayout.id` and `UIComponent.id` are obligatoires for\n * any well-formed payload. When they are present and non-empty, this helper\n * returns them as-is. When they are missing (e.g. consumer passing a \"bare\"\n * chart payload `{ type: 'chart', params: {...} }` without wrapping it in\n * a layout), the helper derives a stable key from the *content* — never\n * from a timestamp or counter.\n *\n * The hash is FNV-1a 32-bit on a deterministically stringified form of the\n * payload (sorted keys, undefined entries skipped). This is intentionally\n * synchronous and dependency-free so consumers can call it inside a Solid\n * memo or render function without ceremony.\n */\n\nconst FNV_OFFSET_BASIS = 0x811c9dc5\nconst FNV_PRIME = 0x01000193\n\nfunction fnv1a(str: string): string {\n let hash = FNV_OFFSET_BASIS\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i)\n hash = Math.imul(hash, FNV_PRIME)\n }\n return (hash >>> 0).toString(36).padStart(7, '0')\n}\n\n/**\n * Deterministic JSON-like serialization. Object keys are sorted ; entries\n * with `undefined` values are skipped (mirroring `JSON.stringify` semantics\n * but with a stable order). Used as the input to `fnv1a()`.\n */\nfunction stableStringify(value: unknown): string {\n if (value === undefined) return 'undefined'\n if (value === null || typeof value !== 'object') return JSON.stringify(value)\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']'\n }\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj)\n .sort()\n .filter((k) => obj[k] !== undefined)\n return (\n '{' +\n keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') +\n '}'\n )\n}\n\n/**\n * Strip fields that should NOT contribute to identity :\n * - top-level `id` : we're computing the absent identity\n * - `metadata.generatedAt` : timestamp of generation, not of identity\n */\nfunction normalizeForHash(input: unknown): unknown {\n if (!input || typeof input !== 'object') return input\n const { id: _id, ...rest } = input as Record<string, unknown>\n void _id\n if (rest.metadata && typeof rest.metadata === 'object' && !Array.isArray(rest.metadata)) {\n const meta = rest.metadata as Record<string, unknown>\n const { generatedAt: _t, ...metaRest } = meta\n void _t\n rest.metadata = Object.keys(metaRest).length > 0 ? metaRest : undefined\n }\n return rest\n}\n\n/**\n * Returns a stable identity key for a UIResource payload.\n *\n * - If `input.id` is a non-empty string, returns it verbatim. This is the\n * path taken by well-formed payloads (cf. spec §Identity).\n * - Otherwise, returns a 7-char base36 FNV-1a hash of the normalized\n * content. Stable across renders, identical for structurally identical\n * payloads.\n *\n * The hash is NOT cryptographic ; it's a dedup/correlation key. Collisions\n * are theoretically possible but vanishingly rare for the payload shapes\n * MCP-UI emits in practice.\n */\nexport function getUiResourceStableKey(input: unknown): string {\n if (input && typeof input === 'object') {\n const id = (input as { id?: unknown }).id\n if (typeof id === 'string' && id.length > 0) return id\n }\n return fnv1a(stableStringify(normalizeForHash(input)))\n}\n"],"names":[],"mappings":";;AAmBA,MAAM,mBAAmB;AACzB,MAAM,YAAY;AAElB,SAAS,MAAM,KAAqB;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAQ,IAAI,WAAW,CAAC;AACxB,WAAO,KAAK,KAAK,MAAM,SAAS;AAAA,EAClC;AACA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAOA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC5E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,eAAe,EAAE,KAAK,GAAG,IAAI;AAAA,EACtD;AACA,QAAM,MAAM;AACZ,QAAM,OAAO,OAAO,KAAK,GAAG,EACzB,KAAA,EACA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,MAAS;AACrC,SACE,MACA,KAAK,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,gBAAgB,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAC3E;AAEJ;AAOA,SAAS,iBAAiB,OAAyB;AACjD,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,EAAE,IAAI,KAAK,GAAG,SAAS;AAE7B,MAAI,KAAK,YAAY,OAAO,KAAK,aAAa,YAAY,CAAC,MAAM,QAAQ,KAAK,QAAQ,GAAG;AACvF,UAAM,OAAO,KAAK;AAClB,UAAM,EAAE,aAAa,IAAI,GAAG,aAAa;AAEzC,SAAK,WAAW,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW;AAAA,EAChE;AACA,SAAO;AACT;AAeO,SAAS,uBAAuB,OAAwB;AAC7D,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,KAAM,MAA2B;AACvC,QAAI,OAAO,OAAO,YAAY,GAAG,SAAS,EAAG,QAAO;AAAA,EACtD;AACA,SAAO,MAAM,gBAAgB,iBAAiB,KAAK,CAAC,CAAC;AACvD;;"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable identity key for UIResource payloads (v6.5.0).
|
|
3
|
+
*
|
|
4
|
+
* Consumers need a way to derive a deterministic key from a layout/component
|
|
5
|
+
* payload — for `<For>` keys, dedup detection, telemetry correlation, etc.
|
|
6
|
+
*
|
|
7
|
+
* Spec semantics : `UILayout.id` and `UIComponent.id` are obligatoires for
|
|
8
|
+
* any well-formed payload. When they are present and non-empty, this helper
|
|
9
|
+
* returns them as-is. When they are missing (e.g. consumer passing a "bare"
|
|
10
|
+
* chart payload `{ type: 'chart', params: {...} }` without wrapping it in
|
|
11
|
+
* a layout), the helper derives a stable key from the *content* — never
|
|
12
|
+
* from a timestamp or counter.
|
|
13
|
+
*
|
|
14
|
+
* The hash is FNV-1a 32-bit on a deterministically stringified form of the
|
|
15
|
+
* payload (sorted keys, undefined entries skipped). This is intentionally
|
|
16
|
+
* synchronous and dependency-free so consumers can call it inside a Solid
|
|
17
|
+
* memo or render function without ceremony.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Returns a stable identity key for a UIResource payload.
|
|
21
|
+
*
|
|
22
|
+
* - If `input.id` is a non-empty string, returns it verbatim. This is the
|
|
23
|
+
* path taken by well-formed payloads (cf. spec §Identity).
|
|
24
|
+
* - Otherwise, returns a 7-char base36 FNV-1a hash of the normalized
|
|
25
|
+
* content. Stable across renders, identical for structurally identical
|
|
26
|
+
* payloads.
|
|
27
|
+
*
|
|
28
|
+
* The hash is NOT cryptographic ; it's a dedup/correlation key. Collisions
|
|
29
|
+
* are theoretically possible but vanishingly rare for the payload shapes
|
|
30
|
+
* MCP-UI emits in practice.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getUiResourceStableKey(input: unknown): string;
|
|
33
|
+
//# sourceMappingURL=stable-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stable-key.d.ts","sourceRoot":"","sources":["../../src/utils/stable-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAsDH;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAM7D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const FNV_OFFSET_BASIS = 2166136261;
|
|
2
|
+
const FNV_PRIME = 16777619;
|
|
3
|
+
function fnv1a(str) {
|
|
4
|
+
let hash = FNV_OFFSET_BASIS;
|
|
5
|
+
for (let i = 0; i < str.length; i++) {
|
|
6
|
+
hash ^= str.charCodeAt(i);
|
|
7
|
+
hash = Math.imul(hash, FNV_PRIME);
|
|
8
|
+
}
|
|
9
|
+
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
10
|
+
}
|
|
11
|
+
function stableStringify(value) {
|
|
12
|
+
if (value === void 0) return "undefined";
|
|
13
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
16
|
+
}
|
|
17
|
+
const obj = value;
|
|
18
|
+
const keys = Object.keys(obj).sort().filter((k) => obj[k] !== void 0);
|
|
19
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
20
|
+
}
|
|
21
|
+
function normalizeForHash(input) {
|
|
22
|
+
if (!input || typeof input !== "object") return input;
|
|
23
|
+
const { id: _id, ...rest } = input;
|
|
24
|
+
if (rest.metadata && typeof rest.metadata === "object" && !Array.isArray(rest.metadata)) {
|
|
25
|
+
const meta = rest.metadata;
|
|
26
|
+
const { generatedAt: _t, ...metaRest } = meta;
|
|
27
|
+
rest.metadata = Object.keys(metaRest).length > 0 ? metaRest : void 0;
|
|
28
|
+
}
|
|
29
|
+
return rest;
|
|
30
|
+
}
|
|
31
|
+
function getUiResourceStableKey(input) {
|
|
32
|
+
if (input && typeof input === "object") {
|
|
33
|
+
const id = input.id;
|
|
34
|
+
if (typeof id === "string" && id.length > 0) return id;
|
|
35
|
+
}
|
|
36
|
+
return fnv1a(stableStringify(normalizeForHash(input)));
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
getUiResourceStableKey
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=stable-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stable-key.js","sources":["../../src/utils/stable-key.ts"],"sourcesContent":["/**\n * Stable identity key for UIResource payloads (v6.5.0).\n *\n * Consumers need a way to derive a deterministic key from a layout/component\n * payload — for `<For>` keys, dedup detection, telemetry correlation, etc.\n *\n * Spec semantics : `UILayout.id` and `UIComponent.id` are obligatoires for\n * any well-formed payload. When they are present and non-empty, this helper\n * returns them as-is. When they are missing (e.g. consumer passing a \"bare\"\n * chart payload `{ type: 'chart', params: {...} }` without wrapping it in\n * a layout), the helper derives a stable key from the *content* — never\n * from a timestamp or counter.\n *\n * The hash is FNV-1a 32-bit on a deterministically stringified form of the\n * payload (sorted keys, undefined entries skipped). This is intentionally\n * synchronous and dependency-free so consumers can call it inside a Solid\n * memo or render function without ceremony.\n */\n\nconst FNV_OFFSET_BASIS = 0x811c9dc5\nconst FNV_PRIME = 0x01000193\n\nfunction fnv1a(str: string): string {\n let hash = FNV_OFFSET_BASIS\n for (let i = 0; i < str.length; i++) {\n hash ^= str.charCodeAt(i)\n hash = Math.imul(hash, FNV_PRIME)\n }\n return (hash >>> 0).toString(36).padStart(7, '0')\n}\n\n/**\n * Deterministic JSON-like serialization. Object keys are sorted ; entries\n * with `undefined` values are skipped (mirroring `JSON.stringify` semantics\n * but with a stable order). Used as the input to `fnv1a()`.\n */\nfunction stableStringify(value: unknown): string {\n if (value === undefined) return 'undefined'\n if (value === null || typeof value !== 'object') return JSON.stringify(value)\n if (Array.isArray(value)) {\n return '[' + value.map(stableStringify).join(',') + ']'\n }\n const obj = value as Record<string, unknown>\n const keys = Object.keys(obj)\n .sort()\n .filter((k) => obj[k] !== undefined)\n return (\n '{' +\n keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') +\n '}'\n )\n}\n\n/**\n * Strip fields that should NOT contribute to identity :\n * - top-level `id` : we're computing the absent identity\n * - `metadata.generatedAt` : timestamp of generation, not of identity\n */\nfunction normalizeForHash(input: unknown): unknown {\n if (!input || typeof input !== 'object') return input\n const { id: _id, ...rest } = input as Record<string, unknown>\n void _id\n if (rest.metadata && typeof rest.metadata === 'object' && !Array.isArray(rest.metadata)) {\n const meta = rest.metadata as Record<string, unknown>\n const { generatedAt: _t, ...metaRest } = meta\n void _t\n rest.metadata = Object.keys(metaRest).length > 0 ? metaRest : undefined\n }\n return rest\n}\n\n/**\n * Returns a stable identity key for a UIResource payload.\n *\n * - If `input.id` is a non-empty string, returns it verbatim. This is the\n * path taken by well-formed payloads (cf. spec §Identity).\n * - Otherwise, returns a 7-char base36 FNV-1a hash of the normalized\n * content. Stable across renders, identical for structurally identical\n * payloads.\n *\n * The hash is NOT cryptographic ; it's a dedup/correlation key. Collisions\n * are theoretically possible but vanishingly rare for the payload shapes\n * MCP-UI emits in practice.\n */\nexport function getUiResourceStableKey(input: unknown): string {\n if (input && typeof input === 'object') {\n const id = (input as { id?: unknown }).id\n if (typeof id === 'string' && id.length > 0) return id\n }\n return fnv1a(stableStringify(normalizeForHash(input)))\n}\n"],"names":[],"mappings":"AAmBA,MAAM,mBAAmB;AACzB,MAAM,YAAY;AAElB,SAAS,MAAM,KAAqB;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,YAAQ,IAAI,WAAW,CAAC;AACxB,WAAO,KAAK,KAAK,MAAM,SAAS;AAAA,EAClC;AACA,UAAQ,SAAS,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAClD;AAOA,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK;AAC5E,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,MAAM,IAAI,eAAe,EAAE,KAAK,GAAG,IAAI;AAAA,EACtD;AACA,QAAM,MAAM;AACZ,QAAM,OAAO,OAAO,KAAK,GAAG,EACzB,KAAA,EACA,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,MAAS;AACrC,SACE,MACA,KAAK,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,IAAI,MAAM,gBAAgB,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAC3E;AAEJ;AAOA,SAAS,iBAAiB,OAAyB;AACjD,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,EAAE,IAAI,KAAK,GAAG,SAAS;AAE7B,MAAI,KAAK,YAAY,OAAO,KAAK,aAAa,YAAY,CAAC,MAAM,QAAQ,KAAK,QAAQ,GAAG;AACvF,UAAM,OAAO,KAAK;AAClB,UAAM,EAAE,aAAa,IAAI,GAAG,aAAa;AAEzC,SAAK,WAAW,OAAO,KAAK,QAAQ,EAAE,SAAS,IAAI,WAAW;AAAA,EAChE;AACA,SAAO;AACT;AAeO,SAAS,uBAAuB,OAAwB;AAC7D,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,KAAM,MAA2B;AACvC,QAAI,OAAO,OAAO,YAAY,GAAG,SAAS,EAAG,QAAO;AAAA,EACtD;AACA,SAAO,MAAM,gBAAgB,iBAAiB,KAAK,CAAC,CAAC;AACvD;"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v6.5.0 — Identity stability + opt-in observability for <UIResourceRenderer>.
|
|
3
|
+
*
|
|
4
|
+
* Coverage targets :
|
|
5
|
+
* 1. Layout content gets `data-mcp-ui-layout-id` from layout.id
|
|
6
|
+
* 2. Layout content without id falls back to a content hash
|
|
7
|
+
* 3. Single-component content gets `data-mcp-ui-component-id` (no layout id)
|
|
8
|
+
* 4. Each rendered child carries `data-mcp-ui-component-id`
|
|
9
|
+
* 5. `onMountDuplicate` callback fires on the 2nd concurrent mount
|
|
10
|
+
* 6. Module-level reporter (`setDuplicateMountReporter`) fires on duplicate
|
|
11
|
+
* 7. Single mount fires no duplicate notification
|
|
12
|
+
* 8. Cleanup on unmount allows the same key to be re-mounted without warn
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
16
|
+
import { render, cleanup } from '@solidjs/testing-library'
|
|
17
|
+
import { UIResourceRenderer } from './UIResourceRenderer'
|
|
18
|
+
import {
|
|
19
|
+
setDuplicateMountReporter,
|
|
20
|
+
_resetRegistry,
|
|
21
|
+
_getMountCount,
|
|
22
|
+
} from '../utils/duplicate-mount-registry'
|
|
23
|
+
import { getUiResourceStableKey } from '../utils/stable-key'
|
|
24
|
+
|
|
25
|
+
const SIMPLE_TEXT_COMPONENT = {
|
|
26
|
+
id: 'text-comp-1',
|
|
27
|
+
type: 'text' as const,
|
|
28
|
+
position: { colStart: 1, colSpan: 12 },
|
|
29
|
+
params: { content: 'Hello' },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SIMPLE_LAYOUT = {
|
|
33
|
+
id: 'layout-1',
|
|
34
|
+
components: [SIMPLE_TEXT_COMPONENT],
|
|
35
|
+
grid: { columns: 12, gap: '1rem' },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('UIResourceRenderer identity (v6.5.0)', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
cleanup()
|
|
41
|
+
_resetRegistry()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('layout content emits data-mcp-ui-layout-id from layout.id', () => {
|
|
45
|
+
const { container } = render(() => <UIResourceRenderer content={SIMPLE_LAYOUT} />)
|
|
46
|
+
const wrapper = container.querySelector('[data-mcp-ui-layout-id="layout-1"]')
|
|
47
|
+
expect(wrapper).toBeTruthy()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('layout without id falls back to a content hash', () => {
|
|
51
|
+
const bareLayout = {
|
|
52
|
+
components: [SIMPLE_TEXT_COMPONENT],
|
|
53
|
+
grid: { columns: 12, gap: '1rem' },
|
|
54
|
+
} as any
|
|
55
|
+
const expectedKey = getUiResourceStableKey(bareLayout)
|
|
56
|
+
const { container } = render(() => <UIResourceRenderer content={bareLayout} />)
|
|
57
|
+
const wrapper = container.querySelector(`[data-mcp-ui-layout-id="${expectedKey}"]`)
|
|
58
|
+
expect(wrapper).toBeTruthy()
|
|
59
|
+
// Hash form (FNV-1a base36) is 7 chars
|
|
60
|
+
expect(expectedKey).toMatch(/^[a-z0-9]{7}$/)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('single-component content emits data-mcp-ui-component-id (no layout id)', () => {
|
|
64
|
+
const { container } = render(() => <UIResourceRenderer content={SIMPLE_TEXT_COMPONENT} />)
|
|
65
|
+
expect(container.querySelector('[data-mcp-ui-layout-id]')).toBeNull()
|
|
66
|
+
const wrappers = container.querySelectorAll('[data-mcp-ui-component-id="text-comp-1"]')
|
|
67
|
+
// Outer wrapper + inner per-component wrapper both carry the id
|
|
68
|
+
expect(wrappers.length).toBeGreaterThanOrEqual(1)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('each child component wrapper inside a layout carries data-mcp-ui-component-id', () => {
|
|
72
|
+
const layout = {
|
|
73
|
+
id: 'multi',
|
|
74
|
+
components: [
|
|
75
|
+
{ id: 'comp-a', type: 'text', position: { colStart: 1, colSpan: 6 }, params: { content: 'A' } },
|
|
76
|
+
{ id: 'comp-b', type: 'text', position: { colStart: 7, colSpan: 6 }, params: { content: 'B' } },
|
|
77
|
+
],
|
|
78
|
+
grid: { columns: 12, gap: '1rem' },
|
|
79
|
+
} as any
|
|
80
|
+
const { container } = render(() => <UIResourceRenderer content={layout} />)
|
|
81
|
+
expect(container.querySelector('[data-mcp-ui-component-id="comp-a"]')).toBeTruthy()
|
|
82
|
+
expect(container.querySelector('[data-mcp-ui-component-id="comp-b"]')).toBeTruthy()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('fires onMountDuplicate on the 2nd concurrent mount of the same key', () => {
|
|
86
|
+
const onDup = vi.fn()
|
|
87
|
+
render(() => (
|
|
88
|
+
<>
|
|
89
|
+
<UIResourceRenderer content={SIMPLE_LAYOUT} onMountDuplicate={onDup} />
|
|
90
|
+
<UIResourceRenderer content={SIMPLE_LAYOUT} onMountDuplicate={onDup} />
|
|
91
|
+
</>
|
|
92
|
+
))
|
|
93
|
+
// Only the 2nd mount triggers the callback (count crosses 2)
|
|
94
|
+
expect(onDup).toHaveBeenCalledTimes(1)
|
|
95
|
+
expect(onDup).toHaveBeenCalledWith(
|
|
96
|
+
expect.objectContaining({ key: 'layout-1', count: 2 })
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('does NOT fire onMountDuplicate on a single mount', () => {
|
|
101
|
+
const onDup = vi.fn()
|
|
102
|
+
render(() => <UIResourceRenderer content={SIMPLE_LAYOUT} onMountDuplicate={onDup} />)
|
|
103
|
+
expect(onDup).not.toHaveBeenCalled()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('module-level setDuplicateMountReporter fires on the 2nd mount', () => {
|
|
107
|
+
const reporter = vi.fn()
|
|
108
|
+
setDuplicateMountReporter(reporter)
|
|
109
|
+
render(() => (
|
|
110
|
+
<>
|
|
111
|
+
<UIResourceRenderer content={SIMPLE_LAYOUT} />
|
|
112
|
+
<UIResourceRenderer content={SIMPLE_LAYOUT} />
|
|
113
|
+
</>
|
|
114
|
+
))
|
|
115
|
+
expect(reporter).toHaveBeenCalledTimes(1)
|
|
116
|
+
expect(reporter).toHaveBeenCalledWith(
|
|
117
|
+
expect.objectContaining({ key: 'layout-1', count: 2 })
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('mounts unique-key payloads independently (no false positives)', () => {
|
|
122
|
+
const onDup = vi.fn()
|
|
123
|
+
const reporter = vi.fn()
|
|
124
|
+
setDuplicateMountReporter(reporter)
|
|
125
|
+
const a = { ...SIMPLE_LAYOUT, id: 'layout-A' }
|
|
126
|
+
const b = { ...SIMPLE_LAYOUT, id: 'layout-B' }
|
|
127
|
+
render(() => (
|
|
128
|
+
<>
|
|
129
|
+
<UIResourceRenderer content={a} onMountDuplicate={onDup} />
|
|
130
|
+
<UIResourceRenderer content={b} onMountDuplicate={onDup} />
|
|
131
|
+
</>
|
|
132
|
+
))
|
|
133
|
+
expect(onDup).not.toHaveBeenCalled()
|
|
134
|
+
expect(reporter).not.toHaveBeenCalled()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('cleanup unregisters the mount so the registry never leaks', () => {
|
|
138
|
+
const { unmount } = render(() => <UIResourceRenderer content={SIMPLE_LAYOUT} />)
|
|
139
|
+
expect(_getMountCount('layout-1')).toBe(1)
|
|
140
|
+
unmount()
|
|
141
|
+
expect(_getMountCount('layout-1')).toBe(0)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('debugDuplicateMounts prop forces a console.warn even when global debug off', () => {
|
|
145
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
146
|
+
try {
|
|
147
|
+
render(() => (
|
|
148
|
+
<>
|
|
149
|
+
<UIResourceRenderer content={SIMPLE_LAYOUT} debugDuplicateMounts />
|
|
150
|
+
<UIResourceRenderer content={SIMPLE_LAYOUT} debugDuplicateMounts />
|
|
151
|
+
</>
|
|
152
|
+
))
|
|
153
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
154
|
+
'[mcp-ui] duplicate UIResourceRenderer mount',
|
|
155
|
+
expect.objectContaining({ key: 'layout-1', count: 2 })
|
|
156
|
+
)
|
|
157
|
+
} finally {
|
|
158
|
+
warnSpy.mockRestore()
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
})
|