@seed-ship/mcp-ui-solid 6.22.0 → 6.24.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 (120) hide show
  1. package/CHANGELOG.md +219 -0
  2. package/README.md +209 -2
  3. package/dist/components/ChartJSRenderer.cjs +192 -66
  4. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  5. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  6. package/dist/components/ChartJSRenderer.js +192 -66
  7. package/dist/components/ChartJSRenderer.js.map +1 -1
  8. package/dist/components/DegradedFallback.cjs +34 -2
  9. package/dist/components/DegradedFallback.cjs.map +1 -1
  10. package/dist/components/DegradedFallback.d.ts +7 -0
  11. package/dist/components/DegradedFallback.d.ts.map +1 -1
  12. package/dist/components/DegradedFallback.js +34 -2
  13. package/dist/components/DegradedFallback.js.map +1 -1
  14. package/dist/components/MissingValue.cjs +43 -0
  15. package/dist/components/MissingValue.cjs.map +1 -0
  16. package/dist/components/MissingValue.d.ts +27 -0
  17. package/dist/components/MissingValue.d.ts.map +1 -0
  18. package/dist/components/MissingValue.js +43 -0
  19. package/dist/components/MissingValue.js.map +1 -0
  20. package/dist/components/UIResourceRenderer.cjs +250 -202
  21. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  22. package/dist/components/UIResourceRenderer.d.ts +2 -1
  23. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  24. package/dist/components/UIResourceRenderer.js +250 -202
  25. package/dist/components/UIResourceRenderer.js.map +1 -1
  26. package/dist/components/chart-data-table.cjs +30 -5
  27. package/dist/components/chart-data-table.cjs.map +1 -1
  28. package/dist/components/chart-data-table.d.ts +18 -1
  29. package/dist/components/chart-data-table.d.ts.map +1 -1
  30. package/dist/components/chart-data-table.js +30 -5
  31. package/dist/components/chart-data-table.js.map +1 -1
  32. package/dist/components/chart-missing-plugin.cjs +194 -0
  33. package/dist/components/chart-missing-plugin.cjs.map +1 -0
  34. package/dist/components/chart-missing-plugin.d.ts +127 -0
  35. package/dist/components/chart-missing-plugin.d.ts.map +1 -0
  36. package/dist/components/chart-missing-plugin.js +194 -0
  37. package/dist/components/chart-missing-plugin.js.map +1 -0
  38. package/dist/context/MCPUIConfigContext.cjs.map +1 -1
  39. package/dist/context/MCPUIConfigContext.d.ts +57 -28
  40. package/dist/context/MCPUIConfigContext.d.ts.map +1 -1
  41. package/dist/context/MCPUIConfigContext.js.map +1 -1
  42. package/dist/context/MCPUIMathContext.cjs +19 -0
  43. package/dist/context/MCPUIMathContext.cjs.map +1 -0
  44. package/dist/context/MCPUIMathContext.d.ts +18 -0
  45. package/dist/context/MCPUIMathContext.d.ts.map +1 -0
  46. package/dist/context/MCPUIMathContext.js +19 -0
  47. package/dist/context/MCPUIMathContext.js.map +1 -0
  48. package/dist/context/MCPUIStringsContext.cjs +12 -1
  49. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  50. package/dist/context/MCPUIStringsContext.d.ts +62 -0
  51. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  52. package/dist/context/MCPUIStringsContext.js +12 -1
  53. package/dist/context/MCPUIStringsContext.js.map +1 -1
  54. package/dist/index.cjs +2 -0
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +3 -1
  57. package/dist/index.d.ts +3 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +2 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/plugins/katex.cjs +23 -0
  62. package/dist/plugins/katex.cjs.map +1 -0
  63. package/dist/plugins/katex.d.cts +7 -0
  64. package/dist/plugins/katex.d.ts +7 -0
  65. package/dist/plugins/katex.d.ts.map +1 -0
  66. package/dist/plugins/katex.js +23 -0
  67. package/dist/plugins/katex.js.map +1 -0
  68. package/dist/services/component-registry.cjs +9 -2
  69. package/dist/services/component-registry.cjs.map +1 -1
  70. package/dist/services/component-registry.d.ts.map +1 -1
  71. package/dist/services/component-registry.js +9 -2
  72. package/dist/services/component-registry.js.map +1 -1
  73. package/dist/services/validation.cjs +31 -8
  74. package/dist/services/validation.cjs.map +1 -1
  75. package/dist/services/validation.d.ts.map +1 -1
  76. package/dist/services/validation.js +31 -8
  77. package/dist/services/validation.js.map +1 -1
  78. package/dist/types/index.d.ts +14 -2
  79. package/dist/types/index.d.ts.map +1 -1
  80. package/dist/utils/degraded-projections.cjs +19 -3
  81. package/dist/utils/degraded-projections.cjs.map +1 -1
  82. package/dist/utils/degraded-projections.d.ts +22 -2
  83. package/dist/utils/degraded-projections.d.ts.map +1 -1
  84. package/dist/utils/degraded-projections.js +19 -3
  85. package/dist/utils/degraded-projections.js.map +1 -1
  86. package/dist/utils/iframe-coep.cjs.map +1 -1
  87. package/dist/utils/iframe-coep.d.ts +7 -6
  88. package/dist/utils/iframe-coep.d.ts.map +1 -1
  89. package/dist/utils/iframe-coep.js.map +1 -1
  90. package/dist/utils/math-markdown.cjs +240 -0
  91. package/dist/utils/math-markdown.cjs.map +1 -0
  92. package/dist/utils/math-markdown.d.ts +16 -0
  93. package/dist/utils/math-markdown.d.ts.map +1 -0
  94. package/dist/utils/math-markdown.js +240 -0
  95. package/dist/utils/math-markdown.js.map +1 -0
  96. package/dist/utils/missing-value.cjs +60 -0
  97. package/dist/utils/missing-value.cjs.map +1 -0
  98. package/dist/utils/missing-value.d.ts +79 -0
  99. package/dist/utils/missing-value.d.ts.map +1 -0
  100. package/dist/utils/missing-value.js +60 -0
  101. package/dist/utils/missing-value.js.map +1 -0
  102. package/package.json +23 -4
  103. package/src/components/ChartJSRenderer.tsx +159 -6
  104. package/src/components/DegradedFallback.tsx +24 -2
  105. package/src/components/MissingValue.tsx +61 -0
  106. package/src/components/UIResourceRenderer.tsx +64 -10
  107. package/src/components/chart-data-table.ts +50 -6
  108. package/src/components/chart-missing-plugin.ts +334 -0
  109. package/src/context/MCPUIConfigContext.tsx +57 -28
  110. package/src/context/MCPUIMathContext.tsx +32 -0
  111. package/src/context/MCPUIStringsContext.tsx +76 -0
  112. package/src/index.ts +4 -0
  113. package/src/plugins/katex.ts +25 -0
  114. package/src/services/component-registry.ts +10 -2
  115. package/src/services/validation.ts +44 -9
  116. package/src/types/index.ts +13 -1
  117. package/src/utils/degraded-projections.ts +44 -4
  118. package/src/utils/iframe-coep.ts +7 -6
  119. package/src/utils/math-markdown.ts +222 -0
  120. package/src/utils/missing-value.ts +127 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,220 @@ 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
+ ## [6.24.0] - 2026-10-03
9
+
10
+ Explicit missing values in charts and tables, and a unit per chart — lot 1 of
11
+ the GeoAI display brief (`deposium_geoai/docs/handoffs/2026-10-03-mcp-ui-geoai-display-brief.md`).
12
+ Released with `@seed-ship/mcp-ui-spec` 5.7.0, whose chart schema accepts both;
13
+ the dependency floor moves to `^5.7.0`.
14
+
15
+ ### The rule
16
+
17
+ Absence is never zero. A `null` keeps its slot — its label stays on the axis,
18
+ its row stays in the table — and every view marks it, visibly and for screen
19
+ readers. Until now `validateComponent` refused any chart carrying a `null`, so
20
+ a host that drops invalid components (Deposium's chat does) showed nothing.
21
+
22
+ Only an explicit entry is a missing value: `null` (or a non-finite number, or a
23
+ point whose `y` is one). A dataset with no entry at all (`data: []`) is not —
24
+ it renders exactly as it did in 6.23.0.
25
+
26
+ ### Added
27
+
28
+ - **Charts accept `null`** in line, bar, radar, scatter and bubble datasets,
29
+ as a number slot or as a point's `y`:
30
+ - a line or a radar breaks at the missing value: `spanGaps: false` is set on
31
+ every dataset of a chart that has one, so nothing joins the neighbours,
32
+ and the date stays on the axis;
33
+ - a missing bar is drawn as a dashed outline at the baseline, so it cannot
34
+ be read as a zero bar (Chart.js draws nothing for both). Stacked or
35
+ overlaid bars get no outline — their baseline belongs to the series below,
36
+ so an outline there would point at the wrong series — and their note says
37
+ the values are not plotted;
38
+ - scatter and bubble charts leave the point out;
39
+ - a series whose every entry is missing keeps its legend entry, marked
40
+ `chartLegendNoData` ("(no data)"), and a chart with nothing to plot says
41
+ so in its plot area (`chartNoData`);
42
+ - a note under the chart explains how this chart shows a missing value
43
+ (`chartMissingGaps`, `chartMissingBars`, `chartMissingPoints`), shown when
44
+ a reader can actually see the mark, and names the series with no data
45
+ (`chartSeriesNoData`, joined with `Intl.ListFormat` in the strings
46
+ `locale`). The canvas's `aria-describedby` includes it. DOM hooks:
47
+ `[data-mcp-chart-notes]` and
48
+ `[data-mcp-chart-note="gaps|bars|omitted|no-data"]`;
49
+ - a tooltip names a missing bar (`missingValue`) instead of printing `0`.
50
+
51
+ Pie, doughnut and polar-area charts refuse `null`, as a bare value or as a
52
+ point's `y`, because Chart.js parses a missing share as `0` and would draw
53
+ every other share wrong. `validateChartComponent` reports
54
+ `MISSING_VALUE_UNSUPPORTED`. If a host bypasses validation, the renderer
55
+ shows its data table with `chartMissingUnsupported` instead of a misleading
56
+ chart.
57
+ - **`unit`** on chart params: the value-axis title (bar, line, scatter and
58
+ bubble, unless `options.scales` already sets one), the tooltip value
59
+ (`chartValueWithUnit`, except on scatter and bubble, whose value is a pair)
60
+ and the column headers of the data view and of the degraded table
61
+ (`chartLabelWithUnit`). `validateChartComponent` reports `INVALID_UNIT` for
62
+ a non-string unit or one longer than 32 characters; an empty string is
63
+ tolerated and means "no unit".
64
+ - **Tables.** A cell that already displayed as `-` — `null`, `undefined`, an
65
+ empty or whitespace-only string, `"undefined"` (after the backend's
66
+ `"X – undefined"` debris is stripped) or a literal `-` — is now muted,
67
+ hidden from assistive technology and followed by a visually-hidden
68
+ `missingValue`. A legend (`missingValueLegend`, `[data-mcp-missing-legend]`)
69
+ follows any table with such a cell. It is computed on every row, so it does
70
+ not flicker while paging. Missing cells sort last in both directions (an
71
+ empty string no longer sorts as `0`), a numeric column is detected from its
72
+ first present value, and search never matches a missing cell.
73
+ - **The chart data view and the degraded tables** mark missing cells the same
74
+ way, under the same legend. `chartToDataTable` and `chartToDegradedTable`
75
+ return an extra `missing` matrix shaped like `rows`, which still hold `''`.
76
+ One exception: the degraded table prints point data (scatter, bubble,
77
+ `{x, y}` series) as JSON, where `"y":null` reads as such, and does not mark
78
+ it. `DegradedFallback` takes an optional `missing` prop; the graph and map
79
+ degraded tables do not change.
80
+ - Ten `MCPUIStrings` keys (275 → 285), English defaults, French in the README
81
+ dictionary: `missingValue`, `missingValueLegend`, `chartMissingGaps`,
82
+ `chartMissingBars`, `chartMissingPoints`, `chartSeriesNoData`,
83
+ `chartLegendNoData`, `chartValueWithUnit`, `chartLabelWithUnit`,
84
+ `chartMissingUnsupported`.
85
+ - `examples/geoai-daily-forecast/`: a synthetic, clearly labelled GeoAI pilot
86
+ (daily table, one chart per variable, comparison, map, thresholds, sources
87
+ and limits) built with `createComparisonLayout`, `createGeographyLayout` and
88
+ `createEvidenceLayout`, with tests for the brief's validation criteria that a
89
+ library can check. Not published, since it is outside `files`;
90
+ type-checked by `pnpm typecheck` (`tsconfig.examples.json`).
91
+
92
+ ### Unchanged on purpose
93
+
94
+ - A chart with no missing entry and no unit builds exactly the 6.23.0
95
+ Chart.js config, and a table with no missing cell renders the 6.23.0 DOM
96
+ (both tested; the table against a capture taken before the change).
97
+ - `renderCellValue` still returns `-` for a missing value; it now shares its
98
+ `"X – undefined"` cleanup with the table marks, so both always agree. CSV
99
+ and TSV exports still write an empty field, JSON still writes `null`.
100
+ - Chart.js's default interaction (`nearest`, `intersect: true`) is kept. A
101
+ missing bar has no height, so its tooltip only appears in `index` mode.
102
+ - No time axis is introduced: daily dates are categorical ISO labels. The
103
+ opt-in QuickChart renderer receives the payload as before; the unit and the
104
+ missing-value outlines, notes and legend belong to the native renderer.
105
+
106
+ ### Changed — for code that reads these types
107
+
108
+ - `ChartComponentParams` datasets' `data` is now `Array<number | null>`, or
109
+ points whose `y` is `number | null`. Code that reads chart payloads under
110
+ `strictNullChecks` has to handle the missing value.
111
+ - `chartToDataTable` and `chartToDegradedTable` always return the extra
112
+ `missing` matrix; code asserting their exact shape sees one more key.
113
+ - `ChartDataTableLabels.withUnit` and `DegradedProjectionLabels.withUnit` are
114
+ new **optional** fields, so a complete labels table written for
115
+ 6.20.0–6.23.0 as an object literal still type-checks (a
116
+ `Record<keyof …, string>` does not, as with any new key).
117
+ `CHART_DATA_TABLE_LABELS` and `DEGRADED_PROJECTION_LABELS` are now typed
118
+ `Required<…>`.
119
+ - The `INVALID_DATA_TYPE` and `INVALID_POINT_DATA` messages now mention
120
+ `null`; the codes do not change.
121
+
122
+ ### For hosts
123
+
124
+ - **Deposium (consumer):** bump to 6.24.0 and add the ten keys to the French
125
+ dictionary; `satisfies Required<MCPUIStrings>` fails until they are there.
126
+ The visually-hidden text uses an inline style, not Tailwind's `sr-only`, so
127
+ the Tailwind configuration needs no change.
128
+ - **Producers:** emit `null` only in line, bar, radar, scatter and bubble
129
+ charts, never `0` for a missing value and never a shortened array. A
130
+ hand-copied schema mirror needs both `null` and `unit` (see the spec 5.7.0
131
+ changelog).
132
+
133
+ ## [6.23.0] - 2026-09-18
134
+
135
+ ### Added
136
+
137
+ - Opt-in `MCPUIMathProvider` and `MathRenderer` for markdown text and table
138
+ cells, including virtualized tables; separate optional `/plugins/katex`
139
+ adapter. No new required config fields and no KaTeX import from the root.
140
+ - Bounded dollar-delimited math parsing, currency guards, isolated Marked
141
+ instances, sanitized MathML-only rendering and literal fallback. Styles,
142
+ annotation subtrees, executable markup and trusted TeX commands stay blocked.
143
+ - SSR keeps escaped source until the existing client hydration upgrade;
144
+ original text/data is retained for copy and export. No CSS or font install.
145
+ - Optional KaTeX peer pinned to 0.18.4 to preserve Node 20 installation
146
+ compatibility; later 0.18.x releases currently require Node >=22.12 via
147
+ Commander. Cell links retain the existing new-tab, rel and styling policy.
148
+
149
+ ## [6.22.1] - 2026-09-18
150
+
151
+ Fixes a typing regression 6.22.0 introduced. Runtime behaviour is identical.
152
+
153
+ ### The mistake
154
+
155
+ 6.22.0 made every `MCPUIConfig` field optional so that adding a policy key
156
+ would stop breaking consumers who *author* a complete config. That fixed one
157
+ direction and broke the other: `MCPUIConfig` is a published type, so anyone who
158
+ *reads* one — `const policy: IframePolicy = config.iframePolicy` — suddenly
159
+ faced `… | undefined` and stopped compiling under `strictNullChecks`. Trading
160
+ one compile break for the opposite one is not a fix.
161
+
162
+ The two demands genuinely conflict, and one name cannot serve both: an author
163
+ must be allowed to omit a key, a reader must never meet `undefined`.
164
+
165
+ ### The fix — two names
166
+
167
+ - **`MCPUIConfigInput`** (new, exported): what a host writes. Every field
168
+ optional, so a new policy key is always additive. This is what
169
+ `MCPUIConfigProvider`'s `config` prop takes. Note it is NOT what
170
+ `MCPUIConfigContext.Provider` takes — see below.
171
+ - **`MCPUIConfig`**: what the library hands back — an interface extending
172
+ `Required<MCPUIConfigInput>`, every key present, which is the 6.21.0 meaning
173
+ restored. Keeping it an interface also preserves consumer extension and
174
+ declaration merging. `useMCPUIConfig()`, `DEFAULT_MCPUI_CONFIG` and
175
+ `MCPUIConfigContext` carry it.
176
+
177
+ `MCPUIConfigContext` keeps the resolved type on purpose: it is exported, so
178
+ `useContext(MCPUIConfigContext)` is a public read surface and typing it with the
179
+ input view would leave a direct reader on `IframePolicy | undefined` — the very
180
+ regression being undone. A host that bypasses `MCPUIConfigProvider` therefore
181
+ passes a complete value, `{ ...DEFAULT_MCPUI_CONFIG, ...mine }`; passing only
182
+ some keys is what the provider's `config` prop is for. `useMCPUIConfig()` still
183
+ merges at runtime, so an untyped host cannot break a renderer.
184
+
185
+ Derived rather than declared twice, so a key is written once and the two views
186
+ cannot drift. The guard test now pins **both** halves: every `MCPUIConfigInput`
187
+ field stays optional, and `MCPUIConfig` stays an interface extending
188
+ `Required<…>`. The contract typecheck also pins interface extension and
189
+ declaration merging. Both sides were verified to fail when their corresponding
190
+ regression is reintroduced.
191
+
192
+ ### How this got through
193
+
194
+ Through 6.22.0, `pnpm typecheck` ran the main `tsconfig.json` alone, and that
195
+ config excludes `**/*.test.ts(x)`: a type-level claim written in a test file
196
+ failed nothing. 6.22.0 shipped both a broken `MCPUIConfig` and a fixture
197
+ annotated with the wrong type, and neither `typecheck` nor `test` said a word.
198
+ `typecheck` now runs a second pass, `tsconfig.contracts.json`, which
199
+ type-checks the handful of test files whose `satisfies` and annotations *are*
200
+ the test. Including every test file instead would surface 53 unrelated errors,
201
+ so that stays a separate piece of work.
202
+
203
+ ### Upgrading
204
+
205
+ Reading is exactly as it was in 6.21.0, whether through `useMCPUIConfig()`,
206
+ `useContext(MCPUIConfigContext)` or a value typed `MCPUIConfig`.
207
+ `MCPUIConfigProvider`'s `config` prop still accepts everything it accepted
208
+ before, `Partial<MCPUIConfig>` included.
209
+
210
+ Two authoring patterns stop compiling, coming from 6.21.0 or from 6.22.0:
211
+
212
+ - **A config annotated `MCPUIConfig`.** From 6.22.0 a partial one fails; from
213
+ 6.21.0 a complete one fails too, since it lacks `chartZoom`. Annotate it
214
+ `MCPUIConfigInput` instead.
215
+ - **A value handed straight to `<MCPUIConfigContext.Provider>`**, annotated or
216
+ not: an inline literal fails the same way. Spread the defaults,
217
+ `{ ...DEFAULT_MCPUI_CONFIG, ...mine }`, or pass the keys to
218
+ `<MCPUIConfigProvider config={…}>`, which does that merge for you.
219
+
220
+ Both replacements keep compiling when the library adds its next policy key.
221
+
8
222
  ## [6.22.0] - 2026-09-18
9
223
 
10
224
  Wheel zoom and drag pan on the surfaces that could not be read when the content
@@ -50,6 +264,11 @@ expanded view only, and why inline zoom is opt-in.
50
264
 
51
265
  ### Changed
52
266
 
267
+ > **Superseded by 6.22.1.** Making the fields of the published `MCPUIConfig`
268
+ > optional broke every consumer that *reads* one. 6.22.1 moves the optional
269
+ > fields to a new `MCPUIConfigInput` and restores `MCPUIConfig` as the
270
+ > resolved type. The entry below describes 6.22.0 as published.
271
+
53
272
  - **Every `MCPUIConfig` field is now optional**, as `MCPUIStrings` has always
54
273
  been. `MCPUIConfig` grows a key in a minor release each time the library
55
274
  gains a policy switch — `iframeFallbackLink` in 6.21.0, `chartZoom` here —
package/README.md CHANGED
@@ -5,6 +5,75 @@ SolidJS components + chat toolkit for MCP-generated UI. Part of the [MCP UI ecos
5
5
  [![npm version](https://img.shields.io/npm/v/@seed-ship/mcp-ui-solid.svg)](https://www.npmjs.com/package/@seed-ship/mcp-ui-solid)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
+ ## What's New in v6.24.0
9
+
10
+ Explicit missing values in charts and tables, and a `unit` per chart. A `null`
11
+ in a line, bar, radar, scatter or bubble dataset is now accepted and shown as
12
+ missing, never as zero. Lines break, a missing bar gets a dashed outline, and a
13
+ note under the chart says how. Table cells with no value are announced to
14
+ screen readers and explained by a legend. Ten new `MCPUIStrings` keys; requires
15
+ `@seed-ship/mcp-ui-spec` 5.7.0. See
16
+ [Missing values and units in charts](#missing-values-and-units-in-charts-v6240).
17
+
18
+ ## What's New in v6.23.0
19
+
20
+ Opt-in LaTeX in markdown text components and table cells, with the same
21
+ currency guards and sanitized MathML output. No change without a provider;
22
+ `MCPUIConfig` is unchanged. See [Math rendering](#math-rendering).
23
+
24
+ ## Math rendering
25
+
26
+ Install the optional peer only if the host wants the supplied KaTeX adapter:
27
+
28
+ ```bash
29
+ pnpm add --save-exact katex@0.18.4
30
+ ```
31
+
32
+ ```tsx
33
+ import { MCPUIMathProvider, UIResourceRenderer } from '@seed-ship/mcp-ui-solid'
34
+ import { renderKatexMath } from '@seed-ship/mcp-ui-solid/plugins/katex'
35
+
36
+ <MCPUIMathProvider renderMath={renderKatexMath}>
37
+ <UIResourceRenderer content={resource} />
38
+ </MCPUIMathProvider>
39
+ ```
40
+
41
+ The adapter's optional peer is pinned to 0.18.4 to retain Node 20 installation
42
+ compatibility. KaTeX 0.18.5–0.18.7 pull in Commander 15 (Node >=22.12);
43
+ do not replace the pin with a caret range on Node 20.
44
+
45
+ The provider applies to `text` with `markdown: true` and string-valued table
46
+ cells, including virtualized rows. It recognizes `$...$` inline and `$$...$$`
47
+ display formulas in their own paragraph (not embedded mid-sentence).
48
+ Code spans/fences and link destinations are not formulas;
49
+ currency such as `$5` and `$5-$10` remains text. Malformed/incomplete formulas
50
+ degrade to their source. Copy/export continues to use the original data.
51
+ Dollar syntax can be ambiguous: `$5$` is a formula. Escape a literal dollar
52
+ as `\$` when composing currency and formulas; numeric math such as `$2 + 3$`
53
+ remains supported. Math-enabled cell links keep their existing new-tab behavior.
54
+
55
+ The adapter emits **MathML only**: no KaTeX stylesheet, fonts, CDN requests,
56
+ HTML layout or inline styles. Native MathML rendering varies by browser and
57
+ may differ from the chat's HTML+MathML layout. There is no automatic loading:
58
+ the explicit plugin import is the opt-in dependency boundary; hosts can load
59
+ that module before mounting the provider if they want a separate chunk.
60
+
61
+ Alternatively pass a synchronous `MathRenderer` function returning MathML
62
+ (`(tex, { displayMode }) => string | null`). The library still sanitizes it;
63
+ return `null` or throw to retain literal source. Passing `null` (or omitting
64
+ `renderMath`) on a nested provider disables inherited math. Arbitrary Marked
65
+ extensions and sanitizer overrides are deliberately not public API.
66
+
67
+ On the server, escaped source is emitted without calling the renderer. The
68
+ existing `SafeHtml` client upgrade renders sanitized formulas after hydration.
69
+ Processing is bounded to 100,000 source characters, 4,096 per expression and
70
+ 128 expressions per parse; oversized input stays escaped text. The adapter
71
+ also limits macro expansion and dimensions, disables trusted commands, and
72
+ does not share macro definitions across formulas. TeX annotation subtrees are
73
+ removed rather than exposing duplicate source after sanitization.
74
+
75
+ See the [design and integration handoff](./docs/briefs/LATEX-MATHML-2026-09-18.md).
76
+
8
77
  ## What's New in v6.21.0
9
78
 
10
79
  - **Iframes work under `Cross-Origin-Embedder-Policy: credentialless`.** Both
@@ -656,6 +725,73 @@ Requires `protomaps-leaflet` peer dependency.
656
725
  }} />
657
726
  ```
658
727
 
728
+ ## Missing values and units in charts (v6.24.0)
729
+
730
+ `null` in a chart dataset is an **explicit missing value**: the value belongs
731
+ to the series' grid (its label stays on the axis) but was not observed. Send
732
+ `null` rather than `0`, and rather than a shorter array, whenever a value is
733
+ absent; a series with no value at all is all `null`.
734
+
735
+ ```tsx
736
+ <UIResourceRenderer content={{
737
+ id: 'wind',
738
+ type: 'chart',
739
+ position: { colStart: 1, colSpan: 12 },
740
+ params: {
741
+ type: 'line',
742
+ title: 'Wind speed',
743
+ unit: 'km/h',
744
+ data: {
745
+ labels: ['2026-10-04', '2026-10-05', '2026-10-06'],
746
+ datasets: [
747
+ { label: 'Lyon', data: [18, null, 31] }, // a hole on day 2
748
+ { label: 'Brest', data: [null, null, null] }, // no value at all
749
+ ],
750
+ },
751
+ },
752
+ }} />
753
+ ```
754
+
755
+ | Chart type | A missing value is drawn as | Note under the chart |
756
+ |---|---|---|
757
+ | `line`, `radar` | a break in the line — never joined to its neighbours (`spanGaps: false` is forced) | `chartMissingGaps` |
758
+ | `bar` | a dashed outline at the baseline, so it cannot read as a zero bar | `chartMissingBars` |
759
+ | `scatter`, `bubble` | nothing: the point is not plotted | `chartMissingPoints` |
760
+ | `bar` with `stacked` scales | nothing: the baseline belongs to the series below, so no outline is drawn | `chartMissingPoints` |
761
+ | `pie`, `doughnut`, `polarArea` | refused: Chart.js would draw a missing share as `0` — validation reports `MISSING_VALUE_UNSUPPORTED` | — |
762
+
763
+ A series whose every entry is `null` keeps its legend entry, marked
764
+ `chartLegendNoData`, and is named in a `chartSeriesNoData` note. A chart with
765
+ nothing to plot says `chartNoData` in its plot area. Only an explicit entry is
766
+ a missing value: a dataset with no entry (`data: []`) renders as it always
767
+ did. The notes are linked to
768
+ the canvas through `aria-describedby`, and the **Data** view marks every
769
+ missing cell with the same `-` and screen-reader text as the table component,
770
+ under a `missingValueLegend` line.
771
+
772
+ `unit` (one per chart — split values with different units into separate
773
+ charts) titles the value axis unless `options.scales` already does, follows
774
+ each tooltip value (`chartValueWithUnit`), and joins the data-view column
775
+ headers (`chartLabelWithUnit`).
776
+
777
+ Daily series read best with ISO dates as plain category labels, as above:
778
+ `timeAxis` needs a Chart.js date adapter the host must register itself.
779
+
780
+ Tables follow the same rule: a cell that displays as `-` — `null`,
781
+ `undefined`, an empty or whitespace-only string, `"undefined"`, or a literal
782
+ `-` — shows a muted `-` with a visually-hidden `missingValue`, a
783
+ `missingValueLegend` line follows the table, missing cells sort last, and
784
+ search never matches them.
785
+
786
+ The outlines, notes, legend marks and `unit` belong to the native Chart.js
787
+ renderer. The opt-in QuickChart renderer receives the payload as is.
788
+
789
+ A synthetic, end-to-end example — daily table, one chart per variable,
790
+ comparison, map, thresholds, sources and limits, built with the three
791
+ presentation recipes — lives in
792
+ [`examples/geoai-daily-forecast/`](./examples/geoai-daily-forecast/) (not
793
+ published; its tests run with the package's).
794
+
659
795
  ## Chat Bus — Agent Interactions (`@experimental`)
660
796
 
661
797
  Bidirectional event/command system for agent-driven chat interactions. Your app keeps full control of its chat UI — the bus adds structured interactivity on top.
@@ -1029,6 +1165,48 @@ import { MCPUIConfigProvider } from '@seed-ship/mcp-ui-solid'
1029
1165
  </MCPUIConfigProvider>
1030
1166
  ```
1031
1167
 
1168
+ When you keep a partial configuration in a variable, name the authored,
1169
+ all-optional view explicitly:
1170
+
1171
+ ```tsx
1172
+ import {
1173
+ MCPUIConfigProvider,
1174
+ type MCPUIConfigInput,
1175
+ } from '@seed-ship/mcp-ui-solid'
1176
+
1177
+ const config: MCPUIConfigInput = {
1178
+ iframeCredentialless: 'always',
1179
+ chartZoom: 'never',
1180
+ }
1181
+
1182
+ <MCPUIConfigProvider config={config}>
1183
+ <App />
1184
+ </MCPUIConfigProvider>
1185
+ ```
1186
+
1187
+ Prefer `MCPUIConfigProvider` for partial overrides. The exported raw context is
1188
+ also a public read surface, so its `Provider` requires the complete, resolved
1189
+ `MCPUIConfig` value. If an integration must provide that context directly,
1190
+ spread the defaults first:
1191
+
1192
+ ```tsx
1193
+ import {
1194
+ DEFAULT_MCPUI_CONFIG,
1195
+ MCPUIConfigContext,
1196
+ } from '@seed-ship/mcp-ui-solid'
1197
+
1198
+ <MCPUIConfigContext.Provider
1199
+ value={{ ...DEFAULT_MCPUI_CONFIG, iframeCredentialless: 'always' }}
1200
+ >
1201
+ <App />
1202
+ </MCPUIConfigContext.Provider>
1203
+ ```
1204
+
1205
+ `MCPUIConfigInput` contains the library's built-in authoring fields.
1206
+ `MCPUIConfig` remains an interface for consumers that extend or augment the
1207
+ resolved contract; `MCPUIConfigProvider` also accepts those augmented fields
1208
+ as partial inline overrides.
1209
+
1032
1210
  | Option | Values | Default | What it does |
1033
1211
  |--------|--------|---------|--------------|
1034
1212
  | `iframeCredentialless` | `'auto'` \| `'always'` \| `'never'` | `'auto'` | `'auto'`: the attribute on the `iframe` component when its host is **not** in `TRUSTED_IFRAME_DOMAINS` (+ `customTrustedIframeDomains`) — those frames already run without cookies. The video embed is excluded, since it has none of that sandboxing. `'always'`: every iframe, video and trusted hosts included — what a COEP host wants. `'never'`: none. |
@@ -1429,6 +1607,20 @@ formatMCPUIString('Export CSV ({count} rows)', { count: 42 }) // 'Export CSV (42
1429
1607
  | `sizeBytes` | `{size} B` | `ArtifactRenderer` — file size below 1 KB (template) |
1430
1608
  | `sizeKilobytes` | `{size} KB` | `ArtifactRenderer` — file size in kilobytes (template) |
1431
1609
  | `sizeMegabytes` | `{size} MB` | `ArtifactRenderer` — file size in megabytes (template) |
1610
+ | `zoomIn` | `Zoom in` | `LightboxOverlay`, `ChartJSRenderer` — zoom-in button `title`/`aria-label` (v6.22.0) |
1611
+ | `zoomOut` | `Zoom out` | `LightboxOverlay`, `ChartJSRenderer` — zoom-out button `title`/`aria-label` (v6.22.0) |
1612
+ | `zoomReset` | `Reset zoom` | `LightboxOverlay`, `ChartJSRenderer` — reset button, shown while zoomed (v6.22.0) |
1613
+ | `zoomLevel` | `Zoom {percent}%` | `LightboxOverlay` — zoom-level readout (template, v6.22.0) |
1614
+ | `missingValue` | `Missing value` | Tables, chart data view, degraded tables — screen-reader text of a missing cell; tooltip value of a missing bar (v6.24.0) |
1615
+ | `missingValueLegend` | `“{marker}” marks a missing value.` | Legend under a table or data view with a missing cell (template, `{marker}` = `-`, v6.24.0) |
1616
+ | `chartMissingGaps` | `Breaks in a line mark missing values.` | `ChartJSRenderer` — note under a line or radar chart with a missing value (v6.24.0) |
1617
+ | `chartMissingBars` | `Dashed outlines mark missing values.` | `ChartJSRenderer` — note under a bar chart with a missing value (v6.24.0) |
1618
+ | `chartMissingPoints` | `Missing values are not plotted.` | `ChartJSRenderer` — note under a scatter, bubble or stacked bar chart with a missing value (v6.24.0) |
1619
+ | `chartSeriesNoData` | `No data for {series}.` | `ChartJSRenderer` — note naming the series with no value at all (template, `{series}` joined as a list in `locale`, v6.24.0) |
1620
+ | `chartLegendNoData` | `{series} (no data)` | `ChartJSRenderer` — legend entry of a series with no value at all (template, v6.24.0) |
1621
+ | `chartValueWithUnit` | `{value} {unit}` | `ChartJSRenderer` — tooltip value followed by the chart `unit` (template, v6.24.0) |
1622
+ | `chartLabelWithUnit` | `{label} ({unit})` | Chart data view and degraded chart tables — column header followed by the chart `unit` (template, v6.24.0) |
1623
+ | `chartMissingUnsupported` | `This chart type cannot show missing values.` | `ChartJSRenderer` — shown instead of a pie, doughnut or polar-area chart that received a missing value (v6.24.0) |
1432
1624
 
1433
1625
  ### `locale` — number, date and sort formatting (default change in v6.20.0)
1434
1626
 
@@ -1766,6 +1958,20 @@ const fr: MCPUIStrings = {
1766
1958
  sizeBytes: '{size} o',
1767
1959
  sizeKilobytes: '{size} Ko',
1768
1960
  sizeMegabytes: '{size} Mo',
1961
+ zoomIn: 'Zoom avant',
1962
+ zoomOut: 'Zoom arrière',
1963
+ zoomReset: 'Réinitialiser le zoom',
1964
+ zoomLevel: 'Zoom {percent} %',
1965
+ missingValue: 'Valeur manquante',
1966
+ missingValueLegend: '« {marker} » signale une valeur manquante.',
1967
+ chartMissingGaps: 'Une interruption de la courbe signale une valeur manquante.',
1968
+ chartMissingBars: 'Un contour en pointillés signale une valeur manquante.',
1969
+ chartMissingPoints: 'Les valeurs manquantes ne sont pas tracées.',
1970
+ chartSeriesNoData: 'Aucune donnée pour {series}.',
1971
+ chartLegendNoData: '{series} (aucune donnée)',
1972
+ chartValueWithUnit: '{value} {unit}',
1973
+ chartLabelWithUnit: '{label} ({unit})',
1974
+ chartMissingUnsupported: 'Ce type de graphique ne peut pas afficher de valeurs manquantes.',
1769
1975
  }
1770
1976
 
1771
1977
  <MCPUIStringsProvider strings={fr}><App /></MCPUIStringsProvider>
@@ -1800,8 +2006,8 @@ exported default table:
1800
2006
  | `adapters/connector.ts` (`/adapters`) | `connectorResultToUILayout` | `options.messages?: Partial<ConnectorAdapterMessages>` | `DEFAULT_CONNECTOR_MESSAGES` | `degradedNotice`, `degradedVersionSuffix`, `versionWarning` |
1801
2007
  | `adapters/macro-run.ts` (`/adapters`) | `macroRunToScratchpadState`, `macroInterrogationToChatPromptConfig` | `options.messages?: Partial<MacroRunAdapterMessages>` | `DEFAULT_MACRO_RUN_MESSAGES` | `agentSectionTitle`, `progressSectionTitle`, `resultSectionTitle`, `runAborted`, `runFailed`, `confirmDefault` |
1802
2008
  | `services/validation.ts` (root, `/validation`) | `validateFieldValue`, `validateFormData` | `messages?: Partial<FormValidationMessages>` | `DEFAULT_VALIDATION_MESSAGES` | `required`, `mustBeChecked`, `minLength`, `maxLength`, `invalidPattern`, `invalidEmail`, `invalidNumber`, `minValue`, `maxValue`, `minDate`, `maxDate`, `invalidOption`, `invalidFormat` |
1803
- | `utils/degraded-projections.ts` (root) | `graphToDegradedTable`, `mapToDegradedTable`, `chartToDegradedTable` | `labels?: Partial<DegradedProjectionLabels>` | `DEGRADED_PROJECTION_LABELS` | `source`, `target`, `label`, `node`, `type`, `lat`, `lng`, `info`, `marker`, `feature`, `series` |
1804
- | `components/chart-data-table.ts` (root) | `chartToDataTable` | `labels?: Partial<ChartDataTableLabels>` | `CHART_DATA_TABLE_LABELS` | `series`, `point`, `label`, `seriesName`, `x`, `y`, `r` |
2009
+ | `utils/degraded-projections.ts` (root) | `graphToDegradedTable`, `mapToDegradedTable`, `chartToDegradedTable` | `labels?: Partial<DegradedProjectionLabels>` | `DEGRADED_PROJECTION_LABELS` | `source`, `target`, `label`, `node`, `type`, `lat`, `lng`, `info`, `marker`, `feature`, `series`, `withUnit` (optional, v6.24.0: `{label} ({unit})`, chart headers when the chart has a unit) |
2010
+ | `components/chart-data-table.ts` (root) | `chartToDataTable` | `labels?: Partial<ChartDataTableLabels>` | `CHART_DATA_TABLE_LABELS` | `series`, `point`, `label`, `seriesName`, `x`, `y`, `r`, `withUnit` (optional, v6.24.0: `{label} ({unit})`, value headers when the chart has a unit) |
1805
2011
  | `hooks/useStreamingUI.ts` (root, `/hooks`) | `useStreamingUI` | `options.messages?: Partial<StreamingUIMessages>` | `DEFAULT_STREAMING_UI_MESSAGES` | `initializing`, `connecting`, `loadingComponent`, `dashboardLoaded`, `errorProgress`, `connectionFailed`, `requestFailed`, `emptyResponse`, `serverSide`, `unknownError` |
1806
2012
 
1807
2013
  Every option is a trailing OPTIONAL parameter, so existing calls keep
@@ -1966,6 +2172,7 @@ import type {
1966
2172
  IframePolicy, ValidationOptions,
1967
2173
  CitationCtx, CitationEntry, DuplicateMountInfo, DuplicateMountReporter,
1968
2174
  MCPUIStrings, MCPUIConfig, // v6.21.0
2175
+ MCPUIConfigInput, // v6.22.1 — the authored, all-optional view
1969
2176
  TelemetryEvent, TelemetrySink, TelemetryOptions, TelemetryDispatcher,
1970
2177
  DataValidation, HallucinatedNumber, DataValidationOptions,
1971
2178
  VerifiedTextContent, DataPreviewContent, MapSectionContent,