@workbench-kit/shell-react 0.0.2-prototype.0.2.10

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 (105) hide show
  1. package/README.md +98 -0
  2. package/package.json +51 -0
  3. package/src/chat/ai-mock-proposals.ts +39 -0
  4. package/src/chat/command-input.ts +52 -0
  5. package/src/chat/command-policy.ts +54 -0
  6. package/src/chat/command-surface.tsx +155 -0
  7. package/src/chat/use-chat-command-proposals.ts +194 -0
  8. package/src/chat/view-data.ts +20 -0
  9. package/src/chat/view.tsx +220 -0
  10. package/src/commands/inspector-surface.tsx +46 -0
  11. package/src/commands/use-command-descriptors.ts +41 -0
  12. package/src/commands/use-context-key-revision.ts +18 -0
  13. package/src/commands/view-data.ts +17 -0
  14. package/src/commands/view.tsx +43 -0
  15. package/src/devtools/WorkbenchDevtoolsPanel.tsx +124 -0
  16. package/src/devtools/WorkbenchDevtoolsShell.tsx +17 -0
  17. package/src/devtools/index.ts +11 -0
  18. package/src/devtools/use-workbench-devtools-snapshot.ts +84 -0
  19. package/src/devtools/workbench-devtools-snapshot.ts +140 -0
  20. package/src/devtools/workbench-devtools.css +82 -0
  21. package/src/editor/area-dnd.ts +69 -0
  22. package/src/editor/area-model.ts +71 -0
  23. package/src/editor/area.css +454 -0
  24. package/src/editor/area.tsx +676 -0
  25. package/src/editor/host-surface.tsx +691 -0
  26. package/src/editor/markdown-document-view-data.ts +16 -0
  27. package/src/editor/pane-visibility.ts +130 -0
  28. package/src/editor/resource.ts +36 -0
  29. package/src/editor/state-storage.ts +182 -0
  30. package/src/editor/tab-context-menu.ts +118 -0
  31. package/src/editor/use-editor.ts +138 -0
  32. package/src/editor/view-providers.tsx +504 -0
  33. package/src/editor/workspace-file-availability.ts +15 -0
  34. package/src/editor/workspace-reconcile.tsx +31 -0
  35. package/src/explorer/context-menu.ts +114 -0
  36. package/src/explorer/create-command-workspace-explorer-port.ts +86 -0
  37. package/src/explorer/reveal.ts +112 -0
  38. package/src/explorer/search-view-data.ts +15 -0
  39. package/src/explorer/search-view.tsx +65 -0
  40. package/src/explorer/use-active-workspace-path.ts +15 -0
  41. package/src/explorer/view-data.ts +58 -0
  42. package/src/explorer/view.tsx +227 -0
  43. package/src/extensions/context-menu.ts +52 -0
  44. package/src/extensions/management-model.ts +313 -0
  45. package/src/extensions/use-extension-management.ts +241 -0
  46. package/src/extensions/view-data.ts +19 -0
  47. package/src/extensions/view.tsx +63 -0
  48. package/src/field-remap/chrome-labels.ts +74 -0
  49. package/src/field-remap/convert-note-editor.tsx +206 -0
  50. package/src/field-remap/convert-palette.tsx +126 -0
  51. package/src/field-remap/demo.tsx +105 -0
  52. package/src/field-remap/detail-panel.tsx +659 -0
  53. package/src/field-remap/editor-surface.tsx +36 -0
  54. package/src/field-remap/flow-adapter.ts +838 -0
  55. package/src/field-remap/flow-ops.ts +442 -0
  56. package/src/field-remap/flow.tsx +948 -0
  57. package/src/field-remap/graph.tsx +559 -0
  58. package/src/field-remap/index.ts +88 -0
  59. package/src/field-remap/io-class-browse.tsx +145 -0
  60. package/src/field-remap/jsonata-transform.ts +166 -0
  61. package/src/field-remap/panel.tsx +466 -0
  62. package/src/field-remap/samples.ts +507 -0
  63. package/src/field-remap/shape-io-editor.tsx +170 -0
  64. package/src/field-remap/table-mapping-adapter.ts +59 -0
  65. package/src/field-remap/transform-options-editor.tsx +312 -0
  66. package/src/field-remap/tree.tsx +404 -0
  67. package/src/field-remap/view-data.ts +16 -0
  68. package/src/field-remap/view.css +1367 -0
  69. package/src/field-remap/view.tsx +86 -0
  70. package/src/index.ts +294 -0
  71. package/src/jdw/document-view-data.ts +31 -0
  72. package/src/jdw/json-form-source-patch.ts +323 -0
  73. package/src/jdw/lab-view-data.ts +25 -0
  74. package/src/jdw/lab-view.css +21 -0
  75. package/src/jdw/lab-view.tsx +79 -0
  76. package/src/jdw/widget-form-view.tsx +68 -0
  77. package/src/jdw/widget-preview-view.tsx +54 -0
  78. package/src/management/keybinding-overrides-storage.ts +49 -0
  79. package/src/management/keybinding-settings.tsx +16 -0
  80. package/src/management/palette-commands.ts +38 -0
  81. package/src/management/preference-settings-storage.ts +50 -0
  82. package/src/management/settings-ids.ts +12 -0
  83. package/src/management/settings.tsx +96 -0
  84. package/src/management/use-command-management.ts +191 -0
  85. package/src/management/use-keybinding-management.ts +130 -0
  86. package/src/shell/model.tsx +149 -0
  87. package/src/shell/provider.tsx +606 -0
  88. package/src/shell/secondary-actions.tsx +59 -0
  89. package/src/shell/settings-constants.ts +9 -0
  90. package/src/shell/settings.tsx +599 -0
  91. package/src/shell/shell.tsx +680 -0
  92. package/src/shell/titlebar-layout-controls.tsx +4 -0
  93. package/src/shell/view-host.tsx +212 -0
  94. package/src/workbench/appearance-storage.ts +107 -0
  95. package/src/workbench/command-host.tsx +243 -0
  96. package/src/workbench/command-palette.ts +208 -0
  97. package/src/workbench/keybinding-bridge.ts +54 -0
  98. package/src/workbench/layout-storage.ts +134 -0
  99. package/src/workbench/profile-modal.tsx +118 -0
  100. package/src/workbench/shell-command-registration.ts +58 -0
  101. package/src/workbench/startup-gate.tsx +136 -0
  102. package/src/workbench/status-sections.ts +43 -0
  103. package/src/workbench/use-persisted-appearance.ts +33 -0
  104. package/src/workbench/user-commands.ts +46 -0
  105. package/src/workbench/workspace-view-state.ts +35 -0
@@ -0,0 +1,466 @@
1
+ import { useEffect, useMemo, useState, type JSX } from 'react';
2
+ import {
3
+ applyMappingOperators,
4
+ convertToShape,
5
+ createBuiltinValueTransformRegistry,
6
+ defineConversion,
7
+ defineDataShape,
8
+ findParentChildMappingConflicts,
9
+ normalizeMappingOperators,
10
+ projectSourceFields,
11
+ projectTargetSlots,
12
+ pruneMappingEdgesForShapes,
13
+ sourceFieldsFromPlainObject,
14
+ targetSlotsFromPlainObject,
15
+ withConversionEdges,
16
+ type MappingEdge,
17
+ type MappingOperator,
18
+ type SourceField,
19
+ type TargetSlot,
20
+ type ValueTransformRegistry,
21
+ } from '@workbench-kit/field-remap';
22
+
23
+ import { FieldRemapFlowMapper, type FieldRemapFlowMapperProps } from './flow.js';
24
+ import {
25
+ FieldRemapIoClassBrowse,
26
+ resolveFieldRemapIoChrome,
27
+ type FieldRemapIoChrome,
28
+ } from './io-class-browse.js';
29
+ import {
30
+ getFieldRemapSample,
31
+ type FieldRemapSampleDefinition,
32
+ type FieldRemapSampleId,
33
+ } from './samples.js';
34
+ import { jsonataValueTransform } from './jsonata-transform.js';
35
+ import {
36
+ FieldRemapShapeIoEditor,
37
+ ingestSourceShape,
38
+ ingestTargetShape,
39
+ } from './shape-io-editor.js';
40
+ import './view.css';
41
+
42
+ export interface FieldRemapPanelProps {
43
+ /** Catalog sample id, or a full sample definition. Defaults to `nested-ab` labels/ids. */
44
+ readonly sample?: FieldRemapSampleId | FieldRemapSampleDefinition | undefined;
45
+ /** Optional host-owned transform registry (defaults to builtins + JSONata). */
46
+ readonly transforms?: ValueTransformRegistry | undefined;
47
+ /**
48
+ * When true (default), show paste-JSON / type editors for host-owned shapes.
49
+ * Prefer {@link FieldRemapPanelProps.ioChrome} for browse-first hosts; when
50
+ * `ioChrome` is omitted, `true` → `edit` and `false` → `none`.
51
+ */
52
+ readonly editableShapes?: boolean | undefined;
53
+ /**
54
+ * I/O chrome mode. `browse` = read-only class/field tree; `edit` = shape IO editor;
55
+ * `none` = hide I/O chrome. Defaults from `editableShapes` when omitted.
56
+ */
57
+ readonly ioChrome?: FieldRemapIoChrome | undefined;
58
+ /**
59
+ * When `false` (default), Flow columns omit `hidden` fields/slots.
60
+ * Browse chrome respects the same flag (show Hidden badges when true).
61
+ */
62
+ readonly includeHidden?: boolean | undefined;
63
+ readonly onIncludeHiddenChange?: ((next: boolean) => void) | undefined;
64
+ readonly className?: string | undefined;
65
+ /**
66
+ * Controlled mapping edges. When provided, the host is the source of truth;
67
+ * pair with `onEdgesChange` for connect/disconnect/transform edits.
68
+ */
69
+ readonly edges?: readonly MappingEdge[] | undefined;
70
+ readonly onEdgesChange?: ((edges: readonly MappingEdge[]) => void) | undefined;
71
+ /** Optional controlled n→m operators (document v2). */
72
+ readonly operators?: readonly MappingOperator[] | undefined;
73
+ readonly onOperatorsChange?: ((operators: readonly MappingOperator[]) => void) | undefined;
74
+ /** Controlled source field tree (host-owned shapes). */
75
+ readonly sources?: readonly SourceField[] | undefined;
76
+ readonly onSourcesChange?: ((fields: readonly SourceField[]) => void) | undefined;
77
+ /** Controlled target slot tree (host-owned shapes). */
78
+ readonly targets?: readonly TargetSlot[] | undefined;
79
+ readonly onTargetsChange?: ((slots: readonly TargetSlot[]) => void) | undefined;
80
+ /** Preview input bag for the primary source shape. */
81
+ readonly sourceSample?: unknown;
82
+ /** Optional target shape sample JSON (shape editor seed; preview uses edges). */
83
+ readonly targetShape?: unknown;
84
+ /** Forwarded to {@link FieldRemapFlowMapper} (default true). */
85
+ readonly showMinimap?: boolean | undefined;
86
+ readonly onPaneContextMenu?: FieldRemapFlowMapperProps['onPaneContextMenu'];
87
+ readonly onNodeContextMenu?: FieldRemapFlowMapperProps['onNodeContextMenu'];
88
+ readonly onEdgeContextMenu?: FieldRemapFlowMapperProps['onEdgeContextMenu'];
89
+ readonly flowActionsRef?: FieldRemapFlowMapperProps['flowActionsRef'];
90
+ /** Forwarded to {@link FieldRemapFlowMapper} chrome label / `t()` injection. */
91
+ readonly labels?: FieldRemapFlowMapperProps['labels'];
92
+ readonly t?: FieldRemapFlowMapperProps['t'];
93
+ }
94
+
95
+ type FieldRemapPreviewResult = {
96
+ readonly output: Record<string, unknown>;
97
+ readonly error?: string;
98
+ };
99
+
100
+ function resolveSample(sample: FieldRemapPanelProps['sample']): FieldRemapSampleDefinition {
101
+ if (!sample) {
102
+ return getFieldRemapSample('nested-ab');
103
+ }
104
+ if (typeof sample === 'string') {
105
+ return getFieldRemapSample(sample);
106
+ }
107
+ return sample;
108
+ }
109
+
110
+ /**
111
+ * Self-contained field-remap workbench panel:
112
+ * schema columns A/B + optional convert wires (XYFlow) and `convertToShape` preview.
113
+ *
114
+ * Uncontrolled: pass `sample=` (Storybook / demos). Remount with `key={sampleId}` when
115
+ * switching catalog entries so edge state resets.
116
+ *
117
+ * Controlled: pass `edges` + `onEdgesChange` (and optionally shapes / operators) so an
118
+ * integrating host can persist bindings without forking panel state.
119
+ */
120
+ export function FieldRemapPanel({
121
+ sample: sampleProp,
122
+ transforms: transformsProp,
123
+ editableShapes = true,
124
+ ioChrome: ioChromeProp,
125
+ includeHidden: includeHiddenProp,
126
+ onIncludeHiddenChange,
127
+ className,
128
+ edges: edgesProp,
129
+ onEdgesChange,
130
+ operators: operatorsProp,
131
+ onOperatorsChange,
132
+ sources: sourcesProp,
133
+ onSourcesChange,
134
+ targets: targetsProp,
135
+ onTargetsChange,
136
+ sourceSample: sourceSampleProp,
137
+ targetShape: targetShapeProp,
138
+ showMinimap,
139
+ onPaneContextMenu,
140
+ onNodeContextMenu,
141
+ onEdgeContextMenu,
142
+ flowActionsRef,
143
+ labels,
144
+ t,
145
+ }: FieldRemapPanelProps): JSX.Element {
146
+ const ioChrome = resolveFieldRemapIoChrome(ioChromeProp, editableShapes);
147
+ const [uncontrolledIncludeHidden, setUncontrolledIncludeHidden] = useState(false);
148
+ const includeHiddenControlled = includeHiddenProp !== undefined;
149
+ const includeHidden = includeHiddenControlled ? includeHiddenProp : uncontrolledIncludeHidden;
150
+ const setIncludeHidden = (next: boolean) => {
151
+ if (!includeHiddenControlled) {
152
+ setUncontrolledIncludeHidden(next);
153
+ }
154
+ onIncludeHiddenChange?.(next);
155
+ };
156
+ const sample = resolveSample(sampleProp);
157
+ const registry = useMemo(() => {
158
+ if (transformsProp) {
159
+ return transformsProp;
160
+ }
161
+ const next = createBuiltinValueTransformRegistry();
162
+ next.register(jsonataValueTransform);
163
+ return next;
164
+ }, [transformsProp]);
165
+
166
+ const edgesControlled = edgesProp !== undefined;
167
+ const operatorsControlled = operatorsProp !== undefined;
168
+ const sourcesControlled = sourcesProp !== undefined;
169
+ const targetsControlled = targetsProp !== undefined;
170
+ const sourceSampleControlled = sourceSampleProp !== undefined;
171
+
172
+ const [uncontrolledEdges, setUncontrolledEdges] = useState<readonly MappingEdge[]>(() => [
173
+ ...(edgesProp ?? sample.edges),
174
+ ]);
175
+ const [uncontrolledOperators, setUncontrolledOperators] = useState<readonly MappingOperator[]>(
176
+ () => [...(operatorsProp ?? sample.operators ?? [])],
177
+ );
178
+ const [result, setResult] = useState<FieldRemapPreviewResult>({ output: {} });
179
+ const [uncontrolledSourceSample, setUncontrolledSourceSample] = useState<unknown>(
180
+ () => sourceSampleProp ?? sample.source,
181
+ );
182
+ const [, setTargetSample] = useState<unknown>(() => targetShapeProp ?? sample.targetShape);
183
+ const [sourceJson, setSourceJson] = useState(() =>
184
+ JSON.stringify(sourceSampleProp ?? sample.source, null, 2),
185
+ );
186
+ const [targetJson, setTargetJson] = useState(() =>
187
+ JSON.stringify(targetShapeProp ?? sample.targetShape, null, 2),
188
+ );
189
+ const [uncontrolledSources, setUncontrolledSources] = useState<readonly SourceField[]>(() =>
190
+ sourcesProp
191
+ ? [...sourcesProp]
192
+ : sourceFieldsFromPlainObject(sourceSampleProp ?? sample.source, {
193
+ idPrefix: sample.sourceIdPrefix,
194
+ }),
195
+ );
196
+ const [uncontrolledTargets, setUncontrolledTargets] = useState<readonly TargetSlot[]>(() =>
197
+ targetsProp
198
+ ? [...targetsProp]
199
+ : targetSlotsFromPlainObject(targetShapeProp ?? sample.targetShape, {
200
+ idPrefix: sample.targetIdPrefix,
201
+ }),
202
+ );
203
+
204
+ const edges = edgesControlled ? edgesProp : uncontrolledEdges;
205
+ const operators = operatorsControlled ? operatorsProp : uncontrolledOperators;
206
+ const sourceFields = sourcesControlled ? sourcesProp : uncontrolledSources;
207
+ const targetSlots = targetsControlled ? targetsProp : uncontrolledTargets;
208
+ const sourceSample = sourceSampleControlled ? sourceSampleProp : uncontrolledSourceSample;
209
+
210
+ const flowSources = useMemo(
211
+ () => projectSourceFields(sourceFields, { includeHidden }),
212
+ [includeHidden, sourceFields],
213
+ );
214
+ const flowTargets = useMemo(
215
+ () => projectTargetSlots(targetSlots, { includeHidden }),
216
+ [includeHidden, targetSlots],
217
+ );
218
+ const flowEdges = useMemo(
219
+ () => pruneMappingEdgesForShapes(edges, flowSources, flowTargets),
220
+ [edges, flowSources, flowTargets],
221
+ );
222
+ const commitFlowEdges = (next: readonly MappingEdge[]) => {
223
+ if (includeHidden) {
224
+ commitEdges(next);
225
+ return;
226
+ }
227
+ const visibleIds = new Set(flowEdges.map((edge) => edge.id));
228
+ const preserved = edges.filter((edge) => !visibleIds.has(edge.id));
229
+ commitEdges([...preserved, ...next]);
230
+ };
231
+
232
+ const commitEdges = (next: readonly MappingEdge[]) => {
233
+ if (!edgesControlled) {
234
+ setUncontrolledEdges(next);
235
+ }
236
+ onEdgesChange?.(next);
237
+ };
238
+
239
+ const commitOperators = (next: readonly MappingOperator[]) => {
240
+ if (!operatorsControlled) {
241
+ setUncontrolledOperators(next);
242
+ }
243
+ onOperatorsChange?.(next);
244
+ };
245
+
246
+ const commitSources = (next: readonly SourceField[]) => {
247
+ if (!sourcesControlled) {
248
+ setUncontrolledSources(next);
249
+ }
250
+ onSourcesChange?.(next);
251
+ };
252
+
253
+ const commitTargets = (next: readonly TargetSlot[]) => {
254
+ if (!targetsControlled) {
255
+ setUncontrolledTargets(next);
256
+ }
257
+ onTargetsChange?.(next);
258
+ };
259
+
260
+ const shapes = useMemo(
261
+ () => [
262
+ defineDataShape({
263
+ id: sample.sourceIdPrefix,
264
+ label: sample.sourceLabel,
265
+ role: 'source',
266
+ fields: sourceFields,
267
+ }),
268
+ defineDataShape({
269
+ id: sample.targetIdPrefix,
270
+ label: sample.targetLabel,
271
+ role: 'target',
272
+ fields: targetSlots,
273
+ }),
274
+ ],
275
+ [sample, sourceFields, targetSlots],
276
+ );
277
+
278
+ const conflicts = useMemo(
279
+ () => findParentChildMappingConflicts(edges, sourceFields, targetSlots),
280
+ [edges, sourceFields, targetSlots],
281
+ );
282
+
283
+ useEffect(() => {
284
+ const controller = new AbortController();
285
+ const conversion = withConversionEdges(
286
+ defineConversion({
287
+ id: `${sample.sourceIdPrefix}→${sample.targetIdPrefix}`,
288
+ sourceShapeIds: [sample.sourceIdPrefix],
289
+ targetShapeId: sample.targetIdPrefix,
290
+ edges: [...sample.edges],
291
+ }),
292
+ edges,
293
+ );
294
+
295
+ void convertToShape({
296
+ conversion,
297
+ shapes,
298
+ inputs: { [sample.sourceIdPrefix]: sourceSample },
299
+ transforms: registry,
300
+ signal: controller.signal,
301
+ })
302
+ .then(async (next) => {
303
+ if (controller.signal.aborted) {
304
+ return;
305
+ }
306
+ const normalizedOps = normalizeMappingOperators(operators);
307
+ if (!normalizedOps?.length) {
308
+ setResult({ output: next.output });
309
+ return;
310
+ }
311
+ const merged = await applyMappingOperators({
312
+ operators: normalizedOps,
313
+ sources: sourceFields,
314
+ targets: targetSlots,
315
+ inputs: { [sample.sourceIdPrefix]: sourceSample },
316
+ transforms: registry,
317
+ output: next.output,
318
+ signal: controller.signal,
319
+ });
320
+ if (!controller.signal.aborted) {
321
+ setResult({ output: merged.output });
322
+ }
323
+ })
324
+ .catch((error: unknown) => {
325
+ if (controller.signal.aborted) {
326
+ return;
327
+ }
328
+ setResult({
329
+ output: {},
330
+ error: error instanceof Error ? error.message : String(error),
331
+ });
332
+ });
333
+
334
+ return () => {
335
+ controller.abort();
336
+ };
337
+ }, [edges, operators, registry, sample, shapes, sourceFields, sourceSample, targetSlots]);
338
+
339
+ const applySourceShape = (parsed: unknown) => {
340
+ const ingested = ingestSourceShape(parsed, sample.sourceIdPrefix);
341
+ if (!sourceSampleControlled) {
342
+ setUncontrolledSourceSample(parsed);
343
+ }
344
+ setSourceJson(ingested.sampleJson);
345
+ commitSources(ingested.fields);
346
+ commitEdges(pruneMappingEdgesForShapes(edges, ingested.fields, targetSlots));
347
+ };
348
+
349
+ const applyTargetShape = (parsed: unknown) => {
350
+ const ingested = ingestTargetShape(parsed, sample.targetIdPrefix);
351
+ setTargetSample(parsed);
352
+ setTargetJson(ingested.sampleJson);
353
+ commitTargets(ingested.fields);
354
+ commitEdges(pruneMappingEdgesForShapes(edges, sourceFields, ingested.fields));
355
+ };
356
+
357
+ return (
358
+ <div
359
+ className={['workbench-field-remap-demo', className].filter(Boolean).join(' ')}
360
+ data-testid="field-remap-demo"
361
+ data-sample-id={sample.id}
362
+ data-edges-mode={edgesControlled ? 'controlled' : 'uncontrolled'}
363
+ >
364
+ <header className="workbench-field-remap-demo__header">
365
+ <h2 className="workbench-field-remap-demo__title">{sample.title}</h2>
366
+ <p className="workbench-field-remap-demo__intro">{sample.description}</p>
367
+ </header>
368
+
369
+ {ioChrome === 'edit' ? (
370
+ <div className="workbench-field-remap-demo__shapes" data-testid="field-remap-shapes">
371
+ <FieldRemapShapeIoEditor
372
+ role="source"
373
+ title={sample.sourceLabel}
374
+ idPrefix={sample.sourceIdPrefix}
375
+ sampleJson={sourceJson}
376
+ fields={sourceFields}
377
+ onSampleJsonChange={setSourceJson}
378
+ onApplySample={applySourceShape}
379
+ onFieldsChange={(next) => commitSources(next as readonly SourceField[])}
380
+ />
381
+ <FieldRemapShapeIoEditor
382
+ role="target"
383
+ title={sample.targetLabel}
384
+ idPrefix={sample.targetIdPrefix}
385
+ sampleJson={targetJson}
386
+ fields={targetSlots}
387
+ onSampleJsonChange={setTargetJson}
388
+ onApplySample={applyTargetShape}
389
+ onFieldsChange={(next) => commitTargets(next as readonly TargetSlot[])}
390
+ />
391
+ </div>
392
+ ) : null}
393
+
394
+ {ioChrome === 'browse' ? (
395
+ <div
396
+ className="workbench-field-remap-demo__shapes"
397
+ data-testid="field-remap-io-browse-wrap"
398
+ >
399
+ <label className="workbench-field-remap-io-browse__toggle">
400
+ <input
401
+ checked={includeHidden}
402
+ onChange={(event) => setIncludeHidden(event.target.checked)}
403
+ type="checkbox"
404
+ />
405
+ Show hidden fields
406
+ </label>
407
+ <FieldRemapIoClassBrowse
408
+ includeHidden={includeHidden}
409
+ sources={sourceFields}
410
+ sourcesTitle={sample.sourceLabel}
411
+ targets={targetSlots}
412
+ targetsTitle={sample.targetLabel}
413
+ />
414
+ </div>
415
+ ) : null}
416
+
417
+ <FieldRemapFlowMapper
418
+ sources={flowSources}
419
+ targets={flowTargets}
420
+ edges={flowEdges}
421
+ transforms={registry}
422
+ onEdgesChange={commitFlowEdges}
423
+ operators={operators}
424
+ onOperatorsChange={commitOperators}
425
+ sourceTitle={sample.sourceLabel}
426
+ targetTitle={sample.targetLabel}
427
+ showMinimap={showMinimap}
428
+ onPaneContextMenu={onPaneContextMenu}
429
+ onNodeContextMenu={onNodeContextMenu}
430
+ onEdgeContextMenu={onEdgeContextMenu}
431
+ flowActionsRef={flowActionsRef}
432
+ labels={labels}
433
+ t={t}
434
+ />
435
+
436
+ {conflicts.length > 0 ? (
437
+ <p
438
+ className="workbench-field-remap-demo__warn"
439
+ role="status"
440
+ data-testid="field-remap-conflicts"
441
+ >
442
+ Warning: parent and child fields are both mapped (
443
+ {conflicts.map((item) => `${item.parentId} / ${item.childId}`).join('; ')}). Prefer one
444
+ level.
445
+ </p>
446
+ ) : null}
447
+
448
+ {result.error ? (
449
+ <p className="workbench-field-remap-demo__error" role="alert">
450
+ {result.error}
451
+ </p>
452
+ ) : null}
453
+
454
+ <div className="workbench-field-remap-demo__panes">
455
+ <section className="workbench-field-remap-demo__pane" aria-labelledby="field-remap-source">
456
+ <h3 id="field-remap-source">{sample.sourceLabel}</h3>
457
+ <pre data-testid="field-remap-input">{JSON.stringify(sourceSample, null, 2)}</pre>
458
+ </section>
459
+ <section className="workbench-field-remap-demo__pane" aria-labelledby="field-remap-target">
460
+ <h3 id="field-remap-target">{sample.targetLabel}</h3>
461
+ <pre data-testid="field-remap-result">{JSON.stringify(result.output, null, 2)}</pre>
462
+ </section>
463
+ </div>
464
+ </div>
465
+ );
466
+ }