@theoremjs/react 0.3.0 → 0.3.1

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 (258) hide show
  1. package/README.md +728 -202
  2. package/client/composer-hints.js +1 -1
  3. package/client/decision-images.d.ts +76 -0
  4. package/client/decision-images.js +139 -0
  5. package/client/decision-transport.d.ts +26 -4
  6. package/client/decision-transport.js +79 -6
  7. package/client/embedding-input.d.ts +84 -0
  8. package/client/embedding-input.js +194 -0
  9. package/client/embedding-space.d.ts +82 -0
  10. package/client/embedding-space.js +389 -0
  11. package/client/embedding-transport.d.ts +40 -0
  12. package/client/embedding-transport.js +71 -0
  13. package/client/failure.d.ts +7 -10
  14. package/client/failure.js +9 -9
  15. package/client/host-call.d.ts +1 -1
  16. package/client/host-call.js +7 -2
  17. package/client/host-transport.d.ts +2 -0
  18. package/client/host-transport.js +3 -2
  19. package/client/index.d.ts +19 -11
  20. package/client/index.js +14 -10
  21. package/client/ink-waveform.d.ts +1 -1
  22. package/client/ink-waveform.js +1 -2
  23. package/client/live/apply-live-turn-tool-event.js +1 -1
  24. package/client/live/live-captions.d.ts +30 -0
  25. package/client/live/live-captions.js +105 -1
  26. package/client/live/live-mic-forward.d.ts +1 -0
  27. package/client/live/live-mic-forward.js +11 -2
  28. package/client/live/live-page-tool.d.ts +34 -0
  29. package/client/live/live-page-tool.js +11 -0
  30. package/client/live/live-state.d.ts +4 -2
  31. package/client/live/live-state.js +2 -0
  32. package/client/live/live-video.js +1 -1
  33. package/client/live/run-live-tool-call.d.ts +7 -2
  34. package/client/live/run-live-tool-call.js +48 -10
  35. package/client/live/use-live-caption-log.d.ts +17 -0
  36. package/client/live/use-live-caption-log.js +51 -0
  37. package/client/live-client.d.ts +52 -4
  38. package/client/live-client.js +250 -59
  39. package/client/live-messages.d.ts +51 -13
  40. package/client/live-messages.js +42 -9
  41. package/client/page-dispatch.d.ts +15 -0
  42. package/client/page-dispatch.js +63 -0
  43. package/client/run-commit.d.ts +16 -43
  44. package/client/run-commit.js +26 -101
  45. package/client/run-session.d.ts +40 -41
  46. package/client/run-session.js +104 -112
  47. package/client/schema-fields.js +27 -6
  48. package/client/shaped-data.js +37 -8
  49. package/client/source-citations.js +1 -1
  50. package/client/space-geometry.d.ts +39 -0
  51. package/client/space-geometry.js +183 -0
  52. package/client/state-fields.js +1 -1
  53. package/client/tool-result.js +30 -12
  54. package/client/tool-resume.d.ts +18 -1
  55. package/client/tool-resume.js +4 -1
  56. package/client/trace-feed.d.ts +1 -1
  57. package/client/trace-story.d.ts +2 -2
  58. package/client/trace-story.js +43 -19
  59. package/client/trace-view.d.ts +2 -2
  60. package/client/trace-view.js +56 -16
  61. package/client/transcript-groups.d.ts +28 -2
  62. package/client/transcript-groups.js +63 -13
  63. package/client/transcript-schema.d.ts +10 -0
  64. package/client/transcript-schema.js +120 -0
  65. package/client/transport.d.ts +114 -26
  66. package/client/transport.js +106 -32
  67. package/client/turn-client.d.ts +17 -8
  68. package/client/turn-client.js +26 -31
  69. package/client/voice-recorder.d.ts +1 -1
  70. package/client/voice-recorder.js +2 -5
  71. package/client/wire-line.js +5 -5
  72. package/client/worklets/mic-capture.js +9 -9
  73. package/components/InkWaveform.d.ts +12 -1
  74. package/components/InkWaveform.js +65 -42
  75. package/components/live/use-live-runner-controls.d.ts +3 -4
  76. package/components/live/use-live-runner-controls.js +6 -10
  77. package/components/live/use-live-runner-model.d.ts +10 -6
  78. package/components/live/use-live-runner-model.js +29 -14
  79. package/components/live/use-live-runner-ui.d.ts +10 -8
  80. package/components/live/use-live-runner-ui.js +19 -11
  81. package/components/live/use-live-session-client.d.ts +4 -4
  82. package/components/live/use-live-session-client.js +11 -8
  83. package/components/use-composer-voice.d.ts +4 -4
  84. package/components/use-composer-voice.js +8 -8
  85. package/components/use-editor-selection.js +4 -1
  86. package/components/use-voice-playback.d.ts +2 -1
  87. package/components/use-voice-playback.js +4 -4
  88. package/hooks/use-described.d.ts +5 -1
  89. package/hooks/use-described.js +11 -9
  90. package/hooks/use-embedding-index.d.ts +27 -0
  91. package/hooks/use-embedding-index.js +43 -0
  92. package/hooks/use-one-call.d.ts +31 -0
  93. package/hooks/use-one-call.js +76 -0
  94. package/hooks/use-page-tool-warnings.d.ts +6 -0
  95. package/hooks/use-page-tool-warnings.js +25 -0
  96. package/hooks/use-second-ticker.d.ts +2 -0
  97. package/hooks/use-second-ticker.js +13 -0
  98. package/hooks/use-theorem-chat-actions.d.ts +25 -21
  99. package/hooks/use-theorem-chat-actions.js +67 -45
  100. package/hooks/use-theorem-chat-state.d.ts +6 -6
  101. package/hooks/use-theorem-chat-state.js +13 -9
  102. package/hooks/use-theorem-chat.d.ts +48 -14
  103. package/hooks/use-theorem-chat.js +329 -141
  104. package/hooks/use-theorem-decision.d.ts +5 -10
  105. package/hooks/use-theorem-decision.js +4 -67
  106. package/hooks/use-theorem-embedding.d.ts +18 -0
  107. package/hooks/use-theorem-embedding.js +12 -0
  108. package/hooks/use-theorem-host.d.ts +5 -4
  109. package/hooks/use-theorem-host.js +26 -8
  110. package/hooks/use-theorem-interface.d.ts +6 -3
  111. package/hooks/use-theorem-interface.js +12 -22
  112. package/index.d.ts +6 -3
  113. package/index.js +5 -2
  114. package/live.d.ts +1 -0
  115. package/live.js +1 -1
  116. package/package.json +6 -5
  117. package/server/checkpoint-ownership.d.ts +6 -0
  118. package/server/checkpoint-ownership.js +42 -0
  119. package/server/conversation-store.d.ts +92 -0
  120. package/server/conversation-store.js +131 -0
  121. package/server/conversation.d.ts +193 -0
  122. package/server/conversation.js +552 -0
  123. package/server/decision-handler.d.ts +12 -4
  124. package/server/decision-handler.js +29 -38
  125. package/server/embedding-handler.d.ts +33 -0
  126. package/server/embedding-handler.js +45 -0
  127. package/server/handler.d.ts +58 -20
  128. package/server/handler.js +441 -238
  129. package/server/mod.d.ts +15 -6
  130. package/server/mod.js +13 -5
  131. package/server/page-dispatch.d.ts +22 -0
  132. package/server/page-dispatch.js +127 -0
  133. package/server/request-body.d.ts +2 -0
  134. package/server/request-body.js +42 -0
  135. package/server/request-check.d.ts +6 -1
  136. package/server/request-check.js +27 -12
  137. package/server/session-authority.d.ts +8 -0
  138. package/server/session-authority.js +78 -0
  139. package/server/session-boundary.d.ts +26 -0
  140. package/server/session-boundary.js +64 -0
  141. package/server/session-identity.d.ts +16 -0
  142. package/server/session-identity.js +41 -0
  143. package/server/session-limits.d.ts +12 -0
  144. package/server/session-limits.js +59 -0
  145. package/server/session-store.d.ts +69 -20
  146. package/server/session-store.js +220 -5
  147. package/server/single-action.d.ts +21 -0
  148. package/server/single-action.js +30 -0
  149. package/server/steer-inbox.js +1 -1
  150. package/server/tool-operations.d.ts +6 -0
  151. package/server/tool-operations.js +132 -0
  152. package/server/turn-input.d.ts +10 -0
  153. package/server/turn-input.js +29 -0
  154. package/styles/ink-controls.css +78 -78
  155. package/ui/CardWall.d.ts +10 -0
  156. package/ui/CardWall.js +61 -0
  157. package/ui/ChatComposerBar.d.ts +28 -5
  158. package/ui/ChatComposerBar.js +85 -30
  159. package/ui/ChatTranscript.d.ts +14 -1
  160. package/ui/ChatTranscript.js +110 -57
  161. package/ui/ConsoleFrame.d.ts +61 -4
  162. package/ui/ConsoleFrame.js +109 -9
  163. package/ui/DecisionAnswers.d.ts +40 -2
  164. package/ui/DecisionAnswers.js +166 -35
  165. package/ui/DecisionImages.d.ts +16 -0
  166. package/ui/DecisionImages.js +46 -0
  167. package/ui/EmbeddingDocuments.d.ts +78 -0
  168. package/ui/EmbeddingDocuments.js +278 -0
  169. package/ui/EmbeddingQuery.d.ts +27 -0
  170. package/ui/EmbeddingQuery.js +95 -0
  171. package/ui/EmbeddingResults.d.ts +41 -0
  172. package/ui/EmbeddingResults.js +165 -0
  173. package/ui/EmbeddingSpace.d.ts +26 -0
  174. package/ui/EmbeddingSpace.js +106 -0
  175. package/ui/LiveCaptionsPanel.d.ts +22 -0
  176. package/ui/LiveCaptionsPanel.js +27 -0
  177. package/ui/LiveRunner.d.ts +6 -4
  178. package/ui/LiveRunner.js +42 -55
  179. package/ui/PaneState.d.ts +33 -0
  180. package/ui/PaneState.js +50 -0
  181. package/ui/SchemaFields.d.ts +1 -1
  182. package/ui/SchemaFields.js +54 -26
  183. package/ui/ShapedData.js +73 -28
  184. package/ui/SidePanel.d.ts +74 -5
  185. package/ui/SidePanel.js +82 -9
  186. package/ui/StateFields.d.ts +10 -2
  187. package/ui/StateFields.js +56 -13
  188. package/ui/TheoremChat.d.ts +65 -14
  189. package/ui/TheoremChat.js +66 -78
  190. package/ui/TheoremDecision.d.ts +12 -20
  191. package/ui/TheoremDecision.js +78 -35
  192. package/ui/TheoremEmbedding.d.ts +27 -0
  193. package/ui/TheoremEmbedding.js +122 -0
  194. package/ui/TheoremHost.d.ts +22 -5
  195. package/ui/TheoremHost.js +127 -37
  196. package/ui/ToolGateCard.d.ts +5 -1
  197. package/ui/ToolGateCard.js +30 -23
  198. package/ui/ToolResult.js +12 -10
  199. package/ui/ToolResultCharts.js +29 -12
  200. package/ui/TraceCharts.d.ts +2 -2
  201. package/ui/TraceCharts.js +48 -17
  202. package/ui/TraceGuardrails.d.ts +3 -2
  203. package/ui/TraceGuardrails.js +24 -6
  204. package/ui/TraceInspector.d.ts +11 -1
  205. package/ui/TraceInspector.js +45 -28
  206. package/ui/TraceInspectorPanel.d.ts +31 -9
  207. package/ui/TraceInspectorPanel.js +77 -39
  208. package/ui/TraceOverview.d.ts +7 -3
  209. package/ui/TraceOverview.js +44 -14
  210. package/ui/TraceSpanDetail.js +24 -22
  211. package/ui/TraceStory.js +9 -8
  212. package/ui/TraceValues.js +19 -9
  213. package/ui/TraceWaterfall.js +12 -9
  214. package/ui/VoiceNote.js +4 -4
  215. package/ui/approval-heading.d.ts +4 -0
  216. package/ui/approval-heading.js +9 -0
  217. package/ui/arrive.d.ts +18 -0
  218. package/ui/arrive.js +46 -0
  219. package/ui/built/theme.css +86 -68
  220. package/ui/built/theme.css.d.ts +9 -0
  221. package/ui/built/theorem.d.ts +3 -3
  222. package/ui/built/theorem.js +34 -5
  223. package/ui/built/theorem.variants.d.ts +8 -2
  224. package/ui/code-language.d.ts +12 -0
  225. package/ui/code-language.js +50 -0
  226. package/ui/disclosure-motion.js +53 -9
  227. package/ui/embedding-space-gl.d.ts +37 -0
  228. package/ui/embedding-space-gl.js +663 -0
  229. package/ui/embedding-space-view.d.ts +8 -0
  230. package/ui/embedding-space-view.js +8 -0
  231. package/ui/glide.d.ts +7 -0
  232. package/ui/glide.js +44 -0
  233. package/ui/icons.js +2 -1
  234. package/ui/index.d.ts +20 -9
  235. package/ui/index.js +20 -10
  236. package/ui/labels-provider.js +5 -2
  237. package/ui/labels.d.ts +374 -85
  238. package/ui/labels.js +1028 -188
  239. package/ui/markdown-plugins.d.ts +9 -0
  240. package/ui/markdown-plugins.js +13 -0
  241. package/ui/mcp-icon.d.ts +6 -0
  242. package/ui/mcp-icon.js +8 -0
  243. package/ui/row-keys.d.ts +19 -0
  244. package/ui/row-keys.js +33 -0
  245. package/ui/theme.d.ts +1 -1
  246. package/ui/theme.js +2 -2
  247. package/ui/theorem-theme.js +32 -10
  248. package/ui/trace-palette.d.ts +2 -16
  249. package/ui/trace-palette.js +2 -16
  250. package/ui/trace-patterns.d.ts +11 -3
  251. package/ui/trace-patterns.js +16 -3
  252. package/ui/transcript-copy-text.js +1 -0
  253. package/ui/wall-columns.d.ts +8 -0
  254. package/ui/wall-columns.js +19 -0
  255. package/client/walk-away.d.ts +0 -34
  256. package/client/walk-away.js +0 -48
  257. package/server/walk-away.d.ts +0 -22
  258. package/server/walk-away.js +0 -41
@@ -9,27 +9,35 @@ import { Selector } from '@astryxdesign/core/Selector';
9
9
  import { Slider } from '@astryxdesign/core/Slider';
10
10
  import { StackItem } from '@astryxdesign/core/Stack';
11
11
  import { Switch } from '@astryxdesign/core/Switch';
12
+ import { Text } from '@astryxdesign/core/Text';
12
13
  import { TextArea } from '@astryxdesign/core/TextArea';
13
14
  import { TextInput } from '@astryxdesign/core/TextInput';
14
15
  import { Tokenizer } from '@astryxdesign/core/Tokenizer';
15
16
  import { VStack } from '@astryxdesign/core/VStack';
16
17
  import { useState } from 'react';
17
- import { blankRow, schemaFields, withField } from "../client/schema-fields.js";
18
+ import { blankRow, schemaFields, withField, } from "../client/schema-fields.js";
18
19
  import { isRow, json } from "../client/shaped-data.js";
19
20
  import { useLabels } from "./labels-provider.js";
21
+ import { useKeyedRows } from "./row-keys.js";
20
22
  import { AddRow, Group, Labelled, RemoveRow } from "./StateFields.js";
21
23
  const NO_SUGGESTIONS = { search: () => [], bootstrap: () => [] };
22
24
  const CODE_FONT = { '--font-family-body': 'var(--font-family-code)' };
25
+ /** Lines a description shows before it is cut short. */
26
+ const DESCRIPTION_LINES = 2;
27
+ /** What a field is for, cut to a couple of lines. Hovering a cut one shows all of it. */
28
+ function Description({ text }) {
29
+ return (_jsx(Text, { type: "supporting", color: "secondary", maxLines: DESCRIPTION_LINES, children: text }));
30
+ }
23
31
  /** A field whose input carries no label of its own, labelled as one. */
24
32
  function Described({ field, children }) {
25
- return (_jsx(Labelled, { label: field.label, isRequired: field.isRequired, description: field.description, children: children }));
33
+ return (_jsx(Labelled, { label: field.label, isRequired: field.isRequired, children: children }));
26
34
  }
27
35
  /** A field the schema doesn't shape (no type, a union, a free object), as its JSON. */
28
36
  function JsonField({ field, value, onChange }) {
29
37
  const t = useLabels();
30
38
  const [text, setText] = useState(() => (value === undefined ? '' : json(value)));
31
39
  const [isValid, setValid] = useState(true);
32
- return (_jsx(TextArea, { label: field.label, description: field.description, isRequired: field.isRequired, size: "sm", rows: 3, value: text, hasSpellCheck: false, status: isValid ? undefined : { type: 'error', message: t('@theorem.decision.invalid_json') }, style: CODE_FONT, onChange: (next) => {
40
+ return (_jsx(TextArea, { label: field.label, isRequired: field.isRequired, size: "sm", rows: 3, value: text, hasSpellCheck: false, status: isValid ? undefined : { type: 'error', message: t('@theorem.decision.invalid_json') }, controlStyle: CODE_FONT, onChange: (next) => {
33
41
  setText(next);
34
42
  if (next.trim() === '') {
35
43
  setValid(true);
@@ -46,51 +54,69 @@ function JsonField({ field, value, onChange }) {
46
54
  } }));
47
55
  }
48
56
  /** A list of objects: each row its own fields, removable, and one more at the end. */
49
- function RowsField({ field, item, value, onChange }) {
57
+ function RowsField({ field, item, value, onChange, }) {
50
58
  const rows = Array.isArray(value) ? value : [];
51
- return (_jsx(Group, { label: field.label, isRequired: field.isRequired, description: field.description, children: _jsxs(VStack, { gap: 3, children: [rows.map((row, index) => (_jsxs(HStack, { gap: 2, vAlign: "start", children: [_jsx(StackItem, { size: "fill", children: _jsx(ObjectFields, { schema: item, value: isRow(row) ? row : {}, onChange: (next) => onChange(rows.map((old, at) => (at === index ? next : old))) }) }), _jsx(RemoveRow, { index: index, onRemove: () => onChange(rows.filter((_, at) => at !== index)) })] }, index))), _jsx(AddRow, { field: field.label, onAdd: () => onChange([...rows, blankRow(item)]) })] }) }));
59
+ const keyed = useKeyedRows(rows);
60
+ return (_jsx(Group, { label: field.label, isRequired: field.isRequired, description: field.description && _jsx(Description, { text: field.description }), children: _jsxs(VStack, { gap: 3, children: [keyed.rows.map(({ item: row, index, key }) => (_jsxs(HStack, { gap: 2, vAlign: "start", children: [_jsx(StackItem, { size: "fill", children: _jsx(ObjectFields, { schema: item, value: isRow(row) ? row : {}, onChange: (next) => onChange(rows.map((old, at) => (at === index ? next : old))) }) }), _jsx(RemoveRow, { index: index, onRemove: () => {
61
+ keyed.drop(index);
62
+ onChange(rows.filter((_, at) => at !== index));
63
+ } })] }, key))), _jsx(AddRow, { field: field.label, onAdd: () => onChange([...rows, blankRow(item)]) })] }) }));
52
64
  }
53
65
  function common(field) {
54
- return { label: field.label, description: field.description, isRequired: field.isRequired, size: 'sm' };
66
+ return {
67
+ label: field.label,
68
+ isRequired: field.isRequired,
69
+ size: 'sm',
70
+ };
55
71
  }
56
72
  /** One of a few options as segments; more as a menu. */
57
- function ChoiceField({ field, options, isMenu, value, onChange }) {
73
+ function ChoiceField({ field, options, isMenu, value, onChange, }) {
58
74
  if (isMenu) {
59
- return _jsx(Selector, { ...common(field), options: options, value: typeof value === 'string' ? value : undefined, hasSearch: options.length > 10, onChange: onChange });
75
+ return (_jsx(Selector, { ...common(field), options: options, value: typeof value === 'string' ? value : undefined, hasSearch: options.length > 10, onChange: onChange }));
60
76
  }
61
77
  return (_jsx(Described, { field: field, children: _jsx(HStack, { children: _jsx(SegmentedControl, { label: field.label, size: "sm", value: typeof value === 'string' ? value : '', onChange: onChange, children: options.map((option) => (_jsx(SegmentedControlItem, { value: option, label: option }, option))) }) }) }));
62
78
  }
63
- function TextField({ field, isLong, type, value, onChange }) {
79
+ function TextField({ field, isLong, type, value, onChange, }) {
64
80
  const text = typeof value === 'string' ? value : '';
65
- return isLong ? _jsx(TextArea, { ...common(field), rows: 3, value: text, onChange: onChange }) : _jsx(TextInput, { ...common(field), type: type, value: text, onChange: onChange });
81
+ return isLong ? (_jsx(TextArea, { ...common(field), rows: 3, value: text, onChange: onChange })) : (_jsx(TextInput, { ...common(field), type: type, value: text, onChange: onChange }));
66
82
  }
67
83
  /** A list of words or numbers as tokens. */
68
84
  function WordsField({ field, value, onChange }) {
69
85
  const t = useLabels();
70
86
  const words = Array.isArray(value) ? value : [];
71
- return (_jsx(Tokenizer, { ...common(field), value: words.map((word, index) => ({ id: `${String(index)}:${String(word)}`, label: String(word) })), searchSource: NO_SUGGESTIONS, hasCreate: true, debounceMs: 0, placeholder: t('@theorem.decision.add_value'), onChange: (items) =>
72
- // Numbers stay numbers when every word reads as one and the list held numbers.
73
- onChange(items.map((item) => (typeof words[0] === 'number' && Number.isFinite(Number(item.label)) ? Number(item.label) : item.label))) }));
87
+ return (_jsx(Tokenizer, { ...common(field), value: words.map((word, index) => ({
88
+ id: `${String(index)}:${String(word)}`,
89
+ label: String(word),
90
+ })), searchSource: NO_SUGGESTIONS, hasCreate: true, debounceMs: 0, placeholder: t('@theorem.decision.add_value'), onChange: (items) => onChange(items.map((item) => typeof words[0] === 'number' && Number.isFinite(Number(item.label))
91
+ ? Number(item.label)
92
+ : item.label)) }));
74
93
  }
75
94
  /** Each control's input, as the schema asks for it. */
76
95
  const CONTROLS = {
77
96
  choice: ({ field, control, value, onChange }) => (_jsx(ChoiceField, { field: field, options: control.options, isMenu: control.isMenu, value: value, onChange: onChange })),
78
- range: ({ field, control, value, onChange }) => (_jsx(Slider, { label: field.label, description: field.description, isRequired: field.isRequired, min: control.min, max: control.max, step: 1, value: typeof value === 'number' ? value : control.min, valueDisplay: "text", onChange: onChange })),
97
+ range: ({ field, control, value, onChange }) => (_jsx(Slider, { label: field.label, isRequired: field.isRequired, min: control.min, max: control.max, step: 1, value: typeof value === 'number' ? value : control.min, valueDisplay: "text", onChange: onChange })),
79
98
  number: ({ field, control, value, onChange }) => (_jsx(NumberInput, { ...common(field), value: typeof value === 'number' ? value : undefined, min: control.min, max: control.max, isIntegerOnly: control.isInteger, isWheelEnabled: false, onChange: (next) => onChange(next ?? undefined) })),
80
- switch: ({ field, value, onChange }) => _jsx(Switch, { label: field.label, description: field.description, value: value === true, onChange: onChange }),
99
+ switch: ({ field, value, onChange }) => (_jsx(Switch, { label: field.label, value: value === true, onChange: onChange })),
81
100
  date: ({ field, value, onChange }) => (_jsx(DateInput, { ...common(field), value: typeof value === 'string' ? value : undefined, onChange: onChange })),
82
- text: ({ field, control, value, onChange }) => _jsx(TextField, { field: field, isLong: control.isLong, type: control.type, value: value, onChange: onChange }),
83
- choices: ({ field, control, value, onChange }) => (_jsx(CheckboxList, { label: field.label, description: field.description, value: Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [], onChange: onChange, children: control.options.map((option) => (_jsx(CheckboxListItem, { label: option, value: option }, option))) })),
84
- words: ({ field, value, onChange }) => _jsx(WordsField, { field: field, value: value, onChange: onChange }),
85
- rows: ({ field, control, value, onChange }) => _jsx(RowsField, { field: field, item: control.item, value: value, onChange: onChange }),
86
- group: ({ field, control, value, onChange }) => (_jsx(Group, { label: field.label, isRequired: field.isRequired, description: field.description, children: _jsx(ObjectFields, { schema: control.schema, value: isRow(value) ? value : {}, onChange: onChange }) })),
87
- json: ({ field, value, onChange }) => _jsx(JsonField, { field: field, value: value, onChange: onChange }),
101
+ text: ({ field, control, value, onChange }) => (_jsx(TextField, { field: field, isLong: control.isLong, type: control.type, value: value, onChange: onChange })),
102
+ choices: ({ field, control, value, onChange }) => (_jsx(CheckboxList, { label: field.label, value: Array.isArray(value)
103
+ ? value.filter((entry) => typeof entry === 'string')
104
+ : [], onChange: onChange, children: control.options.map((option) => (_jsx(CheckboxListItem, { label: option, value: option }, option))) })),
105
+ words: ({ field, value, onChange }) => (_jsx(WordsField, { field: field, value: value, onChange: onChange })),
106
+ rows: ({ field, control, value, onChange }) => (_jsx(RowsField, { field: field, item: control.item, value: value, onChange: onChange })),
107
+ group: ({ field, control, value, onChange }) => (_jsx(Group, { label: field.label, isRequired: field.isRequired, description: field.description && _jsx(Description, { text: field.description }), children: _jsx(ObjectFields, { schema: control.schema, value: isRow(value) ? value : {}, onChange: onChange }) })),
108
+ json: ({ field, value, onChange }) => (_jsx(JsonField, { field: field, value: value, onChange: onChange })),
88
109
  };
89
110
  function Field({ field, value, onChange }) {
90
111
  const control = CONTROLS[field.control.kind];
91
- return control({ field, control: field.control, value, onChange });
112
+ const input = control({ field, control: field.control, value, onChange });
113
+ // why: A group already says its description under its name.
114
+ const isGroup = field.control.kind === 'rows' || field.control.kind === 'group';
115
+ if (!field.description || isGroup)
116
+ return input;
117
+ return (_jsxs(VStack, { gap: 1, children: [input, _jsx(Description, { text: field.description })] }));
92
118
  }
93
- /** Room a short field takes: numbers, dates, menus and one-line text sit side by side. */
119
+ /** Room a short field takes: numbers, dates, menus, and one-line text sit side by side. */
94
120
  const SHORT_FIELD_PX = 200;
95
121
  /** Segments whose labels run this long in all still fit a short field's width. */
96
122
  const SHORT_SEGMENT_CHARS = 18;
@@ -109,14 +135,16 @@ function isShort({ control }) {
109
135
  return false;
110
136
  }
111
137
  }
112
- function ObjectFields({ schema, value, onChange }) {
113
- return (_jsx(Grid, { columns: { minWidth: SHORT_FIELD_PX, repeat: 'fill' }, gap: 3, children: schemaFields(schema).map((field) => (_jsx("div", { style: isShort(field) ? { minWidth: 0, alignSelf: 'end' } : { gridColumn: '1 / -1', minWidth: 0 }, children: _jsx(Field, { field: field, value: value[field.key], onChange: (next) => onChange(withField(value, field.key, next)) }) }, field.key))) }));
138
+ function ObjectFields({ schema, value, onChange, }) {
139
+ return (_jsx(Grid, { columns: { minWidth: SHORT_FIELD_PX, repeat: 'fill' }, gap: 3, children: schemaFields(schema).map((field) => (_jsx("div", { style: isShort(field)
140
+ ? { minWidth: 0, alignSelf: 'start' }
141
+ : { gridColumn: '1 / -1', minWidth: 0 }, children: _jsx(Field, { field: field, value: value[field.key], onChange: (next) => onChange(withField(value, field.key, next)) }) }, field.key))) }));
114
142
  }
115
143
  /**
116
144
  * A tool's request as a form drawn from its input schema: each field the
117
145
  * input its type asks for, labelled by its title or key and described by its
118
146
  * description. Optional fields left empty aren't sent.
119
147
  */
120
- export function SchemaFields({ schema, value, onChange }) {
148
+ export function SchemaFields({ schema, value, onChange, }) {
121
149
  return _jsx(ObjectFields, { schema: schema, value: value, onChange: onChange });
122
150
  }
package/ui/ShapedData.js CHANGED
@@ -14,8 +14,14 @@ import { Token } from '@astryxdesign/core/Token';
14
14
  import { VStack } from '@astryxdesign/core/VStack';
15
15
  import { Component, useMemo, useState } from 'react';
16
16
  import { fieldLabel, fieldReading, isPlain, isProse, isRow, json, MAX_ROWS, plainReading, rowHeading, shapeOf, splitFields, unpacked, withUnit, } from "../client/shaped-data.js";
17
+ import { Arrive, useArrive } from "./arrive.js";
17
18
  import { useLabels } from "./labels-provider.js";
19
+ import { useMarkdownPlugins } from "./markdown-plugins.js";
20
+ import { keyedByContent } from "./row-keys.js";
18
21
  /** Each way a plain value reads, drawn. */
22
+ function ProseReading({ text }) {
23
+ return (_jsx(Markdown, { density: "compact", headingLevelStart: 4, plugins: useMarkdownPlugins(), children: text }));
24
+ }
19
25
  const READINGS = {
20
26
  none: () => _jsx(Text, { color: "secondary", children: "\u2014" }),
21
27
  boolean: ({ value }, _, t) => t(value ? '@theorem.data.yes' : '@theorem.data.no'),
@@ -23,18 +29,18 @@ const READINGS = {
23
29
  link: ({ href }) => (_jsx(Link, { href: href, target: "_blank", rel: "noreferrer", children: href })),
24
30
  date: ({ date }, formats) => formats.date.format(date),
25
31
  'date-time': ({ date }, formats) => formats.dateTime.format(date),
26
- // Prose (an answer, a summary) is usually Markdown; headings start small, under the section's own.
27
- text: ({ text }) => isProse(text) ? (_jsx(Markdown, { density: "compact", headingLevelStart: 4, children: text })) : (_jsx("span", { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: text })),
32
+ // why: Prose (an answer, a summary) is usually Markdown; headings start small, under the section's own.
33
+ text: ({ text }) => isProse(text) ? (_jsx(ProseReading, { text: text })) : (_jsx("span", { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: text })),
28
34
  };
29
35
  function Plain({ value, unit }) {
30
36
  const t = useLabels();
31
37
  const locale = useLocale();
32
38
  const formats = useMemo(() => ({
33
- // No grouping: ids and years read as written. Six significant digits: a timing reads 1.40369.
39
+ // why: No grouping: ids and years read as written. Six significant digits: a timing reads 1.40369.
34
40
  integer: new Intl.NumberFormat(locale, { useGrouping: false }),
35
41
  decimal: new Intl.NumberFormat(locale, { useGrouping: false, maximumSignificantDigits: 6 }),
36
42
  dateTime: new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short' }),
37
- // A bare date is a calendar day, parsed at UTC midnight: read it in UTC, or it slips a day west of Greenwich.
43
+ // why: A bare date is a calendar day, parsed at UTC midnight: read it in UTC, or it slips a day west of Greenwich.
38
44
  date: new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone: 'UTC' }),
39
45
  }), [locale]);
40
46
  const reading = plainReading(value, unit);
@@ -53,7 +59,7 @@ function More({ total }) {
53
59
  return null;
54
60
  return (_jsx(Text, { type: "supporting", color: "secondary", children: t('@theorem.data.more', { count: String(total - MAX_ROWS) }) }));
55
61
  }
56
- function DataTable({ columns, rows, units }) {
62
+ function DataTable({ columns, rows, units, }) {
57
63
  const tableColumns = columns.map((key, index) => {
58
64
  const { label, unit } = fieldLabel(key, units);
59
65
  return {
@@ -74,46 +80,80 @@ function ListRowEnd({ row }) {
74
80
  return (_jsxs(Text, { type: "supporting", color: "secondary", children: [label, " ", _jsx(Plain, { value: value, unit: unit })] }, key));
75
81
  }), row.tags.map((tag) => (_jsx(Token, { label: tag, size: "sm" }, tag)))] }));
76
82
  }
77
- function DataList({ rows }) {
78
- return (_jsxs(VStack, { gap: 1, children: [_jsx(List, { hasDividers: true, density: "compact", children: rows.slice(0, MAX_ROWS).map((row, index) => (_jsx(ListItem
79
- // biome-ignore lint/suspicious/noArrayIndexKey: rows have no identity of their own
80
- , { label: row.title, description: row.description, href: row.href, target: row.href ? '_blank' : undefined, rel: row.href ? 'noreferrer' : undefined, endContent: _jsx(ListRowEnd, { row: row }) }, index))) }), _jsx(More, { total: rows.length })] }));
83
+ function dataListItem(row) {
84
+ return {
85
+ label: row.title,
86
+ description: row.description,
87
+ href: row.href,
88
+ target: row.href ? '_blank' : undefined,
89
+ rel: row.href ? 'noreferrer' : undefined,
90
+ endContent: _jsx(ListRowEnd, { row: row }),
91
+ };
81
92
  }
82
- function Rows({ items, depth }) {
93
+ /** A top-level row eases in with the rest of a returned result. Nested rows stay put. */
94
+ function ArrivingDataRow({ row }) {
95
+ const arrive = useArrive();
96
+ return _jsx(ListItem, { className: arrive?.className, style: arrive?.style, ...dataListItem(row) });
97
+ }
98
+ function DataList({ rows, depth }) {
99
+ return (_jsxs(VStack, { gap: 1, children: [_jsx(List, { hasDividers: true, density: "compact", children: keyedByContent(rows.slice(0, MAX_ROWS), (row) => row.title).map(({ item: row, key }) => depth === 0 ? (_jsx(ArrivingDataRow, { row: row }, key)) : (_jsx(ListItem, { ...dataListItem(row) }, key))) }), _jsx(More, { total: rows.length })] }));
100
+ }
101
+ function ObjectRow({ item, index, depth, className, style, }) {
83
102
  const t = useLabels();
84
- return (_jsxs(VStack, { gap: 1, children: [_jsx(CollapsibleGroup, { type: "multiple", hasDividers: true, density: "compact", children: items.slice(0, MAX_ROWS).map((item, index) => {
85
- const heading = rowHeading(item);
86
- return (_jsx(Collapsible
87
- // biome-ignore lint/suspicious/noArrayIndexKey: rows have no identity of their own
88
- , { value: String(index), trigger: _jsxs(HStack, { gap: 2, vAlign: "center", wrap: "wrap", children: [_jsx(Text, { type: "body", children: heading.title ?? t('@theorem.data.item', { index: String(index + 1) }) }), heading.tags.map((tag) => (_jsx(Token, { label: tag, size: "sm" }, tag)))] }), children: _jsx(Node, { value: heading.rest, depth: depth + 1 }) }, index));
89
- }) }), _jsx(More, { total: items.length })] }));
103
+ const heading = rowHeading(item);
104
+ return (_jsx(Collapsible, { className: className, style: style, value: String(index), trigger: _jsxs(HStack, { gap: 2, vAlign: "center", wrap: "wrap", children: [_jsx(Text, { type: "body", children: heading.title ?? t('@theorem.data.item', { index: String(index + 1) }) }), heading.tags.map((tag) => (_jsx(Token, { label: tag, size: "sm" }, tag)))] }), children: _jsx(Node, { value: heading.rest, depth: depth + 1 }) }));
105
+ }
106
+ /** A top-level object row eases in with the result. One nested under it does not. */
107
+ function ArrivingObjectRow({ item, index, depth, }) {
108
+ const arrive = useArrive();
109
+ return (_jsx(ObjectRow, { item: item, index: index, depth: depth, className: arrive?.className, style: arrive?.style }));
110
+ }
111
+ function Rows({ items, depth }) {
112
+ return (_jsxs(VStack, { gap: 1, children: [_jsx(CollapsibleGroup, { type: "multiple", hasDividers: true, density: "compact", children: keyedByContent(items.slice(0, MAX_ROWS), (item) => JSON.stringify(item) ?? '').map(({ item, index, key }) => depth === 0 ? (_jsx(ArrivingObjectRow, { item: item, index: index, depth: depth }, key)) : (_jsx(ObjectRow, { item: item, index: index, depth: depth }, key))) }), _jsx(More, { total: items.length })] }));
113
+ }
114
+ function FieldSection({ shown, depth, className, style, }) {
115
+ return (_jsx(Collapsible, { className: className, style: style, value: shown.key, trigger: _jsx(Text, { type: "label", children: shown.title }), children: _jsx(VStack, { paddingInlineStart: 3, children: _jsx(Node, { value: shown.value, depth: depth + 1, units: shown.units }) }) }));
116
+ }
117
+ function ArrivingFieldSection({ shown, depth }) {
118
+ const arrive = useArrive();
119
+ return (_jsx(FieldSection, { shown: shown, depth: depth, className: arrive?.className, style: arrive?.style }));
90
120
  }
91
121
  function Fields({ row, depth, units }) {
92
122
  const { plain, sections } = splitFields(row);
93
123
  const fields = plain.length > 0 && (_jsx(MetadataList, { label: { position: 'start', width: '40%' }, children: plain.map(([key, value]) => {
94
124
  const { label, unit } = fieldReading(key, value, units);
95
- return (_jsx(MetadataListItem, { label: label, children: Array.isArray(unpacked(value)) ? _jsx(Node, { value: value, depth: depth + 1 }) : _jsx(Plain, { value: value, unit: unit }) }, key));
125
+ return (_jsx(MetadataListItem, { label: label, children: Array.isArray(unpacked(value)) ? (_jsx(Node, { value: value, depth: depth + 1 })) : (_jsx(Plain, { value: value, unit: unit })) }, key));
96
126
  }) }));
97
127
  return (_jsxs(VStack, { gap: 2, children: [depth > 0 && fields, sections.length > 0 && (_jsx(CollapsibleGroup, { type: "multiple", hasDividers: true, density: "compact",
98
- // The top level's first section starts open: it's usually the payload. The rest wait to be asked for.
99
- defaultValue: depth === 0 ? sections.slice(0, 1).map((shown) => shown.key) : [], children: sections.map((shown) => (_jsx(Collapsible, { value: shown.key, trigger: _jsx(Text, { type: "label", children: shown.title }), children: _jsx(VStack, { paddingInlineStart: 3, children: _jsx(Node, { value: shown.value, depth: depth + 1, units: shown.units }) }) }, shown.key))) })), depth === 0 && fields] }));
128
+ // why: The top level's first section starts open: it's usually the payload. The rest wait to be asked for.
129
+ defaultValue: depth === 0 ? sections.slice(0, 1).map((shown) => shown.key) : [], children: sections.map((shown) => depth === 0 ? (_jsx(ArrivingFieldSection, { shown: shown, depth: depth }, shown.key)) : (_jsx(FieldSection, { shown: shown, depth: depth }, shown.key))) })), depth === 0 && fields ? _jsx(Arrive, { children: fields }) : null] }));
100
130
  }
101
131
  /** Each shape a value takes, drawn. */
102
132
  const SHAPES = {
103
133
  json: (_, { value }) => _jsx(Json, { text: json(value) }),
104
134
  none: (_, { t }) => _jsx(Text, { color: "secondary", children: t('@theorem.data.none') }),
105
135
  plain: (_, { value }) => (_jsx(Text, { type: "body", children: _jsx(Plain, { value: value }) })),
106
- tokens: ({ items }) => (_jsxs(VStack, { gap: 1, children: [_jsx(HStack, { gap: 1, wrap: "wrap", children: items.slice(0, MAX_ROWS).map((item, index) => (_jsx(Token, { label: item === null ? '—' : String(item), size: "sm" }, index))) }), _jsx(More, { total: items.length })] })),
107
- list: ({ rows }) => _jsx(DataList, { rows: rows }),
108
- table: ({ columns, rows }, { units }) => _jsx(DataTable, { columns: columns, rows: rows, units: units }),
136
+ tokens: ({ items }) => (_jsxs(VStack, { gap: 1, children: [_jsx(HStack, { gap: 1, wrap: "wrap", children: keyedByContent(items.slice(0, MAX_ROWS), (item) => String(item)).map(({ item, key }) => (_jsx(Token, { label: item === null ? '—' : String(item), size: "sm" }, key))) }), _jsx(More, { total: items.length })] })),
137
+ list: ({ rows }, { depth }) => _jsx(DataList, { rows: rows, depth: depth }),
138
+ table: ({ columns, rows }, { units }) => (_jsx(DataTable, { columns: columns, rows: rows, units: units })),
109
139
  rows: ({ items }, { depth }) => _jsx(Rows, { items: items, depth: depth }),
110
140
  fields: ({ row }, { depth, units }) => _jsx(Fields, { row: row, depth: depth, units: units }),
111
141
  };
112
- function Node({ value: raw, depth, units }) {
142
+ function Node({ value: raw, depth, units, }) {
113
143
  const t = useLabels();
114
144
  const value = unpacked(raw);
115
145
  const shape = shapeOf(value, depth);
116
- return SHAPES[shape.kind](shape, { value, depth, units, t });
146
+ const body = SHAPES[shape.kind](shape, {
147
+ value,
148
+ depth,
149
+ units,
150
+ t,
151
+ });
152
+ // why: A list, a set of rows, or an object's sections stagger their own children. Anything else at the top of a result is one arrival.
153
+ if (depth !== 0 || shape.kind === 'list' || shape.kind === 'rows' || shape.kind === 'fields') {
154
+ return body;
155
+ }
156
+ return _jsx(Arrive, { children: body });
117
157
  }
118
158
  /** A render the rules didn't foresee throws into the JSON, never into the transcript. */
119
159
  class ShapeBoundary extends Component {
@@ -121,6 +161,9 @@ class ShapeBoundary extends Component {
121
161
  static getDerivedStateFromError() {
122
162
  return { failed: true };
123
163
  }
164
+ componentDidCatch() {
165
+ this.props.onFallback();
166
+ }
124
167
  render() {
125
168
  return this.state.failed ? this.props.fallback : this.props.children;
126
169
  }
@@ -131,10 +174,8 @@ export function ShapedData({ value: raw, title, lead, ledKeys = [] }) {
131
174
  const locale = useLocale();
132
175
  const [view, setView] = useState('data');
133
176
  const value = unpacked(raw);
134
- // A plain value reads the same either way; only structure gets the switch.
135
177
  const structured = !isPlain(value);
136
178
  const text = useMemo(() => (structured ? json(value) : ''), [structured, value]);
137
- // What the lead hasn't shown; nothing when it showed every field.
138
179
  const rest = useMemo(() => {
139
180
  if (!ledKeys.length || !isRow(value))
140
181
  return value;
@@ -142,10 +183,14 @@ export function ShapedData({ value: raw, title, lead, ledKeys = [] }) {
142
183
  return left.length ? Object.fromEntries(left) : undefined;
143
184
  }, [value, ledKeys]);
144
185
  if (text.length > LARGE_JSON_CHARS) {
145
- const size = new Intl.NumberFormat(locale, { style: 'unit', unit: 'kilobyte', maximumFractionDigits: 0 }).format(LARGE_JSON_CHARS / 1000);
186
+ const size = new Intl.NumberFormat(locale, {
187
+ style: 'unit',
188
+ unit: 'kilobyte',
189
+ maximumFractionDigits: 0,
190
+ }).format(LARGE_JSON_CHARS / 1000);
146
191
  return (_jsxs(VStack, { gap: 2, children: [_jsx(Text, { type: "supporting", color: "secondary", children: title }), _jsx(Text, { type: "supporting", color: "secondary", children: t('@theorem.data.large', { size }) }), _jsx(Json, { text: text })] }));
147
192
  }
148
193
  return (_jsxs(VStack, { gap: 2, children: [_jsxs(HStack, { gap: 2, vAlign: "center", hAlign: "between", children: [_jsx(Text, { type: "supporting", color: "secondary", children: title }), structured && (_jsxs(SegmentedControl, { label: t('@theorem.data.view'), size: "sm", value: view, onChange: (next) => {
149
194
  setView(next === 'json' ? 'json' : 'data');
150
- }, children: [_jsx(SegmentedControlItem, { value: "data", label: t('@theorem.data.shaped') }), _jsx(SegmentedControlItem, { value: "json", label: t('@theorem.data.json') })] }))] }), view === 'json' && structured ? (_jsx(Json, { text: text })) : (_jsx(ShapeBoundary, { fallback: _jsx(Json, { text: text }), children: _jsxs(VStack, { gap: 3, children: [lead, rest === undefined ? null : _jsx(Node, { value: rest, depth: 0 })] }) }))] }));
195
+ }, children: [_jsx(SegmentedControlItem, { value: "data", label: t('@theorem.data.shaped') }), _jsx(SegmentedControlItem, { value: "json", label: t('@theorem.data.json') })] }))] }), view === 'json' && structured ? (_jsx(Json, { text: text })) : (_jsx(ShapeBoundary, { fallback: _jsx(Json, { text: text }), onFallback: () => setView('json'), children: _jsxs(VStack, { gap: 3, children: [lead, rest === undefined ? null : _jsx(Node, { value: rest, depth: 0 })] }) }))] }));
151
196
  }
package/ui/SidePanel.d.ts CHANGED
@@ -10,8 +10,9 @@ export declare function useSidePanel(containerRef: RefObject<HTMLDivElement | nu
10
10
  resizable: ResizableRegion;
11
11
  };
12
12
  /** The Layout header row: side panel toggles, at the end. */
13
- export declare function SidePanelHeader({ children }: {
13
+ export declare function SidePanelHeader({ children, floating, }: {
14
14
  children?: ReactNode;
15
+ floating?: boolean;
15
16
  }): import("react").JSX.Element;
16
17
  /** A side panel's words: its name, and its toggle and drag handle actions. */
17
18
  export type SidePanelLabels = {
@@ -41,17 +42,85 @@ export type SidePanelProps = {
41
42
  open?: boolean;
42
43
  /** Card padding; 0 for content that brings its own (e.g. ChatLayout). */
43
44
  padding?: 0;
45
+ /**
46
+ * `end` docks on the trailing edge: the handle, then the panel. `start`
47
+ * docks on the leading edge: the panel, then the handle.
48
+ */
49
+ side?: 'start' | 'end';
50
+ className?: string;
44
51
  children?: ReactNode;
45
52
  };
46
53
  /** Page corners like the host's raised panels, no border or shadow; the card fill, so surface-filled cards read on it. */
47
54
  export declare const RAISED: {
48
55
  background: string;
49
56
  borderRadius: string;
57
+ overflow: string;
50
58
  };
59
+ /** A docked panel's inset: 12px off the outer edge, 16px off the bottom (the page inset). */
60
+ export declare const PANEL_EDGE = 3;
61
+ export declare const PANEL_BOTTOM = 4;
62
+ /** The raised card every docked panel and the content beside one share. */
63
+ export declare function PanelBody({ padding, inset, className, children, }: {
64
+ padding?: 0;
65
+ /**
66
+ * `end` insets the trailing edge (a panel docked there, or the content
67
+ * filling beside a start panel). `start` insets the leading edge. `beside`
68
+ * insets both.
69
+ */
70
+ inset?: 'end' | 'start' | 'beside';
71
+ className?: string;
72
+ children?: ReactNode;
73
+ }): import("react").JSX.Element;
74
+ /** The scroll inside a panel card. The same area the trace uses, so a short body sizes to its rows. */
75
+ export declare function PanelScroll({ label, padding, children, }: {
76
+ label: string;
77
+ padding?: 4;
78
+ children?: ReactNode;
79
+ }): import("react").JSX.Element;
51
80
  /**
52
- * A Layout `end` panel as Astryx's IDE template builds one (a reversed
53
- * `ResizeHandle` before a `LayoutPanel` sized by `useResizable`), holding a
54
- * raised section inset from the layout edge and bottom instead of a flat pane.
81
+ * A Layout side panel as Astryx's IDE template builds one (a `ResizeHandle`
82
+ * beside a `LayoutPanel` sized by `useResizable`), holding a raised section
83
+ * inset from the layout's outer edge and bottom instead of a flat pane.
84
+ * `end` puts the handle first and reverses it; `start` puts the panel first.
55
85
  * For more than one panel, nest Layouts, one panel each, as the template does.
56
86
  */
57
- export declare function SidePanel({ id, labels, resizable, open, padding, children }: SidePanelProps): import("react").JSX.Element;
87
+ export declare function SidePanel({ id, labels, resizable, open, padding, side, className, children, }: SidePanelProps): import("react").JSX.Element;
88
+ /** The element's width, kept current. */
89
+ export declare function useWidth(): {
90
+ ref: (node: HTMLDivElement | null) => (() => void) | undefined;
91
+ width: number;
92
+ };
93
+ /**
94
+ * A raised pane that fills its place, inset from the sides. Its content is
95
+ * told whether the pane is wide enough for two readable columns.
96
+ */
97
+ export declare function RaisedPane({ id, header, children, }: {
98
+ id?: string;
99
+ header?: ReactNode;
100
+ children: (isWide: boolean) => ReactNode;
101
+ }): import("react").JSX.Element;
102
+ /** A raised pane's content. Narrow, the detail takes the overview's place; wide, it sits beside it. */
103
+ export declare function PaneLayout({ isWide, label, detailLabel, lead, overview, detail, }: {
104
+ isWide: boolean;
105
+ label: string;
106
+ detailLabel: string;
107
+ /** Rendered once, ahead of the content. */
108
+ lead?: ReactNode;
109
+ overview: ReactNode;
110
+ detail: ReactNode;
111
+ }): import("react").JSX.Element;
112
+ /** One titled block of a pane's detail; `doc` says what the title means, on hover. */
113
+ export declare function PanePanel({ title, doc, children, }: {
114
+ title: string;
115
+ doc?: string;
116
+ children: ReactNode;
117
+ }): import("react").JSX.Element;
118
+ /** Text as it was written: its line breaks kept, long words wrapped. */
119
+ export declare function Prose({ text }: {
120
+ text: string;
121
+ }): import("react").JSX.Element;
122
+ /** The surface, or the view in its place; the surface stays mounted underneath, so nothing it holds is lost. */
123
+ export declare function InPlace({ view, children }: {
124
+ view: ReactNode;
125
+ children: ReactNode;
126
+ }): import("react").JSX.Element;
package/ui/SidePanel.js CHANGED
@@ -2,8 +2,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Card } from '@astryxdesign/core/Card';
3
3
  import { HStack } from '@astryxdesign/core/HStack';
4
4
  import { IconButton } from '@astryxdesign/core/IconButton';
5
- import { LayoutHeader, LayoutPanel } from '@astryxdesign/core/Layout';
5
+ import { Layout, LayoutContent, LayoutHeader, LayoutPanel } from '@astryxdesign/core/Layout';
6
6
  import { ResizeHandle, useResizable, } from '@astryxdesign/core/Resizable';
7
+ import { ScrollableArea } from '@astryxdesign/core/ScrollableArea';
8
+ import { StackItem } from '@astryxdesign/core/Stack';
9
+ import { Text } from '@astryxdesign/core/Text';
10
+ import { Tooltip as HoverTip } from '@astryxdesign/core/Tooltip';
7
11
  import { VStack } from '@astryxdesign/core/VStack';
8
12
  import { useCallback, useId, useState } from 'react';
9
13
  /**
@@ -29,22 +33,91 @@ export function useSidePanel(containerRef, initiallyOpen) {
29
33
  return { id, open, toggle, resizable };
30
34
  }
31
35
  /** The Layout header row: side panel toggles, at the end. */
32
- export function SidePanelHeader({ children }) {
36
+ export function SidePanelHeader({ children, floating = false, }) {
37
+ if (floating)
38
+ return (_jsx("div", { style: {
39
+ position: 'absolute',
40
+ insetBlockStart: 'var(--spacing-4)',
41
+ insetInlineEnd: 'var(--spacing-4)',
42
+ zIndex: 3,
43
+ }, children: _jsx(HStack, { hAlign: "end", gap: 1, children: children }) }));
33
44
  return (_jsx(LayoutHeader, { hasDivider: false, children: _jsx(HStack, { hAlign: "end", gap: 1, children: children }) }));
34
45
  }
35
46
  /** Shows or hides one side panel (`panelId` from useSidePanel). */
36
47
  export function SidePanelToggle({ labels, icon, panelId, open, onToggle, }) {
37
48
  const action = open ? labels.hide : labels.show;
38
- return (_jsx(IconButton, { label: action, tooltip: action, variant: "ghost", "aria-expanded": open, "aria-controls": panelId, icon: icon, onClick: onToggle }));
49
+ return (_jsx(IconButton, { label: action, tooltip: action, variant: open ? 'secondary' : 'ghost', "aria-expanded": open, "aria-controls": panelId, icon: icon, onClick: onToggle }));
39
50
  }
40
51
  /** Page corners like the host's raised panels, no border or shadow; the card fill, so surface-filled cards read on it. */
41
- export const RAISED = { background: 'var(--color-background-card)', borderRadius: 'var(--radius-page)' };
52
+ export const RAISED = {
53
+ background: 'var(--theorem-pane-background, var(--color-background-card))',
54
+ borderRadius: 'var(--radius-page)',
55
+ overflow: 'hidden',
56
+ };
57
+ /** A docked panel's inset: 12px off the outer edge, 16px off the bottom (the page inset). */
58
+ export const PANEL_EDGE = 3;
59
+ export const PANEL_BOTTOM = 4;
60
+ /** The raised card every docked panel and the content beside one share. */
61
+ export function PanelBody({ padding, inset = 'end', className, children, }) {
62
+ return (_jsx(VStack, { height: "100%", className: className, paddingInlineStart: inset === 'end' ? undefined : PANEL_EDGE, paddingInlineEnd: inset === 'start' ? undefined : PANEL_EDGE, paddingBlockEnd: PANEL_BOTTOM, children: _jsx(Card, { variant: "transparent", height: "100%", padding: padding, style: RAISED, children: children }) }));
63
+ }
64
+ /** The scroll inside a panel card. The same area the trace uses, so a short body sizes to its rows. */
65
+ export function PanelScroll({ label, padding, children, }) {
66
+ return (_jsx(ScrollableArea, { className: "theorem-panel-scroll", label: label, height: "100%", padding: padding, children: children }));
67
+ }
42
68
  /**
43
- * A Layout `end` panel as Astryx's IDE template builds one (a reversed
44
- * `ResizeHandle` before a `LayoutPanel` sized by `useResizable`), holding a
45
- * raised section inset from the layout edge and bottom instead of a flat pane.
69
+ * A Layout side panel as Astryx's IDE template builds one (a `ResizeHandle`
70
+ * beside a `LayoutPanel` sized by `useResizable`), holding a raised section
71
+ * inset from the layout's outer edge and bottom instead of a flat pane.
72
+ * `end` puts the handle first and reverses it; `start` puts the panel first.
46
73
  * For more than one panel, nest Layouts, one panel each, as the template does.
47
74
  */
48
- export function SidePanel({ id, labels, resizable, open = true, padding, children }) {
49
- return (_jsxs(_Fragment, { children: [open ? (_jsx(ResizeHandle, { direction: "horizontal", isReversed: true, hasDivider: false, isAlwaysVisible: false, resizable: resizable.props, label: labels.resize })) : null, _jsx(LayoutPanel, { id: id, label: labels.name, role: "complementary", width: open ? resizable.size : 0, hasDivider: false, padding: 0, isScrollable: false, inert: !open, children: _jsx(VStack, { height: "100%", paddingInlineEnd: 3, paddingBlockEnd: 3, children: _jsx(Card, { variant: "transparent", height: "100%", padding: padding, style: RAISED, children: open ? children : null }) }) })] }));
75
+ export function SidePanel({ id, labels, resizable, open = true, padding, side = 'end', className, children, }) {
76
+ const handle = open ? (_jsx(ResizeHandle, { direction: "horizontal", isReversed: side === 'end', hasDivider: false, isAlwaysVisible: false, resizable: resizable.props, label: labels.resize })) : null;
77
+ const panel = (_jsx(LayoutPanel, { id: id, className: className, label: labels.name, role: "complementary", width: open ? resizable.size : 0, hasDivider: false, padding: 0, isScrollable: false, inert: !open, children: _jsx(PanelBody, { padding: padding, inset: side, children: open ? children : null }) }));
78
+ return side === 'start' ? (_jsxs(_Fragment, { children: [panel, handle] })) : (_jsxs(_Fragment, { children: [handle, panel] }));
79
+ }
80
+ /** The element's width, kept current. */
81
+ export function useWidth() {
82
+ const [width, setWidth] = useState(0);
83
+ const ref = useCallback((node) => {
84
+ if (!node)
85
+ return;
86
+ const observer = new ResizeObserver(([entry]) => setWidth(entry?.contentRect.width ?? 0));
87
+ observer.observe(node);
88
+ return () => observer.disconnect();
89
+ }, []);
90
+ return { ref, width };
91
+ }
92
+ /**
93
+ * A raised pane that fills its place, inset from the sides. Its content is
94
+ * told whether the pane is wide enough for two readable columns.
95
+ */
96
+ export function RaisedPane({ id, header, children, }) {
97
+ const { ref, width } = useWidth();
98
+ return (_jsx(Layout, { height: "fill", style: { position: 'relative' }, header: header, content: _jsx(LayoutContent, { padding: 0, isScrollable: false, children: _jsx(VStack, { height: "100%", padding: 3, children: _jsx(Card, { variant: "transparent", height: "100%", padding: 0, style: RAISED, children: _jsx("div", { id: id, ref: ref, style: { height: '100%' }, children: children(isSidePanelWide(width)) }) }) }) }) }));
99
+ }
100
+ /** A raised pane's content. Narrow, the detail takes the overview's place; wide, it sits beside it. */
101
+ export function PaneLayout({ isWide, label, detailLabel, lead, overview, detail, }) {
102
+ if (!isWide) {
103
+ return (_jsxs(PanelScroll, { label: label, children: [lead, detail || overview] }));
104
+ }
105
+ return (_jsxs(HStack, { height: "100%", gap: 0, children: [_jsx(StackItem, { size: "fill", children: _jsxs(PanelScroll, { label: label, children: [lead, overview] }) }), detail ? (_jsx("div", { style: {
106
+ width: '48%',
107
+ flexShrink: 0,
108
+ height: '100%',
109
+ borderInlineStart: '1px solid var(--color-border)',
110
+ }, children: _jsx(PanelScroll, { label: detailLabel, children: detail }) })) : null] }));
111
+ }
112
+ /** One titled block of a pane's detail; `doc` says what the title means, on hover. */
113
+ export function PanePanel({ title, doc, children, }) {
114
+ return (_jsx(Card, { padding: 3, variant: "muted", style: { background: 'var(--color-background-surface)' }, children: _jsxs(VStack, { gap: 2, children: [_jsx(HoverTip, { content: doc ?? title, children: _jsx(Text, { type: "supporting", color: "secondary", children: title }) }), children] }) }));
115
+ }
116
+ /** Text as it was written: its line breaks kept, long words wrapped. */
117
+ export function Prose({ text }) {
118
+ return _jsx("span", { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }, children: text });
119
+ }
120
+ /** The surface, or the view in its place; the surface stays mounted underneath, so nothing it holds is lost. */
121
+ export function InPlace({ view, children }) {
122
+ return (_jsxs(_Fragment, { children: [view, _jsx("div", { style: { display: view ? 'none' : 'contents' }, children: children })] }));
50
123
  }
@@ -1,15 +1,23 @@
1
1
  import type { DecisionJson } from '@theoremjs/agents';
2
+ import { type DecisionImages } from '../client/decision-images.ts';
2
3
  export type Edit<T = DecisionJson> = {
3
4
  value: T;
4
5
  onChange: (next: T) => void;
5
6
  };
7
+ /** A text area's height: one row per line of its text, within `min` and `max`. Wrapped lines do not count. */
8
+ export declare function textRows(text: string, min: number, max: number): number;
9
+ /** The images the state names, and how to change them; a field whose whole text names one shows it. */
10
+ export declare const StateImages: import("react").Context<{
11
+ images: DecisionImages;
12
+ onImages: (next: DecisionImages) => void;
13
+ }>;
6
14
  export type GroupLabel = {
7
15
  label: string;
8
16
  isRequired?: boolean;
9
- description?: string;
17
+ description?: React.ReactNode;
10
18
  };
11
19
  /** Several fields under one name, in the same label a single field has. */
12
- export declare function Labelled({ label, isRequired, description, children }: GroupLabel & {
20
+ export declare function Labelled({ label, isRequired, description, children, }: GroupLabel & {
13
21
  children: React.ReactNode;
14
22
  }): import("react").JSX.Element;
15
23
  /** Nested fields sit under their name, set in by a rule. */