@vantageos/mosaic 0.4.0 → 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 (150) hide show
  1. package/CHANGELOG.md +39 -15
  2. package/README.md +114 -5
  3. package/dist/PipelineBoard-FucyGArR.d.cts +759 -0
  4. package/dist/PipelineBoard-FucyGArR.d.ts +759 -0
  5. package/dist/PipelineBoard.markdown-DIcvrS-2.d.cts +16 -0
  6. package/dist/PipelineBoard.markdown-DIcvrS-2.d.ts +16 -0
  7. package/dist/{StatusBadge.schema-1tOjB4Ir.d.cts → StatusBadge.schema-BEjSH2_4.d.cts} +2 -2
  8. package/dist/{StatusBadge.schema-1tOjB4Ir.d.ts → StatusBadge.schema-BEjSH2_4.d.ts} +2 -2
  9. package/dist/{Tabs.schema-BzcEyeq8.d.cts → Tabs.schema-Dqr6vwV3.d.cts} +2 -2
  10. package/dist/{Tabs.schema-BzcEyeq8.d.ts → Tabs.schema-Dqr6vwV3.d.ts} +2 -2
  11. package/dist/Timeline-Coo_3tA_.d.cts +114 -0
  12. package/dist/Timeline-Coo_3tA_.d.ts +114 -0
  13. package/dist/Timeline.markdown-C6UPDv1H.d.cts +4 -0
  14. package/dist/Timeline.markdown-C6UPDv1H.d.ts +4 -0
  15. package/dist/TransactionPreview-BzWDn8kE.d.cts +391 -0
  16. package/dist/TransactionPreview-BzWDn8kE.d.ts +391 -0
  17. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.cts +7 -0
  18. package/dist/TransactionPreview.markdown-Dpb0VYgN.d.ts +7 -0
  19. package/dist/artifacts.cjs +186 -2
  20. package/dist/artifacts.cjs.map +1 -1
  21. package/dist/artifacts.js +186 -2
  22. package/dist/artifacts.js.map +1 -1
  23. package/dist/confirmation.cjs +724 -2
  24. package/dist/confirmation.cjs.map +1 -1
  25. package/dist/confirmation.d.cts +2 -1
  26. package/dist/confirmation.d.ts +2 -1
  27. package/dist/confirmation.js +722 -4
  28. package/dist/confirmation.js.map +1 -1
  29. package/dist/display.cjs +1551 -203
  30. package/dist/display.cjs.map +1 -1
  31. package/dist/display.d.cts +6 -229
  32. package/dist/display.d.ts +6 -229
  33. package/dist/display.js +1535 -206
  34. package/dist/display.js.map +1 -1
  35. package/dist/forms.cjs +139 -46
  36. package/dist/forms.cjs.map +1 -1
  37. package/dist/forms.d.cts +2 -2
  38. package/dist/forms.d.ts +2 -2
  39. package/dist/forms.js +139 -46
  40. package/dist/forms.js.map +1 -1
  41. package/dist/host.cjs +822 -0
  42. package/dist/host.cjs.map +1 -0
  43. package/dist/host.d.cts +287 -0
  44. package/dist/host.d.ts +287 -0
  45. package/dist/host.js +794 -0
  46. package/dist/host.js.map +1 -0
  47. package/dist/{index-CwXVgtGb.d.cts → index-BadRcCbm.d.cts} +28 -11
  48. package/dist/{index-CwXVgtGb.d.ts → index-BadRcCbm.d.ts} +28 -11
  49. package/dist/index.cjs +2198 -247
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +42 -8
  52. package/dist/index.d.ts +42 -8
  53. package/dist/index.js +2163 -251
  54. package/dist/index.js.map +1 -1
  55. package/dist/input.d.cts +2 -2
  56. package/dist/input.d.ts +2 -2
  57. package/dist/media.cjs +186 -2
  58. package/dist/media.cjs.map +1 -1
  59. package/dist/media.d.cts +1 -1
  60. package/dist/media.d.ts +1 -1
  61. package/dist/media.js +186 -2
  62. package/dist/media.js.map +1 -1
  63. package/dist/preact/artifacts.js +186 -2
  64. package/dist/preact/artifacts.js.map +1 -1
  65. package/dist/preact/confirmation.d.ts +321 -1
  66. package/dist/preact/confirmation.js +625 -4
  67. package/dist/preact/confirmation.js.map +1 -1
  68. package/dist/preact/display.d.ts +544 -47
  69. package/dist/preact/display.js +1190 -126
  70. package/dist/preact/display.js.map +1 -1
  71. package/dist/preact/forms.d.ts +32 -15
  72. package/dist/preact/forms.js +139 -46
  73. package/dist/preact/forms.js.map +1 -1
  74. package/dist/preact/host.d.ts +307 -0
  75. package/dist/preact/host.js +875 -0
  76. package/dist/preact/host.js.map +1 -0
  77. package/dist/preact/media.d.ts +37 -1
  78. package/dist/preact/media.js +423 -0
  79. package/dist/preact/media.js.map +1 -1
  80. package/dist/preact/progress.d.ts +92 -1
  81. package/dist/preact/progress.js +465 -3
  82. package/dist/preact/progress.js.map +1 -1
  83. package/dist/progress.cjs +521 -2
  84. package/dist/progress.cjs.map +1 -1
  85. package/dist/progress.d.cts +2 -1
  86. package/dist/progress.d.ts +2 -1
  87. package/dist/progress.js +518 -3
  88. package/dist/progress.js.map +1 -1
  89. package/dist/react/artifacts.cjs +186 -2
  90. package/dist/react/artifacts.cjs.map +1 -1
  91. package/dist/react/artifacts.js +186 -2
  92. package/dist/react/artifacts.js.map +1 -1
  93. package/dist/react/confirmation.cjs +626 -2
  94. package/dist/react/confirmation.cjs.map +1 -1
  95. package/dist/react/confirmation.d.cts +3 -3
  96. package/dist/react/confirmation.d.ts +3 -3
  97. package/dist/react/confirmation.js +625 -4
  98. package/dist/react/confirmation.js.map +1 -1
  99. package/dist/react/display.cjs +1076 -123
  100. package/dist/react/display.cjs.map +1 -1
  101. package/dist/react/display.d.cts +13 -27
  102. package/dist/react/display.d.ts +13 -27
  103. package/dist/react/display.js +1064 -126
  104. package/dist/react/display.js.map +1 -1
  105. package/dist/react/forms.cjs +139 -46
  106. package/dist/react/forms.cjs.map +1 -1
  107. package/dist/react/forms.d.cts +1 -1
  108. package/dist/react/forms.d.ts +1 -1
  109. package/dist/react/forms.js +139 -46
  110. package/dist/react/forms.js.map +1 -1
  111. package/dist/react/host.cjs +910 -0
  112. package/dist/react/host.cjs.map +1 -0
  113. package/dist/react/host.d.cts +24 -0
  114. package/dist/react/host.d.ts +24 -0
  115. package/dist/react/host.js +875 -0
  116. package/dist/react/host.js.map +1 -0
  117. package/dist/react/input.d.cts +1 -1
  118. package/dist/react/input.d.ts +1 -1
  119. package/dist/react/media.cjs +426 -0
  120. package/dist/react/media.cjs.map +1 -1
  121. package/dist/react/media.d.cts +19 -1
  122. package/dist/react/media.d.ts +19 -1
  123. package/dist/react/media.js +423 -0
  124. package/dist/react/media.js.map +1 -1
  125. package/dist/react/progress.cjs +467 -2
  126. package/dist/react/progress.cjs.map +1 -1
  127. package/dist/react/progress.d.cts +1 -1
  128. package/dist/react/progress.d.ts +1 -1
  129. package/dist/react/progress.js +465 -3
  130. package/dist/react/progress.js.map +1 -1
  131. package/dist/server/resource.cjs +151 -0
  132. package/dist/server/resource.cjs.map +1 -0
  133. package/dist/server/resource.d.cts +90 -0
  134. package/dist/server/resource.d.ts +90 -0
  135. package/dist/server/resource.js +147 -0
  136. package/dist/server/resource.js.map +1 -0
  137. package/dist/server.cjs +1100 -25
  138. package/dist/server.cjs.map +1 -1
  139. package/dist/server.d.cts +84 -5
  140. package/dist/server.d.ts +84 -5
  141. package/dist/server.js +1087 -27
  142. package/dist/server.js.map +1 -1
  143. package/package.json +250 -233
  144. package/registry.yaml +44 -2
  145. package/dist/Alert.schema-LPJE5gFP.d.cts +0 -71
  146. package/dist/Alert.schema-LPJE5gFP.d.ts +0 -71
  147. package/dist/ProgressBar.schema-CdVYx0Wq.d.cts +0 -22
  148. package/dist/ProgressBar.schema-CdVYx0Wq.d.ts +0 -22
  149. package/dist/{ConfirmDialog-B3CQQGmJ.d.cts → ConfirmDialog-BCtOcmHe.d.cts} +1 -1
  150. package/dist/{ConfirmDialog-B3CQQGmJ.d.ts → ConfirmDialog-BCtOcmHe.d.ts} +1 -1
package/CHANGELOG.md CHANGED
@@ -9,22 +9,24 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
- ### Added
13
- - **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
- - **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
- - 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
- - 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
- - 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+).
12
+ ---
19
13
 
20
- ### Fixed
21
- - CI Gate 2 (PeerDeps optionality smoke) now runs GREEN (unblocks v0.3.0 GA publish gate). Two layered bugs, both packaging-scope:
22
- - **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
- - **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/*`.
24
- - CI `biome check --error-on-warnings` now exits 0 on `main` (unblocks v0.3.0 GA publish gate): suppressed 6 `noNonNullAssertion` lint errors in VirtualList tests (React + Preact) with justified `biome-ignore` comments matching repo convention, plus format/organizeImports auto-fixes across 7 files.
25
- - `forms/Select.tsx` (React + Preact) biome a11y gate: replaced the blanket `biome.json` per-file rule override with scoped, justified `biome-ignore` comments on exactly the 6 flagged ARIA roles per runtime (`useSemanticElements` on the `combobox`/`listbox`/`option` roles, `noNoninteractiveElementToInteractiveRole` on `ul`→`listbox` + `li`→`option`, `useFocusableInteractive` on the `li`). These are biome heuristic false-positives on the WAI-ARIA APG combobox + listbox pattern (options managed via `aria-activedescendant`, intentionally not in the tab order). Comments mirror the existing `noAutofocus` APG-pattern suppression; no JSX/logic changed. Added `tests/a11y/Select.spec.ts` (axe-core) proving 0 violations on the pattern.
26
- - CI `playwright-axe` step now runs GREEN (unblocks v0.3.0 GA `npm-publish` gate, which is `needs: ci`). Root cause from real CI logs: the runner ships no browsers, so every a11y spec died at `browserType.launch` ("Executable doesn't exist"). Fixes: (1) added a `playwright install --with-deps chromium` step before `playwright-axe` in `.github/workflows/ci.yml`; (2) normalized the stale `/storybook/?story=` spec URLs (StatusBadge, MarkdownRenderer, ConfirmDialog, TokenDisplayOnceModal, ProgressBar) to the working `/iframe.html?id=` convention used by `Select.spec.ts`, corrected three wrong story-id slugs (`media-statusbadge--error-state`, `artifacts-markdownrenderer--error-state`, `progress-progressbar--default`), and scoped axe to `#storybook-root` so the Storybook iframe shell's page-level best-practice rules no longer mask component results; (3) `TableView.spec.ts` now waits via `getByRole("table")` (matches the native `<table>` implicit role; the CSS `[role="table"]` selector never matched) and scopes axe to `#storybook-root`.
27
- - `display/TableView.tsx`: fixed a real axe violation (`scrollable-region-focusable`, WCAG 2.1.1 / 2.1.3, serious) — the virtualized scroll viewport is scrollable but was not keyboard-operable. Added `tabIndex={0}` + `role="region"` + `aria-label` (justified `biome-ignore` comments matching repo convention). Full `tests/a11y/` suite now passes 29/29 with axe 0 violations under `CI=true` against the Playwright `webServer`.
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`).
28
30
 
29
31
  ---
30
32
 
@@ -88,6 +90,28 @@ the items above are the entire delta a consumer receives — derived by locating
88
90
  the version to `0.3.1` and listing what touched `packages/mosaic` after it, not by reading a date
89
91
  window (a date window includes the release commits themselves and reported 19).
90
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
+
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.
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`).
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`).
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.
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`.
103
+
104
+ #### Fixed
105
+ - CI Gate 2 (PeerDeps optionality smoke) now runs GREEN (unblocks v0.3.0 GA publish gate). Two layered bugs, both packaging-scope:
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.
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/*`.
108
+ - CI `biome check --error-on-warnings` now exits 0 on `main` (unblocks v0.3.0 GA publish gate): suppressed 6 `noNonNullAssertion` lint errors in VirtualList tests (React + Preact) with justified `biome-ignore` comments matching repo convention, plus format/organizeImports auto-fixes across 7 files.
109
+ - `forms/Select.tsx` (React + Preact) biome a11y gate: replaced the blanket `biome.json` per-file rule override with scoped, justified `biome-ignore` comments on exactly the 6 flagged ARIA roles per runtime (`useSemanticElements` on the `combobox`/`listbox`/`option` roles, `noNoninteractiveElementToInteractiveRole` on `ul`→`listbox` + `li`→`option`, `useFocusableInteractive` on the `li`). These are biome heuristic false-positives on the WAI-ARIA APG combobox + listbox pattern (options managed via `aria-activedescendant`, intentionally not in the tab order). Comments mirror the existing `noAutofocus` APG-pattern suppression; no JSX/logic changed. Added `tests/a11y/Select.spec.ts` (axe-core) proving 0 violations on the pattern.
110
+ - CI `playwright-axe` step now runs GREEN (unblocks v0.3.0 GA `npm-publish` gate, which is `needs: ci`). Root cause from real CI logs: the runner ships no browsers, so every a11y spec died at `browserType.launch` ("Executable doesn't exist"). Fixes: (1) added a `playwright install --with-deps chromium` step before `playwright-axe` in `.github/workflows/ci.yml`; (2) normalized the stale `/storybook/?story=` spec URLs (StatusBadge, MarkdownRenderer, ConfirmDialog, TokenDisplayOnceModal, ProgressBar) to the working `/iframe.html?id=` convention used by `Select.spec.ts`, corrected three wrong story-id slugs (`media-statusbadge--error-state`, `artifacts-markdownrenderer--error-state`, `progress-progressbar--default`), and scoped axe to `#storybook-root` so the Storybook iframe shell's page-level best-practice rules no longer mask component results; (3) `TableView.spec.ts` now waits via `getByRole("table")` (matches the native `<table>` implicit role; the CSS `[role="table"]` selector never matched) and scopes axe to `#storybook-root`.
111
+ - `display/TableView.tsx`: fixed a real axe violation (`scrollable-region-focusable`, WCAG 2.1.1 / 2.1.3, serious) — the virtualized scroll viewport is scrollable but was not keyboard-operable. Added `tabIndex={0}` + `role="region"` + `aria-label` (justified `biome-ignore` comments matching repo convention). Full `tests/a11y/` suite now passes 29/29 with axe 0 violations under `CI=true` against the Playwright `webServer`.
112
+
113
+ ---
114
+
91
115
  ---
92
116
 
93
117
  ## [0.3.1] — 2026-06-13 — hotfix: workspace:* leak in published 0.3.0 manifest (Path B — root-cause fix)
package/README.md CHANGED
@@ -6,10 +6,10 @@ Fleet-wide MCP UI design system. Zod-validated, taxonomy-organized (7 categories
6
6
 
7
7
  ```sh
8
8
  # React 19
9
- npm install @vantageos/mosaic@^0.4.0 @vantageos/mosaic-tokens@^0.7.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.4.0 @vantageos/mosaic-tokens@^0.7.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.
@@ -38,6 +38,9 @@ This constraint is codified locally via the `build` script (the `NODE_OPTIONS` p
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
 
@@ -208,6 +211,112 @@ const resource = createMosaicResource({
208
211
  ```
209
212
 
210
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
+
211
320
  ## Components
212
321
 
213
322
  ### What each subpath actually exports
@@ -223,10 +332,10 @@ node -e '...' # see the release PR for the exact script
223
332
  | Subpath | Exports |
224
333
  |---|---|
225
334
  | `forms` | Checkbox, ErrorDisplay, FieldArray, FormField, FormProvider, Input, MultiSelect, RadioGroup, Select, SubmitButton, Textarea |
226
- | `display` | Badge, EmptyState, Skeleton, StatusBadge, StreamingTableView, TableView, VirtualList |
227
- | `progress` | ProgressBar |
335
+ | `display` | BalanceCard, Badge, EmptyState, MessageFeed, PipelineBoard, Skeleton, StatCard, StatusBadge, StreamingTableView, TableView, VirtualList |
336
+ | `progress` | ProgressBar, Timeline |
228
337
  | `input` | Tabs |
229
- | `confirmation` | Alert, ConfirmDialog, ConfirmModal, TokenDisplayOnceModal |
338
+ | `confirmation` | Alert, ConfirmDialog, ConfirmModal, TokenDisplayOnceModal, TransactionPreview |
230
339
  | `artifacts` | MarkdownRenderer |
231
340
  | `media` | *(none)* |
232
341