@seed-ship/mcp-ui-spec 5.0.5 → 5.0.6

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +75 -0
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [5.0.6] - 2026-05-05
9
+
10
+ Documentation-only patch — no schema changes. Closes Demande 1.1 + 1.2 of
11
+ deposium's `BRIEF-MCPUI-2026-05-10.md`.
12
+
13
+ ### Documented — Runtime Payload Identity contract
14
+
15
+ The README now formalizes the identity contract for runtime `UILayout` /
16
+ `UIComponent` payloads (distinct from the registry-side `Component`
17
+ definitions documented previously) :
18
+
19
+ - **`id` is obligatoire** on every well-formed `UILayout` and `UIComponent`,
20
+ and MUST be stable across renders for the same logical content. Producers
21
+ that emit `wrap-${Date.now()}` or `Math.random()` ids are non-conformant.
22
+ - **Bare-payload fallback** : renderers MUST gracefully accept payloads
23
+ without `id` (e.g. a producer emitting a chart config directly) and
24
+ MUST derive a stable key from the content — not from a counter or
25
+ timestamp. The canonical implementation is `getUiResourceStableKey()`,
26
+ shipped in `@seed-ship/mcp-ui-solid@6.5.0`.
27
+
28
+ This codifies behavior that was previously implicit, so host apps and
29
+ producers can rely on it without reading renderer source.
30
+
8
31
  ## [5.0.5] - 2026-05-03
9
32
 
10
33
  ### Added — `TableComponentParamsSchema.maxHeight`
package/README.md CHANGED
@@ -266,6 +266,81 @@ Individual components support semantic versioning:
266
266
  }
267
267
  ```
268
268
 
269
+ ## Runtime Payload Identity
270
+
271
+ > **Note** — this section concerns *runtime* payloads (`UILayout` / `UIComponent`
272
+ > objects emitted by an LLM and passed to a renderer), distinct from the
273
+ > `Component` *registry* definitions documented above. The runtime types live
274
+ > in [`@seed-ship/mcp-ui-solid`](../mcp-ui-solid) but the identity contract
275
+ > below is part of the spec.
276
+
277
+ ### `id` is obligatoire on well-formed payloads
278
+
279
+ Every `UILayout` and every `UIComponent` emitted by a producer **MUST** carry
280
+ a non-empty string `id`. The id is used by renderers as the `<For>` key, by
281
+ host apps for telemetry correlation, and by debug tooling to detect
282
+ double-mounts of the same content.
283
+
284
+ ```ts
285
+ // ✅ Well-formed
286
+ const layout: UILayout = {
287
+ id: 'dashboard-2024-Q3',
288
+ components: [
289
+ { id: 'metric-revenue', type: 'metric', position: {...}, params: {...} },
290
+ { id: 'chart-trend', type: 'chart', position: {...}, params: {...} },
291
+ ],
292
+ grid: { columns: 12, gap: '1rem' },
293
+ }
294
+ ```
295
+
296
+ The id should be **stable across renders for the same logical content** —
297
+ NOT a `Date.now()`-derived counter, NOT a `Math.random()` token, NOT a UUID
298
+ re-generated per render. A producer that cannot provide a stable id should
299
+ either derive one from the content hash or omit the field entirely (see
300
+ fallback policy below).
301
+
302
+ ### Fallback policy for "bare" payloads
303
+
304
+ Renderers MUST gracefully accept payloads without an `id` (e.g. a producer
305
+ emitting a chart config directly, or a host app pre-shaping a third-party
306
+ JSON response). When `id` is missing or empty, the renderer **MUST** derive
307
+ a stable key from the content itself — typically a hash of the normalized
308
+ payload — and **MUST NOT** generate a timestamp- or counter-based identifier
309
+ that would change on every render.
310
+
311
+ `@seed-ship/mcp-ui-solid` exports the canonical implementation as
312
+ `getUiResourceStableKey(content): string` :
313
+
314
+ - If `content.id` is a non-empty string → returned verbatim
315
+ - Otherwise → FNV-1a hash of the content with `id` and `metadata.generatedAt`
316
+ stripped (the timestamp is excluded so the key is stable across re-emissions
317
+ of the same logical layout)
318
+
319
+ Host apps that pre-process payloads before passing them to a renderer (e.g.
320
+ to wrap a bare chart config into a layout) should reuse this helper so the
321
+ keys they produce match what the renderer would compute internally.
322
+
323
+ ```ts
324
+ import { getUiResourceStableKey } from '@seed-ship/mcp-ui-solid'
325
+
326
+ const key = getUiResourceStableKey(barePayload)
327
+ // → 'dashboard-2024-Q3' (passthrough) or 'a4f3b91' (FNV-1a, 7 chars base36)
328
+ ```
329
+
330
+ ### Why this matters
331
+
332
+ Without a stable identity contract :
333
+
334
+ - `<For>` reconciliation re-mounts components on every render, blowing away
335
+ internal state (chart instances, expanded rows, scroll position).
336
+ - Double-mount detection is impossible — the same logical content gets a
337
+ new key every render and any registry-based guard sees only "new" entries.
338
+ - Cross-instance telemetry can't correlate events to a specific layout.
339
+
340
+ These are framework-parent concerns that cannot be papered over by the
341
+ renderer alone — the spec calls them out so producers and host apps wire
342
+ identity correctly upstream.
343
+
269
344
  ## Related Packages
270
345
 
271
346
  | Package | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-ship/mcp-ui-spec",
3
- "version": "5.0.5",
3
+ "version": "5.0.6",
4
4
  "description": "Component registry specification and JSON Schemas for MCP UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",