@workbench-kit/react 0.0.1-prototype.0 → 0.0.2-prototype.0.1.2

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 (155) hide show
  1. package/package.json +33 -9
  2. package/src/index.ts +545 -43
  3. package/src/jdw/JdwPreview.tsx +38 -0
  4. package/src/jdw/JsonCodeEditorPane.tsx +274 -0
  5. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +29 -0
  6. package/src/jdw/builtins/renderBuiltinWidgetNode.tsx +126 -0
  7. package/src/jdw/createBuiltinJdwRegistry.ts +111 -0
  8. package/src/jdw/cssRenderBackend.tsx +134 -0
  9. package/src/jdw/document.ts +38 -0
  10. package/src/jdw/fixtures/jdw-fixtures.ts +52 -0
  11. package/src/jdw/fixtures/jdw-sample-screens.ts +522 -0
  12. package/src/jdw/index.ts +45 -0
  13. package/src/jdw/parse.ts +1 -0
  14. package/src/jdw/renderJdw.tsx +32 -0
  15. package/src/json-config/JsonConfigWorkbench.tsx +316 -0
  16. package/src/json-config/index.ts +6 -0
  17. package/src/json-config/json-config-editor-state.ts +42 -0
  18. package/src/layout/Panel.tsx +146 -1
  19. package/src/layout/SideBarViewFrame.tsx +367 -328
  20. package/src/layout/WorkbenchCanvas.tsx +849 -0
  21. package/src/layout/WorkbenchFullscreen.tsx +368 -0
  22. package/src/layout/WorkbenchLayout.tsx +5 -0
  23. package/src/layout/WorkbenchLayoutBase.tsx +294 -0
  24. package/src/layout/WorkbenchPropertyPanel.tsx +497 -0
  25. package/src/layout/WorkbenchSidebarActions.tsx +274 -0
  26. package/src/layout/WorkbenchTree.tsx +351 -0
  27. package/src/layout/layoutHelpers.ts +25 -0
  28. package/src/modal/ConfirmDialog.tsx +61 -53
  29. package/src/modal/Modal.tsx +295 -35
  30. package/src/overlay/ContextMenu.tsx +146 -145
  31. package/src/primitives/AbsoluteBox.tsx +46 -0
  32. package/src/primitives/Badge.tsx +12 -12
  33. package/src/primitives/Button.tsx +44 -14
  34. package/src/primitives/Checkbox.tsx +11 -5
  35. package/src/primitives/EmptyState.tsx +27 -26
  36. package/src/primitives/Field.tsx +38 -38
  37. package/src/primitives/IconButton.tsx +5 -2
  38. package/src/primitives/List.tsx +111 -0
  39. package/src/primitives/NumberInput.tsx +38 -0
  40. package/src/primitives/ScrollArea.tsx +42 -0
  41. package/src/primitives/Select.tsx +1 -12
  42. package/src/primitives/StatusBar.tsx +46 -0
  43. package/src/primitives/TextArea.tsx +36 -0
  44. package/src/primitives/TextInput.tsx +11 -2
  45. package/src/primitives/WorkbenchChrome.tsx +424 -0
  46. package/src/primitives/WorkbenchEditor.tsx +394 -0
  47. package/src/primitives/index.ts +105 -0
  48. package/src/primitives/select/Select.tsx +266 -0
  49. package/src/primitives/select/index.ts +2 -0
  50. package/src/primitives/select/options.ts +38 -0
  51. package/src/primitives/select/overlay.ts +63 -0
  52. package/src/primitives/select/select.app.css +9 -0
  53. package/src/primitives/select/select.css +140 -0
  54. package/src/primitives/select/types.ts +18 -0
  55. package/src/primitives/styles.css +955 -0
  56. package/src/scrollbars.css +69 -0
  57. package/src/styles.css +7738 -1762
  58. package/src/utils/codicon.ts +17 -0
  59. package/src/widget-asset/WidgetAssetEditor.tsx +140 -0
  60. package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -0
  61. package/src/widget-asset/WidgetAssetModeControls.tsx +42 -0
  62. package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -0
  63. package/src/widget-asset/WidgetAssetWorkbench.tsx +112 -0
  64. package/src/widget-asset/index.ts +17 -0
  65. package/src/widget-asset/widget-asset-document.ts +37 -0
  66. package/src/widget-asset/widget-asset-mode.ts +3 -0
  67. package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -0
  68. package/src/widget-studio/create-widget-studio-workspace-editor.tsx +79 -0
  69. package/src/widget-studio/index.ts +7 -0
  70. package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +26 -0
  71. package/src/widget-tree/WidgetAssetPalette.tsx +77 -0
  72. package/src/widget-tree/WidgetInspectorPanel.tsx +266 -0
  73. package/src/widget-tree/WidgetSourceEditor.tsx +56 -0
  74. package/src/widget-tree/WidgetTreeLab.tsx +272 -0
  75. package/src/widget-tree/WidgetTreeModeControls.tsx +44 -0
  76. package/src/widget-tree/WidgetTreeSidePanel.tsx +49 -0
  77. package/src/widget-tree/WidgetTreeView.tsx +97 -0
  78. package/src/widget-tree/WidgetTreeWorkbench.tsx +111 -0
  79. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +154 -0
  80. package/src/widget-tree/demo-registry.ts +32 -0
  81. package/src/widget-tree/demo-widget-assets.ts +4 -0
  82. package/src/widget-tree/index.ts +27 -0
  83. package/src/widget-tree/widget-tree-document.ts +20 -0
  84. package/src/widget-tree/widget-tree-layout.ts +31 -0
  85. package/src/widget-tree/widget-tree-mode.ts +7 -0
  86. package/src/widget-tree/widget-tree-play-helpers.ts +74 -0
  87. package/src/workbench/ActivityBar.tsx +52 -52
  88. package/src/workbench/ArtifactShell.tsx +397 -0
  89. package/src/workbench/CommandPalette.tsx +819 -0
  90. package/src/workbench/ConfirmationFlow.tsx +245 -0
  91. package/src/workbench/MarkdownPreview.tsx +151 -0
  92. package/src/workbench/ShortcutCommandBridge.tsx +368 -0
  93. package/src/workbench/SplitView.tsx +136 -136
  94. package/src/workbench/StatusBar.tsx +178 -123
  95. package/src/workbench/StructuredArtifactEditor.tsx +205 -0
  96. package/src/workbench/Timeline.tsx +302 -0
  97. package/src/workbench/WorkbenchCanvasShell.tsx +941 -0
  98. package/src/workbench/WorkbenchShell.tsx +74 -70
  99. package/src/workbench/WorkbenchStandaloneShell.tsx +318 -291
  100. package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -0
  101. package/src/workbench/auth/WorkbenchLoginView.tsx +635 -0
  102. package/src/workbench/auth/index.ts +19 -0
  103. package/src/workbench/chat/ChatComposer.tsx +165 -148
  104. package/src/workbench/chat/ChatMessageItem.tsx +10 -1
  105. package/src/workbench/chat/index.ts +9 -0
  106. package/src/workbench/chat/slashCommand.ts +28 -0
  107. package/src/workbench/chat/useChatRuntimeState.ts +76 -0
  108. package/src/workbench/chat/useSlashCommandSuggest.ts +101 -0
  109. package/src/workbench/commands.ts +520 -490
  110. package/src/workbench/index.ts +443 -98
  111. package/src/workbench/keyboard.ts +53 -0
  112. package/src/workbench/schema/index.ts +48 -0
  113. package/src/workbench/schema/workbenchDocument.ts +31 -0
  114. package/src/workbench/schema/workbenchDocumentActions.ts +6 -0
  115. package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -0
  116. package/src/workbench/schema/workbenchDocumentPatch.ts +13 -0
  117. package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -0
  118. package/src/workbench/settings/NavigationPanel.tsx +47 -0
  119. package/src/workbench/settings/SchemaForm.tsx +420 -0
  120. package/src/workbench/settings/SectionedPanel.tsx +415 -0
  121. package/src/workbench/settings/StructuredDataForm.tsx +539 -0
  122. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +559 -0
  123. package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -0
  124. package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -0
  125. package/src/workbench/settings/StructuredDataTableView.tsx +127 -0
  126. package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -0
  127. package/src/workbench/settings/WorkbenchSettingsModal.tsx +21 -12
  128. package/src/workbench/settings/WorkbenchSettingsNav.tsx +38 -22
  129. package/src/workbench/settings/index.ts +135 -0
  130. package/src/workbench/settings/panel-region.css +21 -0
  131. package/src/workbench/settings/sectionedPanelScrollSpy.ts +143 -0
  132. package/src/workbench/settings/structured-data-schema-panel.css +330 -0
  133. package/src/workbench/settings/structured-data-table-view.css +85 -0
  134. package/src/workbench/settings/structuredDataSchema.ts +886 -0
  135. package/src/workbench/shell.ts +15 -0
  136. package/src/workbench/shellState.ts +203 -203
  137. package/src/workbench/standalone.ts +101 -94
  138. package/src/workbench/status.ts +129 -0
  139. package/src/workbench/theme.ts +44 -0
  140. package/src/workbench/workspace/MultiProviderExplorer.tsx +625 -0
  141. package/src/workbench/workspace/WorkspaceDraftsContext.tsx +111 -0
  142. package/src/workbench/workspace/WorkspaceEditor.tsx +47 -12
  143. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +334 -353
  144. package/src/workbench/workspace/WorkspaceExplorer.tsx +532 -524
  145. package/src/workbench/workspace/WorkspaceFileIcon.tsx +32 -2
  146. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -113
  147. package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -47
  148. package/src/workbench/workspace/index.ts +156 -98
  149. package/src/workbench/workspace/mimeType.ts +45 -0
  150. package/src/workbench/workspace/path.ts +10 -10
  151. package/src/workbench/workspace/search.ts +6 -6
  152. package/src/workbench/workspace/tree.ts +1 -1
  153. package/src/workbench/workspace/types.ts +10 -10
  154. package/src/workbench/workspace/useVirtualWorkspace.ts +111 -98
  155. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -0
@@ -0,0 +1,245 @@
1
+ import { useState, type FormEvent, type ReactNode } from 'react';
2
+ import { Modal } from '../modal/Modal';
3
+ import { Button } from '../primitives/Button';
4
+ import { cx } from '../utils/cx';
5
+ import {
6
+ getWorkbenchStatusLabel,
7
+ isWorkbenchStatusBusy,
8
+ isWorkbenchStatusDisabled,
9
+ type WorkbenchStatus,
10
+ } from './status';
11
+
12
+ export type WorkbenchConfirmationVariant = 'default' | 'danger';
13
+
14
+ export type WorkbenchConfirmationSideEffect =
15
+ | 'external-write'
16
+ | 'none'
17
+ | 'workspace-write'
18
+ | (string & {});
19
+
20
+ export type WorkbenchConfirmationReason = 'cancel' | 'close' | 'confirm';
21
+
22
+ export interface WorkbenchConfirmationAction {
23
+ cancelLabel?: ReactNode;
24
+ closeLabel?: string;
25
+ confirmLabel?: ReactNode;
26
+ danger?: boolean;
27
+ detail?: ReactNode;
28
+ disabled?: boolean;
29
+ disabledReason?: ReactNode;
30
+ id: string;
31
+ message: ReactNode;
32
+ metadata?: Record<string, unknown>;
33
+ pendingLabel?: ReactNode;
34
+ sideEffect?: WorkbenchConfirmationSideEffect;
35
+ status?: WorkbenchStatus;
36
+ title: ReactNode;
37
+ variant?: WorkbenchConfirmationVariant;
38
+ }
39
+
40
+ export interface WorkbenchConfirmationContext {
41
+ action: WorkbenchConfirmationAction;
42
+ actionId: string;
43
+ pending: boolean;
44
+ reason: WorkbenchConfirmationReason;
45
+ sideEffect: WorkbenchConfirmationSideEffect;
46
+ status: WorkbenchStatus;
47
+ variant: WorkbenchConfirmationVariant;
48
+ }
49
+
50
+ export interface WorkbenchConfirmationConfirmContext extends WorkbenchConfirmationContext {
51
+ event: FormEvent<HTMLFormElement>;
52
+ reason: 'confirm';
53
+ }
54
+
55
+ export interface WorkbenchConfirmationCancelContext extends WorkbenchConfirmationContext {
56
+ reason: 'cancel';
57
+ }
58
+
59
+ export interface WorkbenchConfirmationCloseContext extends WorkbenchConfirmationContext {
60
+ reason: 'close';
61
+ }
62
+
63
+ export interface WorkbenchConfirmationFlowProps {
64
+ action?: WorkbenchConfirmationAction | null;
65
+ allowCancelWhilePending?: boolean;
66
+ className?: string;
67
+ onCancel?: (
68
+ action: WorkbenchConfirmationAction,
69
+ context: WorkbenchConfirmationCancelContext,
70
+ ) => void;
71
+ onClose?: (
72
+ action: WorkbenchConfirmationAction,
73
+ context: WorkbenchConfirmationCloseContext,
74
+ ) => void;
75
+ onConfirm?: (
76
+ action: WorkbenchConfirmationAction,
77
+ context: WorkbenchConfirmationConfirmContext,
78
+ ) => Promise<void> | void;
79
+ onConfirmError?: (
80
+ error: unknown,
81
+ action: WorkbenchConfirmationAction,
82
+ context: WorkbenchConfirmationConfirmContext,
83
+ ) => void;
84
+ open?: boolean;
85
+ pending?: boolean;
86
+ }
87
+
88
+ export function getWorkbenchConfirmationVariant(
89
+ action: WorkbenchConfirmationAction,
90
+ ): WorkbenchConfirmationVariant {
91
+ return action.variant ?? (action.danger ? 'danger' : 'default');
92
+ }
93
+
94
+ export function getWorkbenchConfirmationStatus({
95
+ action,
96
+ pending = false,
97
+ }: {
98
+ action: WorkbenchConfirmationAction;
99
+ pending?: boolean;
100
+ }): WorkbenchStatus {
101
+ if (pending) return 'running';
102
+ if (action.status) return action.status;
103
+ if (action.disabled) return 'disabled';
104
+ return 'idle';
105
+ }
106
+
107
+ export function isWorkbenchConfirmationActionDisabled(action: WorkbenchConfirmationAction) {
108
+ return Boolean(action.disabled || (action.status && isWorkbenchStatusDisabled(action.status)));
109
+ }
110
+
111
+ export function getWorkbenchConfirmationConfirmLabel({
112
+ action,
113
+ pending = false,
114
+ }: {
115
+ action: WorkbenchConfirmationAction;
116
+ pending?: boolean;
117
+ }) {
118
+ if (pending) return action.pendingLabel ?? 'Working...';
119
+ return action.confirmLabel ?? 'Confirm';
120
+ }
121
+
122
+ export function getWorkbenchConfirmationButtonVariant(
123
+ action: WorkbenchConfirmationAction,
124
+ ): 'danger' | 'primary' {
125
+ return getWorkbenchConfirmationVariant(action) === 'danger' ? 'danger' : 'primary';
126
+ }
127
+
128
+ export function getWorkbenchConfirmationSideEffect(
129
+ action: WorkbenchConfirmationAction,
130
+ ): WorkbenchConfirmationSideEffect {
131
+ return action.sideEffect ?? 'none';
132
+ }
133
+
134
+ export function WorkbenchConfirmationFlow({
135
+ action,
136
+ allowCancelWhilePending = false,
137
+ className,
138
+ onCancel,
139
+ onClose,
140
+ onConfirm,
141
+ onConfirmError,
142
+ open,
143
+ pending,
144
+ }: WorkbenchConfirmationFlowProps) {
145
+ const [internalPending, setInternalPending] = useState(false);
146
+
147
+ if (!action || open === false) return null;
148
+
149
+ const resolvedPending = pending ?? internalPending;
150
+ const status = getWorkbenchConfirmationStatus({ action, pending: resolvedPending });
151
+ const variant = getWorkbenchConfirmationVariant(action);
152
+ const sideEffect = getWorkbenchConfirmationSideEffect(action);
153
+ const confirmDisabled = resolvedPending || isWorkbenchConfirmationActionDisabled(action);
154
+ const cancelDisabled = resolvedPending && !allowCancelWhilePending;
155
+ const statusLabel = getWorkbenchStatusLabel(status);
156
+
157
+ const createContext = (reason: WorkbenchConfirmationReason): WorkbenchConfirmationContext => ({
158
+ action,
159
+ actionId: action.id,
160
+ pending: resolvedPending,
161
+ reason,
162
+ sideEffect,
163
+ status,
164
+ variant,
165
+ });
166
+
167
+ const handleClose = () => {
168
+ if (cancelDisabled) return;
169
+ onClose?.(action, { ...createContext('close'), reason: 'close' });
170
+ };
171
+
172
+ const handleCancel = () => {
173
+ if (cancelDisabled) return;
174
+ onCancel?.(action, { ...createContext('cancel'), reason: 'cancel' });
175
+ };
176
+
177
+ const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
178
+ event.preventDefault();
179
+ if (confirmDisabled) return;
180
+
181
+ const context: WorkbenchConfirmationConfirmContext = {
182
+ ...createContext('confirm'),
183
+ event,
184
+ reason: 'confirm',
185
+ };
186
+
187
+ const result = onConfirm?.(action, context);
188
+ if (!result || typeof result.then !== 'function') return;
189
+
190
+ setInternalPending(true);
191
+ try {
192
+ await result;
193
+ } catch (error) {
194
+ onConfirmError?.(error, action, context);
195
+ } finally {
196
+ setInternalPending(false);
197
+ }
198
+ };
199
+
200
+ return (
201
+ <Modal
202
+ bodyClassName="ui-workbench-confirmation-flow__body"
203
+ className={cx('ui-workbench-confirmation-flow', className)}
204
+ closeLabel={action.closeLabel ?? 'Close confirmation'}
205
+ footer={
206
+ <>
207
+ <Button disabled={cancelDisabled} onClick={handleCancel}>
208
+ {action.cancelLabel ?? 'Cancel'}
209
+ </Button>
210
+ <Button
211
+ aria-busy={isWorkbenchStatusBusy(status) ? true : undefined}
212
+ className="ui-workbench-confirmation-flow__confirm"
213
+ data-status={status}
214
+ disabled={confirmDisabled}
215
+ type="submit"
216
+ variant={getWorkbenchConfirmationButtonVariant(action)}
217
+ >
218
+ {getWorkbenchConfirmationConfirmLabel({ action, pending: resolvedPending })}
219
+ </Button>
220
+ </>
221
+ }
222
+ title={action.title}
223
+ titleSuffix={
224
+ sideEffect !== 'none' ? (
225
+ <span className="ui-workbench-confirmation-flow__side-effect">{sideEffect}</span>
226
+ ) : null
227
+ }
228
+ onClose={handleClose}
229
+ onSubmit={handleSubmit}
230
+ >
231
+ <p className="ui-workbench-confirmation-flow__message">{action.message}</p>
232
+ {action.detail ? (
233
+ <div className="ui-workbench-confirmation-flow__detail">{action.detail}</div>
234
+ ) : null}
235
+ {action.disabledReason ? (
236
+ <div className="ui-workbench-confirmation-flow__disabled-reason">
237
+ {action.disabledReason}
238
+ </div>
239
+ ) : null}
240
+ <span className="ui-visually-hidden" aria-live="polite">
241
+ {statusLabel}
242
+ </span>
243
+ </Modal>
244
+ );
245
+ }
@@ -0,0 +1,151 @@
1
+ import Markdown from 'react-markdown';
2
+ import { Children, isValidElement, type ComponentPropsWithoutRef, type ReactNode } from 'react';
3
+ import { cx } from '../utils/cx';
4
+
5
+ export interface WorkbenchMarkdownPreviewProps extends ComponentPropsWithoutRef<'article'> {
6
+ source: string;
7
+ }
8
+
9
+ export function WorkbenchMarkdownPreview({
10
+ className,
11
+ source,
12
+ ...props
13
+ }: WorkbenchMarkdownPreviewProps) {
14
+ return (
15
+ <article className={cx('ui-workbench-markdown-preview', className)} {...props}>
16
+ <Markdown
17
+ components={{
18
+ a: ({ children, href, ...anchorProps }) => (
19
+ <a
20
+ href={href}
21
+ rel="noreferrer"
22
+ target={href?.startsWith('#') ? undefined : '_blank'}
23
+ {...anchorProps}
24
+ >
25
+ {children}
26
+ </a>
27
+ ),
28
+ code: ({ className: codeClassName, ...codeProps }) => (
29
+ <code
30
+ className={cx('ui-workbench-markdown-preview__code', codeClassName)}
31
+ {...codeProps}
32
+ />
33
+ ),
34
+ pre: ({ children }) => {
35
+ const codeBlock = getMarkdownCodeBlock(children);
36
+
37
+ if (codeBlock?.language === 'mermaid') {
38
+ return <WorkbenchMermaidPreview source={codeBlock.source} />;
39
+ }
40
+
41
+ return <pre>{children}</pre>;
42
+ },
43
+ }}
44
+ >
45
+ {source}
46
+ </Markdown>
47
+ </article>
48
+ );
49
+ }
50
+
51
+ function WorkbenchMermaidPreview({ source }: { source: string }) {
52
+ const summary = summarizeMermaidSource(source);
53
+
54
+ return (
55
+ <figure
56
+ className="ui-workbench-markdown-preview__mermaid"
57
+ data-testid="markdown-mermaid-preview"
58
+ >
59
+ <figcaption className="ui-workbench-markdown-preview__mermaid-header">
60
+ <span className="ui-workbench-markdown-preview__mermaid-title">
61
+ <i aria-hidden className="codicon codicon-git-branch" />
62
+ Mermaid
63
+ </span>
64
+ <span className="ui-workbench-markdown-preview__mermaid-kind">{summary.kind}</span>
65
+ </figcaption>
66
+ <div
67
+ className="ui-workbench-markdown-preview__mermaid-canvas"
68
+ aria-label="Mermaid diagram preview"
69
+ >
70
+ {summary.nodes.length > 0 ? (
71
+ <div className="ui-workbench-markdown-preview__mermaid-flow">
72
+ {summary.nodes.map((node, index) => (
73
+ <span
74
+ key={`${node}-${index}`}
75
+ className="ui-workbench-markdown-preview__mermaid-node"
76
+ >
77
+ {node}
78
+ </span>
79
+ ))}
80
+ </div>
81
+ ) : (
82
+ <pre className="ui-workbench-markdown-preview__mermaid-source">{source.trim()}</pre>
83
+ )}
84
+ </div>
85
+ </figure>
86
+ );
87
+ }
88
+
89
+ function getMarkdownCodeBlock(children: ReactNode): { language: string; source: string } | null {
90
+ const [child] = Children.toArray(children);
91
+ if (!isValidElement<{ className?: string | undefined; children?: ReactNode }>(child)) {
92
+ return null;
93
+ }
94
+
95
+ const className = child.props.className;
96
+ if (typeof className !== 'string') {
97
+ return null;
98
+ }
99
+
100
+ const languageMatch = /\blanguage-([a-z0-9_-]+)\b/i.exec(className);
101
+ if (!languageMatch) {
102
+ return null;
103
+ }
104
+
105
+ return {
106
+ language: languageMatch[1]?.toLowerCase() ?? '',
107
+ source: extractTextContent(child.props.children).replace(/\n$/, ''),
108
+ };
109
+ }
110
+
111
+ function extractTextContent(value: ReactNode): string {
112
+ return Children.toArray(value)
113
+ .map((child) => {
114
+ if (typeof child === 'string' || typeof child === 'number') {
115
+ return String(child);
116
+ }
117
+
118
+ if (isValidElement<{ children?: ReactNode }>(child)) {
119
+ return extractTextContent(child.props.children);
120
+ }
121
+
122
+ return '';
123
+ })
124
+ .join('');
125
+ }
126
+
127
+ function summarizeMermaidSource(source: string): { kind: string; nodes: string[] } {
128
+ const lines = source
129
+ .split(/\r?\n/)
130
+ .map((line) => line.trim())
131
+ .filter(Boolean);
132
+ const declaration = lines[0] ?? 'mermaid';
133
+ const kind = declaration.split(/\s+/)[0] ?? 'mermaid';
134
+ const nodes = lines
135
+ .slice(1)
136
+ .flatMap((line) => line.split(/-->|---|==>|-.->|->/))
137
+ .map((segment) => cleanMermaidNodeLabel(segment))
138
+ .filter((segment) => segment.length > 0)
139
+ .filter((segment, index, all) => all.indexOf(segment) === index)
140
+ .slice(0, 6);
141
+
142
+ return { kind, nodes };
143
+ }
144
+
145
+ function cleanMermaidNodeLabel(value: string): string {
146
+ return value
147
+ .replace(/^[A-Za-z0-9_]+(?=\[|\(|\{)/, '')
148
+ .replace(/^[A-Za-z0-9_]+$/, '')
149
+ .replace(/[[\]{}()"]/g, '')
150
+ .trim();
151
+ }