@burdenoff/fluidgrids-fe-sdk 2026.1031.7 → 2026.1101.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 (51) hide show
  1. package/dist/{ExecutionDetailPage-4LPCNWM3.js → ExecutionDetailPage-Q77U6YJO.js} +11 -8
  2. package/dist/{ExecutionsListPage-HNPUKYD7.js → ExecutionsListPage-X4H4FVLN.js} +6 -5
  3. package/dist/{WorkflowBuilder-QFSCYCUC.js → WorkflowBuilder-RJPRUMQR.js} +11 -8
  4. package/dist/{WorkflowsList-RV4BRJO7.js → WorkflowsList-R2JJK7BL.js} +7 -6
  5. package/dist/agentTools-MISTH7JI.js +349 -0
  6. package/dist/{chunk-77HIU3CX.js → chunk-26HGYPBC.js} +1 -1
  7. package/dist/{chunk-76UUEMTN.js → chunk-37VUN74D.js} +4 -4
  8. package/dist/{chunk-QPZCJKII.js → chunk-3ERT6FQK.js} +3 -3
  9. package/dist/chunk-472HJ3S3.js +3929 -0
  10. package/dist/{chunk-WNZV4SLU.js → chunk-4QHCXTHW.js} +4 -4
  11. package/dist/chunk-AASHQI77.js +126 -0
  12. package/dist/chunk-CGXQVZGY.js +260 -0
  13. package/dist/chunk-CRDG2SQQ.js +185 -0
  14. package/dist/chunk-DQ2ZCXYX.js +21875 -0
  15. package/dist/chunk-F7DD7BOB.js +1164 -0
  16. package/dist/chunk-GTEBQQ7J.js +24 -0
  17. package/dist/{chunk-ALFMJSD6.js → chunk-H7XPXONT.js} +1 -1
  18. package/dist/{chunk-UO5DUKYN.js → chunk-HKKSXOFO.js} +8 -7
  19. package/dist/chunk-IA2ZCHNQ.js +1336 -0
  20. package/dist/{chunk-3M36XWAH.js → chunk-IGRIN7NL.js} +8 -7
  21. package/dist/chunk-JDLQ6QIB.js +283 -0
  22. package/dist/{chunk-AD7BICOT.js → chunk-JKBCLBHY.js} +1 -1
  23. package/dist/{chunk-6JX5UPDW.js → chunk-JNZCXFLD.js} +1 -1
  24. package/dist/{chunk-SEFOT2LD.js → chunk-LWLHPVIA.js} +1 -1
  25. package/dist/{chunk-JWROEOG5.js → chunk-LYTR6IQO.js} +1 -38
  26. package/dist/chunk-MR2B67NW.js +338 -0
  27. package/dist/{chunk-RIHYPPMQ.js → chunk-NLYQ3XDE.js} +1 -1
  28. package/dist/{chunk-6ILIBJFH.js → chunk-NZJWKVER.js} +7904 -7943
  29. package/dist/chunk-OCJTTHTS.js +46 -0
  30. package/dist/chunk-OSQ3OCR3.js +1193 -0
  31. package/dist/{chunk-VJL3DFJT.js → chunk-REO4S5MY.js} +2 -2
  32. package/dist/{chunk-XKQMCPSB.js → chunk-T2UUYYVJ.js} +4 -4
  33. package/dist/{chunk-BXR6BWXM.js → chunk-W7MTSATB.js} +5 -5
  34. package/dist/{chunk-A47PSD7A.js → chunk-WF6GMBR4.js} +7 -4
  35. package/dist/chunk-WYCB6OQL.js +19709 -0
  36. package/dist/{chunk-YOLPHVOE.js → chunk-YKFR7BZJ.js} +1 -1
  37. package/dist/{chunk-26NENGXA.js → chunk-YSXNJGCY.js} +4 -4
  38. package/dist/code-editor-field-JKCUR6BI.js +3 -0
  39. package/dist/executions.js +12 -9
  40. package/dist/history.js +4 -3
  41. package/dist/index.js +41 -32
  42. package/dist/node-detail-view-PCYFQX62.js +2034 -0
  43. package/dist/observability.js +5 -4
  44. package/dist/quick-config-panel-BRZZHOSI.js +402 -0
  45. package/dist/settings.js +6 -5
  46. package/dist/templates.js +10 -7
  47. package/dist/workflow-builder.js +12 -9
  48. package/dist/workflows-list.js +7 -6
  49. package/package.json +4 -1
  50. package/dist/chunk-NMEI7TQW.js +0 -2628
  51. package/dist/chunk-S7XV65DB.js +0 -50350
@@ -0,0 +1,2034 @@
1
+ import { NDV_Z_INDEX, NodeExecutionActions, ReadOnlyFieldset, NodeParamsForm } from './chunk-472HJ3S3.js';
2
+ import { CodeEditorField } from './chunk-AASHQI77.js';
3
+ import { useNodeConfig, latestNodeLog, nodeTitle, nodeSubtitle, FieldTree } from './chunk-IA2ZCHNQ.js';
4
+ import { RunOnSection } from './chunk-CGXQVZGY.js';
5
+ import './chunk-GTEBQQ7J.js';
6
+ import { useRunHistory, RunHistoryView, SignalRunDialog } from './chunk-OSQ3OCR3.js';
7
+ import './chunk-YKFR7BZJ.js';
8
+ import { TableView, JsonView } from './chunk-MR2B67NW.js';
9
+ import './chunk-JDLQ6QIB.js';
10
+ import './chunk-7KKYIRLH.js';
11
+ import { useFlowStore, useDurableFunctionNodesEnabled, CODE_WORKFLOW_NODE_TYPE, fieldsFromValue, useNodeExecution, getNodeDetail, readCodeWorkflowState, useCodeWorkflowInterface, clearCodeWorkflowDefinitionCache, diagnosticsByPath, interfaceHandlers, declaredSignals, throwIfResultError, childRunIdOf, defaultSourcePath, describeOperationError, projectLanguage, changedSourceFiles, isEditableSourcePath, sourceLanguageOf, interfaceFields } from './chunk-NZJWKVER.js';
12
+ import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS } from './chunk-ZUVFD5KV.js';
13
+ import './chunk-JL7CASN5.js';
14
+ import './chunk-OCJTTHTS.js';
15
+ import './chunk-PXIFUMLA.js';
16
+ import './chunk-6AMNEX2Y.js';
17
+ import './chunk-G77IGC3Y.js';
18
+ import { useWorkflowPinnedDataQuery, usePinNodeDataMutation, useUnpinNodeDataMutation, NodeStatesDocument, WorkflowCodeSourceDocument, WorkflowEditLeaseStatusDocument, BuildWorkflowCodeDocument } from './chunk-IHEV5HTD.js';
19
+ import { useTr } from './chunk-UULVIT25.js';
20
+ import { getFluidGridsClientSafe, getFluidGridsClient } from './chunk-NTWG3CJX.js';
21
+ import './chunk-62GWEYJ5.js';
22
+ import * as React from 'react';
23
+ import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
24
+ import { Icon } from '@iconify/react';
25
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Skeleton } from '@burdenoff/fe-libs/ui';
26
+ import { cn } from '@burdenoff/fe-libs/utils';
27
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
28
+ import { useNavigate } from 'react-router';
29
+ import { Button } from '@burdenoff/fe-libs/button';
30
+
31
+ var SECTION_LABEL = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
32
+ var inputCx = "w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-sm outline-none transition-colors focus:border-primary disabled:opacity-50";
33
+ function msToSeconds(ms, fallbackMs) {
34
+ return Math.round((ms ?? fallbackMs) / 1e3 * 10) / 10;
35
+ }
36
+ function secondsInputToMs(raw, minMs) {
37
+ const seconds = parseFloat(raw);
38
+ if (Number.isNaN(seconds)) return minMs;
39
+ return Math.max(minMs, Math.round(seconds * 1e3));
40
+ }
41
+ function NodeSettingsPanel({
42
+ config
43
+ }) {
44
+ const tr = useTr();
45
+ const {
46
+ nodes,
47
+ node,
48
+ errorConfig,
49
+ writeErrorConfig,
50
+ notes,
51
+ writeNotes,
52
+ alwaysOutputData,
53
+ executeOnce,
54
+ runOn,
55
+ writeExecutionSettings
56
+ } = config;
57
+ const onError = errorConfig?.onError ?? "stop";
58
+ const retryEnabled = (errorConfig?.retryConfig?.maxRetries ?? 0) > 0;
59
+ const timeoutEnabled = !!errorConfig?.timeout?.duration;
60
+ const branchTargets = nodes.filter((n) => n.id !== node?.id);
61
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", "data-testid": "ndv-settings-panel", children: [
62
+ /* @__PURE__ */ jsx(
63
+ RunOnSection,
64
+ {
65
+ value: runOn,
66
+ onChange: (next) => writeExecutionSettings({ runOn: next }),
67
+ sectionLabelClassName: SECTION_LABEL
68
+ }
69
+ ),
70
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
71
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.retrySection", "Retry on Fail") }),
72
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
73
+ /* @__PURE__ */ jsx(
74
+ "input",
75
+ {
76
+ type: "checkbox",
77
+ checked: retryEnabled,
78
+ onChange: (e) => writeErrorConfig({
79
+ retryConfig: e.target.checked ? {
80
+ maxRetries: errorConfig?.retryConfig?.maxRetries || 3,
81
+ retryDelay: errorConfig?.retryConfig?.retryDelay || 1e3,
82
+ backoffMultiplier: errorConfig?.retryConfig?.backoffMultiplier ?? 2,
83
+ maxDelay: errorConfig?.retryConfig?.maxDelay ?? 3e4
84
+ } : void 0
85
+ }),
86
+ "data-testid": "ndv-settings-retry-enabled",
87
+ className: "size-4"
88
+ }
89
+ ),
90
+ /* @__PURE__ */ jsx("span", { children: tr(
91
+ "fluidgrids.builder.nodeSettings.retryEnabledLabel",
92
+ "Retry this node on failure"
93
+ ) })
94
+ ] }),
95
+ retryEnabled && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3 pl-6", children: [
96
+ /* @__PURE__ */ jsx(
97
+ Field,
98
+ {
99
+ label: tr(
100
+ "fluidgrids.builder.nodeSettings.maxRetries",
101
+ "Max tries"
102
+ ),
103
+ children: /* @__PURE__ */ jsx(
104
+ "input",
105
+ {
106
+ type: "number",
107
+ min: 1,
108
+ max: 20,
109
+ value: errorConfig?.retryConfig?.maxRetries ?? 3,
110
+ onChange: (e) => writeErrorConfig({
111
+ retryConfig: {
112
+ ...errorConfig?.retryConfig ?? {
113
+ retryDelay: 1e3,
114
+ maxRetries: 3
115
+ },
116
+ maxRetries: Math.max(
117
+ 1,
118
+ parseInt(e.target.value, 10) || 1
119
+ )
120
+ }
121
+ }),
122
+ "data-testid": "ndv-settings-max-retries",
123
+ className: inputCx
124
+ }
125
+ )
126
+ }
127
+ ),
128
+ /* @__PURE__ */ jsx(
129
+ Field,
130
+ {
131
+ label: tr(
132
+ "fluidgrids.builder.nodeSettings.retryDelay",
133
+ "Wait (seconds)"
134
+ ),
135
+ children: /* @__PURE__ */ jsx(
136
+ "input",
137
+ {
138
+ type: "number",
139
+ min: 0,
140
+ step: 0.1,
141
+ value: msToSeconds(errorConfig?.retryConfig?.retryDelay, 1e3),
142
+ onChange: (e) => writeErrorConfig({
143
+ retryConfig: {
144
+ ...errorConfig?.retryConfig ?? {
145
+ maxRetries: 3,
146
+ retryDelay: 1e3
147
+ },
148
+ retryDelay: secondsInputToMs(e.target.value, 0)
149
+ }
150
+ }),
151
+ "data-testid": "ndv-settings-retry-delay",
152
+ className: inputCx
153
+ }
154
+ )
155
+ }
156
+ ),
157
+ /* @__PURE__ */ jsx(
158
+ Field,
159
+ {
160
+ label: tr(
161
+ "fluidgrids.builder.nodeSettings.backoffMultiplier",
162
+ "Backoff \xD7"
163
+ ),
164
+ children: /* @__PURE__ */ jsx(
165
+ "input",
166
+ {
167
+ type: "number",
168
+ min: 1,
169
+ step: 0.5,
170
+ value: errorConfig?.retryConfig?.backoffMultiplier ?? 2,
171
+ onChange: (e) => writeErrorConfig({
172
+ retryConfig: {
173
+ ...errorConfig?.retryConfig ?? {
174
+ maxRetries: 3,
175
+ retryDelay: 1e3
176
+ },
177
+ backoffMultiplier: parseFloat(e.target.value) || 1
178
+ }
179
+ }),
180
+ "data-testid": "ndv-settings-backoff-multiplier",
181
+ className: inputCx
182
+ }
183
+ )
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsx(
187
+ Field,
188
+ {
189
+ label: tr(
190
+ "fluidgrids.builder.nodeSettings.maxDelay",
191
+ "Max wait (seconds)"
192
+ ),
193
+ children: /* @__PURE__ */ jsx(
194
+ "input",
195
+ {
196
+ type: "number",
197
+ min: 0,
198
+ step: 1,
199
+ value: msToSeconds(errorConfig?.retryConfig?.maxDelay, 3e4),
200
+ onChange: (e) => writeErrorConfig({
201
+ retryConfig: {
202
+ ...errorConfig?.retryConfig ?? {
203
+ maxRetries: 3,
204
+ retryDelay: 1e3
205
+ },
206
+ maxDelay: secondsInputToMs(e.target.value, 0)
207
+ }
208
+ }),
209
+ "data-testid": "ndv-settings-max-delay",
210
+ className: inputCx
211
+ }
212
+ )
213
+ }
214
+ )
215
+ ] })
216
+ ] }),
217
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
218
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.onErrorSection", "On Error") }),
219
+ /* @__PURE__ */ jsxs(
220
+ "select",
221
+ {
222
+ value: onError,
223
+ onChange: (e) => writeErrorConfig({
224
+ onError: e.target.value,
225
+ errorBranchId: e.target.value === "branch" ? errorConfig?.errorBranchId : void 0
226
+ }),
227
+ "data-testid": "ndv-settings-on-error",
228
+ className: cn(inputCx, "max-w-xs"),
229
+ children: [
230
+ /* @__PURE__ */ jsx("option", { value: "stop", children: tr(
231
+ "fluidgrids.builder.nodeSettings.onErrorStop",
232
+ "Stop workflow (default)"
233
+ ) }),
234
+ /* @__PURE__ */ jsx("option", { value: "continue", children: tr(
235
+ "fluidgrids.builder.nodeSettings.onErrorContinue",
236
+ "Continue on fail"
237
+ ) }),
238
+ /* @__PURE__ */ jsx("option", { value: "branch", children: tr(
239
+ "fluidgrids.builder.nodeSettings.onErrorBranch",
240
+ "Route to error branch"
241
+ ) })
242
+ ]
243
+ }
244
+ ),
245
+ onError === "branch" && /* @__PURE__ */ jsx("div", { className: "pl-2", children: /* @__PURE__ */ jsx(
246
+ Field,
247
+ {
248
+ label: tr(
249
+ "fluidgrids.builder.nodeSettings.errorBranchTarget",
250
+ "Error branch target"
251
+ ),
252
+ children: /* @__PURE__ */ jsxs(
253
+ "select",
254
+ {
255
+ value: errorConfig?.errorBranchId ?? "",
256
+ onChange: (e) => writeErrorConfig({
257
+ errorBranchId: e.target.value || void 0
258
+ }),
259
+ "data-testid": "ndv-settings-error-branch-id",
260
+ className: cn(inputCx, "max-w-xs"),
261
+ children: [
262
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
263
+ "fluidgrids.builder.nodeSettings.selectNode",
264
+ "Select a node\u2026"
265
+ ) }),
266
+ branchTargets.map((n) => /* @__PURE__ */ jsx("option", { value: n.id, children: nodeTitle(n) }, n.id))
267
+ ]
268
+ }
269
+ )
270
+ }
271
+ ) })
272
+ ] }),
273
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
274
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.timeoutSection", "Timeout") }),
275
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
276
+ /* @__PURE__ */ jsx(
277
+ "input",
278
+ {
279
+ type: "checkbox",
280
+ checked: timeoutEnabled,
281
+ onChange: (e) => writeErrorConfig({
282
+ timeout: e.target.checked ? { duration: errorConfig?.timeout?.duration || 3e4 } : void 0
283
+ }),
284
+ "data-testid": "ndv-settings-timeout-enabled",
285
+ className: "size-4"
286
+ }
287
+ ),
288
+ /* @__PURE__ */ jsx("span", { children: tr(
289
+ "fluidgrids.builder.nodeSettings.timeoutEnabledLabel",
290
+ "Abort this node if it runs too long"
291
+ ) })
292
+ ] }),
293
+ timeoutEnabled && /* @__PURE__ */ jsxs("div", { className: "space-y-3 pl-6", children: [
294
+ /* @__PURE__ */ jsx(
295
+ Field,
296
+ {
297
+ label: tr(
298
+ "fluidgrids.builder.nodeSettings.timeoutDuration",
299
+ "Timeout (seconds)"
300
+ ),
301
+ children: /* @__PURE__ */ jsx(
302
+ "input",
303
+ {
304
+ type: "number",
305
+ min: 0.1,
306
+ step: 0.5,
307
+ value: msToSeconds(errorConfig?.timeout?.duration, 3e4),
308
+ onChange: (e) => writeErrorConfig({
309
+ timeout: {
310
+ ...errorConfig?.timeout ?? { duration: 3e4 },
311
+ duration: secondsInputToMs(e.target.value, 100)
312
+ }
313
+ }),
314
+ "data-testid": "ndv-settings-timeout-duration",
315
+ className: cn(inputCx, "max-w-[10rem]")
316
+ }
317
+ )
318
+ }
319
+ ),
320
+ /* @__PURE__ */ jsx(
321
+ Field,
322
+ {
323
+ label: tr(
324
+ "fluidgrids.builder.nodeSettings.onTimeout",
325
+ "On timeout"
326
+ ),
327
+ children: /* @__PURE__ */ jsxs(
328
+ "select",
329
+ {
330
+ value: errorConfig?.timeout?.onTimeout ?? "stop",
331
+ onChange: (e) => writeErrorConfig({
332
+ timeout: {
333
+ ...errorConfig?.timeout ?? { duration: 3e4 },
334
+ onTimeout: e.target.value,
335
+ timeoutBranchId: e.target.value === "branch" ? errorConfig?.timeout?.timeoutBranchId : void 0
336
+ }
337
+ }),
338
+ "data-testid": "ndv-settings-on-timeout",
339
+ className: cn(inputCx, "max-w-xs"),
340
+ children: [
341
+ /* @__PURE__ */ jsx("option", { value: "stop", children: tr(
342
+ "fluidgrids.builder.nodeSettings.onTimeoutStop",
343
+ "Use Retry/On Error settings above"
344
+ ) }),
345
+ /* @__PURE__ */ jsx("option", { value: "continue", children: tr(
346
+ "fluidgrids.builder.nodeSettings.onTimeoutContinue",
347
+ "Continue on timeout"
348
+ ) }),
349
+ /* @__PURE__ */ jsx("option", { value: "branch", children: tr(
350
+ "fluidgrids.builder.nodeSettings.onTimeoutBranch",
351
+ "Route to a specific branch"
352
+ ) })
353
+ ]
354
+ }
355
+ )
356
+ }
357
+ ),
358
+ errorConfig?.timeout?.onTimeout === "branch" && /* @__PURE__ */ jsx(
359
+ Field,
360
+ {
361
+ label: tr(
362
+ "fluidgrids.builder.nodeSettings.timeoutBranchTarget",
363
+ "Timeout branch target"
364
+ ),
365
+ children: /* @__PURE__ */ jsxs(
366
+ "select",
367
+ {
368
+ value: errorConfig?.timeout?.timeoutBranchId ?? "",
369
+ onChange: (e) => writeErrorConfig({
370
+ timeout: {
371
+ ...errorConfig?.timeout ?? { duration: 3e4 },
372
+ timeoutBranchId: e.target.value || void 0
373
+ }
374
+ }),
375
+ "data-testid": "ndv-settings-timeout-branch-id",
376
+ className: cn(inputCx, "max-w-xs"),
377
+ children: [
378
+ /* @__PURE__ */ jsx("option", { value: "", children: tr(
379
+ "fluidgrids.builder.nodeSettings.selectNode",
380
+ "Select a node\u2026"
381
+ ) }),
382
+ branchTargets.map((n) => /* @__PURE__ */ jsx("option", { value: n.id, children: nodeTitle(n) }, n.id))
383
+ ]
384
+ }
385
+ )
386
+ }
387
+ )
388
+ ] })
389
+ ] }),
390
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
391
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.otherSection", "Other") }),
392
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
393
+ /* @__PURE__ */ jsx(
394
+ "input",
395
+ {
396
+ type: "checkbox",
397
+ checked: alwaysOutputData,
398
+ onChange: (e) => writeExecutionSettings({ alwaysOutputData: e.target.checked }),
399
+ "data-testid": "ndv-settings-always-output-data",
400
+ className: "size-4"
401
+ }
402
+ ),
403
+ /* @__PURE__ */ jsx("span", { children: tr(
404
+ "fluidgrids.builder.nodeSettings.alwaysOutputDataLabel",
405
+ "Always output data"
406
+ ) })
407
+ ] }),
408
+ /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
409
+ /* @__PURE__ */ jsx(
410
+ "input",
411
+ {
412
+ type: "checkbox",
413
+ checked: executeOnce,
414
+ onChange: (e) => writeExecutionSettings({ executeOnce: e.target.checked }),
415
+ "data-testid": "ndv-settings-execute-once",
416
+ className: "size-4"
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsx("span", { children: tr(
420
+ "fluidgrids.builder.nodeSettings.executeOnceLabel",
421
+ "Execute once"
422
+ ) })
423
+ ] })
424
+ ] }),
425
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
426
+ /* @__PURE__ */ jsx("div", { className: SECTION_LABEL, children: tr("fluidgrids.builder.nodeSettings.notesSection", "Notes") }),
427
+ /* @__PURE__ */ jsx(
428
+ "textarea",
429
+ {
430
+ value: notes,
431
+ onChange: (e) => writeNotes(e.target.value),
432
+ rows: 4,
433
+ placeholder: tr(
434
+ "fluidgrids.builder.nodeSettings.notesPlaceholder",
435
+ "Leave a note for teammates about what this node does\u2026"
436
+ ),
437
+ "data-testid": "ndv-settings-notes",
438
+ className: cn(inputCx, "resize-y")
439
+ }
440
+ )
441
+ ] })
442
+ ] });
443
+ }
444
+ function Field({
445
+ label,
446
+ children
447
+ }) {
448
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
449
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-foreground", children: label }),
450
+ children
451
+ ] });
452
+ }
453
+ function useMediaQuery(query) {
454
+ const [matches, setMatches] = React.useState(false);
455
+ React.useEffect(() => {
456
+ const mql = window.matchMedia(query);
457
+ const onChange = (e) => setMatches(e.matches);
458
+ setMatches(mql.matches);
459
+ mql.addEventListener("change", onChange);
460
+ return () => mql.removeEventListener("change", onChange);
461
+ }, [query]);
462
+ return matches;
463
+ }
464
+ function entryOf(definitionJson) {
465
+ if (typeof definitionJson !== "object" || definitionJson === null || Array.isArray(definitionJson))
466
+ return void 0;
467
+ const code = definitionJson.code;
468
+ if (typeof code !== "object" || code === null || Array.isArray(code))
469
+ return void 0;
470
+ const entry = code.entry;
471
+ return typeof entry === "string" ? entry : void 0;
472
+ }
473
+ async function fetchWorkflowCodeSource(workflowKey, version) {
474
+ const result = await getFluidGridsClient().query({
475
+ query: WorkflowCodeSourceDocument,
476
+ variables: { workflowKey, version },
477
+ fetchPolicy: "network-only"
478
+ });
479
+ throwIfResultError(result);
480
+ const source = result.data?.workflowCodeSource;
481
+ if (!source) return null;
482
+ const definition = result.data?.workflowDefinition;
483
+ return {
484
+ buildId: source.buildId,
485
+ files: source.files.map((file) => ({
486
+ path: file.path,
487
+ content: file.content
488
+ })),
489
+ revision: definition?.revision ?? null,
490
+ entry: entryOf(definition?.definitionJson)
491
+ };
492
+ }
493
+ async function fetchEditLease(workflowKey) {
494
+ try {
495
+ const result = await getFluidGridsClient().query({
496
+ query: WorkflowEditLeaseStatusDocument,
497
+ variables: { workflowKey },
498
+ fetchPolicy: "network-only"
499
+ });
500
+ throwIfResultError(result);
501
+ return result.data?.workflowEditLease ?? null;
502
+ } catch {
503
+ return null;
504
+ }
505
+ }
506
+ async function runWorkflowCodeBuild(variables) {
507
+ const result = await getFluidGridsClient().mutate({ mutation: BuildWorkflowCodeDocument, variables });
508
+ throwIfResultError(result);
509
+ const data = result.data?.buildWorkflowCode;
510
+ if (!data) throw new Error("The build returned no result");
511
+ return data;
512
+ }
513
+ function leaseBlocksEditing(lease) {
514
+ return !!lease && lease.available && lease.held && !lease.heldByCaller;
515
+ }
516
+ function useWorkflowCodeEditor({
517
+ workflowKey,
518
+ version,
519
+ enabled,
520
+ canEdit,
521
+ onSaved
522
+ }) {
523
+ const [loading, setLoading] = useState(false);
524
+ const [loadError, setLoadError] = useState(null);
525
+ const [source, setSource] = useState(null);
526
+ const [contents, setContents] = useState({});
527
+ const [selectedPath, setSelectedPath] = useState();
528
+ const [lease, setLease] = useState(null);
529
+ const [building, setBuilding] = useState(null);
530
+ const [buildError, setBuildError] = useState(null);
531
+ const [lastBuild, setLastBuild] = useState(null);
532
+ const request = useRef(0);
533
+ const onSavedRef = useRef(onSaved);
534
+ onSavedRef.current = onSaved;
535
+ const reload = useCallback(async () => {
536
+ if (!enabled || !workflowKey || !version) return;
537
+ const id = ++request.current;
538
+ setLoading(true);
539
+ setLoadError(null);
540
+ try {
541
+ const [loaded, currentLease] = await Promise.all([
542
+ fetchWorkflowCodeSource(workflowKey, version),
543
+ fetchEditLease(workflowKey)
544
+ ]);
545
+ if (id !== request.current) return;
546
+ setSource(loaded);
547
+ setLease(currentLease);
548
+ setContents(
549
+ Object.fromEntries(
550
+ (loaded?.files ?? []).map((file) => [file.path, file.content])
551
+ )
552
+ );
553
+ setSelectedPath(
554
+ loaded ? defaultSourcePath(loaded.files, loaded.entry) : void 0
555
+ );
556
+ setLastBuild(null);
557
+ setBuildError(null);
558
+ } catch (err) {
559
+ if (id !== request.current) return;
560
+ setLoadError(describeOperationError(err));
561
+ } finally {
562
+ if (id === request.current) setLoading(false);
563
+ }
564
+ }, [enabled, workflowKey, version]);
565
+ useEffect(() => {
566
+ void reload();
567
+ }, [reload]);
568
+ const language = useMemo(
569
+ () => projectLanguage(source?.files ?? []),
570
+ [source]
571
+ );
572
+ const readOnlyReason = language === "python" ? "python" : !canEdit ? "permission" : leaseBlocksEditing(lease) ? "lease" : null;
573
+ const changed = useMemo(
574
+ () => changedSourceFiles(source?.files ?? [], contents),
575
+ [source, contents]
576
+ );
577
+ const build = useCallback(
578
+ async (commit, description) => {
579
+ if (!source || readOnlyReason) return null;
580
+ setBuilding(commit ? "save" : "check");
581
+ setBuildError(null);
582
+ try {
583
+ const files = changed.length ? changed : selectedPath && contents[selectedPath] !== void 0 ? [{ path: selectedPath, content: contents[selectedPath] }] : [];
584
+ const result = await runWorkflowCodeBuild({
585
+ input: {
586
+ workflowKey,
587
+ baseVersion: version,
588
+ files,
589
+ commit,
590
+ ...commit && source.revision !== null ? { revision: source.revision } : {},
591
+ ...commit && description ? { description } : {}
592
+ }
593
+ });
594
+ setLastBuild(result);
595
+ if (commit && result.success && result.version) {
596
+ onSavedRef.current?.(result.version, result);
597
+ }
598
+ return result;
599
+ } catch (err) {
600
+ setBuildError(describeOperationError(err));
601
+ return null;
602
+ } finally {
603
+ setBuilding(null);
604
+ }
605
+ },
606
+ [
607
+ source,
608
+ readOnlyReason,
609
+ changed,
610
+ selectedPath,
611
+ contents,
612
+ workflowKey,
613
+ version
614
+ ]
615
+ );
616
+ const check = useCallback(() => build(false), [build]);
617
+ const save = useCallback(
618
+ (description) => build(true, description),
619
+ [build]
620
+ );
621
+ const setContent = useCallback((path, content) => {
622
+ setContents(
623
+ (current) => current[path] === content ? current : { ...current, [path]: content }
624
+ );
625
+ }, []);
626
+ return {
627
+ loading,
628
+ loadError,
629
+ source,
630
+ contents,
631
+ selectedPath,
632
+ select: setSelectedPath,
633
+ setContent,
634
+ changed,
635
+ language,
636
+ lease,
637
+ readOnlyReason,
638
+ isEditable: (path) => !readOnlyReason && isEditableSourcePath(path),
639
+ building,
640
+ buildError,
641
+ lastBuild,
642
+ diagnostics: lastBuild?.diagnostics ?? [],
643
+ check,
644
+ save,
645
+ reload
646
+ };
647
+ }
648
+ var NO_DIAGNOSTICS = [];
649
+ function editorLanguage(path) {
650
+ const language = sourceLanguageOf(path);
651
+ if (language === "python") return "python";
652
+ if (language === "typescript") return "typescript";
653
+ return "javascript";
654
+ }
655
+ function SourceFileButton({
656
+ path,
657
+ selected,
658
+ dirty,
659
+ problems,
660
+ onSelect
661
+ }) {
662
+ const tr = useTr();
663
+ return /* @__PURE__ */ jsxs(
664
+ "button",
665
+ {
666
+ type: "button",
667
+ onClick: onSelect,
668
+ "aria-current": selected ? "true" : void 0,
669
+ className: cn(
670
+ "flex w-full items-center gap-1 px-2 py-1.5 text-left font-mono text-xs",
671
+ selected ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"
672
+ ),
673
+ title: path,
674
+ children: [
675
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: path }),
676
+ dirty && /* @__PURE__ */ jsxs(Fragment, { children: [
677
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\u2022" }),
678
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: tr(
679
+ "fluidgrids.builder.codeWorkflow.source.fileChanged",
680
+ "changed"
681
+ ) })
682
+ ] }),
683
+ problems > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
684
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-destructive", children: problems }),
685
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: tr(
686
+ "fluidgrids.builder.codeWorkflow.source.fileProblems",
687
+ "{{count}} problem(s)",
688
+ { count: problems }
689
+ ) })
690
+ ] })
691
+ ]
692
+ }
693
+ );
694
+ }
695
+ function VersionDescriptionInput({
696
+ value,
697
+ onChange
698
+ }) {
699
+ const tr = useTr();
700
+ return /* @__PURE__ */ jsx(
701
+ "input",
702
+ {
703
+ type: "text",
704
+ value,
705
+ onChange: (e) => onChange(e.target.value),
706
+ placeholder: tr(
707
+ "fluidgrids.builder.codeWorkflow.source.descriptionPlaceholder",
708
+ "What changed (optional)"
709
+ ),
710
+ "aria-label": tr(
711
+ "fluidgrids.builder.codeWorkflow.source.descriptionLabel",
712
+ "Version description"
713
+ ),
714
+ className: "min-w-48 flex-1 rounded-md border border-input bg-background px-3 py-1.5 text-sm text-foreground",
715
+ "data-testid": "code-workflow-save-description"
716
+ }
717
+ );
718
+ }
719
+ function CodeWorkflowSourceEditor({
720
+ workflowKey,
721
+ version,
722
+ enabled,
723
+ canEdit,
724
+ onSaved
725
+ }) {
726
+ const tr = useTr();
727
+ const navigate = useNavigate();
728
+ const [description, setDescription] = useState("");
729
+ const editor = useWorkflowCodeEditor({
730
+ workflowKey,
731
+ version,
732
+ enabled,
733
+ canEdit,
734
+ onSaved: (savedVersion) => {
735
+ onSaved?.(savedVersion);
736
+ navigate(
737
+ `../workflow/${encodeURIComponent(workflowKey)}/${encodeURIComponent(savedVersion)}`
738
+ );
739
+ }
740
+ });
741
+ const byPath = useMemo(
742
+ () => diagnosticsByPath(editor.diagnostics),
743
+ [editor.diagnostics]
744
+ );
745
+ const selected = editor.selectedPath;
746
+ const selectedEditable = selected ? editor.isEditable(selected) : false;
747
+ if (editor.loading && !editor.source) {
748
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2 p-4", "aria-busy": "true", children: [
749
+ /* @__PURE__ */ jsx("div", { className: "h-6 w-1/3 animate-pulse rounded bg-muted" }),
750
+ /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded bg-muted" })
751
+ ] });
752
+ }
753
+ if (editor.loadError) {
754
+ return /* @__PURE__ */ jsxs("div", { role: "alert", className: "space-y-2 p-4 text-sm text-destructive", children: [
755
+ /* @__PURE__ */ jsxs("p", { children: [
756
+ tr(
757
+ "fluidgrids.builder.codeWorkflow.source.loadError",
758
+ "Could not load the source:"
759
+ ),
760
+ " ",
761
+ editor.loadError
762
+ ] }),
763
+ /* @__PURE__ */ jsx(
764
+ Button,
765
+ {
766
+ variant: "outline",
767
+ size: "sm",
768
+ onClick: () => void editor.reload(),
769
+ children: tr("fluidgrids.builder.codeWorkflow.source.retry", "Try again")
770
+ }
771
+ )
772
+ ] });
773
+ }
774
+ if (!editor.source) {
775
+ return /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: tr(
776
+ "fluidgrids.builder.codeWorkflow.source.none",
777
+ "This version has no code bundle. Push the project with fluidgrids push to attach one."
778
+ ) });
779
+ }
780
+ const readOnlyNote = editor.readOnlyReason === "python" ? tr(
781
+ "fluidgrids.builder.codeWorkflow.source.pythonReadOnly",
782
+ "Python projects are read-only in the browser. Edit them locally and run fluidgrids push."
783
+ ) : editor.readOnlyReason === "lease" ? tr(
784
+ "fluidgrids.builder.codeWorkflow.source.leaseReadOnly",
785
+ "Someone else is editing this workflow. The source is read-only until they finish."
786
+ ) : editor.readOnlyReason === "permission" ? tr(
787
+ "fluidgrids.builder.codeWorkflow.source.permissionReadOnly",
788
+ "You can read this source but not change it."
789
+ ) : null;
790
+ const result = editor.lastBuild;
791
+ return /* @__PURE__ */ jsxs(
792
+ "div",
793
+ {
794
+ className: "flex min-h-0 flex-1 flex-col gap-3 p-4",
795
+ "data-testid": "code-workflow-source",
796
+ children: [
797
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
798
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:package-variant-closed", className: "size-4" }),
799
+ /* @__PURE__ */ jsx("code", { className: "truncate font-mono", title: editor.source.buildId, children: editor.source.buildId }),
800
+ editor.changed.length > 0 && /* @__PURE__ */ jsx("span", { className: "rounded bg-primary/10 px-1.5 py-0.5 text-primary", children: tr(
801
+ "fluidgrids.builder.codeWorkflow.source.changed",
802
+ "{{count}} changed",
803
+ { count: editor.changed.length }
804
+ ) })
805
+ ] }),
806
+ readOnlyNote && /* @__PURE__ */ jsx(
807
+ "p",
808
+ {
809
+ className: "rounded-md border border-border bg-muted px-3 py-2 text-xs text-muted-foreground",
810
+ "data-testid": "code-workflow-source-readonly",
811
+ children: readOnlyNote
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [
815
+ /* @__PURE__ */ jsx(
816
+ "nav",
817
+ {
818
+ "aria-label": tr(
819
+ "fluidgrids.builder.codeWorkflow.source.files",
820
+ "Files"
821
+ ),
822
+ className: "w-48 shrink-0 overflow-auto rounded-md border border-border",
823
+ children: editor.source.files.map((file) => {
824
+ const problems = byPath.get(file.path)?.length ?? 0;
825
+ const dirty = editor.changed.some((c) => c.path === file.path);
826
+ return /* @__PURE__ */ jsx(
827
+ SourceFileButton,
828
+ {
829
+ path: file.path,
830
+ selected: file.path === selected,
831
+ dirty,
832
+ problems,
833
+ onSelect: () => editor.select(file.path)
834
+ },
835
+ file.path
836
+ );
837
+ })
838
+ }
839
+ ),
840
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: selected && /* @__PURE__ */ jsx(
841
+ CodeEditorField,
842
+ {
843
+ value: editor.contents[selected] ?? "",
844
+ language: editorLanguage(selected),
845
+ label: selected,
846
+ readOnly: !selectedEditable,
847
+ onChange: (value) => editor.setContent(selected, value),
848
+ diagnostics: byPath.get(selected) ?? NO_DIAGNOSTICS,
849
+ minHeight: "320px",
850
+ maxHeight: "56vh",
851
+ "data-testid": "code-workflow-source-editor"
852
+ },
853
+ selected
854
+ ) })
855
+ ] }),
856
+ (editor.buildError || result) && /* @__PURE__ */ jsxs(
857
+ "div",
858
+ {
859
+ "data-testid": "code-workflow-build-result",
860
+ className: cn(
861
+ "rounded-md border px-3 py-2 text-xs",
862
+ editor.buildError || result && !result.success ? "border-destructive/40 bg-destructive/5 text-destructive" : "border-border bg-muted text-foreground"
863
+ ),
864
+ children: [
865
+ editor.buildError ? /* @__PURE__ */ jsx("p", { children: editor.buildError }) : result?.success ? /* @__PURE__ */ jsx("p", { children: result.version ? tr(
866
+ "fluidgrids.builder.codeWorkflow.source.saved",
867
+ "Saved as {{version}} ({{buildId}}).",
868
+ { version: result.version, buildId: result.buildId ?? "" }
869
+ ) : tr(
870
+ "fluidgrids.builder.codeWorkflow.source.checked",
871
+ "No problems found. Build {{buildId}}.",
872
+ { buildId: result.buildId ?? "" }
873
+ ) }) : /* @__PURE__ */ jsx("p", { children: tr(
874
+ "fluidgrids.builder.codeWorkflow.source.failed",
875
+ "The build found {{count}} problem(s).",
876
+ { count: result?.diagnostics.length ?? 0 }
877
+ ) }),
878
+ result && result.diagnostics.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-1 max-h-40 space-y-0.5 overflow-auto font-mono", children: result.diagnostics.map((d, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
879
+ "button",
880
+ {
881
+ type: "button",
882
+ className: "text-left hover:underline",
883
+ onClick: () => d.path && editor.select(d.path.replace(/^\.\//, "")),
884
+ children: [
885
+ d.path ?? "",
886
+ d.line ? `:${d.line}` : "",
887
+ d.column ? `:${d.column}` : "",
888
+ " ",
889
+ d.severity,
890
+ d.code ? ` ${d.code}` : "",
891
+ ": ",
892
+ d.message
893
+ ]
894
+ }
895
+ ) }, `${d.path ?? ""}:${d.line ?? 0}:${index}`)) })
896
+ ]
897
+ }
898
+ ),
899
+ !editor.readOnlyReason && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
900
+ /* @__PURE__ */ jsx(
901
+ VersionDescriptionInput,
902
+ {
903
+ value: description,
904
+ onChange: setDescription
905
+ }
906
+ ),
907
+ /* @__PURE__ */ jsxs(
908
+ Button,
909
+ {
910
+ variant: "outline",
911
+ size: "sm",
912
+ onClick: () => void editor.check(),
913
+ disabled: editor.building !== null,
914
+ "data-testid": "code-workflow-check",
915
+ children: [
916
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:check-decagram-outline", className: "mr-1 size-4" }),
917
+ editor.building === "check" ? tr(
918
+ "fluidgrids.builder.codeWorkflow.source.checking",
919
+ "Checking..."
920
+ ) : tr("fluidgrids.builder.codeWorkflow.source.check", "Check")
921
+ ]
922
+ }
923
+ ),
924
+ /* @__PURE__ */ jsxs(
925
+ Button,
926
+ {
927
+ size: "sm",
928
+ onClick: () => void editor.save(description.trim() || void 0),
929
+ disabled: editor.building !== null || editor.changed.length === 0,
930
+ "data-testid": "code-workflow-save",
931
+ children: [
932
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:content-save-outline", className: "mr-1 size-4" }),
933
+ editor.building === "save" ? tr("fluidgrids.builder.codeWorkflow.source.saving", "Saving...") : tr(
934
+ "fluidgrids.builder.codeWorkflow.source.save",
935
+ "Save as new version"
936
+ )
937
+ ]
938
+ }
939
+ )
940
+ ] })
941
+ ]
942
+ }
943
+ );
944
+ }
945
+ function FieldsTable({ title, schema }) {
946
+ const tr = useTr();
947
+ const fields = interfaceFields(schema);
948
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-1", children: [
949
+ /* @__PURE__ */ jsx("h4", { className: "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: title }),
950
+ fields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
951
+ "fluidgrids.builder.codeWorkflow.interface.noFields",
952
+ "Not declared"
953
+ ) }) : /* @__PURE__ */ jsx("ul", { className: "divide-y divide-border rounded-md border border-border", children: fields.map((field) => /* @__PURE__ */ jsxs(
954
+ "li",
955
+ {
956
+ className: "flex items-baseline gap-2 px-2 py-1 text-xs",
957
+ children: [
958
+ /* @__PURE__ */ jsxs("code", { className: "font-mono text-foreground", children: [
959
+ field.name,
960
+ field.required ? "" : "?"
961
+ ] }),
962
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", children: field.type }),
963
+ field.description && /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: field.description })
964
+ ]
965
+ },
966
+ field.name
967
+ )) })
968
+ ] });
969
+ }
970
+ function CodeWorkflowInterface({
971
+ iface,
972
+ loading = false,
973
+ error = null,
974
+ onRetry,
975
+ childRunId,
976
+ canSignal
977
+ }) {
978
+ const tr = useTr();
979
+ const [signalName, setSignalName] = useState(null);
980
+ const handlers = interfaceHandlers(iface);
981
+ const targets = declaredSignals(iface);
982
+ if (loading) {
983
+ return /* @__PURE__ */ jsxs(
984
+ "div",
985
+ {
986
+ className: "space-y-2 p-4",
987
+ role: "status",
988
+ "aria-label": tr(
989
+ "fluidgrids.builder.codeWorkflow.interface.loading",
990
+ "Loading the function's interface"
991
+ ),
992
+ "data-testid": "code-workflow-interface-loading",
993
+ children: [
994
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/3" }),
995
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-16 w-full" }),
996
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-1/4" })
997
+ ]
998
+ }
999
+ );
1000
+ }
1001
+ if (error) {
1002
+ return /* @__PURE__ */ jsxs(
1003
+ "div",
1004
+ {
1005
+ className: "space-y-2 p-4",
1006
+ "data-testid": "code-workflow-interface-error",
1007
+ children: [
1008
+ /* @__PURE__ */ jsx(
1009
+ "p",
1010
+ {
1011
+ role: "alert",
1012
+ className: "text-sm text-[var(--color-status-error-text)]",
1013
+ children: tr(
1014
+ "fluidgrids.builder.codeWorkflow.interface.error",
1015
+ "Could not load the function's interface: {{message}}",
1016
+ { message: error }
1017
+ )
1018
+ }
1019
+ ),
1020
+ onRetry && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: tr("fluidgrids.builder.codeWorkflow.interface.retry", "Try again") })
1021
+ ]
1022
+ }
1023
+ );
1024
+ }
1025
+ if (!iface) {
1026
+ return /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: tr(
1027
+ "fluidgrids.builder.codeWorkflow.interface.none",
1028
+ "The function declares no interface."
1029
+ ) });
1030
+ }
1031
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4", "data-testid": "code-workflow-interface", children: [
1032
+ /* @__PURE__ */ jsx(
1033
+ FieldsTable,
1034
+ {
1035
+ title: tr("fluidgrids.builder.codeWorkflow.interface.inputs", "Inputs"),
1036
+ schema: iface.input
1037
+ }
1038
+ ),
1039
+ /* @__PURE__ */ jsx(
1040
+ FieldsTable,
1041
+ {
1042
+ title: tr(
1043
+ "fluidgrids.builder.codeWorkflow.interface.outputs",
1044
+ "Outputs"
1045
+ ),
1046
+ schema: iface.output
1047
+ }
1048
+ ),
1049
+ /* @__PURE__ */ jsxs("section", { className: "space-y-1", children: [
1050
+ /* @__PURE__ */ jsx("h4", { className: "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: tr("fluidgrids.builder.codeWorkflow.interface.signals", "Signals") }),
1051
+ handlers.signals.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tr(
1052
+ "fluidgrids.builder.codeWorkflow.interface.noSignals",
1053
+ "None declared"
1054
+ ) }) : /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: handlers.signals.map((name) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 text-xs", children: [
1055
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: name }),
1056
+ canSignal && childRunId && /* @__PURE__ */ jsx(
1057
+ Button,
1058
+ {
1059
+ variant: "outline",
1060
+ size: "sm",
1061
+ className: "ml-auto h-6 px-2 text-xs",
1062
+ onClick: () => setSignalName(name),
1063
+ "data-testid": `code-workflow-send-signal-${name}`,
1064
+ children: tr(
1065
+ "fluidgrids.builder.codeWorkflow.interface.sendSignal",
1066
+ "Send signal"
1067
+ )
1068
+ }
1069
+ )
1070
+ ] }, name)) })
1071
+ ] }),
1072
+ (handlers.queries.length > 0 || handlers.updates.length > 0) && /* @__PURE__ */ jsxs("section", { className: "space-y-1 text-xs", children: [
1073
+ /* @__PURE__ */ jsx("h4", { className: "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: tr(
1074
+ "fluidgrids.builder.codeWorkflow.interface.queriesUpdates",
1075
+ "Queries and updates"
1076
+ ) }),
1077
+ /* @__PURE__ */ jsx("p", { className: "font-mono text-foreground", children: [...handlers.queries, ...handlers.updates].join(", ") }),
1078
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: tr(
1079
+ "fluidgrids.builder.codeWorkflow.interface.phaseC",
1080
+ "Running queries and updates from the builder is not available yet."
1081
+ ) })
1082
+ ] }),
1083
+ childRunId && /* @__PURE__ */ jsx(
1084
+ SignalRunDialog,
1085
+ {
1086
+ runId: childRunId,
1087
+ targets,
1088
+ open: signalName !== null,
1089
+ onOpenChange: (open) => !open && setSignalName(null),
1090
+ initialName: signalName ?? void 0
1091
+ }
1092
+ )
1093
+ ] });
1094
+ }
1095
+ var CHILD_RUN_POLL_INTERVAL_MS = 2e3;
1096
+ function useCodeWorkflowChildRun(runId, nodeId, enabled, active = false) {
1097
+ const key = enabled && runId && nodeId ? `${runId}\0${nodeId}` : null;
1098
+ const [found, setFound] = useState({ key: null, childRunId: void 0 });
1099
+ const [loading, setLoading] = useState(false);
1100
+ const childRunId = found.key === key ? found.childRunId : void 0;
1101
+ const hasChild = childRunId !== void 0;
1102
+ useEffect(() => {
1103
+ const client = getFluidGridsClientSafe();
1104
+ if (!key || !runId || !nodeId || !client || hasChild) {
1105
+ setLoading(false);
1106
+ return;
1107
+ }
1108
+ let cancelled = false;
1109
+ let timer = null;
1110
+ let inFlight = false;
1111
+ const lookup = (first) => {
1112
+ if (inFlight) return;
1113
+ inFlight = true;
1114
+ if (first) setLoading(true);
1115
+ client.query({
1116
+ query: NodeStatesDocument,
1117
+ variables: { runId },
1118
+ fetchPolicy: "network-only"
1119
+ }).then((result) => {
1120
+ throwIfResultError(result);
1121
+ if (cancelled) return;
1122
+ const next = childRunIdOf(result.data?.nodeStates ?? [], nodeId);
1123
+ setFound({ key, childRunId: next });
1124
+ if (next && timer) {
1125
+ clearInterval(timer);
1126
+ timer = null;
1127
+ }
1128
+ }).catch(() => {
1129
+ }).finally(() => {
1130
+ inFlight = false;
1131
+ if (!cancelled && first) setLoading(false);
1132
+ });
1133
+ };
1134
+ lookup(true);
1135
+ if (active) {
1136
+ timer = setInterval(() => lookup(false), CHILD_RUN_POLL_INTERVAL_MS);
1137
+ }
1138
+ return () => {
1139
+ cancelled = true;
1140
+ if (timer) clearInterval(timer);
1141
+ };
1142
+ }, [key, runId, nodeId, active, hasChild]);
1143
+ return { childRunId, loading: loading && !hasChild };
1144
+ }
1145
+ function CodeWorkflowTrace({
1146
+ childRunId,
1147
+ locating,
1148
+ active
1149
+ }) {
1150
+ const tr = useTr();
1151
+ const history = useRunHistory(childRunId, {
1152
+ enabled: !!childRunId,
1153
+ live: active
1154
+ });
1155
+ if (!childRunId) {
1156
+ return /* @__PURE__ */ jsx(
1157
+ "p",
1158
+ {
1159
+ className: "p-4 text-sm text-muted-foreground",
1160
+ "data-testid": "code-workflow-trace-empty",
1161
+ children: locating ? tr(
1162
+ "fluidgrids.builder.codeWorkflow.trace.locating",
1163
+ "Looking for the function's run\u2026"
1164
+ ) : tr(
1165
+ "fluidgrids.builder.codeWorkflow.trace.none",
1166
+ "Run the workflow to see the function's history here."
1167
+ )
1168
+ }
1169
+ );
1170
+ }
1171
+ return /* @__PURE__ */ jsx("div", { className: "p-4", "data-testid": "code-workflow-trace", children: /* @__PURE__ */ jsx(
1172
+ RunHistoryView,
1173
+ {
1174
+ events: history.events,
1175
+ loading: history.loading,
1176
+ error: history.error,
1177
+ active,
1178
+ streaming: history.streaming,
1179
+ truncated: history.truncated,
1180
+ historyLength: history.historyLength,
1181
+ onRefresh: () => void history.refetch(),
1182
+ compact: true,
1183
+ "data-testid": "code-workflow-trace-history"
1184
+ }
1185
+ ) });
1186
+ }
1187
+ function CodeWorkflowPanel({
1188
+ tab,
1189
+ nodeId,
1190
+ args,
1191
+ onClose
1192
+ }) {
1193
+ const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
1194
+ const version = useFlowStore((s) => s.workflow.version);
1195
+ const currentRunId = useFlowStore((s) => s.execution.currentRunId);
1196
+ const isExecuting = useFlowStore((s) => s.execution.isExecuting);
1197
+ const { allowed: canEdit } = useFluidGridsPermission(
1198
+ FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE
1199
+ );
1200
+ const { allowed: canSignal } = useFluidGridsPermission(
1201
+ FLUIDGRIDS_PERMISSIONS.WORKFLOW_RUN_EXECUTE
1202
+ );
1203
+ const state = readCodeWorkflowState(args);
1204
+ const iface = useCodeWorkflowInterface(state.workflowFn, tab === "interface");
1205
+ const child = useCodeWorkflowChildRun(
1206
+ currentRunId,
1207
+ nodeId,
1208
+ tab === "trace" || tab === "interface",
1209
+ isExecuting
1210
+ );
1211
+ if (tab === "source") {
1212
+ return /* @__PURE__ */ jsx(
1213
+ CodeWorkflowSourceEditor,
1214
+ {
1215
+ workflowKey,
1216
+ version,
1217
+ enabled: !!workflowKey && !!version,
1218
+ canEdit,
1219
+ onSaved: () => {
1220
+ clearCodeWorkflowDefinitionCache();
1221
+ onClose();
1222
+ }
1223
+ }
1224
+ );
1225
+ }
1226
+ if (tab === "trace") {
1227
+ return /* @__PURE__ */ jsx(
1228
+ CodeWorkflowTrace,
1229
+ {
1230
+ childRunId: child.childRunId,
1231
+ locating: child.loading,
1232
+ active: isExecuting
1233
+ }
1234
+ );
1235
+ }
1236
+ return /* @__PURE__ */ jsx(
1237
+ CodeWorkflowInterface,
1238
+ {
1239
+ iface: iface.iface,
1240
+ loading: iface.loading,
1241
+ error: iface.error,
1242
+ onRetry: iface.retry,
1243
+ childRunId: child.childRunId,
1244
+ canSignal
1245
+ }
1246
+ );
1247
+ }
1248
+ var SECTION_LABEL2 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
1249
+ function NodeDetailView() {
1250
+ const tr = useTr();
1251
+ const ndv = useFlowStore((s) => s.workflow.ndv);
1252
+ const closeNdv = useFlowStore((s) => s.actions.ndv.close);
1253
+ const showNodePropertiesOf = useFlowStore(
1254
+ (s) => s.actions.sidebar.showNodePropertiesOf
1255
+ );
1256
+ const open = !!ndv;
1257
+ const config = useNodeConfig(ndv ?? null, { webhookEnabled: open });
1258
+ const {
1259
+ node,
1260
+ nodes,
1261
+ sourceIds,
1262
+ selectedSource,
1263
+ setSelectedSource,
1264
+ currentOp,
1265
+ inputFields,
1266
+ inputData,
1267
+ outputFields
1268
+ } = config;
1269
+ const [tab, setTab] = useState(
1270
+ "parameters"
1271
+ );
1272
+ const durableEnabled = useDurableFunctionNodesEnabled();
1273
+ const isCodeWorkflow = durableEnabled && node?.type === CODE_WORKFLOW_NODE_TYPE;
1274
+ const [outputView, setOutputView] = useState("schema");
1275
+ const [inputView, setInputView] = useState("schema");
1276
+ const executionLogs = useFlowStore((s) => s.execution.logs);
1277
+ const currentRunId = useFlowStore((s) => s.execution.currentRunId);
1278
+ const sourceRun = useFlowStore((s) => s.execution.sourceRun);
1279
+ const isExecuting = useFlowStore((s) => s.execution.isExecuting);
1280
+ const workflowNodes = useFlowStore((s) => s.workflow.nodes);
1281
+ const workflowEdges = useFlowStore((s) => s.workflow.edges);
1282
+ const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
1283
+ const workflowId = useFlowStore((s) => s.workflow.id);
1284
+ const workflowVersion = useFlowStore((s) => s.workflow.version);
1285
+ const stepPlan = useFlowStore((s) => s.execution.stepPlan);
1286
+ const nodeLog = useMemo(
1287
+ () => latestNodeLog(executionLogs, ndv?.nodeId, currentRunId),
1288
+ [executionLogs, ndv?.nodeId, currentRunId]
1289
+ );
1290
+ const stepPlanForNode = stepPlan?.runId === currentRunId && stepPlan.targetNodeId === ndv?.nodeId ? stepPlan : null;
1291
+ const hasRealOutput = !!nodeLog?.output && Object.keys(nodeLog.output).length > 0;
1292
+ const hasRealInput = inputData !== void 0;
1293
+ const realOutputFields = useMemo(
1294
+ () => hasRealOutput ? fieldsFromValue(nodeLog.output) : [],
1295
+ [hasRealOutput, nodeLog]
1296
+ );
1297
+ const workflowDbId = useFlowStore((s) => s.workflow.dbId);
1298
+ const { data: pinnedDataResult, refetch: refetchPinnedData } = useWorkflowPinnedDataQuery({
1299
+ variables: { id: workflowDbId },
1300
+ skip: !workflowDbId || !open,
1301
+ fetchPolicy: "cache-and-network"
1302
+ });
1303
+ const pinnedNodeData = pinnedDataResult?.workflowDefinition?.pinnedNodeData ?? {};
1304
+ const isPinned = !!ndv?.nodeId && !!pinnedNodeData[ndv.nodeId];
1305
+ const [pinNodeData, { loading: pinning }] = usePinNodeDataMutation();
1306
+ const [unpinNodeData, { loading: unpinning }] = useUnpinNodeDataMutation();
1307
+ const togglePin = async () => {
1308
+ if (!workflowDbId || !ndv?.nodeId || config.readOnly) return;
1309
+ try {
1310
+ if (isPinned) {
1311
+ await unpinNodeData({
1312
+ variables: { id: workflowDbId, nodeId: ndv.nodeId }
1313
+ });
1314
+ } else {
1315
+ const dataToPin = nodeLog?.output ?? currentOp?.output_fields ?? {};
1316
+ await pinNodeData({
1317
+ variables: { id: workflowDbId, nodeId: ndv.nodeId, data: dataToPin }
1318
+ });
1319
+ }
1320
+ await refetchPinnedData();
1321
+ } catch (err) {
1322
+ console.error("[NodeDetailView] Failed to toggle pinned data", err);
1323
+ }
1324
+ };
1325
+ const { executeStep, executeFromNode } = useNodeExecution();
1326
+ const threePaneLayout = useMediaQuery("(min-width: 1024px)");
1327
+ const [pane, setPane] = useState("parameters");
1328
+ const panesRef = useRef(null);
1329
+ const dragCleanupRef = useRef(null);
1330
+ const [leftPct, setLeftPct] = useState(30);
1331
+ const [rightPct, setRightPct] = useState(30);
1332
+ const startDrag = (which) => (e) => {
1333
+ e.preventDefault();
1334
+ const el = panesRef.current;
1335
+ if (!el) return;
1336
+ const rect = el.getBoundingClientRect();
1337
+ const onMove = (ev) => {
1338
+ const pct = (ev.clientX - rect.left) / rect.width * 100;
1339
+ if (which === "left") setLeftPct(Math.min(55, Math.max(15, pct)));
1340
+ else setRightPct(Math.min(55, Math.max(15, 100 - pct)));
1341
+ };
1342
+ const cleanup = () => {
1343
+ window.removeEventListener("mousemove", onMove);
1344
+ window.removeEventListener("mouseup", cleanup);
1345
+ dragCleanupRef.current = null;
1346
+ };
1347
+ dragCleanupRef.current = cleanup;
1348
+ window.addEventListener("mousemove", onMove);
1349
+ window.addEventListener("mouseup", cleanup);
1350
+ };
1351
+ useEffect(() => () => dragCleanupRef.current?.(), []);
1352
+ const requestedTab = ndv?.tab;
1353
+ useEffect(() => {
1354
+ if (open) {
1355
+ setTab(requestedTab ?? "parameters");
1356
+ setPane("parameters");
1357
+ }
1358
+ }, [open, ndv?.nodeId, requestedTab]);
1359
+ const openClassicPanel = () => {
1360
+ if (!ndv) return;
1361
+ showNodePropertiesOf({
1362
+ id: ndv.nodeId,
1363
+ type: ndv.type
1364
+ });
1365
+ closeNdv();
1366
+ };
1367
+ const headerIcon = getNodeDetail(node?.type).icon;
1368
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (o) => !o && closeNdv(), children: /* @__PURE__ */ jsxs(
1369
+ DialogContent,
1370
+ {
1371
+ style: {
1372
+ width: "94vw",
1373
+ height: "92vh",
1374
+ maxWidth: "none",
1375
+ maxHeight: "92vh",
1376
+ // Above the app shell navbar so the header (title/close) isn't occluded.
1377
+ zIndex: NDV_Z_INDEX
1378
+ },
1379
+ className: "flex flex-col gap-0 overflow-hidden border-border bg-background p-0",
1380
+ "data-testid": "ndv-modal",
1381
+ children: [
1382
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "flex shrink-0 flex-row items-center gap-3 space-y-0 border-b border-border px-4 py-3", children: [
1383
+ /* @__PURE__ */ jsx("div", { className: "flex size-7 shrink-0 items-center justify-center rounded-md bg-muted", children: /* @__PURE__ */ jsx(Icon, { icon: headerIcon, className: "size-4 text-foreground" }) }),
1384
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1385
+ /* @__PURE__ */ jsx(DialogTitle, { className: "truncate text-sm font-semibold leading-tight", children: nodeTitle(node) }),
1386
+ nodeSubtitle(node) && /* @__PURE__ */ jsx(DialogDescription, { className: "truncate text-xs leading-tight", children: nodeSubtitle(node) })
1387
+ ] }),
1388
+ /* @__PURE__ */ jsx(
1389
+ "button",
1390
+ {
1391
+ type: "button",
1392
+ onClick: openClassicPanel,
1393
+ className: "rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
1394
+ children: tr(
1395
+ "fluidgrids.builder.nodeDetailView.sidePanelButton",
1396
+ "Side panel"
1397
+ )
1398
+ }
1399
+ ),
1400
+ /* @__PURE__ */ jsx(
1401
+ "button",
1402
+ {
1403
+ type: "button",
1404
+ "aria-label": tr(
1405
+ "fluidgrids.builder.nodeDetailView.closeAriaLabel",
1406
+ "Close"
1407
+ ),
1408
+ onClick: closeNdv,
1409
+ className: "rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground",
1410
+ children: /* @__PURE__ */ jsx(Icon, { icon: "mdi:close", className: "size-4" })
1411
+ }
1412
+ )
1413
+ ] }),
1414
+ ndv && /* @__PURE__ */ jsx(
1415
+ NodeExecutionActions,
1416
+ {
1417
+ targetNodeId: ndv.nodeId,
1418
+ nodes: workflowNodes,
1419
+ edges: workflowEdges,
1420
+ logs: executionLogs,
1421
+ workflowKey: workflowKey || workflowId,
1422
+ workflowVersion: workflowVersion || "1.0.0",
1423
+ sourceRun,
1424
+ isExecuting,
1425
+ onExecuteStep: async (targetId) => {
1426
+ await executeStep(targetId);
1427
+ },
1428
+ onContinueFromNode: async (sourceRunId, targetId) => {
1429
+ const result = await executeFromNode(sourceRunId, targetId);
1430
+ if (!result.success && result.quotaExhausted) closeNdv();
1431
+ },
1432
+ testIdPrefix: "ndv",
1433
+ className: "border-t-0 border-b"
1434
+ }
1435
+ ),
1436
+ stepPlanForNode && /* @__PURE__ */ jsx(StepExecutionPlanSummary, { plan: stepPlanForNode }),
1437
+ !threePaneLayout && /* @__PURE__ */ jsxs(
1438
+ "div",
1439
+ {
1440
+ className: "flex shrink-0 items-center gap-4 border-b border-border px-4",
1441
+ "data-testid": "ndv-pane-switcher",
1442
+ children: [
1443
+ /* @__PURE__ */ jsx(
1444
+ TabButton,
1445
+ {
1446
+ active: pane === "input",
1447
+ onClick: () => setPane("input"),
1448
+ children: tr("fluidgrids.builder.nodeDetailView.inputSection", "Input")
1449
+ }
1450
+ ),
1451
+ /* @__PURE__ */ jsx(
1452
+ TabButton,
1453
+ {
1454
+ active: pane === "parameters",
1455
+ onClick: () => setPane("parameters"),
1456
+ children: tr(
1457
+ "fluidgrids.builder.nodeDetailView.tabParameters",
1458
+ "Parameters"
1459
+ )
1460
+ }
1461
+ ),
1462
+ /* @__PURE__ */ jsx(
1463
+ TabButton,
1464
+ {
1465
+ active: pane === "output",
1466
+ onClick: () => setPane("output"),
1467
+ children: tr("fluidgrids.builder.nodeDetailView.outputSection", "Output")
1468
+ }
1469
+ )
1470
+ ]
1471
+ }
1472
+ ),
1473
+ /* @__PURE__ */ jsxs("div", { ref: panesRef, className: "flex min-h-0 flex-1", children: [
1474
+ /* @__PURE__ */ jsxs(
1475
+ "section",
1476
+ {
1477
+ style: threePaneLayout ? { width: `${leftPct}%` } : void 0,
1478
+ className: cn(
1479
+ "flex min-h-0 flex-col bg-card/40",
1480
+ threePaneLayout ? "min-w-[180px]" : pane === "input" ? "w-full flex-1" : "hidden"
1481
+ ),
1482
+ children: [
1483
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-2.5", children: [
1484
+ /* @__PURE__ */ jsx("span", { className: SECTION_LABEL2, children: tr("fluidgrids.builder.nodeDetailView.inputSection", "Input") }),
1485
+ hasRealInput && /* @__PURE__ */ jsx(
1486
+ "span",
1487
+ {
1488
+ className: "rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-primary",
1489
+ title: tr(
1490
+ "fluidgrids.builder.nodeDetailView.realDataBadgeHint",
1491
+ "Showing real data from the last execution."
1492
+ ),
1493
+ children: tr(
1494
+ "fluidgrids.builder.nodeDetailView.realDataBadge",
1495
+ "Live"
1496
+ )
1497
+ }
1498
+ ),
1499
+ sourceIds.length > 0 && /* @__PURE__ */ jsx(
1500
+ "select",
1501
+ {
1502
+ className: "ml-auto max-w-[60%] truncate rounded-md border border-border-default bg-bg-surface px-2 py-1 text-xs text-text-primary scheme-normal dark:scheme-dark outline-none focus:border-action-primary-border [&_option]:bg-bg-surface [&_option]:text-text-primary",
1503
+ value: selectedSource ?? "",
1504
+ onChange: (e) => setSelectedSource(e.target.value),
1505
+ "data-testid": "ndv-source-selector",
1506
+ children: sourceIds.map((sid) => {
1507
+ const sn = nodes.find((n) => n.id === sid);
1508
+ return /* @__PURE__ */ jsx("option", { value: sid, children: nodeTitle(sn) }, sid);
1509
+ })
1510
+ }
1511
+ ),
1512
+ (hasRealInput || inputFields.length > 0) && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
1513
+ /* @__PURE__ */ jsx(
1514
+ ToggleChip,
1515
+ {
1516
+ active: inputView === "schema",
1517
+ onClick: () => setInputView("schema"),
1518
+ children: tr(
1519
+ "fluidgrids.builder.nodeDetailView.outputViewSchema",
1520
+ "Schema"
1521
+ )
1522
+ }
1523
+ ),
1524
+ /* @__PURE__ */ jsx(
1525
+ ToggleChip,
1526
+ {
1527
+ active: inputView === "table",
1528
+ onClick: () => setInputView("table"),
1529
+ children: tr(
1530
+ "fluidgrids.builder.nodeDetailView.outputViewTable",
1531
+ "Table"
1532
+ )
1533
+ }
1534
+ ),
1535
+ /* @__PURE__ */ jsx(
1536
+ ToggleChip,
1537
+ {
1538
+ active: inputView === "json",
1539
+ onClick: () => setInputView("json"),
1540
+ children: tr(
1541
+ "fluidgrids.builder.nodeDetailView.outputViewJson",
1542
+ "JSON"
1543
+ )
1544
+ }
1545
+ )
1546
+ ] })
1547
+ ] }),
1548
+ /* @__PURE__ */ jsxs(
1549
+ "div",
1550
+ {
1551
+ className: "min-h-0 flex-1 overflow-auto px-2 pb-3",
1552
+ "data-testid": "ndv-input-tree",
1553
+ children: [
1554
+ hasRealInput ? inputView === "schema" ? /* @__PURE__ */ jsx(FieldTree, { fields: inputFields, draggable: true }) : inputView === "table" ? /* @__PURE__ */ jsx(TableView, { data: inputData }) : /* @__PURE__ */ jsx(JsonView, { data: inputData }) : inputFields.length > 0 ? /* @__PURE__ */ jsx(FieldTree, { fields: inputFields, draggable: true }) : /* @__PURE__ */ jsx(
1555
+ EmptyState,
1556
+ {
1557
+ icon: "mdi:tray-arrow-down",
1558
+ title: tr(
1559
+ "fluidgrids.builder.nodeDetailView.noInputTitle",
1560
+ "No input data"
1561
+ ),
1562
+ hint: sourceIds.length === 0 ? tr(
1563
+ "fluidgrids.builder.nodeDetailView.noInputHintDisconnected",
1564
+ "Connect an upstream node to map its fields."
1565
+ ) : tr(
1566
+ "fluidgrids.builder.nodeDetailView.noInputHintNoFields",
1567
+ "The selected source has no declared output fields."
1568
+ )
1569
+ }
1570
+ ),
1571
+ nodeLog?.inputArgs && /* @__PURE__ */ jsxs(
1572
+ "details",
1573
+ {
1574
+ className: "mt-4 border-t border-border px-2 pt-3 text-xs",
1575
+ "data-testid": "ndv-executed-parameters",
1576
+ children: [
1577
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium", children: tr(
1578
+ "fluidgrids.builder.nodeDetailView.executedParameters",
1579
+ "Executed parameters (read-only)"
1580
+ ) }),
1581
+ /* @__PURE__ */ jsx("p", { className: "my-2 text-muted-foreground", children: tr(
1582
+ "fluidgrids.builder.nodeDetailView.executedParametersHint",
1583
+ "Values passed to this node after mapping. Drag fields from the upstream source above to create mappings."
1584
+ ) }),
1585
+ /* @__PURE__ */ jsx(JsonView, { data: nodeLog.inputArgs })
1586
+ ]
1587
+ }
1588
+ )
1589
+ ]
1590
+ }
1591
+ )
1592
+ ]
1593
+ }
1594
+ ),
1595
+ threePaneLayout && /* @__PURE__ */ jsx(ResizeHandle, { onStart: startDrag("left") }),
1596
+ /* @__PURE__ */ jsxs(
1597
+ "section",
1598
+ {
1599
+ className: cn(
1600
+ "flex min-h-0 flex-1 flex-col",
1601
+ threePaneLayout ? "min-w-[280px]" : pane === "parameters" ? "w-full" : "hidden"
1602
+ ),
1603
+ children: [
1604
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-4 border-b border-border px-4", children: [
1605
+ /* @__PURE__ */ jsx(
1606
+ TabButton,
1607
+ {
1608
+ active: tab === "parameters",
1609
+ onClick: () => setTab("parameters"),
1610
+ children: tr(
1611
+ "fluidgrids.builder.nodeDetailView.tabParameters",
1612
+ "Parameters"
1613
+ )
1614
+ }
1615
+ ),
1616
+ isCodeWorkflow && /* @__PURE__ */ jsxs(Fragment, { children: [
1617
+ /* @__PURE__ */ jsx(
1618
+ TabButton,
1619
+ {
1620
+ active: tab === "source",
1621
+ onClick: () => setTab("source"),
1622
+ children: tr(
1623
+ "fluidgrids.builder.nodeDetailView.tabSource",
1624
+ "Source"
1625
+ )
1626
+ }
1627
+ ),
1628
+ /* @__PURE__ */ jsx(
1629
+ TabButton,
1630
+ {
1631
+ active: tab === "trace",
1632
+ onClick: () => setTab("trace"),
1633
+ children: tr("fluidgrids.builder.nodeDetailView.tabTrace", "Trace")
1634
+ }
1635
+ ),
1636
+ /* @__PURE__ */ jsx(
1637
+ TabButton,
1638
+ {
1639
+ active: tab === "interface",
1640
+ onClick: () => setTab("interface"),
1641
+ children: tr(
1642
+ "fluidgrids.builder.nodeDetailView.tabInterface",
1643
+ "Interface"
1644
+ )
1645
+ }
1646
+ )
1647
+ ] }),
1648
+ /* @__PURE__ */ jsx(
1649
+ TabButton,
1650
+ {
1651
+ active: tab === "settings",
1652
+ onClick: () => setTab("settings"),
1653
+ children: tr(
1654
+ "fluidgrids.builder.nodeDetailView.tabSettings",
1655
+ "Settings"
1656
+ )
1657
+ }
1658
+ )
1659
+ ] }),
1660
+ isCodeWorkflow && ndv && (tab === "source" || tab === "trace" || tab === "interface") ? /* @__PURE__ */ jsx(
1661
+ "div",
1662
+ {
1663
+ className: "flex min-h-0 flex-1 flex-col overflow-auto",
1664
+ "data-testid": `ndv-code-workflow-${tab}`,
1665
+ children: /* @__PURE__ */ jsx(
1666
+ CodeWorkflowPanel,
1667
+ {
1668
+ tab,
1669
+ nodeId: ndv.nodeId,
1670
+ args: node?.data?.args?.state,
1671
+ onClose: closeNdv
1672
+ }
1673
+ )
1674
+ }
1675
+ ) : tab === "parameters" ? /* @__PURE__ */ jsx(
1676
+ "div",
1677
+ {
1678
+ className: "min-h-0 flex-1 space-y-4 overflow-auto p-4",
1679
+ "data-testid": "ndv-params",
1680
+ children: /* @__PURE__ */ jsx(ReadOnlyFieldset, { className: "space-y-4", children: /* @__PURE__ */ jsx(
1681
+ NodeParamsForm,
1682
+ {
1683
+ config,
1684
+ nodeType: node?.type,
1685
+ testIdPrefix: "ndv"
1686
+ }
1687
+ ) })
1688
+ }
1689
+ ) : /* @__PURE__ */ jsx(
1690
+ "div",
1691
+ {
1692
+ className: "min-h-0 flex-1 overflow-auto p-4",
1693
+ "data-testid": "ndv-settings",
1694
+ children: /* @__PURE__ */ jsx(ReadOnlyFieldset, { children: /* @__PURE__ */ jsx(NodeSettingsPanel, { config }) })
1695
+ }
1696
+ )
1697
+ ]
1698
+ }
1699
+ ),
1700
+ threePaneLayout && /* @__PURE__ */ jsx(ResizeHandle, { onStart: startDrag("right") }),
1701
+ /* @__PURE__ */ jsxs(
1702
+ "section",
1703
+ {
1704
+ style: threePaneLayout ? { width: `${rightPct}%` } : void 0,
1705
+ className: cn(
1706
+ "flex min-h-0 flex-col bg-card/40",
1707
+ threePaneLayout ? "min-w-[180px]" : pane === "output" ? "w-full flex-1" : "hidden"
1708
+ ),
1709
+ children: [
1710
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-2.5", children: [
1711
+ /* @__PURE__ */ jsx("span", { className: SECTION_LABEL2, children: tr(
1712
+ "fluidgrids.builder.nodeDetailView.outputSection",
1713
+ "Output"
1714
+ ) }),
1715
+ hasRealOutput && /* @__PURE__ */ jsx(
1716
+ "span",
1717
+ {
1718
+ className: "rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-primary",
1719
+ title: tr(
1720
+ "fluidgrids.builder.nodeDetailView.realDataBadgeHint",
1721
+ "Showing real data from the last execution."
1722
+ ),
1723
+ children: tr(
1724
+ "fluidgrids.builder.nodeDetailView.realDataBadge",
1725
+ "Live"
1726
+ )
1727
+ }
1728
+ ),
1729
+ nodeLog?.usedPinnedData && /* @__PURE__ */ jsx("span", { className: "rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-status-warning-text", children: tr(
1730
+ "fluidgrids.builder.nodeDetailView.pinnedDataUsedBadge",
1731
+ "Pinned"
1732
+ ) }),
1733
+ ndv?.nodeId && workflowDbId && /* @__PURE__ */ jsxs(
1734
+ "button",
1735
+ {
1736
+ type: "button",
1737
+ onClick: togglePin,
1738
+ disabled: pinning || unpinning || config.readOnly,
1739
+ "data-testid": "ndv-pin-toggle",
1740
+ title: tr(
1741
+ isPinned ? "fluidgrids.builder.nodeDetailView.unpinHint" : "fluidgrids.builder.nodeDetailView.pinHint",
1742
+ isPinned ? "Unpin \u2014 TEST runs will call this node for real again." : "Pin this output \u2014 TEST runs will replay it instead of calling the node."
1743
+ ),
1744
+ className: cn(
1745
+ "flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide transition-colors disabled:opacity-50",
1746
+ isPinned ? "bg-status-warning-bg-subtle text-status-warning-text" : "text-muted-foreground hover:text-foreground"
1747
+ ),
1748
+ children: [
1749
+ /* @__PURE__ */ jsx(
1750
+ Icon,
1751
+ {
1752
+ icon: isPinned ? "mdi:pin" : "mdi:pin-outline",
1753
+ className: "size-3"
1754
+ }
1755
+ ),
1756
+ tr(
1757
+ isPinned ? "fluidgrids.builder.nodeDetailView.unpinButton" : "fluidgrids.builder.nodeDetailView.pinButton",
1758
+ isPinned ? "Pinned" : "Pin"
1759
+ )
1760
+ ]
1761
+ }
1762
+ ),
1763
+ (hasRealOutput || outputFields.length > 0) && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
1764
+ /* @__PURE__ */ jsx(
1765
+ ToggleChip,
1766
+ {
1767
+ active: outputView === "schema",
1768
+ onClick: () => setOutputView("schema"),
1769
+ children: tr(
1770
+ "fluidgrids.builder.nodeDetailView.outputViewSchema",
1771
+ "Schema"
1772
+ )
1773
+ }
1774
+ ),
1775
+ /* @__PURE__ */ jsx(
1776
+ ToggleChip,
1777
+ {
1778
+ active: outputView === "table",
1779
+ onClick: () => setOutputView("table"),
1780
+ children: tr(
1781
+ "fluidgrids.builder.nodeDetailView.outputViewTable",
1782
+ "Table"
1783
+ )
1784
+ }
1785
+ ),
1786
+ /* @__PURE__ */ jsx(
1787
+ ToggleChip,
1788
+ {
1789
+ active: outputView === "json",
1790
+ onClick: () => setOutputView("json"),
1791
+ children: tr(
1792
+ "fluidgrids.builder.nodeDetailView.outputViewJson",
1793
+ "JSON"
1794
+ )
1795
+ }
1796
+ )
1797
+ ] })
1798
+ ] }),
1799
+ /* @__PURE__ */ jsx(
1800
+ "div",
1801
+ {
1802
+ className: "min-h-0 flex-1 overflow-auto px-2 pb-3",
1803
+ "data-testid": "ndv-output-tree",
1804
+ children: hasRealOutput ? outputView === "schema" ? /* @__PURE__ */ jsx(FieldTree, { fields: realOutputFields, draggable: true }) : outputView === "table" ? /* @__PURE__ */ jsx(TableView, { data: nodeLog.output }) : /* @__PURE__ */ jsx(JsonView, { data: nodeLog.output }) : outputFields.length === 0 ? /* @__PURE__ */ jsx(
1805
+ EmptyState,
1806
+ {
1807
+ icon: "mdi:tray-arrow-up",
1808
+ title: tr(
1809
+ "fluidgrids.builder.nodeDetailView.noOutputTitle",
1810
+ "No output data"
1811
+ ),
1812
+ hint: tr(
1813
+ "fluidgrids.builder.nodeDetailView.noOutputHint",
1814
+ "This operation has no declared output fields. Run this node to see its real output here."
1815
+ )
1816
+ }
1817
+ ) : outputView === "schema" ? /* @__PURE__ */ jsx(FieldTree, { fields: outputFields, draggable: true }) : outputView === "table" ? /* @__PURE__ */ jsx(TableView, { data: null }) : /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap break-words px-2 text-[11px] leading-relaxed text-muted-foreground", children: JSON.stringify(currentOp?.output_fields ?? {}, null, 2) })
1818
+ }
1819
+ )
1820
+ ]
1821
+ }
1822
+ )
1823
+ ] })
1824
+ ]
1825
+ }
1826
+ ) });
1827
+ }
1828
+ function StepExecutionPlanSummary({ plan }) {
1829
+ const tr = useTr();
1830
+ return /* @__PURE__ */ jsxs(
1831
+ "section",
1832
+ {
1833
+ className: "shrink-0 border-b border-border bg-muted/30 px-4 py-2.5",
1834
+ "data-testid": "ndv-step-execution-plan",
1835
+ "aria-live": "polite",
1836
+ children: [
1837
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1", children: [
1838
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-foreground", children: tr(
1839
+ "fluidgrids.builder.nodeDetailView.stepPlanTitle",
1840
+ "TEST execution plan"
1841
+ ) }),
1842
+ /* @__PURE__ */ jsx(
1843
+ PlanMetric,
1844
+ {
1845
+ label: tr(
1846
+ "fluidgrids.builder.nodeDetailView.stepPlanExecute",
1847
+ "Execute"
1848
+ ),
1849
+ count: plan.executeNodeIds.length
1850
+ }
1851
+ ),
1852
+ /* @__PURE__ */ jsx(
1853
+ PlanMetric,
1854
+ {
1855
+ label: tr("fluidgrids.builder.nodeDetailView.stepPlanReuse", "Reuse"),
1856
+ count: plan.reusedNodeIds.length
1857
+ }
1858
+ ),
1859
+ /* @__PURE__ */ jsx(
1860
+ PlanMetric,
1861
+ {
1862
+ label: tr(
1863
+ "fluidgrids.builder.nodeDetailView.stepPlanPinned",
1864
+ "Pinned"
1865
+ ),
1866
+ count: plan.pinnedNodeIds.length
1867
+ }
1868
+ ),
1869
+ /* @__PURE__ */ jsx(
1870
+ PlanMetric,
1871
+ {
1872
+ label: tr(
1873
+ "fluidgrids.builder.nodeDetailView.stepPlanBlockers",
1874
+ "Blockers"
1875
+ ),
1876
+ count: plan.blockers.length
1877
+ }
1878
+ )
1879
+ ] }),
1880
+ plan.sourceRunId && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-[11px] text-muted-foreground", children: [
1881
+ tr(
1882
+ "fluidgrids.builder.nodeDetailView.stepPlanSource",
1883
+ "Using compatible TEST data from run"
1884
+ ),
1885
+ " ",
1886
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: plan.sourceRunId })
1887
+ ] }),
1888
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 grid gap-2 sm:grid-cols-2", children: [
1889
+ /* @__PURE__ */ jsx(
1890
+ PlanNodeList,
1891
+ {
1892
+ label: tr(
1893
+ "fluidgrids.builder.nodeDetailView.stepPlanExecuteNodes",
1894
+ "Nodes to execute"
1895
+ ),
1896
+ nodeIds: plan.executeNodeIds,
1897
+ emptyLabel: tr(
1898
+ "fluidgrids.builder.nodeDetailView.stepPlanExecuteNodesEmpty",
1899
+ "No nodes need execution."
1900
+ )
1901
+ }
1902
+ ),
1903
+ /* @__PURE__ */ jsx(
1904
+ PlanNodeList,
1905
+ {
1906
+ label: tr(
1907
+ "fluidgrids.builder.nodeDetailView.stepPlanReusedNodes",
1908
+ "Reused nodes"
1909
+ ),
1910
+ nodeIds: plan.reusedNodeIds,
1911
+ emptyLabel: tr(
1912
+ "fluidgrids.builder.nodeDetailView.stepPlanReusedNodesEmpty",
1913
+ "No prior node data reused."
1914
+ )
1915
+ }
1916
+ ),
1917
+ /* @__PURE__ */ jsx(
1918
+ PlanNodeList,
1919
+ {
1920
+ label: tr(
1921
+ "fluidgrids.builder.nodeDetailView.stepPlanPinnedNodes",
1922
+ "Pinned nodes"
1923
+ ),
1924
+ nodeIds: plan.pinnedNodeIds,
1925
+ emptyLabel: tr(
1926
+ "fluidgrids.builder.nodeDetailView.stepPlanPinnedNodesEmpty",
1927
+ "No pinned data used."
1928
+ )
1929
+ }
1930
+ ),
1931
+ /* @__PURE__ */ jsx(
1932
+ PlanNodeList,
1933
+ {
1934
+ label: tr(
1935
+ "fluidgrids.builder.nodeDetailView.stepPlanBlockerNodes",
1936
+ "Blockers"
1937
+ ),
1938
+ nodeIds: plan.blockers,
1939
+ emptyLabel: tr(
1940
+ "fluidgrids.builder.nodeDetailView.stepPlanBlockerNodesEmpty",
1941
+ "No blockers."
1942
+ )
1943
+ }
1944
+ )
1945
+ ] })
1946
+ ]
1947
+ }
1948
+ );
1949
+ }
1950
+ function PlanMetric({ label, count }) {
1951
+ return /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
1952
+ label,
1953
+ ": ",
1954
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: count })
1955
+ ] });
1956
+ }
1957
+ function PlanNodeList({
1958
+ label,
1959
+ nodeIds,
1960
+ emptyLabel
1961
+ }) {
1962
+ return /* @__PURE__ */ jsxs("div", { className: "min-w-0 rounded-md border border-border bg-background/60 px-2 py-1.5", children: [
1963
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
1964
+ nodeIds.length > 0 ? /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: nodeIds.map((nodeId) => /* @__PURE__ */ jsx(
1965
+ "code",
1966
+ {
1967
+ className: "max-w-full truncate rounded bg-muted px-1 py-0.5 font-mono text-[10px] text-foreground",
1968
+ children: nodeId
1969
+ },
1970
+ `${label}-${nodeId}`
1971
+ )) }) : /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: emptyLabel })
1972
+ ] });
1973
+ }
1974
+ function ResizeHandle({ onStart }) {
1975
+ return /* @__PURE__ */ jsx(
1976
+ "div",
1977
+ {
1978
+ onMouseDown: onStart,
1979
+ role: "separator",
1980
+ "aria-orientation": "vertical",
1981
+ className: "group flex w-1.5 shrink-0 cursor-col-resize items-center justify-center bg-border/60 hover:bg-primary/40",
1982
+ children: /* @__PURE__ */ jsx("div", { className: "h-8 w-0.5 rounded bg-muted-foreground/40 group-hover:bg-primary" })
1983
+ }
1984
+ );
1985
+ }
1986
+ function TabButton({
1987
+ active,
1988
+ onClick,
1989
+ children
1990
+ }) {
1991
+ return /* @__PURE__ */ jsx(
1992
+ "button",
1993
+ {
1994
+ type: "button",
1995
+ onClick,
1996
+ className: cn(
1997
+ "-mb-px border-b-2 py-2.5 text-sm transition-colors",
1998
+ active ? "border-primary font-medium text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
1999
+ ),
2000
+ children
2001
+ }
2002
+ );
2003
+ }
2004
+ function ToggleChip({
2005
+ active,
2006
+ onClick,
2007
+ children
2008
+ }) {
2009
+ return /* @__PURE__ */ jsx(
2010
+ "button",
2011
+ {
2012
+ type: "button",
2013
+ onClick,
2014
+ className: cn(
2015
+ "rounded px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide transition-colors",
2016
+ active ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"
2017
+ ),
2018
+ children
2019
+ }
2020
+ );
2021
+ }
2022
+ function EmptyState({
2023
+ icon,
2024
+ title,
2025
+ hint
2026
+ }) {
2027
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 px-6 text-center", children: [
2028
+ /* @__PURE__ */ jsx(Icon, { icon, className: "size-7 text-muted-foreground/50" }),
2029
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-foreground/80", children: title }),
2030
+ /* @__PURE__ */ jsx("div", { className: "max-w-[14rem] text-xs text-muted-foreground", children: hint })
2031
+ ] });
2032
+ }
2033
+
2034
+ export { NodeDetailView };