@tangle-network/ui 11.16.3 → 11.16.5

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,19 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.16.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 44ce397: Let model tool-schema converters accept the OpenUI node schema: container `children` no longer declares a nonempty minimum, which formed a required recursive loop that opencode rejected for `render_ui`. The persistence gate still rejects an empty stack, grid, or card children list.
8
+
9
+ ## 11.16.4
10
+
11
+ ### Patch Changes
12
+
13
+ - 11fbba5: Chat text renders at the 15px body size. The user bubble, assistant prose, legacy `ChatMessage` and thinking timer wrote their size tokens as `text-[var(--font-size-*)]`, which Tailwind compiles to a color, so assistant prose fell back to the Markdown default of 14px and the user bubble inherited 16px. They now use `text-[length:var(...)]`, and the dist check fails on the untyped form.
14
+
15
+ The streaming caret follows the last word instead of starting a new line. A code block that scrolls sideways takes keyboard focus as a named region (axe `scrollable-region-focusable`). The Show more toggle under a tool's output or error sits inside the card's inset instead of against its edge.
16
+
3
17
  ## 11.16.3
4
18
 
5
19
  ### Patch Changes
@@ -53,13 +53,14 @@ function spacingBefore(prev, next) {
53
53
  return "mt-3";
54
54
  }
55
55
  const ROW_BLEED = "-mx-3";
56
+ const STREAMING_CARET = "[&>:last-child:not(ul,ol)]:after:ml-0.5 [&>:last-child:not(ul,ol)]:after:inline-block [&>:last-child:not(ul,ol)]:after:h-[1em] [&>:last-child:not(ul,ol)]:after:w-0.5 [&>:last-child:not(ul,ol)]:after:animate-pulse [&>:last-child:not(ul,ol)]:after:rounded-full [&>:last-child:not(ul,ol)]:after:bg-muted-foreground [&>:last-child:not(ul,ol)]:after:align-text-bottom [&>:last-child:not(ul,ol)]:after:content-[''] [&>:is(ul,ol):last-child>li:last-child]:after:ml-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:inline-block [&>:is(ul,ol):last-child>li:last-child]:after:h-[1em] [&>:is(ul,ol):last-child>li:last-child]:after:w-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:animate-pulse [&>:is(ul,ol):last-child>li:last-child]:after:rounded-full [&>:is(ul,ol):last-child>li:last-child]:after:bg-muted-foreground [&>:is(ul,ol):last-child>li:last-child]:after:align-text-bottom [&>:is(ul,ol):last-child>li:last-child]:after:content-['']";
56
57
  function AssistantMessage({ item }) {
57
58
  return /* @__PURE__ */ jsxs("div", { children: [
58
59
  item.content && /* @__PURE__ */ jsx(Markdown, {
59
- className: "tangle-prose text-[var(--font-size-base)] leading-[1.5]",
60
+ className: cn("tangle-prose text-[length:var(--font-size-base)] leading-[1.5]", item.isStreaming && STREAMING_CARET),
60
61
  children: item.content
61
62
  }),
62
- item.isStreaming && /* @__PURE__ */ jsx("span", {
63
+ item.isStreaming && !item.content && /* @__PURE__ */ jsx("span", {
63
64
  "aria-hidden": true,
64
65
  "data-streaming-caret": "",
65
66
  className: "ml-0.5 inline-block h-[1em] w-0.5 animate-pulse rounded-full bg-muted-foreground align-text-bottom"
@@ -19,10 +19,10 @@ function ChatMessage({ role, content, toolCalls, isStreaming, timestamp, classNa
19
19
  }), /* @__PURE__ */ jsxs("div", {
20
20
  className: cn("min-w-0 max-w-[85%] space-y-1", isUser ? "rounded-[var(--radius-lg)] bg-foreground px-[var(--chat-message-px)] py-[var(--chat-message-py)] text-background" : isAssistant ? "text-foreground" : "rounded-[var(--radius-lg)] border border-border bg-card px-[var(--chat-message-px)] py-[var(--chat-message-py)]"),
21
21
  children: [isUser ? /* @__PURE__ */ jsx("div", {
22
- className: "whitespace-pre-wrap text-[var(--font-size-base)] leading-[var(--line-height-base)]",
22
+ className: "whitespace-pre-wrap text-[length:var(--font-size-base)] leading-[var(--line-height-base)]",
23
23
  children: content
24
24
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [content && /* @__PURE__ */ jsx(Markdown, {
25
- className: "tangle-prose text-[var(--font-size-base)] leading-[var(--line-height-base)]",
25
+ className: "tangle-prose text-[length:var(--font-size-base)] leading-[var(--line-height-base)]",
26
26
  children: content
27
27
  }), isStreaming && /* @__PURE__ */ jsx("span", { className: "ml-0.5 inline-block h-4 w-2 animate-pulse rounded-sm bg-[var(--brand-cool)] align-text-bottom" })] }), toolCalls]
28
28
  })]
@@ -29,7 +29,7 @@ function ThinkingIndicator({ className }) {
29
29
  })
30
30
  ]
31
31
  }), elapsed > 3 && /* @__PURE__ */ jsxs("span", {
32
- className: "text-[var(--font-size-xs)] tabular-nums text-muted-foreground",
32
+ className: "text-[length:var(--font-size-xs)] tabular-nums text-muted-foreground",
33
33
  children: [elapsed, "s"]
34
34
  })]
35
35
  });
@@ -23,10 +23,10 @@ function UserBubble({ text, timestamp, actions }) {
23
23
  className: "relative w-full min-w-0 rounded-2xl bg-foreground px-4 py-3 text-background",
24
24
  children: [timestamp ? /* @__PURE__ */ jsx("span", {
25
25
  "data-user-message-time": "",
26
- className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
26
+ className: "pointer-events-none absolute right-full top-1/2 mr-3 -translate-y-1/2 whitespace-nowrap text-[length:var(--font-size-xs)] text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100",
27
27
  children: formatTime(timestamp)
28
28
  }) : null, /* @__PURE__ */ jsx("div", {
29
- className: "break-words whitespace-pre-wrap text-[var(--font-size-base)] leading-[1.5]",
29
+ className: "break-words whitespace-pre-wrap text-[length:var(--font-size-base)] leading-[1.5]",
30
30
  children: text
31
31
  })]
32
32
  }), actions ? /* @__PURE__ */ jsx(MessageActions, { children: actions }) : null]
@@ -1,4 +1,4 @@
1
- import { focusRing } from "../lib/focus.js";
1
+ import { focusRing, focusRingInset } from "../lib/focus.js";
2
2
  import { cn } from "../lib/utils.js";
3
3
  import { memo, useCallback, useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -93,6 +93,12 @@ const CodeBlock = memo(({ code, language, label, showLineNumbers = false, light,
93
93
  overflowX: "auto"
94
94
  };
95
95
  const codeTagProps = { style: { fontFamily: "var(--font-mono, 'JetBrains Mono', ui-monospace, monospace)" } };
96
+ const scrollRegion = {
97
+ tabIndex: 0,
98
+ role: "region",
99
+ "aria-label": headerLabel ? `${headerLabel} code` : "Code",
100
+ className: focusRingInset
101
+ };
96
102
  return /* @__PURE__ */ jsxs("div", {
97
103
  className: cn("group relative overflow-hidden rounded-lg border font-mono", "bg-card border-border", className),
98
104
  ...props,
@@ -121,8 +127,10 @@ const CodeBlock = memo(({ code, language, label, showLineNumbers = false, light,
121
127
  customStyle,
122
128
  codeTagProps,
123
129
  wrapLines: false,
130
+ ...scrollRegion,
124
131
  children: code
125
132
  }) : /* @__PURE__ */ jsx("pre", {
133
+ ...scrollRegion,
126
134
  style: {
127
135
  ...syntaxTheme.hljs,
128
136
  ...customStyle
@@ -1592,7 +1592,6 @@ const schema33 = {
1592
1592
  "wrap": { "type": "boolean" },
1593
1593
  "children": {
1594
1594
  "type": "array",
1595
- "minItems": 1,
1596
1595
  "items": { "$ref": "#/$defs/node" }
1597
1596
  }
1598
1597
  },
@@ -1807,7 +1806,6 @@ const schema46 = {
1807
1806
  ] },
1808
1807
  "children": {
1809
1808
  "type": "array",
1810
- "minItems": 1,
1811
1809
  "items": { "$ref": "#/$defs/node" }
1812
1810
  }
1813
1811
  },
@@ -1909,30 +1907,19 @@ function validate27(data, { instancePath = "", parentData, parentDataProperty, r
1909
1907
  const _errs7 = errors;
1910
1908
  if (errors === _errs7) {
1911
1909
  if (Array.isArray(data4)) {
1912
- if (data4.length < 1) {
1913
- validate27.errors = [{
1914
- instancePath: instancePath + "/children",
1915
- schemaPath: "#/properties/children/minItems",
1916
- keyword: "minItems",
1917
- params: { limit: 1 },
1918
- message: "must NOT have fewer than 1 items"
1919
- }];
1920
- return false;
1921
- } else {
1922
- const len0 = data4.length;
1923
- for (let i0 = 0; i0 < len0; i0++) {
1924
- const _errs9 = errors;
1925
- if (!wrapper1.validate(data4[i0], {
1926
- instancePath: instancePath + "/children/" + i0,
1927
- parentData: data4,
1928
- parentDataProperty: i0,
1929
- rootData
1930
- })) {
1931
- vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);
1932
- errors = vErrors.length;
1933
- }
1934
- if (!(_errs9 === errors)) break;
1910
+ const len0 = data4.length;
1911
+ for (let i0 = 0; i0 < len0; i0++) {
1912
+ const _errs9 = errors;
1913
+ if (!wrapper1.validate(data4[i0], {
1914
+ instancePath: instancePath + "/children/" + i0,
1915
+ parentData: data4,
1916
+ parentDataProperty: i0,
1917
+ rootData
1918
+ })) {
1919
+ vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);
1920
+ errors = vErrors.length;
1935
1921
  }
1922
+ if (!(_errs9 === errors)) break;
1936
1923
  }
1937
1924
  } else {
1938
1925
  validate27.errors = [{
@@ -2319,30 +2306,19 @@ function validate29(data, { instancePath = "", parentData, parentDataProperty, r
2319
2306
  const _errs34 = errors;
2320
2307
  if (errors === _errs34) {
2321
2308
  if (Array.isArray(data16)) {
2322
- if (data16.length < 1) {
2323
- validate29.errors = [{
2324
- instancePath: instancePath + "/children",
2325
- schemaPath: "#/properties/children/minItems",
2326
- keyword: "minItems",
2327
- params: { limit: 1 },
2328
- message: "must NOT have fewer than 1 items"
2329
- }];
2330
- return false;
2331
- } else {
2332
- const len1 = data16.length;
2333
- for (let i1 = 0; i1 < len1; i1++) {
2334
- const _errs36 = errors;
2335
- if (!wrapper1.validate(data16[i1], {
2336
- instancePath: instancePath + "/children/" + i1,
2337
- parentData: data16,
2338
- parentDataProperty: i1,
2339
- rootData
2340
- })) {
2341
- vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);
2342
- errors = vErrors.length;
2343
- }
2344
- if (!(_errs36 === errors)) break;
2309
+ const len1 = data16.length;
2310
+ for (let i1 = 0; i1 < len1; i1++) {
2311
+ const _errs36 = errors;
2312
+ if (!wrapper1.validate(data16[i1], {
2313
+ instancePath: instancePath + "/children/" + i1,
2314
+ parentData: data16,
2315
+ parentDataProperty: i1,
2316
+ rootData
2317
+ })) {
2318
+ vErrors = vErrors === null ? wrapper1.validate.errors : vErrors.concat(wrapper1.validate.errors);
2319
+ errors = vErrors.length;
2345
2320
  }
2321
+ if (!(_errs36 === errors)) break;
2346
2322
  }
2347
2323
  } else {
2348
2324
  validate29.errors = [{
@@ -4139,30 +4115,19 @@ function validate23(data, { instancePath = "", parentData, parentDataProperty, r
4139
4115
  const _errs10 = errors;
4140
4116
  if (errors === _errs10) {
4141
4117
  if (Array.isArray(data6)) {
4142
- if (data6.length < 1) {
4143
- validate23.errors = [{
4144
- instancePath: instancePath + "/children",
4145
- schemaPath: "#/properties/children/minItems",
4146
- keyword: "minItems",
4147
- params: { limit: 1 },
4148
- message: "must NOT have fewer than 1 items"
4149
- }];
4150
- return false;
4151
- } else {
4152
- const len0 = data6.length;
4153
- for (let i0 = 0; i0 < len0; i0++) {
4154
- const _errs12 = errors;
4155
- if (!validate24(data6[i0], {
4156
- instancePath: instancePath + "/children/" + i0,
4157
- parentData: data6,
4158
- parentDataProperty: i0,
4159
- rootData
4160
- })) {
4161
- vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors);
4162
- errors = vErrors.length;
4163
- }
4164
- if (!(_errs12 === errors)) break;
4118
+ const len0 = data6.length;
4119
+ for (let i0 = 0; i0 < len0; i0++) {
4120
+ const _errs12 = errors;
4121
+ if (!validate24(data6[i0], {
4122
+ instancePath: instancePath + "/children/" + i0,
4123
+ parentData: data6,
4124
+ parentDataProperty: i0,
4125
+ rootData
4126
+ })) {
4127
+ vErrors = vErrors === null ? validate24.errors : vErrors.concat(validate24.errors);
4128
+ errors = vErrors.length;
4165
4129
  }
4130
+ if (!(_errs12 === errors)) break;
4166
4131
  }
4167
4132
  } else {
4168
4133
  validate23.errors = [{
@@ -4532,30 +4497,19 @@ function validate38(data, { instancePath = "", parentData, parentDataProperty, r
4532
4497
  const _errs10 = errors;
4533
4498
  if (errors === _errs10) {
4534
4499
  if (Array.isArray(data6)) {
4535
- if (data6.length < 1) {
4536
- validate38.errors = [{
4537
- instancePath: instancePath + "/children",
4538
- schemaPath: "#/properties/children/minItems",
4539
- keyword: "minItems",
4540
- params: { limit: 1 },
4541
- message: "must NOT have fewer than 1 items"
4542
- }];
4543
- return false;
4544
- } else {
4545
- const len0 = data6.length;
4546
- for (let i0 = 0; i0 < len0; i0++) {
4547
- const _errs12 = errors;
4548
- if (!wrapper3.validate(data6[i0], {
4549
- instancePath: instancePath + "/children/" + i0,
4550
- parentData: data6,
4551
- parentDataProperty: i0,
4552
- rootData
4553
- })) {
4554
- vErrors = vErrors === null ? wrapper3.validate.errors : vErrors.concat(wrapper3.validate.errors);
4555
- errors = vErrors.length;
4556
- }
4557
- if (!(_errs12 === errors)) break;
4500
+ const len0 = data6.length;
4501
+ for (let i0 = 0; i0 < len0; i0++) {
4502
+ const _errs12 = errors;
4503
+ if (!wrapper3.validate(data6[i0], {
4504
+ instancePath: instancePath + "/children/" + i0,
4505
+ parentData: data6,
4506
+ parentDataProperty: i0,
4507
+ rootData
4508
+ })) {
4509
+ vErrors = vErrors === null ? wrapper3.validate.errors : vErrors.concat(wrapper3.validate.errors);
4510
+ errors = vErrors.length;
4558
4511
  }
4512
+ if (!(_errs12 === errors)) break;
4559
4513
  }
4560
4514
  } else {
4561
4515
  validate38.errors = [{
@@ -4944,30 +4898,19 @@ function validate40(data, { instancePath = "", parentData, parentDataProperty, r
4944
4898
  const _errs34 = errors;
4945
4899
  if (errors === _errs34) {
4946
4900
  if (Array.isArray(data16)) {
4947
- if (data16.length < 1) {
4948
- validate40.errors = [{
4949
- instancePath: instancePath + "/children",
4950
- schemaPath: "#/properties/children/minItems",
4951
- keyword: "minItems",
4952
- params: { limit: 1 },
4953
- message: "must NOT have fewer than 1 items"
4954
- }];
4955
- return false;
4956
- } else {
4957
- const len1 = data16.length;
4958
- for (let i1 = 0; i1 < len1; i1++) {
4959
- const _errs36 = errors;
4960
- if (!wrapper3.validate(data16[i1], {
4961
- instancePath: instancePath + "/children/" + i1,
4962
- parentData: data16,
4963
- parentDataProperty: i1,
4964
- rootData
4965
- })) {
4966
- vErrors = vErrors === null ? wrapper3.validate.errors : vErrors.concat(wrapper3.validate.errors);
4967
- errors = vErrors.length;
4968
- }
4969
- if (!(_errs36 === errors)) break;
4901
+ const len1 = data16.length;
4902
+ for (let i1 = 0; i1 < len1; i1++) {
4903
+ const _errs36 = errors;
4904
+ if (!wrapper3.validate(data16[i1], {
4905
+ instancePath: instancePath + "/children/" + i1,
4906
+ parentData: data16,
4907
+ parentDataProperty: i1,
4908
+ rootData
4909
+ })) {
4910
+ vErrors = vErrors === null ? wrapper3.validate.errors : vErrors.concat(wrapper3.validate.errors);
4911
+ errors = vErrors.length;
4970
4912
  }
4913
+ if (!(_errs36 === errors)) break;
4971
4914
  }
4972
4915
  } else {
4973
4916
  validate40.errors = [{
@@ -6733,30 +6676,19 @@ function validate34(data, { instancePath = "", parentData, parentDataProperty, r
6733
6676
  const _errs7 = errors;
6734
6677
  if (errors === _errs7) {
6735
6678
  if (Array.isArray(data4)) {
6736
- if (data4.length < 1) {
6737
- validate34.errors = [{
6738
- instancePath: instancePath + "/children",
6739
- schemaPath: "#/properties/children/minItems",
6740
- keyword: "minItems",
6741
- params: { limit: 1 },
6742
- message: "must NOT have fewer than 1 items"
6743
- }];
6744
- return false;
6745
- } else {
6746
- const len0 = data4.length;
6747
- for (let i0 = 0; i0 < len0; i0++) {
6748
- const _errs9 = errors;
6749
- if (!validate35(data4[i0], {
6750
- instancePath: instancePath + "/children/" + i0,
6751
- parentData: data4,
6752
- parentDataProperty: i0,
6753
- rootData
6754
- })) {
6755
- vErrors = vErrors === null ? validate35.errors : vErrors.concat(validate35.errors);
6756
- errors = vErrors.length;
6757
- }
6758
- if (!(_errs9 === errors)) break;
6679
+ const len0 = data4.length;
6680
+ for (let i0 = 0; i0 < len0; i0++) {
6681
+ const _errs9 = errors;
6682
+ if (!validate35(data4[i0], {
6683
+ instancePath: instancePath + "/children/" + i0,
6684
+ parentData: data4,
6685
+ parentDataProperty: i0,
6686
+ rootData
6687
+ })) {
6688
+ vErrors = vErrors === null ? validate35.errors : vErrors.concat(validate35.errors);
6689
+ errors = vErrors.length;
6759
6690
  }
6691
+ if (!(_errs9 === errors)) break;
6760
6692
  }
6761
6693
  } else {
6762
6694
  validate34.errors = [{
@@ -7124,30 +7056,19 @@ function validate49(data, { instancePath = "", parentData, parentDataProperty, r
7124
7056
  const _errs10 = errors;
7125
7057
  if (errors === _errs10) {
7126
7058
  if (Array.isArray(data6)) {
7127
- if (data6.length < 1) {
7128
- validate49.errors = [{
7129
- instancePath: instancePath + "/children",
7130
- schemaPath: "#/properties/children/minItems",
7131
- keyword: "minItems",
7132
- params: { limit: 1 },
7133
- message: "must NOT have fewer than 1 items"
7134
- }];
7135
- return false;
7136
- } else {
7137
- const len0 = data6.length;
7138
- for (let i0 = 0; i0 < len0; i0++) {
7139
- const _errs12 = errors;
7140
- if (!wrapper6.validate(data6[i0], {
7141
- instancePath: instancePath + "/children/" + i0,
7142
- parentData: data6,
7143
- parentDataProperty: i0,
7144
- rootData
7145
- })) {
7146
- vErrors = vErrors === null ? wrapper6.validate.errors : vErrors.concat(wrapper6.validate.errors);
7147
- errors = vErrors.length;
7148
- }
7149
- if (!(_errs12 === errors)) break;
7059
+ const len0 = data6.length;
7060
+ for (let i0 = 0; i0 < len0; i0++) {
7061
+ const _errs12 = errors;
7062
+ if (!wrapper6.validate(data6[i0], {
7063
+ instancePath: instancePath + "/children/" + i0,
7064
+ parentData: data6,
7065
+ parentDataProperty: i0,
7066
+ rootData
7067
+ })) {
7068
+ vErrors = vErrors === null ? wrapper6.validate.errors : vErrors.concat(wrapper6.validate.errors);
7069
+ errors = vErrors.length;
7150
7070
  }
7071
+ if (!(_errs12 === errors)) break;
7151
7072
  }
7152
7073
  } else {
7153
7074
  validate49.errors = [{
@@ -7279,30 +7200,19 @@ function validate51(data, { instancePath = "", parentData, parentDataProperty, r
7279
7200
  const _errs7 = errors;
7280
7201
  if (errors === _errs7) {
7281
7202
  if (Array.isArray(data4)) {
7282
- if (data4.length < 1) {
7283
- validate51.errors = [{
7284
- instancePath: instancePath + "/children",
7285
- schemaPath: "#/properties/children/minItems",
7286
- keyword: "minItems",
7287
- params: { limit: 1 },
7288
- message: "must NOT have fewer than 1 items"
7289
- }];
7290
- return false;
7291
- } else {
7292
- const len0 = data4.length;
7293
- for (let i0 = 0; i0 < len0; i0++) {
7294
- const _errs9 = errors;
7295
- if (!wrapper6.validate(data4[i0], {
7296
- instancePath: instancePath + "/children/" + i0,
7297
- parentData: data4,
7298
- parentDataProperty: i0,
7299
- rootData
7300
- })) {
7301
- vErrors = vErrors === null ? wrapper6.validate.errors : vErrors.concat(wrapper6.validate.errors);
7302
- errors = vErrors.length;
7303
- }
7304
- if (!(_errs9 === errors)) break;
7203
+ const len0 = data4.length;
7204
+ for (let i0 = 0; i0 < len0; i0++) {
7205
+ const _errs9 = errors;
7206
+ if (!wrapper6.validate(data4[i0], {
7207
+ instancePath: instancePath + "/children/" + i0,
7208
+ parentData: data4,
7209
+ parentDataProperty: i0,
7210
+ rootData
7211
+ })) {
7212
+ vErrors = vErrors === null ? wrapper6.validate.errors : vErrors.concat(wrapper6.validate.errors);
7213
+ errors = vErrors.length;
7305
7214
  }
7215
+ if (!(_errs9 === errors)) break;
7306
7216
  }
7307
7217
  } else {
7308
7218
  validate51.errors = [{
@@ -9322,30 +9232,19 @@ function validate45(data, { instancePath = "", parentData, parentDataProperty, r
9322
9232
  const _errs34 = errors;
9323
9233
  if (errors === _errs34) {
9324
9234
  if (Array.isArray(data16)) {
9325
- if (data16.length < 1) {
9326
- validate45.errors = [{
9327
- instancePath: instancePath + "/children",
9328
- schemaPath: "#/properties/children/minItems",
9329
- keyword: "minItems",
9330
- params: { limit: 1 },
9331
- message: "must NOT have fewer than 1 items"
9332
- }];
9333
- return false;
9334
- } else {
9335
- const len1 = data16.length;
9336
- for (let i1 = 0; i1 < len1; i1++) {
9337
- const _errs36 = errors;
9338
- if (!validate46(data16[i1], {
9339
- instancePath: instancePath + "/children/" + i1,
9340
- parentData: data16,
9341
- parentDataProperty: i1,
9342
- rootData
9343
- })) {
9344
- vErrors = vErrors === null ? validate46.errors : vErrors.concat(validate46.errors);
9345
- errors = vErrors.length;
9346
- }
9347
- if (!(_errs36 === errors)) break;
9235
+ const len1 = data16.length;
9236
+ for (let i1 = 0; i1 < len1; i1++) {
9237
+ const _errs36 = errors;
9238
+ if (!validate46(data16[i1], {
9239
+ instancePath: instancePath + "/children/" + i1,
9240
+ parentData: data16,
9241
+ parentDataProperty: i1,
9242
+ rootData
9243
+ })) {
9244
+ vErrors = vErrors === null ? validate46.errors : vErrors.concat(validate46.errors);
9245
+ errors = vErrors.length;
9348
9246
  }
9247
+ if (!(_errs36 === errors)) break;
9349
9248
  }
9350
9249
  } else {
9351
9250
  validate45.errors = [{
@@ -29,7 +29,6 @@ const primitive = { type: [
29
29
  ] };
30
30
  const children = {
31
31
  type: "array",
32
- minItems: 1,
33
32
  items: { $ref: "#/$defs/node" }
34
33
  };
35
34
  const actions = {
@@ -26,6 +26,10 @@ function findUnsupportedOpenUINode(value) {
26
26
  message: `Unsupported OpenUI node type: ${String(record.type)}.`
27
27
  };
28
28
  if (record.type === "stack" || record.type === "grid" || record.type === "card") {
29
+ if (Array.isArray(record.children) && record.children.length === 0) return {
30
+ path: `${path}.children`,
31
+ message: "OpenUI containers need at least one child."
32
+ };
29
33
  if (Array.isArray(record.children)) for (const [index, child] of record.children.entries()) {
30
34
  const issue = visit(child, `${path}.children[${index}]`, depth + 1);
31
35
  if (issue) return issue;
@@ -7,7 +7,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
7
7
  * "Show more / Show less" toggle. Content shorter than the cap renders
8
8
  * as-is with no toggle.
9
9
  */
10
- function ExpandableContent({ children, collapsedMaxPx = 180, fadeClassName = "from-muted", className }) {
10
+ function ExpandableContent({ children, collapsedMaxPx = 180, fadeClassName = "from-muted", className, toggleClassName }) {
11
11
  const [expanded, setExpanded] = useState(false);
12
12
  const [overflowing, setOverflowing] = useState(false);
13
13
  const contentRef = useRef(null);
@@ -32,7 +32,7 @@ function ExpandableContent({ children, collapsedMaxPx = 180, fadeClassName = "fr
32
32
  }), overflowing ? /* @__PURE__ */ jsx("button", {
33
33
  type: "button",
34
34
  onClick: () => setExpanded((value) => !value),
35
- className: "mt-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground",
35
+ className: cn("mt-1 text-[11px] font-medium text-muted-foreground transition-colors hover:text-foreground", toggleClassName),
36
36
  children: expanded ? "Show less" : "Show more"
37
37
  }) : null]
38
38
  });
@@ -13,6 +13,7 @@ import { memo } from "react";
13
13
  import { jsx, jsxs } from "react/jsx-runtime";
14
14
  import { AlertCircle, ArrowLeft, ArrowRight, FileText, Loader2 } from "lucide-react";
15
15
  //#region src/run/expanded-tool-detail.tsx
16
+ const FLUSH_TOGGLE = "mx-3 mb-2";
16
17
  const EXT_LANG = {
17
18
  ts: "typescript",
18
19
  tsx: "tsx",
@@ -131,6 +132,7 @@ const ExpandedToolDetail = memo(({ part }) => {
131
132
  })]
132
133
  }), /* @__PURE__ */ jsx(ExpandableContent, {
133
134
  fadeClassName: "from-card",
135
+ toggleClassName: FLUSH_TOGGLE,
134
136
  children: /* @__PURE__ */ jsx(CodeBlock, {
135
137
  code: outputStr.length > 2e3 ? outputStr.slice(0, 2e3) + "\n...(truncated)" : outputStr,
136
138
  language: "json",
@@ -148,6 +150,7 @@ const ExpandedToolDetail = memo(({ part }) => {
148
150
  })]
149
151
  }), /* @__PURE__ */ jsx(ExpandableContent, {
150
152
  fadeClassName: "from-[var(--surface-danger-bg)]",
153
+ toggleClassName: FLUSH_TOGGLE,
151
154
  children: /* @__PURE__ */ jsx("pre", {
152
155
  className: "bg-[var(--surface-danger-bg)] p-3 text-xs font-mono whitespace-pre-wrap break-all text-[var(--surface-danger-text)]",
153
156
  children: error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.16.3",
3
+ "version": "11.16.5",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -253,20 +253,6 @@ describe("AgentTimeline tool actions", () => {
253
253
  expect(classes[4]).toContain("mt-4") // prose → user
254
254
  })
255
255
 
256
- it("softens the streaming cursor to a muted caret", () => {
257
- const { container } = render(
258
- <AgentTimeline
259
- items={[
260
- { id: "a-1", kind: "message", role: "assistant", content: "Typing", isStreaming: true },
261
- ]}
262
- />,
263
- )
264
- const caret = container.querySelector("[data-streaming-caret]")
265
- expect(caret).not.toBeNull()
266
- expect(caret!.className).toContain("bg-muted-foreground")
267
- expect(caret!.className).not.toContain("bg-primary")
268
- })
269
-
270
256
  it("renders the source part's real input in the expanded detail", async () => {
271
257
  const user = userEvent.setup()
272
258
  const probePart: ToolPart = {
@@ -162,13 +162,29 @@ function spacingBefore(prev: StepKind | undefined, next: StepKind): string {
162
162
  // wider than the bleed, so nothing clips.
163
163
  const ROW_BLEED = "-mx-3";
164
164
 
165
+ // The streaming caret ends the last line of text. It is drawn as the `::after`
166
+ // of the prose's last block (the last item of a trailing list), so it follows
167
+ // the final word instead of starting a line of its own below the paragraph.
168
+ const STREAMING_CARET =
169
+ "[&>:last-child:not(ul,ol)]:after:ml-0.5 [&>:last-child:not(ul,ol)]:after:inline-block [&>:last-child:not(ul,ol)]:after:h-[1em] [&>:last-child:not(ul,ol)]:after:w-0.5 [&>:last-child:not(ul,ol)]:after:animate-pulse [&>:last-child:not(ul,ol)]:after:rounded-full [&>:last-child:not(ul,ol)]:after:bg-muted-foreground [&>:last-child:not(ul,ol)]:after:align-text-bottom [&>:last-child:not(ul,ol)]:after:content-[''] " +
170
+ "[&>:is(ul,ol):last-child>li:last-child]:after:ml-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:inline-block [&>:is(ul,ol):last-child>li:last-child]:after:h-[1em] [&>:is(ul,ol):last-child>li:last-child]:after:w-0.5 [&>:is(ul,ol):last-child>li:last-child]:after:animate-pulse [&>:is(ul,ol):last-child>li:last-child]:after:rounded-full [&>:is(ul,ol):last-child>li:last-child]:after:bg-muted-foreground [&>:is(ul,ol):last-child>li:last-child]:after:align-text-bottom [&>:is(ul,ol):last-child>li:last-child]:after:content-['']";
171
+
165
172
  function AssistantMessage({ item }: { item: AgentTimelineMessageItem }) {
166
173
  return (
167
174
  <div>
168
175
  {item.content && (
169
- <Markdown className="tangle-prose text-[var(--font-size-base)] leading-[1.5]">{item.content}</Markdown>
176
+ <Markdown
177
+ // `length:` types the token: a bare `text-[var(--x)]` compiles to a
178
+ // color, which left the reading size at the Markdown default.
179
+ className={cn(
180
+ "tangle-prose text-[length:var(--font-size-base)] leading-[1.5]",
181
+ item.isStreaming && STREAMING_CARET,
182
+ )}
183
+ >
184
+ {item.content}
185
+ </Markdown>
170
186
  )}
171
- {item.isStreaming && (
187
+ {item.isStreaming && !item.content && (
172
188
  <span
173
189
  aria-hidden
174
190
  data-streaming-caret=""