@vantageos/mosaic 0.3.1 → 0.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.
Files changed (157) hide show
  1. package/CHANGELOG.md +88 -2
  2. package/LICENSE +110 -0
  3. package/README.md +193 -21
  4. package/dist/PipelineBoard-FucyGArR.d.cts +759 -0
  5. package/dist/PipelineBoard-FucyGArR.d.ts +759 -0
  6. package/dist/PipelineBoard.markdown-DIcvrS-2.d.cts +16 -0
  7. package/dist/PipelineBoard.markdown-DIcvrS-2.d.ts +16 -0
  8. package/dist/{StatusBadge.schema-1tOjB4Ir.d.cts → StatusBadge.schema-BEjSH2_4.d.cts} +2 -2
  9. package/dist/{StatusBadge.schema-1tOjB4Ir.d.ts → StatusBadge.schema-BEjSH2_4.d.ts} +2 -2
  10. package/dist/{Tabs.schema-BzcEyeq8.d.cts → Tabs.schema-Dqr6vwV3.d.cts} +2 -2
  11. package/dist/{Tabs.schema-BzcEyeq8.d.ts → Tabs.schema-Dqr6vwV3.d.ts} +2 -2
  12. package/dist/Timeline-Coo_3tA_.d.cts +114 -0
  13. package/dist/Timeline-Coo_3tA_.d.ts +114 -0
  14. package/dist/Timeline.markdown-C6UPDv1H.d.cts +4 -0
  15. package/dist/Timeline.markdown-C6UPDv1H.d.ts +4 -0
  16. package/dist/TransactionPreview-BzWDn8kE.d.cts +391 -0
  17. package/dist/TransactionPreview-BzWDn8kE.d.ts +391 -0
  18. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.cts +7 -0
  19. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.ts +7 -0
  20. package/dist/artifacts.cjs +190 -2
  21. package/dist/artifacts.cjs.map +1 -1
  22. package/dist/artifacts.js +190 -2
  23. package/dist/artifacts.js.map +1 -1
  24. package/dist/confirmation.cjs +728 -2
  25. package/dist/confirmation.cjs.map +1 -1
  26. package/dist/confirmation.d.cts +2 -1
  27. package/dist/confirmation.d.ts +2 -1
  28. package/dist/confirmation.js +726 -4
  29. package/dist/confirmation.js.map +1 -1
  30. package/dist/display.cjs +1555 -203
  31. package/dist/display.cjs.map +1 -1
  32. package/dist/display.d.cts +6 -229
  33. package/dist/display.d.ts +6 -229
  34. package/dist/display.js +1539 -206
  35. package/dist/display.js.map +1 -1
  36. package/dist/forms.cjs +139 -46
  37. package/dist/forms.cjs.map +1 -1
  38. package/dist/forms.d.cts +2 -2
  39. package/dist/forms.d.ts +2 -2
  40. package/dist/forms.js +139 -46
  41. package/dist/forms.js.map +1 -1
  42. package/dist/host.cjs +822 -0
  43. package/dist/host.cjs.map +1 -0
  44. package/dist/host.d.cts +287 -0
  45. package/dist/host.d.ts +287 -0
  46. package/dist/host.js +794 -0
  47. package/dist/host.js.map +1 -0
  48. package/dist/{index-CwXVgtGb.d.cts → index-BadRcCbm.d.cts} +28 -11
  49. package/dist/{index-CwXVgtGb.d.ts → index-BadRcCbm.d.ts} +28 -11
  50. package/dist/index.cjs +2202 -247
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +42 -8
  53. package/dist/index.d.ts +42 -8
  54. package/dist/index.js +2167 -251
  55. package/dist/index.js.map +1 -1
  56. package/dist/input.d.cts +2 -2
  57. package/dist/input.d.ts +2 -2
  58. package/dist/media.cjs +190 -2
  59. package/dist/media.cjs.map +1 -1
  60. package/dist/media.d.cts +1 -1
  61. package/dist/media.d.ts +1 -1
  62. package/dist/media.js +190 -2
  63. package/dist/media.js.map +1 -1
  64. package/dist/preact/artifacts.js +190 -2
  65. package/dist/preact/artifacts.js.map +1 -1
  66. package/dist/preact/confirmation.d.ts +321 -1
  67. package/dist/preact/confirmation.js +629 -4
  68. package/dist/preact/confirmation.js.map +1 -1
  69. package/dist/preact/display.d.ts +570 -31
  70. package/dist/preact/display.js +1248 -126
  71. package/dist/preact/display.js.map +1 -1
  72. package/dist/preact/forms.d.ts +32 -15
  73. package/dist/preact/forms.js +139 -46
  74. package/dist/preact/forms.js.map +1 -1
  75. package/dist/preact/host.d.ts +307 -0
  76. package/dist/preact/host.js +875 -0
  77. package/dist/preact/host.js.map +1 -0
  78. package/dist/preact/media.d.ts +37 -1
  79. package/dist/preact/media.js +423 -0
  80. package/dist/preact/media.js.map +1 -1
  81. package/dist/preact/progress.d.ts +92 -1
  82. package/dist/preact/progress.js +469 -3
  83. package/dist/preact/progress.js.map +1 -1
  84. package/dist/progress.cjs +525 -2
  85. package/dist/progress.cjs.map +1 -1
  86. package/dist/progress.d.cts +2 -1
  87. package/dist/progress.d.ts +2 -1
  88. package/dist/progress.js +522 -3
  89. package/dist/progress.js.map +1 -1
  90. package/dist/react/artifacts.cjs +190 -2
  91. package/dist/react/artifacts.cjs.map +1 -1
  92. package/dist/react/artifacts.js +190 -2
  93. package/dist/react/artifacts.js.map +1 -1
  94. package/dist/react/confirmation.cjs +630 -2
  95. package/dist/react/confirmation.cjs.map +1 -1
  96. package/dist/react/confirmation.d.cts +3 -3
  97. package/dist/react/confirmation.d.ts +3 -3
  98. package/dist/react/confirmation.js +629 -4
  99. package/dist/react/confirmation.js.map +1 -1
  100. package/dist/react/display.cjs +1137 -123
  101. package/dist/react/display.cjs.map +1 -1
  102. package/dist/react/display.d.cts +42 -14
  103. package/dist/react/display.d.ts +42 -14
  104. package/dist/react/display.js +1122 -126
  105. package/dist/react/display.js.map +1 -1
  106. package/dist/react/forms.cjs +139 -46
  107. package/dist/react/forms.cjs.map +1 -1
  108. package/dist/react/forms.d.cts +1 -1
  109. package/dist/react/forms.d.ts +1 -1
  110. package/dist/react/forms.js +139 -46
  111. package/dist/react/forms.js.map +1 -1
  112. package/dist/react/host.cjs +910 -0
  113. package/dist/react/host.cjs.map +1 -0
  114. package/dist/react/host.d.cts +24 -0
  115. package/dist/react/host.d.ts +24 -0
  116. package/dist/react/host.js +875 -0
  117. package/dist/react/host.js.map +1 -0
  118. package/dist/react/input.d.cts +1 -1
  119. package/dist/react/input.d.ts +1 -1
  120. package/dist/react/media.cjs +426 -0
  121. package/dist/react/media.cjs.map +1 -1
  122. package/dist/react/media.d.cts +19 -1
  123. package/dist/react/media.d.ts +19 -1
  124. package/dist/react/media.js +423 -0
  125. package/dist/react/media.js.map +1 -1
  126. package/dist/react/progress.cjs +471 -2
  127. package/dist/react/progress.cjs.map +1 -1
  128. package/dist/react/progress.d.cts +1 -1
  129. package/dist/react/progress.d.ts +1 -1
  130. package/dist/react/progress.js +469 -3
  131. package/dist/react/progress.js.map +1 -1
  132. package/dist/server/resource.cjs +151 -0
  133. package/dist/server/resource.cjs.map +1 -0
  134. package/dist/server/resource.d.cts +90 -0
  135. package/dist/server/resource.d.ts +90 -0
  136. package/dist/server/resource.js +147 -0
  137. package/dist/server/resource.js.map +1 -0
  138. package/dist/server.cjs +1100 -25
  139. package/dist/server.cjs.map +1 -1
  140. package/dist/server.d.cts +84 -5
  141. package/dist/server.d.ts +84 -5
  142. package/dist/server.js +1087 -27
  143. package/dist/server.js.map +1 -1
  144. package/dist/tokens.cjs +73 -22
  145. package/dist/tokens.cjs.map +1 -1
  146. package/dist/tokens.d.cts +23 -3
  147. package/dist/tokens.d.ts +23 -3
  148. package/dist/tokens.js +73 -22
  149. package/dist/tokens.js.map +1 -1
  150. package/package.json +250 -231
  151. package/registry.yaml +51 -2
  152. package/dist/Alert.schema-LPJE5gFP.d.cts +0 -71
  153. package/dist/Alert.schema-LPJE5gFP.d.ts +0 -71
  154. package/dist/ProgressBar.schema-CdVYx0Wq.d.cts +0 -22
  155. package/dist/ProgressBar.schema-CdVYx0Wq.d.ts +0 -22
  156. package/dist/{ConfirmDialog-B3CQQGmJ.d.cts → ConfirmDialog-BCtOcmHe.d.cts} +1 -1
  157. package/dist/{ConfirmDialog-B3CQQGmJ.d.ts → ConfirmDialog-BCtOcmHe.d.ts} +1 -1
package/CHANGELOG.md CHANGED
@@ -9,15 +9,99 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ---
13
+
14
+ ## [0.5.0] — 2026-10-11 — MCP App kit for ChatGPT and Claude (D0)
15
+
16
+ ### Added
17
+ - **Behaviour change — declared secrets never reach `content[]`.** `createMosaicResource` (published `./server`) now strips every field declared in `MOSAIC_NEVER_SERIALISE` from the embedded props BY DEFAULT; pass `includeSecrets: true` to keep the previous behaviour. `createMosaicToolResult` carries a declared secret only in `_meta["mosaic/secrets"]` and throws `MosaicSecretLeakError` (naming the field, never the value) if one appears in `content[]`, or if a declared field does not exist on the view. A `TableView` column flagged `sensitive` is masked in markdown and stripped from the embedded props. `TokenDisplayOnceModal`'s fallback carries label, scope, expiry and an issuer-supplied fingerprint, never the token.
18
+ - **Behaviour notes for the #75 fallbacks.** `openLink` now hands the host the PARSED href, so `https://x.example` is sent as `https://x.example/` (trailing slash; a raw string with an embedded newline can no longer reach the host). The TransactionPreview markdown fallback joins the swap route with `→` instead of `>` (`USDC → WFUSE → FUSE`), because `>` is escaped as markdown. The secret guard in `createMosaicToolResult` compares case-folded and NFKC-normalised text on both sides.
19
+ - **Gate 4 script fail-closed** (`scripts/cross-runtime-subpath-coverage-check.py`): exit 2 naming the input for any unreadable input (package root, `package.json`, registry, built entry, empty category set, a registry that yields no categories or has no `components` list, or the PyYAML and built-in readers disagreeing) and exit 1 only for a real coverage gap; the default `--package-root` is the package the script lives in, not the working directory; the registry cross-check no longer skips without PyYAML (built-in reader) or with a missing registry (`--skip-registry-check` is the explicit opt-out).
20
+ - **MCP Apps views and host layer, delivery 2 (task k17ehg71z31ec50fkh95p3xxr58fjm98; Eta corrections k178bt8enj99wv1gtc8h3315kx8g0w13).**
21
+ - **Views** (React + Preact barrels, FR+EN, Zod, Skeleton loading, `{}` parses as an empty state, A-1 markdown): `StatCard` (R08) and `BalanceCard` (R09, Fuse balance, exact decimal strings) in `display`; `MessageFeed` (R03, read-only, plain text, safe links) and `PipelineBoard` (R06, read-only, fullscreen board / inline summary, stage filter chips) in `display`; `Timeline` (R02) in `progress`; `TransactionPreview` (R11, prepare-only, swap-quote variant, **no sign / send / approve control**, "NOT SIGNED - preparation only") in `confirmation`.
22
+ - **`TableView` extended** (R01 / R10, every new prop optional): sortable columns with `aria-sort`, `single` / `multiple` selection (`onSelectionChange` rows + keys), sticky header, compact density, column `align` / `width`, `amount` (exact decimal string compare) and `icon` cell kinds, `loading` Skeleton, serialisable `rows` in the schema.
23
+ - **`RadioGroup` / `MultiSelect`** (R25): `appearance` `segmented` | `chips` (RadioGroup) and `chips` (MultiSelect: visible toggle buttons, `aria-pressed`, no dropdown).
24
+ - **Host layer** (`./host`, `./react/host`, `./preact/host`): safe-area insets (H01), host style variables bridged onto mosaic-tokens + host fonts + transparent background (H02), `sendMessage` / `openLink` helpers that refuse what the host cannot honour (H04, H18), typed model-context blocks + `openai/modelContext` read + `selectionModelContext` (H03), hooks `useMosaicHostLayout`, `useSendMessage`, `useOpenLink`.
25
+ - **Server**: `buildMcpAppResource({ visibility, hostFonts })` (H05); `createMosaicToolResult` returning `content: [text, ui:// resource]` (A-1) and a `<name>ToMarkdown` for all 12 components (H17). `createMosaicResource` now supports 12 components, honours its `uri` and `markdownFallback` options, and `_meta.ui.fallback` (kept: published contract) carries the data instead of a title and note.
26
+ - **Adapter**: `startSizeReporting` / `postSizeChanged` send `ui/notifications/size-changed` from the raw adapter (H15).
27
+ - **Fix — host-context dispatcher** (Eta #74 re-gate): a foreign handler that overwrites `app.onhostcontextchanged` while subscribers are live is adopted as `previous` and the dispatcher reinstalled on the next subscribe; resubscribe-after-full-unsubscribe is pinned by a test that goes RED when `dispatchers.delete(app)` is removed.
28
+ - **MCP Apps host layer for ChatGPT and Claude (task k17ehg71z31ec50fkh95p3xxr58fjm98, items 1-2).** New subpaths `./host` (framework-agnostic core: host theme/display mode/locale/container dimensions, `openai/deepLink` when present, `requestDisplayMode` limited to inline | fullscreen with a typed AND runtime refusal of `pip` for a ChatGPT host, `updateModelContext` over `ui/update-model-context`, host theme mapped onto mosaic-tokens `data-theme`), `./react/host` and `./preact/host` (hooks over the core) and `./server/resource` (`buildMcpAppResource`: pure, no React/DOM; `_meta.ui` standard keys first with the four csp arrays explicit, `domain` derived from `servedUrl` for Claude, `openai/*` keys only when passed: `_meta["openai/ui"]` display modes on the resource, `openai/outputTemplate` and `openai/ui` entrypoints on the tool; deepLink typed as `{ url }`; ChatGPT detected from `openai/*` keys, no user-agent sniffing). Composes with ext-apps `registerAppResource`/`registerAppTool`, which pass `_meta` through. No new dependency: the ext-apps `App` is typed structurally.
29
+ - **Fix — `react/media` and `preact/media` were empty barrels; Gate 4 passed anyway.** Both now re-export `StatusBadge` (+ props type, schema, validator) from the existing `display` components (display exports kept, no new component), and Gate 4 now fails any runtime subpath entry whose built `.js` or `.d.ts` exports zero symbols, naming the entry (`exports-zero-symbols`).
30
+
31
+ ---
32
+
33
+ ## [0.4.0] — 2026-10-04
34
+
12
35
  ### Added
36
+ - **Badge primitive, cross-runtime.** `Badge` is exported from `./display`, `./react/display` and
37
+ `./preact/display`, with its schema, stories, i18n strings and a 205-line React test. Landed on
38
+ `main` in #61 against `target 0.3.2` and unpublished until this release, so no consumer could
39
+ import it.
40
+ - **`docs/GA-RELEASE-V0.3.1.md`** — the canonical release procedure (bilingual EN+FR): the five
41
+ fail-closed CI gate jobs, the `ETA_APPROVED_TASK_ID` approval hook, the
42
+ `prepublishOnly`/`postpublish` workspace-rewrite pattern, the 0.3.0 → 0.3.1 incident and its
43
+ lesson, the cross-runtime architecture and the migration guide. Written for 0.3.1 and itself
44
+ unpublished until now, which is why this release follows it rather than a recalled procedure.
45
+
46
+ ### Documentation
47
+ - **The package README was frozen at 0.3.1 and is rewritten against the built artifact.**
48
+ It told a consumer to `npm install @vantageos/mosaic@^0.2.0 @vantageos/mosaic-tokens@^0.2.0`,
49
+ neither of which is what ships. Corrected to the versions actually published.
50
+ - **Six sentences claiming form primitives were still unshipped are deleted.** Each had
51
+ been appended as a primitive landed and never removed, so each sat directly above a
52
+ table row documenting the very primitive it called missing. All of Input, Textarea,
53
+ Select, Checkbox, MultiSelect, RadioGroup and FieldArray are exported — read from
54
+ `dist/react/forms.d.ts`, not from the prose.
55
+ - **A derived per-subpath export inventory replaces a partial list that read as complete.**
56
+ It records that `media` exports no components although it is one of the seven categories
57
+ gated for cross-runtime parity.
58
+ - **Bundle sizes are the `size-limit` output with their gates**, replacing figures for
59
+ surfaces the gate no longer measures.
60
+ - **The changelog link pointed at the repo root**, which is not the file the tarball
61
+ carries; it now points at this one.
62
+ - **The README stated MIT while `package.json` carried no `license` field** and `files`
63
+ shipped no LICENSE, so every version up to 0.3.1 reached consumers with no licence terms
64
+ at all.
65
+
66
+ ### Added — licence
67
+ - **`FSL-1.1-Apache-2.0` is now declared and shipped.** `package.json` carries the
68
+ `license` field, `LICENSE` is in `files`, and the file is byte-identical to the one every
69
+ sibling `@vantageos` package ships
70
+ (sha256 `3d458972e6e84e5d2361a886ef64b07aefdc38dd8955e281ea8c2ae8849646a4`). Verified
71
+ against what the registry serves for `mosaic-blocks`, `mosaic-tokens` and `mcp-doctor` —
72
+ this package was the only one of the four publishing `license: NONE`. Nothing about the
73
+ terms is new; they were simply never attached to this package.
74
+
75
+ ### Changed
76
+ - **`@vantageos/mosaic-tokens` moves from `^0.2.0` to `^0.7.0`** in the published manifest. This is
77
+ why the release is `0.4.0` and not the `0.3.2` the source commit targeted: `^0.2.0` does **not**
78
+ satisfy `0.7.0` (a caret on a `0.x` range pins the minor), so a consumer upgrading receives a
79
+ five-minor jump in a runtime dependency. On a `0.x` package the minor is the only available
80
+ signal for that, and a patch number would have understated it.
81
+
82
+ The range was never edited by hand — the manifest carries `workspace:*` and `prepublishOnly`
83
+ resolves it to whatever the sibling package is at, which is now `0.7.0`. That is correct
84
+ behaviour and it is precisely why the published version has to be derived from the packed
85
+ tarball rather than from the commit that said `target 0.3.2`.
86
+
87
+ ### Note on this release
88
+ Nothing else changed in the source. `latest` served `0.3.1` from 2026-06-13 until this version, so
89
+ the items above are the entire delta a consumer receives — derived by locating the commit that set
90
+ the version to `0.3.1` and listing what touched `packages/mosaic` after it, not by reading a date
91
+ window (a date window includes the release commits themselves and reported 19).
92
+
93
+
94
+ ### Recorded late — shipped by 0.4.0 at the latest, never listed in a release section
95
+
96
+ These entries sat under `[Unreleased]` after their code had shipped. Each one's commit is an ancestor of the 0.4.0 release commit `12475a6` (`git merge-base --is-ancestor <sha> 12475a6`).
97
+
13
98
  - **D-PR1 — Pre-publish dry-run evidence for v0.3.0 GA.** New doc `docs/D-PR1-PRE-PUBLISH-DRY-RUN-V0.3.0.md` (bilingual EN+FR) capturing the hard-evidence pre-publish gate run in a fresh sandbox worktree off `origin/main` @ `7ae5450`: sandbox build-pass (RULE #19, `SANDBOX_BUILD_PASS_ID=7ae5450`, build exit 0), Gate 1 build-parity (exit 0), `npm pack --dry-run` (150 files / 511.8 kB / unpacked 3.0 MB / version 0.3.0 / sha `31caed2f`, CHANGELOG + README + package.json + registry.yaml + full `dist/` surface present — **no real publish**), corrected vendored cross-runtime subpath coverage gate (`--ga-mode`, exit 0, 0 missing), and the full gate set re-run green (tsc, biome, vitest 854, playwright-axe 29, size-limit, registry-drift, storybook-test 103, Gate 2 peer-resolution, Gate 3 coherence 10). Documents the **D-T1 blocker**: the canonical B-PR1 skill / B-PR2 publish hook carry a category-prefixed layout defect (`./forms/react`) vs Mosaic's runtime-prefixed layout (`./react/forms`) and **false-block** the real publish — must be fixed before `tag v0.3.0` (Sigma task `k176e5rpz`); plus `ETA_APPROVED_TASK_ID` must be set after Eta approval. Mission `k57b6d1b` · task `k177e1wr`. No source/build/gate code changed — docs-only.
14
99
  - **CI Gate 4 — Cross-runtime subpath coverage (B-PR3).** New fail-fast CI gate asserting that **every bare component category** in `packages/mosaic/package.json` `exports` (`./forms`, `./progress`, `./display`, `./artifacts`, `./confirmation`, `./media`, `./input`) also exports a **non-empty** `dist` artifact under **both** `./react/<cat>` and `./preact/<cat>`. This is the gate that would have caught the original v0.3.0 GA blocker (foundation components present as bare exports but missing/empty under `/react` + `/preact`).
15
100
  - New vendored script `packages/mosaic/scripts/cross-runtime-subpath-coverage-check.py` — vendored from the canonical B-PR1 skill `mosaic-cross-runtime-subpath-coverage-check` (VantageRegistry; PR #20). Stdlib-only, with an optional PyYAML registry cross-check that degrades gracefully when PyYAML is absent (no Python dependency added to CI). Carries one documented deviation: a `RUNTIME_PREFIXED` toggle adapting the canonical category-prefixed layout (`./forms/react`) to this repo's runtime-prefixed layout (`./react/forms`).
16
101
  - New job `cross-runtime-subpath-coverage` (`needs: build-parity-cross-runtime`) builds tokens + mosaic so `dist/` exists, then runs `python3 .../cross-runtime-subpath-coverage-check.py --package-root packages/mosaic --ga-mode` (exit 1 fails CI with a `component × runtime` diagnostic). CI-side equivalent of the B-PR2 npm-publish hook.
17
102
  - New doc `docs/CROSS-RUNTIME-GATE.md` (bilingual EN+FR). Additive change — no existing gate (Gate 1 build-parity, Gate 2 peerDeps smoke, Gate 3 coherence, `ci` job) weakened or replaced. Mission `k57b6d1b` · task `k176pzsszyy4v3f505khwg33gx88j36n`.
18
- - Wave 3 components TBD (DatePicker, Avatar, FilterChip, Pagination — demand-driven T21+).
19
103
 
20
- ### Fixed
104
+ #### Fixed
21
105
  - CI Gate 2 (PeerDeps optionality smoke) now runs GREEN (unblocks v0.3.0 GA publish gate). Two layered bugs, both packaging-scope:
22
106
  - **Build (tsup):** the preact runtime bundles that re-export the shared React implementation (`preact/progress`, `preact/input`, `preact/confirmation`, `preact/forms`) shipped `import … from "react/jsx-runtime"` and `import … from "react"`. `esbuildOptions.alias` (`react → preact/compat`) never fired because tsup auto-externalises peerDeps (`react`, `react/jsx-runtime`) *before* alias resolution, so the alias was a no-op for those specifiers. A react-only consumer (which has react) could therefore resolve `@vantageos/mosaic/preact/*` even with preact absent — optionality broken in both directions. Fix: replaced the alias with an esbuild `onResolve` plugin (`mosaic-react-to-preact`) that deterministically rewrites the whole react family (`react`, `react-dom`, `react-dom/client`, `react/jsx-runtime`, `react/jsx-dev-runtime`) to their preact equivalents and marks them external; paired with `noExternal: [/^react…/]` on the preact pass so tsup's own external plugin returns undefined for the react family and lets the rewrite run. Added `preact/jsx-runtime` to `PREACT_EXTERNAL`. Preact bundles now import only preact specifiers; react bundles unchanged.
23
107
  - **Smoke fixture (run-smoke.sh):** the fixtures installed mosaic via `npm install file:../../../packages/mosaic`, which npm symlinks into the monorepo source tree; Node then resolved the bundle's runtime imports by climbing out of the fixture into the monorepo root `node_modules` (where both react and preact are hoisted devDeps), producing a false PASS. Rewrote the driver to model a real npm consumer faithfully: `pnpm pack` both packages (rewriting the `workspace:*` tokens dep to a concrete version) and install the tarballs into an isolated `mktemp -d` consumer outside the repo tree, with `--legacy-peer-deps` so npm does not auto-install the *transitive* peer react pulled by `@tanstack/react-virtual` into a preact-only consumer. Fixture assertions (`index.mjs`) untouched. Smoke now PASSES: react-only resolves `react/*` and fails `preact/*`; preact-only resolves `preact/*` and fails `react/*`.
@@ -28,6 +112,8 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
28
112
 
29
113
  ---
30
114
 
115
+ ---
116
+
31
117
  ## [0.3.1] — 2026-06-13 — hotfix: workspace:* leak in published 0.3.0 manifest (Path B — root-cause fix)
32
118
 
33
119
  ### Fixed
package/LICENSE ADDED
@@ -0,0 +1,110 @@
1
+ Functional Source License, Version 1.1, Apache 2.0 Future License
2
+
3
+ Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ Notice
8
+
9
+ Copyright 2026 VantageOS (ElPi Corp)
10
+
11
+ Terms and Conditions
12
+
13
+ Licensor: VantageOS (ElPi Corp)
14
+
15
+ Licensed Work: VantagePeers
16
+ The Licensed Work is (c) 2026 VantageOS (ElPi Corp)
17
+
18
+ Additional Use Grant: You may make production use of the Licensed Work,
19
+ provided Your use does not include offering the Licensed
20
+ Work to third parties as a hosted or managed service
21
+ where the service provides users with access to any
22
+ substantial set of the features or functionality of the
23
+ Licensed Work.
24
+
25
+ Change Date: 2028-04-03 (two years from publication)
26
+
27
+ Change License: Apache License, Version 2.0
28
+
29
+ For information about alternative licensing arrangements for the Licensed Work,
30
+ please contact: contact@vantageos.com
31
+
32
+ License text below is provided for informational purposes only.
33
+
34
+ ---
35
+
36
+ Functional Source License, Version 1.1, Apache 2.0 Future License
37
+
38
+ 1. Purpose
39
+
40
+ This license gives you broad permission to use, modify, and share this
41
+ software, with one important condition: you may not use it to compete with us.
42
+
43
+ 2. Acceptance
44
+
45
+ To use the software, you must agree to these terms. If you do not agree, you
46
+ may not use the software.
47
+
48
+ 3. Copyright License
49
+
50
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-
51
+ sublicensable, non-transferable license to use, copy, distribute, make
52
+ available, and prepare derivative works of the software, in each case subject
53
+ to the limitations below.
54
+
55
+ 4. Limitations
56
+
57
+ You may not make the functionality of the software available to third parties
58
+ as a service, or otherwise use the software to provide a service to third
59
+ parties that competes with the licensor.
60
+
61
+ 5. Patents
62
+
63
+ The licensor grants you a license, under any patent claims the licensor can
64
+ license or becomes able to license, to make, have made, use, sell, offer for
65
+ sale, import, and have imported the software, in each case subject to the
66
+ limitations and conditions in this license.
67
+
68
+ 6. Fair Use
69
+
70
+ This license is not intended to limit any rights you may have under applicable
71
+ fair use or other laws.
72
+
73
+ 7. No Other Rights
74
+
75
+ These terms do not grant any other rights. The licensor reserves all rights not
76
+ expressly granted.
77
+
78
+ 8. Termination
79
+
80
+ Your license is automatically terminated if you violate the terms. However, if
81
+ you cure the violation within 30 days of discovering it, your license is
82
+ reinstated retroactively.
83
+
84
+ 9. Disclaimer
85
+
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87
+ IMPLIED.
88
+
89
+ 10. Limitation of Liability
90
+
91
+ TO THE EXTENT PERMITTED BY LAW, THE LICENSOR IS NOT LIABLE FOR ANY DAMAGES
92
+ ARISING FROM THE USE OF THE SOFTWARE.
93
+
94
+ 11. Change Date and License
95
+
96
+ After the Change Date, the licensor grants you the rights under the Change
97
+ License.
98
+
99
+ 12. Definitions
100
+
101
+ "Compete" means providing a product or service that is substantially similar
102
+ to, or a replacement for, all or a significant portion of the Licensed Work.
103
+
104
+ ---
105
+
106
+ Apache License, Version 2.0
107
+
108
+ After the Change Date (2028-04-03), this software will be available under the
109
+ Apache License, Version 2.0. See https://www.apache.org/licenses/LICENSE-2.0
110
+ for full text.
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @vantageos/mosaic
2
2
 
3
- Fleet-wide MCP UI design system. Zod-validated, taxonomy-organized (6 categories: progress, input, display, artifacts, confirmation, media), streaming-ready, **cross-runtime React 19 + Preact 10**. Built for MCP Apps (SEP-1865 extension).
3
+ Fleet-wide MCP UI design system. Zod-validated, taxonomy-organized (7 categories: artifacts, confirmation, display, forms, input, media, progress), streaming-ready, **cross-runtime React 19 + Preact 10**. Built for MCP Apps (SEP-1865 extension).
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```sh
8
8
  # React 19
9
- npm install @vantageos/mosaic@^0.2.0 @vantageos/mosaic-tokens@^0.2.0 react react-dom
9
+ npm install @vantageos/mosaic@^0.5.0 @vantageos/mosaic-tokens@^0.7.0 react react-dom
10
10
 
11
11
  # Preact 10
12
- npm install @vantageos/mosaic@^0.2.0 @vantageos/mosaic-tokens@^0.2.0 preact
12
+ npm install @vantageos/mosaic@^0.5.0 @vantageos/mosaic-tokens@^0.7.0 preact
13
13
  ```
14
14
 
15
15
  All runtime peers are marked optional via `peerDependenciesMeta` — install only what your runtime needs.
@@ -32,19 +32,22 @@ This constraint is codified locally via the `build` script (the `NODE_OPTIONS` p
32
32
  |---|---|---|
33
33
  | `@vantageos/mosaic` | React 19 (back-compat) | v0.1.x consumers (Sigma VP, Theta CRM) |
34
34
  | `@vantageos/mosaic/<cat>` | React 19 (back-compat) | Tree-shakable category imports v0.1.x style |
35
- | `@vantageos/mosaic/react` | React 19 | New v0.2.0+ React consumers — explicit runtime opt-in |
35
+ | `@vantageos/mosaic/react` | React 19 | React consumers — explicit runtime opt-in (the preferred surface since v0.2.0) |
36
36
  | `@vantageos/mosaic/react/<cat>` | React 19 | Tree-shakable category imports under explicit react/ prefix |
37
37
  | `@vantageos/mosaic/preact` | Preact 10 | Mu vantage-bridge iframe, Chi gptpowerups (LLM-host target) |
38
38
  | `@vantageos/mosaic/preact/<cat>` | Preact 10 | Tree-shakable category imports under preact/ prefix |
39
39
  | `@vantageos/mosaic/tokens` | runtime-free | Re-export of `@vantageos/mosaic-tokens` |
40
40
  | `@vantageos/mosaic/server` | Node | `createMosaicResource()` MCP UI builder (runtime-agnostic) |
41
+ | `@vantageos/mosaic/server/resource` | Node | `buildMcpAppResource()` MCP Apps `_meta` helper (pure, no React, no DOM) |
42
+ | `@vantageos/mosaic/host` | runtime-free | Host-context core (theme, display mode, locale, model context) |
43
+ | `@vantageos/mosaic/react/host` · `/preact/host` | React 19 · Preact 10 | Hooks over the host-context core |
41
44
 
42
45
  `<cat>` = `progress` | `input` | `display` | `artifacts` | `confirmation` | `media`.
43
46
 
44
47
  ## Quick start
45
48
 
46
49
  ```tsx
47
- // React 19 — preferred v0.2.0+ surface
50
+ // React 19 — the preferred runtime-explicit surface
48
51
  import { ProgressBar } from "@vantageos/mosaic/react/progress";
49
52
  import { ConfirmDialog } from "@vantageos/mosaic/react/confirmation";
50
53
  import "@vantageos/mosaic-tokens/css"; // declares --mosaic-* vars on :root
@@ -64,7 +67,7 @@ export function MyComponent({ progress, label }: { progress: number; label: stri
64
67
  }
65
68
  ```
66
69
 
67
- ## Forms (v0.3.0-alpha.1)
70
+ ## Forms
68
71
 
69
72
  `@vantageos/mosaic/{react,preact}/forms` — composite form primitives wrapping `react-hook-form` + `@hookform/resolvers/zod`. Default validation mode is `onBlur` (Chi co-validated, Day 102 DM). Cross-runtime: same imports, React 19 path or Preact 10 path.
70
73
 
@@ -130,17 +133,14 @@ export function SignupForm({ onSubmit }: { onSubmit: (data: z.infer<typeof schem
130
133
  | `<SubmitButton label="..." loadingLabel="..." />` | Bound to the surrounding `FormProvider`. Disabled while invalid OR submitting. |
131
134
  | `<Input name="..." type="text\|email\|password\|number\|url" label="..." placeholder? disabled? autoComplete? />` | Single-field `<input>` bound to the surrounding `FormProvider`. `label` is required (consumer-driven i18n). Emits `aria-invalid` + `aria-describedby` on validation error. |
132
135
 
133
- Field primitives (Textarea, Select, Checkbox, MultiSelect, RadioGroup, FieldArray) land in T12-T20 — see `docs/v0.3.0-plan.md` §7.
134
136
  | `<Textarea name="..." rows? maxLength? autoResize? placeholder? disabled? label? />` | Multi-line text input field primitive. `rows` default 3, `maxLength` enforced via shared logic gate, optional `autoResize` grows to content. `aria-invalid` + `aria-describedby` on error. |
135
137
 
136
- Remaining field primitives (Input shipped T11, Select, Checkbox, MultiSelect, RadioGroup, FieldArray) land in T13-T20 — see `docs/v0.3.0-plan.md` §7.
137
138
  | `useFieldArray({ name, control? })` | Thin wrapper around RHF's `useFieldArray`. Returns `{ fields, append, remove, move, swap }` + the rest of RHF's native return for advanced cases. Reads `control` from `FormProvider` when omitted. |
138
139
  | `<FieldArray name="...">{({ field, index }, { append, remove, move, swap, fields }) => ...}</FieldArray>` | Render-prop wrapper around `useFieldArray`. Emits `role="list"` shell + `role="listitem"` per row, keyed by RHF's stable `field.id` (NOT array index). Powers PromptForm Add Variable, Hermes variable mappings, Demeter filter chips. |
139
140
 
140
141
  | `<Checkbox name="..." label="..." indeterminate? description? disabled? />` | Boolean checkbox primitive. `indeterminate=true` → `aria-checked="mixed"` + DOM `.indeterminate=true` via ref. `description` wired via `aria-describedby`. `aria-invalid` + `aria-describedby` on error. |
141
142
  | `<RadioGroup name="..." label="..." options={[...]} orientation? disabled? />` | WCAG-AA radiogroup. Roving tabIndex, Arrow key selection sync, Home/End, Space/Enter. |
142
143
 
143
- Remaining field primitives (Input, Textarea, Select, MultiSelect) land in T11-T15 + T18-T20 — see `docs/v0.3.0-plan.md` §7.
144
144
 
145
145
  ### RadioGroup
146
146
 
@@ -191,13 +191,10 @@ export function PlanSelector() {
191
191
  - `aria-disabled` on disabled options; disabled options skipped in arrow nav
192
192
  - `aria-orientation` reflects `orientation` prop
193
193
 
194
- Field primitives (Textarea, Select, MultiSelect, RadioGroup, FieldArray) land in T12-T20 — see `docs/v0.3.0-plan.md` §7.
195
194
  | `<MultiSelect name="..." label="..." options={...} placeholder? disabled? searchable? maxItems? />` | Multi-value dropdown. RHF value is `string[]`. Selected items render as removable chips (Backspace/Delete on trigger removes last, per-chip × removes specific). WCAG-AA combobox (`role=combobox aria-multiselectable=true`), Arrow/Enter keyboard nav, optional case-insensitive search, optional `maxItems` cap. |
196
195
 
197
- Remaining field primitives (Input + Textarea + MultiSelect shipped T11/T12/T15; Select, Checkbox, RadioGroup, FieldArray) land in T13-T20 — see `docs/v0.3.0-plan.md` §7.
198
196
  | `<Select name="..." label="..." options={[...]} />` | Single-select dropdown (combobox+listbox APG). Optional `searchable` prop enables in-popup filter. Full keyboard nav + type-ahead. WCAG-AA strict. |
199
197
 
200
- Field primitives (Input, Textarea, Checkbox, MultiSelect, RadioGroup, FieldArray) land in T11, T14-T20 — see `docs/v0.3.0-plan.md` §7.
201
198
 
202
199
 
203
200
 
@@ -214,10 +211,163 @@ const resource = createMosaicResource({
214
211
  ```
215
212
 
216
213
 
214
+ ## MCP Apps host layer (ChatGPT and Claude)
215
+
216
+ Standard MCP Apps keys come first; `openai/*` keys are strictly additive. A view built
217
+ on this layer works with no `openai` key at all, and never requests `pip` for ChatGPT.
218
+ `@modelcontextprotocol/ext-apps` is **not** a dependency: the core types the `App`
219
+ structurally, so any `App` instance fits.
220
+
221
+ ### Resource helper (server, pure)
222
+
223
+ ```ts
224
+ import { buildMcpAppResource } from "@vantageos/mosaic/server/resource";
225
+
226
+ const { resource, toolMeta } = buildMcpAppResource({
227
+ uri: "ui://mosaic/tasks",
228
+ html, // your bundled single-file view
229
+ csp: { connectDomains: ["https://api.example.com"] }, // the 4 arrays are always emitted
230
+ servedUrl: "https://example.com/mcp", // Claude `ui.domain` is DERIVED from it, never typed
231
+ prefersBorder: true,
232
+ openai: { outputTemplate: "ui://mosaic/tasks", preferredDisplayMode: "inline" }, // optional
233
+ });
234
+ // resource._meta = { ui: { csp, prefersBorder, domain }, "openai/ui"?: { availableDisplayModes?, preferredDisplayMode? } }
235
+ // toolMeta = { ui: { resourceUri }, "openai/outputTemplate"?, "openai/ui"?: { entrypoints } }
236
+ ```
237
+
238
+ `domain` is `{sha256(servedUrl) first 32 hex}.claudemcpcontent.com` and is absent when
239
+ `servedUrl` is not given. `openai.availableDisplayModes` / `preferredDisplayMode` accept
240
+ `inline` | `fullscreen` only; `pip` throws.
241
+
242
+ It composes with `@modelcontextprotocol/ext-apps/server`: `registerAppResource` and
243
+ `registerAppTool` pass `_meta` through (they only default the MIME type and back-fill the
244
+ deprecated flat `ui/resourceUri`), so feed them `resource._meta` and `toolMeta`; this helper adds
245
+ the csp defaults, the derived Claude `domain` and the additive `openai/*` keys they do not produce.
246
+
247
+ ### Host context (view)
248
+
249
+ ```tsx
250
+ import { useMosaicHostTheme, useRequestDisplayMode, useUpdateModelContext } from "@vantageos/mosaic/react/host";
251
+ // Preact: the same names from "@vantageos/mosaic/preact/host"
252
+
253
+ const ctx = useMosaicHostTheme(app); // theme, displayMode, locale, containerDimensions, deepLink
254
+ const goFullscreen = useRequestDisplayMode(app); // "inline" | "fullscreen"; pip refused for ChatGPT
255
+ const tellModel = useUpdateModelContext(app); // ui/update-model-context
256
+ ```
257
+
258
+ `ctx.theme` is mapped onto mosaic-tokens (`data-theme` light/dark, applied to `<html>`).
259
+ `ctx.deepLink` reads `hostContext["openai/deepLink"]` (`{ url }`) when present and is `undefined`
260
+ otherwise. ChatGPT is detected from `openai/*` keys in the host context or experimental host
261
+ capabilities (the spec defines no host-identification field; no user-agent sniffing). Framework-free equivalents (`readHostContext`, `requestDisplayMode`,
262
+ `updateModelContext`, `subscribeHostContext`, `applyMosaicTheme`) live in
263
+ `@vantageos/mosaic/host`.
264
+
265
+ ### Host layout, messages and links (view)
266
+
267
+ ```tsx
268
+ import { useMosaicHostLayout, useSendMessage, useOpenLink } from "@vantageos/mosaic/react/host";
269
+
270
+ useMosaicHostLayout(app); // data-theme + safe-area padding + host style variables + host fonts
271
+ const send = useSendMessage(app); // ui/message: await send("Show the overdue tasks")
272
+ const open = useOpenLink(app); // ui/open-link: await open("https://wallet.example/swap?q=1")
273
+ ```
274
+
275
+ * **Safe-area insets** (`hostContext.safeAreaInsets`, MCP Apps spec): applied as root padding and as
276
+ `--mosaic-safe-area-<side>` variables (for `scroll-padding`).
277
+ * **Host style variables** (`hostContext.styles.variables`, 76 standardized keys) are written on the
278
+ root and bridged onto the mosaic-tokens custom properties they correspond to (`HOST_TO_MOSAIC_TOKENS`);
279
+ variables the host does not pass leave the mosaic-tokens defaults untouched. Host fonts
280
+ (`styles.css.fonts`) are injected once; add the Claude font origin to `csp.resourceDomains` with
281
+ `buildMcpAppResource({ hostFonts: true })`. `applyTransparentBackground(document)` sets html/body to
282
+ `transparent` (Claude transparent theming).
283
+ * **`sendMessage`**: options `{ target, send }` need the host to advertise `openai/message` and a
284
+ non-mobile platform; otherwise it THROWS rather than silently dropping `send: false`.
285
+ * **`openLink`**: http(s) only; the host's denial reason is thrown (`HostRequestError`).
286
+ * **Typed model context**: `textBlock(text, { title, thumbnail, background })` builds blocks with
287
+ `_meta["openai/title"]`, `_meta["openai/thumbnail"]` and `annotations.audience: ["assistant"]`;
288
+ `selectionModelContext(rows, columns, locale)` turns a table selection into one titled block (feed it
289
+ to `updateModelContext` from `onSelectionChange`); `hostContext["openai/modelContext"]` is read into
290
+ `ctx.modelContext` (`null` = cleared by the user).
291
+ * **Tool visibility**: `buildMcpAppResource({ visibility: ["app"] })` emits `_meta.ui.visibility`
292
+ (omitted = the spec default `["model", "app"]`).
293
+ * **Size reporting**: a view that talks to the host through the raw adapter calls
294
+ `startSizeReporting()` (`ui/notifications/size-changed` through a ResizeObserver). The ext-apps `App`
295
+ does this itself with `autoResize`.
296
+
297
+ ### Tool results with an A-1 markdown fallback (server)
298
+
299
+ ```ts
300
+ import { createMosaicToolResult } from "@vantageos/mosaic/server";
301
+
302
+ return createMosaicToolResult("PipelineBoard", { stages, currency: "EUR" }, "en");
303
+ // { content: [ { type: "text", text: "# Pipeline\n\n| Stage | Deals | Total | ..." },
304
+ // { type: "resource", resource: { uri: "ui://mosaic/pipelineboard", ... } } ] }
305
+ ```
306
+
307
+ The text block carries the view's DATA (App standard A-1: a client with no UI layer gets an equivalent,
308
+ meaningful answer). All 12 supported components have a pure `<name>ToMarkdown(props, locale)`, also
309
+ exported from `@vantageos/mosaic/server`. `_meta.ui.fallback` is kept (it is part of the published
310
+ `createMosaicResource` result) and now holds the same text instead of a title and a note. A one-time
311
+ token's value is never written into `content[]`.
312
+
313
+ **Secret guard.** `MOSAIC_NEVER_SERIALISE` lists, per view, the fields whose value must never reach
314
+ `content[]`. `createMosaicToolResult` renders the fallback from the props without them, delivers them in
315
+ the result `_meta["mosaic/secrets"]`, and refuses (`MosaicSecretLeakError`) if any non-empty declared
316
+ value still appears, at any length. The comparison is case-folded and NFKC-normalised on both sides, so
317
+ `ABCD` is an echo of `abcd`; a very short secret can therefore also collide with unrelated text and
318
+ refuse loudly (the issuer keeps it out of the other props).
319
+
217
320
  ## Components
218
321
 
322
+ ### What each subpath actually exports
323
+
324
+ Derived from the built type declarations, not maintained by hand:
325
+
326
+ ```sh
327
+ # capitalised exports per subpath, excluding the
328
+ # Props/Schema/Validated/Option/Args/Return/Options/Shape/Mode/Type/Controls suffixes
329
+ node -e '...' # see the release PR for the exact script
330
+ ```
331
+
332
+ | Subpath | Exports |
333
+ |---|---|
334
+ | `forms` | Checkbox, ErrorDisplay, FieldArray, FormField, FormProvider, Input, MultiSelect, RadioGroup, Select, SubmitButton, Textarea |
335
+ | `display` | BalanceCard, Badge, EmptyState, MessageFeed, PipelineBoard, Skeleton, StatCard, StatusBadge, StreamingTableView, TableView, VirtualList |
336
+ | `progress` | ProgressBar, Timeline |
337
+ | `input` | Tabs |
338
+ | `confirmation` | Alert, ConfirmDialog, ConfirmModal, TokenDisplayOnceModal, TransactionPreview |
339
+ | `artifacts` | MarkdownRenderer |
340
+ | `media` | *(none)* |
341
+
342
+ Two things this table says that prose would have hidden. **`media` exports no
343
+ components**, although it is one of the seven categories gated for cross-runtime
344
+ parity — the subpath exists and is empty of components. And the sections below
345
+ document only a few of these; the table is the inventory, the sections are
346
+ examples. Previously the sections WERE presented as the inventory, which is how
347
+ six separate sentences came to claim that form primitives were still unshipped
348
+ while a table row documenting each one sat directly beneath them.
349
+
350
+ The same list for `preact` is identical by construction — cross-runtime parity is
351
+ gated in CI (`scripts/verify-build-parity.sh`).
352
+
219
353
  ### Display
220
354
 
355
+ #### Badge
356
+
357
+ Static label primitive, cross-runtime. New in 0.4.0 — it was merged to `main` well
358
+ before and shipped in no published version until then.
359
+
360
+ ```tsx
361
+ import { Badge } from "@vantageos/mosaic/react/display";
362
+ // Preact: import { Badge } from "@vantageos/mosaic/preact/display";
363
+
364
+ <Badge variant="success">Active</Badge>
365
+ ```
366
+
367
+ See `src/components/display/Badge.schema.ts` for the prop contract and
368
+ `Badge.stories.tsx` for the rendered variants — both ship in the repo, and the
369
+ schema is the authority on the props rather than this snippet.
370
+
221
371
  #### VirtualList
222
372
 
223
373
  Generic virtualized list for large datasets. Uses @tanstack/react-virtual v3 (already bundled via TableView). Cross-runtime: React 19 + Preact 10.
@@ -260,7 +410,7 @@ type Task = { id: string; title: string; status: string };
260
410
  ## Doctrine
261
411
 
262
412
  - **Pattern 1 (Zod runtime validation)** — every component validates props at the MCP host boundary; invalid props render an a11y fallback (`role="alert"`), never white-screen.
263
- - **Pattern 2 (6-category taxonomy)** — components live under exactly one category. Bilingual `category.meta.json` per category.
413
+ - **Pattern 2 (category taxonomy)** — components live under exactly one category. Bilingual `category.meta.json` per category. The category list is the bare subpaths in the `exports` map, which is the authority; a count written here would be wrong at the next category added, as it was.
264
414
  - **Pattern 3 (Registry-gated)** — `registry.yaml` declares every component; CI gate ensures parity between source and registry.
265
415
  - **Pattern 4 (Streaming-hydration ready)** — components opt-in to MCP Apps streaming via `getStreamFor()` callbacks.
266
416
 
@@ -278,18 +428,40 @@ Consumer-driven i18n contract: components NEVER render raw alphabetic text as JS
278
428
  // <ProgressBar value={50} label="Uploading…" />
279
429
  ```
280
430
 
281
- ## Bundle sizes (v0.2.0)
431
+ ## Bundle sizes
282
432
 
283
- | Surface | gz |
284
- |---|---|
285
- | `@vantageos/mosaic-tokens/dist/index.js` | 649 B (87% under the 5 KB gate) |
286
- | `@vantageos/mosaic-tokens/src/tokens.css` | 560 B (81% under the 3 KB gate) |
287
- | Per-component subpath (React) | tree-shakeable, see size-limit.json |
433
+ Every figure below is the output of `pnpm --filter @vantageos/mosaic run size-limit`
434
+ against the built `dist/`, and the gates are the `limit` values in
435
+ `packages/mosaic/.size-limit.json`. Re-run the command rather than trusting this
436
+ table: a size written by hand is wrong at the next dependency bump.
437
+
438
+ | Surface | gz | gate |
439
+ |---|---|---|
440
+ | `dist/index.js` | 133.21 kB | 250 kB |
441
+ | `dist/react/forms.js` | 20.79 kB | 50 kB |
442
+ | `dist/preact/forms.js` | 20.79 kB | 50 kB |
443
+
444
+ Per-component subpaths are tree-shakeable; only the three surfaces above are gated.
288
445
 
289
446
  ## License
290
447
 
291
- MIT. © VantageOS / ElPi Corp.
448
+ **FSL-1.1-Apache-2.0** — Functional Source License, Version 1.1, Apache 2.0 Future
449
+ License. © VantageOS / ElPi Corp.
450
+
451
+ The full text ships in the published tarball as [`LICENSE`](./LICENSE), and
452
+ `package.json` carries `"license": "FSL-1.1-Apache-2.0"`, so a consumer receives the
453
+ terms with the package rather than having to find them.
454
+
455
+ This is the same licence as every sibling `@vantageos` package — verified against
456
+ what the registry actually serves for `@vantageos/mosaic-blocks`,
457
+ `@vantageos/mosaic-tokens` and `@vantageos/mcp-doctor`, not against a convention
458
+ recalled from elsewhere, and the file is byte-identical to theirs
459
+ (sha256 `3d458972e6e84e5d2361a886ef64b07aefdc38dd8955e281ea8c2ae8849646a4`).
460
+
461
+ Versions up to and including 0.3.1 shipped with no licence field and no licence
462
+ file. From 0.4.0 they are present.
292
463
 
293
464
  ## Changelog
294
465
 
295
- See repo root [`CHANGELOG.md`](../../CHANGELOG.md).
466
+ [`CHANGELOG.md`](./CHANGELOG.md), which ships inside the published tarball — the
467
+ repo-root file this used to point at is not what a consumer receives.