@ethisyscore/plugin-ui 1.110.0 → 1.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethisyscore/plugin-ui",
3
- "version": "1.110.0",
3
+ "version": "1.112.0",
4
4
  "description": "Plugin-UI umbrella SDK: client bridge + a11y/l10n primitives + brokered-MCP client (WI 4858).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -27,6 +27,10 @@
27
27
  "import": "./dist/token/index.js",
28
28
  "require": "./dist/token/index.cjs"
29
29
  },
30
+ "./markdown": {
31
+ "types": "./dist/markdown/index.d.ts",
32
+ "import": "./dist/markdown/index.js"
33
+ },
30
34
  "./platform-react": {
31
35
  "types": "./dist/platform-react/index.d.ts",
32
36
  "import": "./dist/platform-react/index.js",
@@ -38,6 +42,7 @@
38
42
  "require": "./dist/tailwind-preset/index.cjs"
39
43
  },
40
44
  "./plugin-base.css": "./styles/plugin-base.css",
45
+ "./markdown.css": "./styles/markdown.css",
41
46
  "./icons": {
42
47
  "types": "./dist/icons/index.d.ts",
43
48
  "import": "./dist/icons/index.js",
@@ -99,7 +104,13 @@
99
104
  "dependencies": {
100
105
  "@ethisyscore/components-react": "^1.69.1",
101
106
  "@ethisyscore/core-utils": "^1.69.1",
102
- "@ethisyscore/extension-runtime": "^1.69.1"
107
+ "@ethisyscore/extension-runtime": "^1.69.1",
108
+ "hast-util-sanitize": "^5.0.2",
109
+ "react-markdown": "^10.1.0",
110
+ "rehype-sanitize": "^6.0.0",
111
+ "remark-breaks": "^4.0.0",
112
+ "remark-gfm": "^4.0.1",
113
+ "unified": "^11.0.5"
103
114
  },
104
115
  "peerDependencies": {
105
116
  "@mui/material": ">=5",
@@ -0,0 +1,284 @@
1
+ /*
2
+ * @ethisyscore/plugin-ui — typography for markdown rendered by `MarkdownView`.
3
+ *
4
+ * USAGE — THERE ARE TWO SANCTIONED IMPORT FORMS, ONE PER CONTEXT.
5
+ *
6
+ * In the HOST app, which owns the document, import it bare:
7
+ *
8
+ * @import "@ethisyscore/plugin-ui/markdown.css";
9
+ *
10
+ * In a PLUGIN, import it into the auto-scoped layer, next to the base stylesheet:
11
+ *
12
+ * @import "@ethisyscore/plugin-ui/plugin-base.css";
13
+ * @import "@ethisyscore/plugin-ui/markdown.css" layer(utilities);
14
+ *
15
+ * WHY THE PLUGIN FORM DIFFERS, AND WHY A BARE IMPORT THERE FAILS THE BUILD. Every
16
+ * selector in this file targets a caller-applied class (`.ethisys-md`), not a plugin
17
+ * root — it has to, because the host app uses the same file and has no
18
+ * `.ehx-plugin-root`. A plugin's compiled CSS is injected into the HOST <head>, so
19
+ * the SDK's `ethisys-scope-plugin-css` guard rejects any selector that is not scoped
20
+ * to a plugin root, and a bare import here fails the build with 31 leaking selectors.
21
+ * `layer(utilities)` is the SDK's documented adoption path for a stylesheet a plugin
22
+ * wants to keep (see `vendor-css.mjs` in any plugin UI): the scoping pass prefixes
23
+ * every selector in that layer with
24
+ * `:where(.ehx-plugin-root, [data-ehx-plugin-portal])` at ZERO added specificity, so
25
+ * the rules ship scoped inside PLUGIN_CSS and the cascade inside the plugin surface
26
+ * is unchanged. Place the import BEFORE the Tailwind utilities import so a utility
27
+ * that ties on specificity still wins on source order.
28
+ *
29
+ * A vite-plugin test (`markdown-css-scope.test.ts`) asserts this very file passes the
30
+ * guard when adopted that way, so the two forms above cannot silently stop working.
31
+ *
32
+ * EITHER WAY, put the class on YOUR OWN wrapper — `MarkdownView` emits no element:
33
+ *
34
+ * <Box className="ethisys-md"> <MarkdownView>{body}</MarkdownView></Box>
35
+ * <Box className="ethisys-md ethisys-md--dense"> …chat bubble… </Box>
36
+ *
37
+ * WHY THIS SHIPS FROM THE SDK rather than from the host's global stylesheet, where
38
+ * its ancestor (`style/plugins/ds-markdown.css`) lived: a plugin whose bubble rhythm
39
+ * comes from host CSS cannot be deployed before the host that defines it, which makes
40
+ * every markdown change a cross-repo release dance. Shipping the styles alongside the
41
+ * component makes each plugin self-contained and the two deploys order-independent.
42
+ *
43
+ * Plain CSS, no `@apply` — matching `plugin-base.css`, so the file is valid wherever
44
+ * it is imported and does not depend on the consumer's Tailwind config being loaded.
45
+ * The literal sizes below are gogo's type scale (base 0.875rem/14px, NOT stock
46
+ * Tailwind's 1rem), carried over from the utilities the previous stylesheet used.
47
+ * Colour comes from the host design tokens by inheritance, as `plugin-base.css` does.
48
+ */
49
+
50
+ /* --------------------------------------------------------------- */
51
+ /* Document rhythm */
52
+ /* --------------------------------------------------------------- */
53
+
54
+ .ethisys-md {
55
+ font-family: var(--font-body), ui-sans-serif;
56
+ font-weight: 400;
57
+ }
58
+
59
+ .ethisys-md h1,
60
+ .ethisys-md h2,
61
+ .ethisys-md h3,
62
+ .ethisys-md h4,
63
+ .ethisys-md h5,
64
+ .ethisys-md h6 {
65
+ font-family: var(--font-heading), ui-sans-serif;
66
+ font-weight: 700;
67
+ }
68
+
69
+ .ethisys-md h1 {
70
+ margin-top: 1.625rem;
71
+ margin-bottom: 0.875rem;
72
+ font-size: 1.625rem;
73
+ }
74
+
75
+ .ethisys-md h2 {
76
+ margin-top: 1.5rem;
77
+ margin-bottom: 0.75rem;
78
+ font-size: 1.5rem;
79
+ }
80
+
81
+ .ethisys-md h3 {
82
+ margin-top: 1.375rem;
83
+ margin-bottom: 0.625rem;
84
+ font-size: 1.375rem;
85
+ }
86
+
87
+ .ethisys-md h4 {
88
+ margin-top: 1.25rem;
89
+ margin-bottom: 0.5rem;
90
+ font-size: 1.25rem;
91
+ }
92
+
93
+ .ethisys-md h5 {
94
+ margin-top: 1rem;
95
+ margin-bottom: 0.375rem;
96
+ font-size: 1.125rem;
97
+ }
98
+
99
+ .ethisys-md h6 {
100
+ margin-top: 0.875rem;
101
+ margin-bottom: 0.25rem;
102
+ font-size: 1rem;
103
+ }
104
+
105
+ .ethisys-md p {
106
+ margin-bottom: 0.5rem;
107
+ font-size: 0.875rem;
108
+ font-weight: 400;
109
+ line-height: 1.25rem;
110
+ }
111
+
112
+ .ethisys-md ul,
113
+ .ethisys-md ol {
114
+ margin-bottom: 0.5rem;
115
+ list-style-position: inside;
116
+ }
117
+
118
+ .ethisys-md ul {
119
+ list-style-type: disc;
120
+ }
121
+
122
+ .ethisys-md ol {
123
+ list-style-type: decimal;
124
+ }
125
+
126
+ .ethisys-md li {
127
+ line-height: 1.5rem;
128
+ }
129
+
130
+ .ethisys-md pre {
131
+ margin-top: 0.25rem;
132
+ margin-bottom: 0.25rem;
133
+ overflow-x: auto;
134
+ border-radius: var(--border-radius-sm);
135
+ padding: 0.5rem;
136
+ background-color: hsl(var(--grey-50));
137
+ }
138
+
139
+ .ethisys-md code {
140
+ border-radius: var(--border-radius-sm);
141
+ padding-inline: 0.25rem;
142
+ font-family: ui-monospace, monospace;
143
+ font-size: 0.8125rem;
144
+ background-color: hsl(var(--grey-50));
145
+ }
146
+
147
+ .ethisys-md pre code {
148
+ padding: 0;
149
+ background-color: transparent;
150
+ }
151
+
152
+ /* The placeholder left where an untrusted remote image was blocked. */
153
+ .ethisys-md-blocked-image {
154
+ color: hsl(var(--text-secondary));
155
+ font-style: italic;
156
+ }
157
+
158
+ /* --------------------------------------------------------------- */
159
+ /* Chat-bubble rhythm */
160
+ /* --------------------------------------------------------------- */
161
+ /*
162
+ * Applied ALONGSIDE `.ethisys-md` on a message bubble's content wrapper. It tightens
163
+ * the paragraph and list margins for a dense layout, and carries VERBATIM the two
164
+ * width guards the previous stylesheet documented at length. Both are load-bearing on
165
+ * narrow surfaces (the chat dock popup is ~360px wide, and a bubble inside it is
166
+ * capped at 85% of that):
167
+ *
168
+ * 1. `min-width: 0` on the wrapper. Without it the wrapper's intrinsic min-width is
169
+ * `auto` — equal to its widest atomic child. When the message is a short word with
170
+ * no break opportunities (e.g. "asde") the wrapper refuses to shrink below the
171
+ * paragraph's measured text width. In a flex parent that nominally allows shrink
172
+ * the wrapper still wins and pushes the rightmost glyph past the bubble's rounded
173
+ * corner — the "asde clips the right edge" bug, while "asd" sits inside the safe
174
+ * zone by a pixel or two.
175
+ *
176
+ * 2. `overflow-wrap: anywhere` on paragraphs, code and links. Continuous strings
177
+ * (URLs, code identifiers, joined-no-space text) break inside themselves when the
178
+ * bubble cannot widen further, rather than punching through the right edge. The
179
+ * gentler `anywhere` is preferred over `word-break: break-word`, which would split
180
+ * short words even when the bubble has room.
181
+ *
182
+ * The `max-width: 100%` lines keep the bubble the owner of the width.
183
+ *
184
+ * DROPPED from the previous stylesheet, no longer needed: every `--dsr-*` / `--ds-*`
185
+ * variable, the `.ds-markdown-answer` wrapper overrides, and the
186
+ * `background-color: transparent` reset — all of which existed only to undo
187
+ * ds-markdown's opaque white root. `MarkdownView` emits no wrapper and no background.
188
+ */
189
+
190
+ .ethisys-md--dense {
191
+ min-width: 0;
192
+ max-width: 100%;
193
+ font-size: 0.875rem;
194
+ line-height: 1.25rem;
195
+ }
196
+
197
+ .ethisys-md--dense p {
198
+ margin-bottom: 0;
199
+ max-width: 100%;
200
+ font-size: 0.75rem;
201
+ line-height: 1.25rem;
202
+ overflow-wrap: anywhere;
203
+ }
204
+
205
+ .ethisys-md--dense p + p {
206
+ margin-top: 0.25rem;
207
+ }
208
+
209
+ .ethisys-md--dense ul,
210
+ .ethisys-md--dense ol {
211
+ margin-top: 0.25rem;
212
+ margin-bottom: 0;
213
+ margin-left: 0.5rem;
214
+ padding-left: 1.25rem;
215
+ font-size: 0.75rem;
216
+ line-height: 1.25rem;
217
+ }
218
+
219
+ .ethisys-md--dense code {
220
+ overflow-wrap: anywhere;
221
+ }
222
+
223
+ .ethisys-md--dense pre {
224
+ margin-top: 0.25rem;
225
+ margin-bottom: 0.25rem;
226
+ }
227
+
228
+ .ethisys-md--dense a {
229
+ color: hsl(var(--primary));
230
+ text-decoration-line: underline;
231
+ overflow-wrap: anywhere;
232
+ }
233
+
234
+ /*
235
+ * Blockquotes and tables.
236
+ *
237
+ * These were missing from the first cut and browser smoke-testing caught it: a `>`
238
+ * quote rendered identically to a paragraph (no rule, no indent, body-coloured text,
239
+ * no `::before`), and GFM table cells had zero borders and zero padding, so a table
240
+ * read as unspaced columns. Markdown that produces no visible change is markdown the
241
+ * author has no reason to trust, so both are styled here rather than per-plugin.
242
+ */
243
+
244
+ .ethisys-md blockquote {
245
+ margin: 0.5rem 0;
246
+ border-left: 3px solid hsl(var(--grey-100));
247
+ padding-left: 0.75rem;
248
+ /* --text-secondary, not a --grey-* step: the grey scale is surface tones
249
+ (--grey-400 is 79% lightness in light mode) and would be unreadable as text. */
250
+ color: hsl(var(--text-secondary));
251
+ }
252
+
253
+ /*
254
+ * `display: block` rather than a wrapper element: the table can then scroll on its own
255
+ * when it is wider than the surface, which keeps this a stylesheet-only fix and leaves
256
+ * MarkdownView's component map untouched. The trade-off is that the table stops
257
+ * stretching to fill its container — acceptable, and better than a chat or ticket
258
+ * layout being forced wide by one table.
259
+ */
260
+ .ethisys-md table {
261
+ display: block;
262
+ overflow-x: auto;
263
+ max-width: 100%;
264
+ margin: 0.5rem 0;
265
+ border-collapse: collapse;
266
+ }
267
+
268
+ .ethisys-md th,
269
+ .ethisys-md td {
270
+ border: 1px solid hsl(var(--grey-100));
271
+ padding: 0.25rem 0.5rem;
272
+ text-align: left;
273
+ }
274
+
275
+ .ethisys-md th {
276
+ font-weight: 600;
277
+ background-color: hsl(var(--grey-50));
278
+ }
279
+
280
+ .ethisys-md hr {
281
+ margin: 1rem 0;
282
+ border: 0;
283
+ border-top: 1px solid hsl(var(--grey-100));
284
+ }
@@ -61,6 +61,27 @@
61
61
  border-radius: var(--border-radius-3xl);
62
62
  }
63
63
 
64
+ /* Form-field spacing standard — the plugin-side counterpart of the host's
65
+ `.form-fields` class. The FormStack (<form>) and FieldStack (<div>) primitives
66
+ from `@ethisyscore/plugin-ui/components/ui` wear this class to lay their fields
67
+ out in a 24px flex column, so a form gets the standard without having to be a
68
+ MUI Stack in a particular DOM position. Flex column (not block) so adjacent
69
+ field margins can't collapse; the child rule zeroes any control's own bottom
70
+ margin for parity with the host. Root-scoped so it never touches the host's
71
+ own `.form-fields`, and in `@layer components` so a plugin's Tailwind gap/mb
72
+ utilities still win. */
73
+ @layer components {
74
+ .ehx-plugin-root .form-fields {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 1.5rem;
78
+ }
79
+
80
+ .ehx-plugin-root .form-fields > .MuiFormControl-root {
81
+ margin-bottom: 0;
82
+ }
83
+ }
84
+
64
85
  /* Portal container rendered by `PluginPortalScope` at the end of every plugin
65
86
  surface. MUI's portalling components (Modal/Popper and everything built on
66
87
  them) default to it, which keeps dialogs, menus, poppers and tooltips inside