@cueplusplus/ui 0.22.0 → 0.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # @cueplusplus/ui
2
2
 
3
+ ## 0.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dae9dec: **A field's edge can reach 3:1: `data-field-edge="contrast"`** (cue-ui-60). Every shipped theme's
8
+ `--cue-border` measured 1.07:1 to 2.02:1 against `surface-1`, under WCAG 1.4.11's 3:1 for the line
9
+ that marks out where to type. The border stays as it is, because it is a box edge and a quiet one on
10
+ purpose, and a field's edge becomes a role of its own.
11
+
12
+ - **`--cue-field-edge`** is each block's border moved toward its ink until it clears 3:1 on its
13
+ `surface-1`, written per block as a literal. Every theme package, `theme-base`'s own palette and
14
+ `createTheme()` write it, and the contrast report holds it to 3:1 as the required pair
15
+ `field-edge/surface-1`.
16
+ - **Opt in with `data-field-edge="contrast"`** on any element, `<html>` included. Every recessed
17
+ control inside then draws its resting edge in `--cue-field-edge`, disabled ones too, and over a
18
+ paint pack's stroke. Hover, focus and invalid edges keep their own colours. Outside the attribute,
19
+ nothing changes.
20
+ - **`edgeToContrast(edge, ground, toward, minimum = 3)`** is the arithmetic, exported from
21
+ `@cueplusplus/theme-base/contrast` and `@cueplusplus/ui/theming`.
22
+ - `@cueplusplus/tokens` publishes `field-edge` as a derived name, with `var(--cue-border)` as its
23
+ template. A stylesheet built before this release falls back to the plain border.
24
+
25
+ **For a theme package of your own:** `theme-tools` now writes one more line per block. Run
26
+ `generate` once after upgrading; until then `check` reports the stylesheet out of date.
27
+
28
+ - 39884b3: **`MarkdownText` renders GitHub tables** (cue-ui-58). An agent's table used to arrive as one
29
+ paragraph of pipes: the parser read CommonMark only, and the sanitizer allowed no table elements.
30
+ Tables are now the one part of GFM the parser reads. Each column is aligned as its delimiter row
31
+ says, header cells sit on `surface-2`, and the table scrolls sideways inside a named,
32
+ keyboard-reachable region rather than widening the turn. Task lists, footnotes and autolink literals
33
+ stay out, and raw HTML is still refused: a cell's markup is text, and alignment is the only style a
34
+ cell carries.
35
+
36
+ **`remarkPlugins`** adds parser plugins after the table syntax. It is a seam for the parser, not
37
+ for the page: whatever a plugin puts in the tree passes the same sanitizer, so it can change what
38
+ the markdown means but cannot widen what renders.
39
+
40
+ ### Migrating
41
+ - **`@cueplusplus/ui/elements/markdown` has two more optional peers**, the table half of GFM:
42
+ `pnpm add micromark-extension-gfm-table mdast-util-gfm-table`. Like `react-markdown` and
43
+ `rehype-sanitize`, they are needed only where this subpath is imported. A build that imports it
44
+ without them fails to resolve the module, and names it.
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies [dae9dec]
49
+ - @cueplusplus/tokens@0.23.0
50
+ - @cueplusplus/theme-base@1.4.0
51
+
3
52
  ## 0.22.0
4
53
 
5
54
  ### Minor Changes
package/README.md CHANGED
@@ -105,7 +105,7 @@ their own, so importing a button never pulls a charting library into your bundle
105
105
  | `@cueplusplus/ui/chat` | the multi-agent transcript: messages, composer, HITL questions, agent colour |
106
106
  | `@cueplusplus/ui/agent-runtime` | the streaming layer under Chat; needs `@assistant-ui/react` |
107
107
  | `@cueplusplus/ui/elements` | the agent's own surface, vendored from assistant-ui and repainted onto the tokens; needs `lucide-react` and `heat-graph` (+ `./elements.css`) |
108
- | `@cueplusplus/ui/elements/markdown` | `MarkdownText`; needs `react-markdown`, `rehype-sanitize` |
108
+ | `@cueplusplus/ui/elements/markdown` | `MarkdownText`; needs `react-markdown`, `rehype-sanitize`, `micromark-extension-gfm-table`, `mdast-util-gfm-table` |
109
109
  | `@cueplusplus/ui/elements/generative` | the generative-UI renderer and its spec (+ `./elements/generative.css`) |
110
110
  | `@cueplusplus/ui/elements/replay` | the scripted session-replay frame |
111
111
  | `@cueplusplus/ui/dmx` | 512 bytes at wire rate: the bar, the strip, the patch bar, the channel grid |
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import Markdown from "react-markdown";
2
3
  //#region src/elements/markdown.d.ts
3
4
  /** A fenced block, handed to a highlighter that was opted into. */
4
5
  interface MarkdownCodeBlock {
@@ -50,6 +51,16 @@ interface MarkdownTextProps extends Omit<React.ComponentPropsWithoutRef<"div">,
50
51
  * behaviour they inherited.
51
52
  */
52
53
  renderCodeBlock?: (block: MarkdownCodeBlock) => React.ReactNode;
54
+ /**
55
+ * More remark plugins, run after the component's own table syntax.
56
+ *
57
+ * A seam for the parser, not for the page: whatever a plugin adds to the
58
+ * tree still passes the same sanitizer, so a plugin can change what the
59
+ * markdown *means* — a mention syntax, a glossary link — but cannot widen
60
+ * what renders. An element outside the subset is dropped, and an attribute
61
+ * the schema does not name never arrives.
62
+ */
63
+ remarkPlugins?: React.ComponentProps<typeof Markdown>["remarkPlugins"];
53
64
  }
54
65
  /**
55
66
  * Model output, rendered.
@@ -84,9 +95,11 @@ interface MarkdownTextProps extends Omit<React.ComponentPropsWithoutRef<"div">,
84
95
  * - **Attributes are enumerated, not filtered.** An element that is not in the
85
96
  * schema renders nothing; an element that is renders only the attributes
86
97
  * named beside it — `href`/`title` on a link, `src`/`alt`/`title` on an
87
- * image, `language-…` on a `code`. No element in the subset accepts `id`,
98
+ * image, `language-…` on a `code`, and `align` on a table cell, held to
99
+ * `left`, `center` or `right`. No element in the subset accepts `id`,
88
100
  * `style`, `srcset`, `formaction` or any `on*` handler, so none of them has
89
- * an attribute to arrive on.
101
+ * an attribute to arrive on; the one inline style anything carries is a
102
+ * cell's `text-align`, which the renderer writes from that `align`.
90
103
  * - **URLs are allow-listed twice.** `http`, `https` and `mailto` on both
91
104
  * `href` and `src`; a relative URL stays relative; everything else — a
92
105
  * `javascript:` link, a `data:text/html` image, `vbscript:`, `blob:`,
@@ -98,10 +111,14 @@ interface MarkdownTextProps extends Omit<React.ComponentPropsWithoutRef<"div">,
98
111
  *
99
112
  * **The subset.** Paragraphs, ATX and setext headings, bullet and ordered
100
113
  * lists, block quotes, thematic breaks, fenced and indented code, inline code,
101
- * emphasis, strong, strikethrough, links, images, and hard breaks. No GFM
102
- * plugin is enabled, so no tables, task lists, footnotes or autolink literals —
103
- * an explicit `<https://…>` autolink still works. Fenced blocks are plain text
104
- * unless {@link MarkdownTextProps.renderCodeBlock} is given.
114
+ * emphasis, strong, strikethrough, links, images, hard breaks, and GitHub
115
+ * tables. Tables are the one part of GFM the parser reads, each column aligned
116
+ * as its delimiter row says and wrapped in a named region that scrolls
117
+ * sideways; task lists, footnotes and autolink literals stay out — an explicit
118
+ * `<https://…>` autolink still works. Fenced blocks are plain text unless
119
+ * {@link MarkdownTextProps.renderCodeBlock} is given, and
120
+ * {@link MarkdownTextProps.remarkPlugins} adds parser plugins without widening
121
+ * the subset.
105
122
  *
106
123
  * **Streaming.** Pass `streaming` while the turn is arriving and the token the
107
124
  * model is halfway through typing is hidden rather than rendered as
@@ -110,10 +127,11 @@ interface MarkdownTextProps extends Omit<React.ComponentPropsWithoutRef<"div">,
110
127
  * words appear as they arrive; only their formatting waits for the closing
111
128
  * token.
112
129
  *
113
- * `react-markdown` and `rehype-sanitize` are optional peers of this subpath
114
- * alone, which is why this component is not re-exported from
130
+ * `react-markdown`, `rehype-sanitize` and GFM's table pair
131
+ * (`micromark-extension-gfm-table`, `mdast-util-gfm-table`) are optional peers
132
+ * of this subpath alone, which is why this component is not re-exported from
115
133
  * `@cueplusplus/ui/elements`: a console that renders a transcript without
116
- * markdown should install neither.
134
+ * markdown should install none of them.
117
135
  *
118
136
  * @example
119
137
  * <Message agent="researcher">
@@ -125,6 +143,8 @@ interface MarkdownTextProps extends Omit<React.ComponentPropsWithoutRef<"div">,
125
143
  * <MarkdownText renderCodeBlock={({ language, code }) => <Highlight lang={language} code={code} />}>
126
144
  * {answer}
127
145
  * </MarkdownText>
146
+ * @example
147
+ * <MarkdownText remarkPlugins={[remarkMentions]}>{turn.text}</MarkdownText>
128
148
  */
129
149
  declare const MarkdownText: React.ForwardRefExoticComponent<MarkdownTextProps & React.RefAttributes<HTMLDivElement>>;
130
150
  //#endregion
@@ -4,6 +4,8 @@ import { FOCUS_RING } from "../system/_focus-ring.js";
4
4
  import { codeScroll, codeSurface, field, mono } from "./surfaces.js";
5
5
  import * as React from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
+ import { gfmTableFromMarkdown } from "mdast-util-gfm-table";
8
+ import { gfmTable } from "micromark-extension-gfm-table";
7
9
  import Markdown from "react-markdown";
8
10
  import rehypeSanitize from "rehype-sanitize";
9
11
  //#region src/elements/markdown.tsx
@@ -19,9 +21,13 @@ import rehypeSanitize from "rehype-sanitize";
19
21
  * `MarkdownText`'s JSDoc quotes it, and a test asserts nothing outside it
20
22
  * survives.
21
23
  *
24
+ * The six table elements are here because GFM's table syntax is the one part
25
+ * of GFM the parser reads (see {@link remarkTables}): a table is inert, and it
26
+ * is how an agent presents a comparison.
27
+ *
22
28
  * Deliberately absent, and each for a reason: `input` (GFM task lists would
23
- * put a control inside a transcript), `table` and friends (no GFM plugin is
24
- * enabled, so the parser never produces them), `svg`/`math` (an untrusted
29
+ * put a control inside a transcript), `sup`/`section` (footnotes, which a
30
+ * reply does not need), `svg`/`math` (an untrusted
25
31
  * vector is a script host in several browsers' history), `details`/`summary`
26
32
  * (disclosure is a component, not a token stream), and every sectioning or
27
33
  * embedding tag — `iframe`, `object`, `embed`, `script`, `style`, `form`.
@@ -46,6 +52,12 @@ const ALLOWED_TAGS = [
46
52
  "p",
47
53
  "pre",
48
54
  "strong",
55
+ "table",
56
+ "tbody",
57
+ "td",
58
+ "th",
59
+ "thead",
60
+ "tr",
49
61
  "ul"
50
62
  ];
51
63
  /**
@@ -72,6 +84,9 @@ const ALLOWED_PROTOCOLS = [
72
84
  * handler unreachable by construction instead of by deny-list. `code` may
73
85
  * carry a `className`, and only one matching `language-…`, because that is how
74
86
  * a fenced block's info string reaches {@link MarkdownTextProps.renderCodeBlock}.
87
+ * A table cell may carry `align`, and only one of the three values the
88
+ * delimiter row can write; the renderer turns it into `text-align`, which is
89
+ * the one inline style anything in this surface ever carries.
75
90
  */
76
91
  const SCHEMA = {
77
92
  tagNames: [...ALLOWED_TAGS],
@@ -84,6 +99,18 @@ const SCHEMA = {
84
99
  ],
85
100
  code: [["className", /^language-[\w+#.-]+$/]],
86
101
  ol: ["start"],
102
+ th: [[
103
+ "align",
104
+ "left",
105
+ "center",
106
+ "right"
107
+ ]],
108
+ td: [[
109
+ "align",
110
+ "left",
111
+ "center",
112
+ "right"
113
+ ]],
87
114
  "*": []
88
115
  },
89
116
  protocols: {
@@ -304,6 +331,20 @@ function sealStream(source) {
304
331
  for (let index = 0; index < tail.length; index += 1) if (!erase.has(index)) sealed += tail[index];
305
332
  return head + sealed;
306
333
  }
334
+ /**
335
+ * GitHub's table syntax, as a remark plugin, and nothing else from GFM.
336
+ *
337
+ * `remark-gfm` would bring task lists (a checkbox in a transcript), footnotes
338
+ * and autolink literals along with tables. This registers the table tokenizer
339
+ * and its tree handlers alone, which is what `remark-gfm` itself does for each
340
+ * of its five extensions, one at a time. The sanitizer still decides what a
341
+ * table may render as: the six elements and a cell's `align`.
342
+ */
343
+ function remarkTables() {
344
+ const data = this.data();
345
+ (data.micromarkExtensions ??= []).push(gfmTable());
346
+ (data.fromMarkdownExtensions ??= []).push(gfmTableFromMarkdown());
347
+ }
307
348
  /** The text and info string of a fenced block, read off its hast node. */
308
349
  function readCodeBlock(node) {
309
350
  const code = node?.children?.find((child) => child.tagName === "code");
@@ -347,9 +388,11 @@ function readCodeBlock(node) {
347
388
  * - **Attributes are enumerated, not filtered.** An element that is not in the
348
389
  * schema renders nothing; an element that is renders only the attributes
349
390
  * named beside it — `href`/`title` on a link, `src`/`alt`/`title` on an
350
- * image, `language-…` on a `code`. No element in the subset accepts `id`,
391
+ * image, `language-…` on a `code`, and `align` on a table cell, held to
392
+ * `left`, `center` or `right`. No element in the subset accepts `id`,
351
393
  * `style`, `srcset`, `formaction` or any `on*` handler, so none of them has
352
- * an attribute to arrive on.
394
+ * an attribute to arrive on; the one inline style anything carries is a
395
+ * cell's `text-align`, which the renderer writes from that `align`.
353
396
  * - **URLs are allow-listed twice.** `http`, `https` and `mailto` on both
354
397
  * `href` and `src`; a relative URL stays relative; everything else — a
355
398
  * `javascript:` link, a `data:text/html` image, `vbscript:`, `blob:`,
@@ -361,10 +404,14 @@ function readCodeBlock(node) {
361
404
  *
362
405
  * **The subset.** Paragraphs, ATX and setext headings, bullet and ordered
363
406
  * lists, block quotes, thematic breaks, fenced and indented code, inline code,
364
- * emphasis, strong, strikethrough, links, images, and hard breaks. No GFM
365
- * plugin is enabled, so no tables, task lists, footnotes or autolink literals —
366
- * an explicit `<https://…>` autolink still works. Fenced blocks are plain text
367
- * unless {@link MarkdownTextProps.renderCodeBlock} is given.
407
+ * emphasis, strong, strikethrough, links, images, hard breaks, and GitHub
408
+ * tables. Tables are the one part of GFM the parser reads, each column aligned
409
+ * as its delimiter row says and wrapped in a named region that scrolls
410
+ * sideways; task lists, footnotes and autolink literals stay out — an explicit
411
+ * `<https://…>` autolink still works. Fenced blocks are plain text unless
412
+ * {@link MarkdownTextProps.renderCodeBlock} is given, and
413
+ * {@link MarkdownTextProps.remarkPlugins} adds parser plugins without widening
414
+ * the subset.
368
415
  *
369
416
  * **Streaming.** Pass `streaming` while the turn is arriving and the token the
370
417
  * model is halfway through typing is hidden rather than rendered as
@@ -373,10 +420,11 @@ function readCodeBlock(node) {
373
420
  * words appear as they arrive; only their formatting waits for the closing
374
421
  * token.
375
422
  *
376
- * `react-markdown` and `rehype-sanitize` are optional peers of this subpath
377
- * alone, which is why this component is not re-exported from
423
+ * `react-markdown`, `rehype-sanitize` and GFM's table pair
424
+ * (`micromark-extension-gfm-table`, `mdast-util-gfm-table`) are optional peers
425
+ * of this subpath alone, which is why this component is not re-exported from
378
426
  * `@cueplusplus/ui/elements`: a console that renders a transcript without
379
- * markdown should install neither.
427
+ * markdown should install none of them.
380
428
  *
381
429
  * @example
382
430
  * <Message agent="researcher">
@@ -388,8 +436,10 @@ function readCodeBlock(node) {
388
436
  * <MarkdownText renderCodeBlock={({ language, code }) => <Highlight lang={language} code={code} />}>
389
437
  * {answer}
390
438
  * </MarkdownText>
439
+ * @example
440
+ * <MarkdownText remarkPlugins={[remarkMentions]}>{turn.text}</MarkdownText>
391
441
  */
392
- const MarkdownText = React.forwardRef(function MarkdownText({ className, children, streaming = false, target = "_blank", headingLevel = 2, renderCodeBlock, ...elementProps }, ref) {
442
+ const MarkdownText = React.forwardRef(function MarkdownText({ className, children, streaming = false, target = "_blank", headingLevel = 2, renderCodeBlock, remarkPlugins, ...elementProps }, ref) {
393
443
  const source = streaming ? sealStream(children) : children;
394
444
  const components = React.useMemo(() => markdownComponents({
395
445
  target,
@@ -408,6 +458,7 @@ const MarkdownText = React.forwardRef(function MarkdownText({ className, childre
408
458
  ...elementProps,
409
459
  children: /* @__PURE__ */ jsx(Markdown, {
410
460
  components,
461
+ remarkPlugins: [remarkTables, ...remarkPlugins ?? []],
411
462
  rehypePlugins: [[rehypeSanitize, SCHEMA]],
412
463
  skipHtml: true,
413
464
  urlTransform: safeUrl,
@@ -522,7 +573,45 @@ function markdownComponents(options) {
522
573
  children: renderCodeBlock(block)
523
574
  })
524
575
  });
525
- }
576
+ },
577
+ table: ({ children }) => /* @__PURE__ */ jsx("div", {
578
+ role: "region",
579
+ "aria-label": "Table",
580
+ tabIndex: 0,
581
+ "data-slot": "markdown-table-scroll",
582
+ className: cn("rounded-control border border-border outline-none", codeScroll, FOCUS_RING),
583
+ children: /* @__PURE__ */ jsx("table", {
584
+ "data-slot": "markdown-table",
585
+ className: "w-full border-collapse text-(length:--cue-text-ui)",
586
+ children
587
+ })
588
+ }),
589
+ thead: ({ children }) => /* @__PURE__ */ jsx("thead", {
590
+ "data-slot": "markdown-table-head",
591
+ className: "bg-surface-2",
592
+ children
593
+ }),
594
+ tbody: ({ children }) => /* @__PURE__ */ jsx("tbody", {
595
+ "data-slot": "markdown-table-body",
596
+ children
597
+ }),
598
+ tr: ({ children }) => /* @__PURE__ */ jsx("tr", {
599
+ "data-slot": "markdown-table-row",
600
+ className: "border-b border-border last:border-b-0",
601
+ children
602
+ }),
603
+ th: ({ children, style }) => /* @__PURE__ */ jsx("th", {
604
+ "data-slot": "markdown-table-header",
605
+ style,
606
+ className: "px-(--cue-space-3) py-(--cue-space-2) text-start align-bottom font-medium text-fg",
607
+ children
608
+ }),
609
+ td: ({ children, style }) => /* @__PURE__ */ jsx("td", {
610
+ "data-slot": "markdown-table-cell",
611
+ style,
612
+ className: "px-(--cue-space-3) py-(--cue-space-2) align-top text-fg",
613
+ children
614
+ })
526
615
  };
527
616
  }
528
617
  //#endregion
@@ -105,7 +105,10 @@ import { FormControlSize } from "./_chassis-size.js";
105
105
  * **`field-ground`** is `styles.css`'s opt-in relative fill (spec
106
106
  * 2026-10-08-field-ground R3): inside `[data-field-ground="relative"]` it
107
107
  * re-declares this field's own `--cue-sunken` one step off the ground it stands
108
- * on; anywhere else it compiles to nothing that applies.
108
+ * on; anywhere else it compiles to nothing that applies. The same hook carries
109
+ * the contrast edge (cue-ui-60): inside `[data-field-edge="contrast"]` this
110
+ * field's resting stroke reads `--cue-field-edge`, its theme's border moved to
111
+ * 3:1 on `surface-1`.
109
112
  *
110
113
  * **The resting shade** ({@link REST_SHADE_CLASS}, spec §3.10) is the one
111
114
  * shadow this recipe draws: the stroke paints a border and a ground and no
@@ -112,7 +112,10 @@ import { INPUT_FACE } from "../system/_style-input-face.js";
112
112
  * **`field-ground`** is `styles.css`'s opt-in relative fill (spec
113
113
  * 2026-10-08-field-ground R3): inside `[data-field-ground="relative"]` it
114
114
  * re-declares this field's own `--cue-sunken` one step off the ground it stands
115
- * on; anywhere else it compiles to nothing that applies.
115
+ * on; anywhere else it compiles to nothing that applies. The same hook carries
116
+ * the contrast edge (cue-ui-60): inside `[data-field-edge="contrast"]` this
117
+ * field's resting stroke reads `--cue-field-edge`, its theme's border moved to
118
+ * 3:1 on `surface-1`.
116
119
  *
117
120
  * **The resting shade** ({@link REST_SHADE_CLASS}, spec §3.10) is the one
118
121
  * shadow this recipe draws: the stroke paints a border and a ground and no
package/dist/styles.css CHANGED
@@ -179,9 +179,26 @@
179
179
  * the chain the stroke already reads, `var(--cue-style-field-fill,
180
180
  * var(--cue-sunken))`: a named fill still wins, a disabled field follows, and
181
181
  * no field's class grows by more than this utility's name. A consumer's own
182
- * field can wear `field-ground` too. */
182
+ * field can wear `field-ground` too.
183
+ *
184
+ * The contrast edge (cue-ui-60), and opt-in too. A theme's `--cue-border` is a
185
+ * box edge, quiet on purpose, and against `surface-1` it measured 1.07:1 to
186
+ * 2.02:1 in every shipped theme: under WCAG 1.4.11's 3:1 for the line that
187
+ * marks out where to type. `--cue-field-edge` is that border moved toward the
188
+ * ink until it clears 3:1 (each theme writes it per block, as a literal), and
189
+ * inside an ancestor carrying `data-field-edge="contrast"` a field's resting
190
+ * stroke reads it. Declared on the field for the reason the fill is: resolved
191
+ * here, it is the theme the field stands in, island or not. It sets both ends
192
+ * of the stroke, so it wins over a paint pack's gradient edge — the opt-in is
193
+ * a reader's need, and a pack is a look. The hover, focus and invalid edges are
194
+ * their own colours and do not move. Same hook as the relative fill, so no
195
+ * field's class grows for it. */
183
196
  @utility field-ground {
184
197
  :where([data-field-ground="relative"]) & {
185
198
  --cue-sunken: color-mix(in oklab, var(--cue-ground, var(--cue-bg)), var(--cue-style-field-toward, var(--cue-fg)) var(--cue-style-field-step, 6%));
186
199
  }
200
+ :where([data-field-edge="contrast"]) & {
201
+ --cue-paint-stroke-from: var(--cue-field-edge, var(--cue-border));
202
+ --cue-paint-stroke-to: var(--cue-field-edge, var(--cue-border));
203
+ }
187
204
  }
@@ -1,2 +1,2 @@
1
- import { CONTRAST_REQUIREMENTS, ContrastCheck, ContrastInput, ContrastReport, ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport as contrastReport$1, relativeLuminance } from "@cueplusplus/theme-base/contrast";
2
- export { CONTRAST_REQUIREMENTS, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport$1 as contrastReport, relativeLuminance };
1
+ import { CONTRAST_REQUIREMENTS, ContrastCheck, ContrastInput, ContrastReport, ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport as contrastReport$1, edgeToContrast, relativeLuminance } from "@cueplusplus/theme-base/contrast";
2
+ export { CONTRAST_REQUIREMENTS, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport$1 as contrastReport, edgeToContrast, relativeLuminance };
@@ -1,2 +1,2 @@
1
- import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport as contrastReport$1, relativeLuminance } from "@cueplusplus/theme-base/contrast";
2
- export { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport$1 as contrastReport, relativeLuminance };
1
+ import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport as contrastReport$1, edgeToContrast, relativeLuminance } from "@cueplusplus/theme-base/contrast";
2
+ export { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport$1 as contrastReport, edgeToContrast, relativeLuminance };
@@ -1,5 +1,5 @@
1
1
  import { formatColorString } from "../color/_convert.js";
2
- import { WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport } from "./contrast.js";
2
+ import { WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, edgeToContrast } from "./contrast.js";
3
3
  import { RESOLVED_MONO, assertCssValue, assertThemeName, derivedTokens, serializeSystemModeAliases, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
4
4
  import { COLOR_CONTRACT, resolve } from "@cueplusplus/theme-base";
5
5
  import { clampChroma, converter, formatHex, inGamut, parse } from "culori";
@@ -462,7 +462,10 @@ function createTheme(anchors) {
462
462
  if (anchors.radiusScale !== void 0 && !(anchors.radiusScale > 0)) throw new TypeError(`radiusScale must be a positive number, got ${anchors.radiusScale}`);
463
463
  const dark = resolveDark(anchors, preset.dark);
464
464
  const light = anchors.supportsLight ?? preset.light !== null ? resolveLight(dark, preset.light) : null;
465
- const derived = Object.entries(derivedTokens());
465
+ const fieldEdge = (colors) => edgeToContrast(colors["border"], colors["surface-1"], colors["fg"]);
466
+ const derivedFor = (colors) => Object.entries(derivedTokens()).map(([property, value]) => property === tokenProperty("field-edge") ? [property, fieldEdge(colors)] : [property, value]);
467
+ const derived = derivedFor(dark.colors);
468
+ const derivedLight = light === null ? null : derivedFor(light.colors);
466
469
  const fonts = resolveFonts$1(anchors, preset.fonts);
467
470
  const radius = anchors.radiusScale === void 0 ? null : radiusBlocks(name, anchors.radiusScale);
468
471
  const darkPaletteSections = [
@@ -482,7 +485,7 @@ function createTheme(anchors) {
482
485
  }]];
483
486
  const lightSections = light === null ? null : [{ declarations: colorDeclarations(light.colors) }, {
484
487
  comment: DERIVED_COMMENT,
485
- declarations: derived
488
+ declarations: derivedLight ?? derived
486
489
  }];
487
490
  const blocks = [
488
491
  {
@@ -502,10 +505,16 @@ function createTheme(anchors) {
502
505
  ];
503
506
  const inputs = [{
504
507
  mode: "dark",
505
- tokens: dark.colors
508
+ tokens: {
509
+ ...dark.colors,
510
+ "field-edge": fieldEdge(dark.colors)
511
+ }
506
512
  }, ...light === null ? [] : [{
507
513
  mode: "light",
508
- tokens: light.colors,
514
+ tokens: {
515
+ ...light.colors,
516
+ "field-edge": fieldEdge(light.colors)
517
+ },
509
518
  provisional: light.provisional
510
519
  }]];
511
520
  const explicitCss = serializeThemeCss(blocks, { banner: `@cueplusplus/ui — theme "${name}"` }).trimEnd();
@@ -522,7 +531,7 @@ function createTheme(anchors) {
522
531
  sections: lightSections ?? darkPaletteSections
523
532
  }])}\n`,
524
533
  tokens: Object.fromEntries(darkSections.flatMap((section) => section.declarations)),
525
- light: light === null ? null : Object.fromEntries([...colorDeclarations(light.colors), ...derived]),
534
+ light: light === null ? null : Object.fromEntries([...colorDeclarations(light.colors), ...derivedLight ?? derived]),
526
535
  report: contrastReport(inputs)
527
536
  };
528
537
  }
@@ -1,4 +1,4 @@
1
- import { CONTRAST_REQUIREMENTS, ContrastCheck, ContrastInput, ContrastReport, ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance } from "./contrast.js";
1
+ import { CONTRAST_REQUIREMENTS, ContrastCheck, ContrastInput, ContrastReport, ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, edgeToContrast, relativeLuminance } from "./contrast.js";
2
2
  import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CreatedTheme, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, ThemeAnchors, ThemeColorToken, createTheme } from "./create-theme.js";
3
3
  import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, RESOLVED_MONO, SerializeThemeCssOptions, THEME_NAME_PATTERN, ThemeBlock, ThemeBlockSection, ThemeSelectorOptions, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
4
- export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, type CreatedTheme, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, RESOLVED_MONO, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, type SerializeThemeCssOptions, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, type ThemeAnchors, type ThemeBlock, type ThemeBlockSection, type ThemeColorToken, type ThemeSelectorOptions, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
4
+ export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, type CreatedTheme, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, RESOLVED_MONO, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, type SerializeThemeCssOptions, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, type ThemeAnchors, type ThemeBlock, type ThemeBlockSection, type ThemeColorToken, type ThemeSelectorOptions, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, edgeToContrast, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
@@ -1,4 +1,4 @@
1
- import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance } from "./contrast.js";
1
+ import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, edgeToContrast, relativeLuminance } from "./contrast.js";
2
2
  import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, RESOLVED_MONO, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
3
3
  import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, createTheme } from "./create-theme.js";
4
- export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, RESOLVED_MONO, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
4
+ export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, RESOLVED_MONO, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, edgeToContrast, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
@@ -242,7 +242,8 @@ const DERIVED_TOKEN_TEMPLATES = [
242
242
  ["accent-fill", "var(--cue-accent)"],
243
243
  ["accent-fill-fg", "var(--cue-accent-fg)"],
244
244
  ["accent-fill-hover", "var(--cue-accent-hover)"],
245
- ["shade", "var(--cue-fg)"]
245
+ ["shade", "var(--cue-fg)"],
246
+ ["field-edge", "var(--cue-border)"]
246
247
  ];
247
248
  /**
248
249
  * {@link DERIVED_TOKEN_TEMPLATES} as a property → value map.
@@ -4,11 +4,13 @@
4
4
  "group": "elements",
5
5
  "importPath": "@cueplusplus/ui/elements/markdown",
6
6
  "peerDependencies": [
7
+ "mdast-util-gfm-table",
8
+ "micromark-extension-gfm-table",
7
9
  "react-markdown",
8
10
  "rehype-sanitize"
9
11
  ],
10
12
  "clientOnly": true,
11
- "description": "Model output, rendered.\n\nThe one component in this library that turns a string into markup, and the\nonly one that has to be argued for rather than described. Everywhere else —\n`Message`, `AskBox`, every element in this group — text is text: a string\narrives from a model and is placed in the DOM as characters, because a\ncomponent that quietly parsed it would put every consumer of this library one\nprompt away from an injection. This one parses, so it states in full what it\nwill and will not do with what it is given.\n\n**Threat model.** The input is hostile. Assume the string was written by a\nmodel that was told to write it by a web page it fetched, a file it read, or\na tool result it did not verify — the practical shape of prompt injection is\nnot \"the model turned evil\", it is \"the model faithfully relayed an\nattacker's bytes\". Three things follow.\n\n- **No raw HTML, ever, and not by filtering it.** Markup in the source has no\n path to the DOM, and the first reason is an absence rather than a setting.\n `react-markdown` hands `remark-rehype` its own `allowDangerousHtml: true`\n and spreads it *over* whatever `remarkRehypeOptions` says, so the option\n cannot be turned off from here and `<img onerror=…>` does become a `raw`\n node. A `raw` node is a span of source text rather than an element, and the\n one thing that turns it into markup is `rehype-raw`. Nothing in this package\n depends on that plugin, and a test asserts it never will — so the string has\n no parser to reach. `rehype-sanitize` then drops the `raw` type outright,\n and react-markdown's own `skipHtml` removes whatever is left. Three gates,\n none of which is a deny-list of dangerous tags. A `<script>`, an\n `<iframe>`, an `<svg>` and an `onclick=` are all the same non-event: there\n is nothing in the tree for them to be.\n- **Attributes are enumerated, not filtered.** An element that is not in the\n schema renders nothing; an element that is renders only the attributes\n named beside it — `href`/`title` on a link, `src`/`alt`/`title` on an\n image, `language-…` on a `code`. No element in the subset accepts `id`,\n `style`, `srcset`, `formaction` or any `on*` handler, so none of them has\n an attribute to arrive on.\n- **URLs are allow-listed twice.** `http`, `https` and `mailto` on both\n `href` and `src`; a relative URL stays relative; everything else — a\n `javascript:` link, a `data:text/html` image, `vbscript:`, `blob:`,\n `file:`, `//host` — loses the attribute and leaves an inert element\n behind. Every link also carries `rel=\"noopener noreferrer nofollow\"`,\n which is not a prop: reverse tabnabbing, referrer leakage of a private\n console URL, and SEO laundering through a transcript are all things a\n caller should not be able to turn off by accident.\n\n**The subset.** Paragraphs, ATX and setext headings, bullet and ordered\nlists, block quotes, thematic breaks, fenced and indented code, inline code,\nemphasis, strong, strikethrough, links, images, and hard breaks. No GFM\nplugin is enabled, so no tables, task lists, footnotes or autolink literals —\nan explicit `<https://…>` autolink still works. Fenced blocks are plain text\nunless {@link MarkdownTextProps.renderCodeBlock} is given.\n\n**Streaming.** Pass `streaming` while the turn is arriving and the token the\nmodel is halfway through typing is hidden rather than rendered as\npunctuation: no asterisks flickering through every bold run, no half-written\nURL, no fence that opens a code block one frame and closes it the next. The\nwords appear as they arrive; only their formatting waits for the closing\ntoken.\n\n`react-markdown` and `rehype-sanitize` are optional peers of this subpath\nalone, which is why this component is not re-exported from\n`@cueplusplus/ui/elements`: a console that renders a transcript without\nmarkdown should install neither.",
13
+ "description": "Model output, rendered.\n\nThe one component in this library that turns a string into markup, and the\nonly one that has to be argued for rather than described. Everywhere else —\n`Message`, `AskBox`, every element in this group — text is text: a string\narrives from a model and is placed in the DOM as characters, because a\ncomponent that quietly parsed it would put every consumer of this library one\nprompt away from an injection. This one parses, so it states in full what it\nwill and will not do with what it is given.\n\n**Threat model.** The input is hostile. Assume the string was written by a\nmodel that was told to write it by a web page it fetched, a file it read, or\na tool result it did not verify — the practical shape of prompt injection is\nnot \"the model turned evil\", it is \"the model faithfully relayed an\nattacker's bytes\". Three things follow.\n\n- **No raw HTML, ever, and not by filtering it.** Markup in the source has no\n path to the DOM, and the first reason is an absence rather than a setting.\n `react-markdown` hands `remark-rehype` its own `allowDangerousHtml: true`\n and spreads it *over* whatever `remarkRehypeOptions` says, so the option\n cannot be turned off from here and `<img onerror=…>` does become a `raw`\n node. A `raw` node is a span of source text rather than an element, and the\n one thing that turns it into markup is `rehype-raw`. Nothing in this package\n depends on that plugin, and a test asserts it never will — so the string has\n no parser to reach. `rehype-sanitize` then drops the `raw` type outright,\n and react-markdown's own `skipHtml` removes whatever is left. Three gates,\n none of which is a deny-list of dangerous tags. A `<script>`, an\n `<iframe>`, an `<svg>` and an `onclick=` are all the same non-event: there\n is nothing in the tree for them to be.\n- **Attributes are enumerated, not filtered.** An element that is not in the\n schema renders nothing; an element that is renders only the attributes\n named beside it — `href`/`title` on a link, `src`/`alt`/`title` on an\n image, `language-…` on a `code`, and `align` on a table cell, held to\n `left`, `center` or `right`. No element in the subset accepts `id`,\n `style`, `srcset`, `formaction` or any `on*` handler, so none of them has\n an attribute to arrive on; the one inline style anything carries is a\n cell's `text-align`, which the renderer writes from that `align`.\n- **URLs are allow-listed twice.** `http`, `https` and `mailto` on both\n `href` and `src`; a relative URL stays relative; everything else — a\n `javascript:` link, a `data:text/html` image, `vbscript:`, `blob:`,\n `file:`, `//host` — loses the attribute and leaves an inert element\n behind. Every link also carries `rel=\"noopener noreferrer nofollow\"`,\n which is not a prop: reverse tabnabbing, referrer leakage of a private\n console URL, and SEO laundering through a transcript are all things a\n caller should not be able to turn off by accident.\n\n**The subset.** Paragraphs, ATX and setext headings, bullet and ordered\nlists, block quotes, thematic breaks, fenced and indented code, inline code,\nemphasis, strong, strikethrough, links, images, hard breaks, and GitHub\ntables. Tables are the one part of GFM the parser reads, each column aligned\nas its delimiter row says and wrapped in a named region that scrolls\nsideways; task lists, footnotes and autolink literals stay out — an explicit\n`<https://…>` autolink still works. Fenced blocks are plain text unless\n{@link MarkdownTextProps.renderCodeBlock} is given, and\n{@link MarkdownTextProps.remarkPlugins} adds parser plugins without widening\nthe subset.\n\n**Streaming.** Pass `streaming` while the turn is arriving and the token the\nmodel is halfway through typing is hidden rather than rendered as\npunctuation: no asterisks flickering through every bold run, no half-written\nURL, no fence that opens a code block one frame and closes it the next. The\nwords appear as they arrive; only their formatting waits for the closing\ntoken.\n\n`react-markdown`, `rehype-sanitize` and GFM's table pair\n(`micromark-extension-gfm-table`, `mdast-util-gfm-table`) are optional peers\nof this subpath alone, which is why this component is not re-exported from\n`@cueplusplus/ui/elements`: a console that renders a transcript without\nmarkdown should install none of them.",
12
14
  "props": [
13
15
  {
14
16
  "name": "children",
@@ -71,6 +73,17 @@
71
73
  "kind": "callback",
72
74
  "fixture": "callback.log"
73
75
  }
76
+ },
77
+ {
78
+ "name": "remarkPlugins",
79
+ "type": "PluggableList | null",
80
+ "required": false,
81
+ "defaultValue": null,
82
+ "description": "More remark plugins, run after the component's own table syntax.\n\nA seam for the parser, not for the page: whatever a plugin adds to the\ntree still passes the same sanitizer, so a plugin can change what the\nmarkdown *means* — a mention syntax, a glossary link — but cannot widen\nwhat renders. An element outside the subset is dropped, and an attribute\nthe schema does not name never arrives.",
83
+ "control": {
84
+ "kind": "opaque",
85
+ "nullable": true
86
+ }
74
87
  }
75
88
  ],
76
89
  "typeReferences": [
@@ -95,6 +108,7 @@
95
108
  "--cue-space-3",
96
109
  "--cue-space-5",
97
110
  "--cue-style-focus-width",
111
+ "--cue-surface-2",
98
112
  "--cue-text-body",
99
113
  "--cue-text-emphasis",
100
114
  "--cue-text-label",
@@ -105,7 +119,7 @@
105
119
  "examples": [
106
120
  {
107
121
  "title": "Usage",
108
- "code": "<Message agent=\"researcher\">\n <MarkdownText>{turn.text}</MarkdownText>\n</Message>\n<MarkdownText streaming={turn.status === \"running\"}>{turn.text}</MarkdownText>\n<MarkdownText renderCodeBlock={({ language, code }) => <Highlight lang={language} code={code} />}>\n {answer}\n</MarkdownText>",
122
+ "code": "<Message agent=\"researcher\">\n <MarkdownText>{turn.text}</MarkdownText>\n</Message>\n<MarkdownText streaming={turn.status === \"running\"}>{turn.text}</MarkdownText>\n<MarkdownText renderCodeBlock={({ language, code }) => <Highlight lang={language} code={code} />}>\n {answer}\n</MarkdownText>\n<MarkdownText remarkPlugins={[remarkMentions]}>{turn.text}</MarkdownText>",
109
123
  "language": "tsx"
110
124
  }
111
125
  ],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "library": "@cueplusplus/ui",
4
- "version": "0.22.0",
4
+ "version": "0.23.0",
5
5
  "generatedAt": "1970-01-01T00:00:00.000Z",
6
6
  "themes": [
7
7
  "command",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "tokens": {
40
40
  "path": "./tokens.json",
41
- "sha256": "38d9502fd7813d7d3d0c9eaa02ae5850b4f9855c6954c8671569cf08e97ea73d"
41
+ "sha256": "a1f258bdddd984a1fc1eae5e2508d512fc568c82b7e3628e7c604f073c98e9df"
42
42
  },
43
43
  "systemApis": [
44
44
  {
@@ -2320,7 +2320,7 @@
2320
2320
  "mdUrl": "/docs/components/markdown-text.md",
2321
2321
  "jsonUrl": "/r/components/markdown-text.json",
2322
2322
  "path": "./components/markdown-text.json",
2323
- "sha256": "0ab4a2b9ca29f5c4faf89b705e23233cc8d472fa1d8779949c3236b0ebb4eddb"
2323
+ "sha256": "09114d7367b8b47786418e5362e657ac5ae8ec3b5f831da985332f6a96d2be42"
2324
2324
  },
2325
2325
  {
2326
2326
  "name": "MathBlock",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "library": "@cueplusplus/tokens",
4
- "version": "0.22.0",
4
+ "version": "0.23.0",
5
5
  "themes": [
6
6
  {
7
7
  "name": "command",
@@ -283,6 +283,7 @@
283
283
  "--cue-fg",
284
284
  "--cue-fg-muted",
285
285
  "--cue-fg-subtle",
286
+ "--cue-field-edge",
286
287
  "--cue-focus",
287
288
  "--cue-font-mono",
288
289
  "--cue-font-theme-mono",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueplusplus/ui",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "CUE++ design system components: Tailwind v4 styled wrappers over Base UI, driven by @cueplusplus/tokens.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -145,8 +145,8 @@
145
145
  "clsx": "^2.1.1",
146
146
  "culori": "^4.0.2",
147
147
  "tailwind-merge": "^3.6.0",
148
- "@cueplusplus/theme-base": "^1.3.0",
149
- "@cueplusplus/tokens": "0.22.0"
148
+ "@cueplusplus/theme-base": "^1.4.0",
149
+ "@cueplusplus/tokens": "0.23.0"
150
150
  },
151
151
  "peerDependencies": {
152
152
  "@assistant-ui/react": "^0.15.16",
@@ -160,6 +160,8 @@
160
160
  "embla-carousel-react": "^8.6.0",
161
161
  "heat-graph": "^0.0.15",
162
162
  "lucide-react": "^1.31.0",
163
+ "mdast-util-gfm-table": "^2.0.0",
164
+ "micromark-extension-gfm-table": "^2.1.1",
163
165
  "react": "^19.0.0",
164
166
  "react-aria-components": "^1.20.0",
165
167
  "react-day-picker": "^10.0.1",
@@ -201,6 +203,12 @@
201
203
  "lucide-react": {
202
204
  "optional": true
203
205
  },
206
+ "mdast-util-gfm-table": {
207
+ "optional": true
208
+ },
209
+ "micromark-extension-gfm-table": {
210
+ "optional": true
211
+ },
204
212
  "react-aria-components": {
205
213
  "optional": true
206
214
  },
@@ -249,6 +257,8 @@
249
257
  "heat-graph": "^0.0.15",
250
258
  "jsdom": "^30.0.1",
251
259
  "lucide-react": "^1.31.0",
260
+ "mdast-util-gfm-table": "^2.0.0",
261
+ "micromark-extension-gfm-table": "^2.1.1",
252
262
  "publint": "^0.3.23",
253
263
  "react": "^19.2.0",
254
264
  "react-aria-components": "^1.20.0",
@@ -262,7 +272,7 @@
262
272
  "tsdown": "0.22.14",
263
273
  "typescript": "^5.7.0",
264
274
  "vitest": "^4.1.10",
265
- "@cueplusplus/theme-tools": "1.2.0",
275
+ "@cueplusplus/theme-tools": "1.3.0",
266
276
  "@repo/docgen": "0.0.0",
267
277
  "@repo/eslint-config": "0.0.0",
268
278
  "@repo/typescript-config": "0.0.0"