@burdenoff/fluidgrids-fe-sdk 2026.1109.9 → 2026.1110.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 (56) hide show
  1. package/dist/{ExecutionDetailPage-2ORSV3YN.js → ExecutionDetailPage-UXQFBLF3.js} +14 -14
  2. package/dist/{ExecutionsListPage-MY45NECW.js → ExecutionsListPage-A4GCUNFR.js} +11 -11
  3. package/dist/{WorkflowBuilder-MWC2BCTQ.js → WorkflowBuilder-ZLVMV2UA.js} +21 -15
  4. package/dist/{WorkflowsList-6AG7CFZM.js → WorkflowsList-XPNGNR4A.js} +8 -8
  5. package/dist/{agentTools-5O2KPQYJ.js → agentTools-V26FOMTS.js} +5 -7
  6. package/dist/{chunk-ACME5GOU.js → chunk-2DBAQR6F.js} +2 -2
  7. package/dist/{chunk-MYE6CMGK.js → chunk-2H4XAES7.js} +3 -3
  8. package/dist/{chunk-IZSWG5G3.js → chunk-3F4DIVKT.js} +1 -1
  9. package/dist/chunk-3JGAI5YC.js +1437 -0
  10. package/dist/{chunk-MSNCU6US.js → chunk-5TGPH3CX.js} +4 -4
  11. package/dist/{chunk-U52PO4QQ.js → chunk-5V7PMLQN.js} +1320 -58
  12. package/dist/{chunk-TKSVFP36.js → chunk-5ZAOXCIQ.js} +6 -6
  13. package/dist/{chunk-POCNG3PM.js → chunk-6CPQVLHT.js} +15 -16
  14. package/dist/{chunk-2LERIIZM.js → chunk-7UFE2CHD.js} +308 -1026
  15. package/dist/{chunk-VAWYR6P4.js → chunk-A6Q4FWB6.js} +9 -1
  16. package/dist/{chunk-G4NAU6FF.js → chunk-CBX2CXGH.js} +8 -8
  17. package/dist/{chunk-5UA262ZO.js → chunk-CKF2FBUP.js} +1 -1
  18. package/dist/{chunk-5YLS3WH5.js → chunk-D4L5BN64.js} +1 -1
  19. package/dist/{chunk-VYXTP6YF.js → chunk-DDINUPO3.js} +2185 -5390
  20. package/dist/{chunk-DFFQYK2X.js → chunk-DIIGFBYT.js} +1 -1
  21. package/dist/{chunk-3T3C3475.js → chunk-G7BT24PD.js} +550 -3
  22. package/dist/chunk-HORSE7WX.js +54 -0
  23. package/dist/{chunk-7ADEPA4W.js → chunk-I3DBTNWQ.js} +6 -6
  24. package/dist/{chunk-HZ7FQCFU.js → chunk-JNSHR2J2.js} +1 -1
  25. package/dist/{chunk-ZXLXZFWO.js → chunk-K22NDWSD.js} +2 -2
  26. package/dist/{chunk-JXCJRBWD.js → chunk-K2X63CR2.js} +1 -5
  27. package/dist/{chunk-Q526TUOC.js → chunk-L5MZ74DA.js} +1 -1
  28. package/dist/{chunk-WKF72AS2.js → chunk-OVZA4IM7.js} +4 -4
  29. package/dist/{chunk-GR3QNLFS.js → chunk-OZNFRJKC.js} +5 -5
  30. package/dist/{chunk-NEB4Y7AX.js → chunk-PI4AW4T2.js} +241 -34
  31. package/dist/{chunk-TNMXKPJX.js → chunk-PICOXEUO.js} +4 -4
  32. package/dist/{chunk-QWDF72GZ.js → chunk-QGRVYPQC.js} +2 -2
  33. package/dist/{chunk-THIMC2ZD.js → chunk-WGUHISC2.js} +29 -11
  34. package/dist/{chunk-B3USL2JQ.js → chunk-X7XVRJK2.js} +2 -2
  35. package/dist/{chunk-4EZFAL24.js → chunk-XLU63IUB.js} +6 -6
  36. package/dist/{chunk-CX3LABFK.js → chunk-Z6D6YL7C.js} +1 -1
  37. package/dist/{chunk-NCXK2AO6.js → chunk-ZQTANHKA.js} +2 -2
  38. package/dist/chunk-ZZBZPBO5.js +1644 -0
  39. package/dist/executions.js +15 -15
  40. package/dist/history.js +7 -9
  41. package/dist/index.d.ts +367 -159
  42. package/dist/index.js +592 -68
  43. package/dist/{node-detail-view-ZFGS3FWU.js → node-detail-view-2AFXRNX6.js} +19 -864
  44. package/dist/nodes.js +2 -2
  45. package/dist/observability.js +7 -7
  46. package/dist/{quick-config-panel-ZDGYZR7Y.js → quick-config-panel-U4AELSCT.js} +12 -54
  47. package/dist/settings.js +9 -9
  48. package/dist/templates.js +21 -14
  49. package/dist/workflow-builder.js +22 -16
  50. package/dist/workflow-preview.js +2 -2
  51. package/dist/workflows-list.js +8 -8
  52. package/package.json +1 -1
  53. package/dist/chunk-GTEBQQ7J.js +0 -24
  54. package/dist/chunk-O2OFNGAF.js +0 -338
  55. package/dist/chunk-QTOPG7SR.js +0 -501
  56. package/dist/chunk-U7UNWR3D.js +0 -260
@@ -1,25 +1,62 @@
1
+ import { asRecord, ConditionTreeBuilder, SwitchRulesEditor, casesFromRules, isStringOptionList, optionText, coerceNumber, paramsFromOperation, resolveLoadOptionsSpec, optionListValues, parseMappingValue, optionListText } from './chunk-PI4AW4T2.js';
1
2
  import { CodeEditorField, normalizeCodeLanguage } from './chunk-KZCNJAMK.js';
2
- import { Popover, PopoverTrigger, PopoverContent } from './chunk-GTEBQQ7J.js';
3
- import { asRecord, ConditionTreeBuilder, SwitchRulesEditor, casesFromRules, isStringOptionList, optionText, coerceNumber, paramsFromOperation, resolveLoadOptionsSpec, optionListValues, parseMappingValue, optionListText } from './chunk-NEB4Y7AX.js';
4
- import { AgentDefinitionSelectField } from './chunk-QTOPG7SR.js';
5
- import { isAgentNodeType } from './chunk-WZ3EJVS6.js';
6
3
  import { readAssertRulesSetting, rowsFromRules, normaliseAssertRules, ruleFromRow, describeAssertRuleProblem, operatorSpec, ASSERT_OPERATORS, emptyAssertRuleRow } from './chunk-JDLQ6QIB.js';
7
- import { isParamVisible, ConnectionSelector, structuredParameterType, useRunGuard, FluidGridsContext, ExpressionEditor, formatParameterValue, parseParameterValue, useFluidGridsAnalytics, openRunQuotaUpgradePrompt } from './chunk-VYXTP6YF.js';
4
+ import { AgentDefinitionSelectField, ConnectionSelector } from './chunk-ZZBZPBO5.js';
5
+ import { isAgentNodeType } from './chunk-WZ3EJVS6.js';
6
+ import { FluidGridsContext, isParamVisible, structuredParameterType, useRunGuard, formatParameterValue, parseParameterValue, useFluidGridsAnalytics, openRunQuotaUpgradePrompt } from './chunk-DDINUPO3.js';
8
7
  import { useBuilderReadOnly } from './chunk-SDZEAIIE.js';
9
- import { useFlowStore } from './chunk-JXCJRBWD.js';
10
- import { useRunFromNodeEligibilityQuery, PreviewScheduleDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument } from './chunk-VAWYR6P4.js';
8
+ import { useFlowStore } from './chunk-K2X63CR2.js';
9
+ import { getGlobalStoreClient } from './chunk-TYI2XOSI.js';
10
+ import { cn } from './chunk-JL7CASN5.js';
11
+ import { useRunFromNodeEligibilityQuery, PreviewScheduleDocument, WebhookTestListenerDocument, StopWebhookTestListenerDocument, ListenForWebhookTestEventDocument } from './chunk-A6Q4FWB6.js';
11
12
  import { useTr } from './chunk-UULVIT25.js';
12
13
  import { getFluidGridsClientSafe, getFluidGridsClient } from './chunk-PDODDDHY.js';
13
14
  import { throwIfResultError, describeOperationError } from './chunk-JKLZ6YZ3.js';
14
- import { useId, useState, useMemo, useRef, useEffect, useContext, useCallback } from 'react';
15
+ import { cn as cn$1 } from '@burdenoff/fe-libs/utils';
16
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
+ import * as React from 'react';
18
+ import { useContext, useId, useState, useMemo, useRef, useEffect, useCallback } from 'react';
19
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
15
20
  import { Icon } from '@iconify/react';
16
- import { cn } from '@burdenoff/fe-libs/utils';
17
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
18
21
  import { gql } from '@apollo/client';
22
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Card, Button } from '@burdenoff/fe-libs/ui';
19
23
  import cronstrue from 'cronstrue';
20
24
  import { digest } from 'ohash';
21
25
  import { X, Plus } from 'lucide-react';
22
26
 
27
+ function ReadOnlyFieldset({
28
+ className,
29
+ children
30
+ }) {
31
+ const readOnly = useBuilderReadOnly();
32
+ return /* @__PURE__ */ jsx(
33
+ "fieldset",
34
+ {
35
+ disabled: readOnly,
36
+ "data-readonly": readOnly ? "true" : void 0,
37
+ className: cn$1("m-0 min-w-0 border-0 p-0", className),
38
+ children
39
+ }
40
+ );
41
+ }
42
+ var Popover = PopoverPrimitive.Root;
43
+ var PopoverTrigger = PopoverPrimitive.Trigger;
44
+ var PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
45
+ PopoverPrimitive.Content,
46
+ {
47
+ ref,
48
+ "data-slot": "popover-content",
49
+ align,
50
+ sideOffset,
51
+ className: cn(
52
+ "z-50 w-72 rounded-card border border-border-subtle bg-bg-elevated p-4 text-text-primary shadow-elevation-3 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
53
+ className
54
+ ),
55
+ ...props
56
+ }
57
+ ) }));
58
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
59
+
23
60
  // src/builder/components/ndv/layering.ts
24
61
  var NDV_Z_INDEX = 1e3;
25
62
  var NDV_OVERLAY_Z_INDEX = NDV_Z_INDEX + 1;
@@ -85,7 +122,7 @@ function WaitResumeUrlField() {
85
122
  {
86
123
  type: "button",
87
124
  onClick: handleCopy,
88
- className: cn(
125
+ className: cn$1(
89
126
  "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-3 py-2 text-xs font-medium transition-colors",
90
127
  copied ? "border-primary bg-primary/10 text-primary" : "bg-background text-foreground hover:bg-muted"
91
128
  ),
@@ -326,7 +363,7 @@ function CopyableUrl({
326
363
  {
327
364
  type: "button",
328
365
  onClick: handleCopy,
329
- className: cn(
366
+ className: cn$1(
330
367
  "inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border px-3 py-2 text-xs font-medium transition-colors",
331
368
  copied ? "border-primary bg-primary/10 text-primary" : "bg-background text-foreground hover:bg-muted"
332
369
  ),
@@ -668,7 +705,7 @@ function WorkflowPickerField({
668
705
  onClick: () => setOpen((o) => !o),
669
706
  className: "flex items-center justify-between gap-2 rounded-md border border-input bg-background px-2.5 py-1.5 text-left text-sm outline-none transition-colors hover:border-primary",
670
707
  children: [
671
- /* @__PURE__ */ jsx("span", { className: cn("truncate", !value && "text-muted-foreground"), children: selected ? `${selected.name} (${selected.workflowKey.slice(0, 8)}\u2026)` : value || tr(
708
+ /* @__PURE__ */ jsx("span", { className: cn$1("truncate", !value && "text-muted-foreground"), children: selected ? `${selected.name} (${selected.workflowKey.slice(0, 8)}\u2026)` : value || tr(
672
709
  "fluidgrids.builder.workflowPicker.placeholder",
673
710
  "Select a workflow to call"
674
711
  ) }),
@@ -713,7 +750,7 @@ function WorkflowPickerField({
713
750
  setOpen(false);
714
751
  setSearch("");
715
752
  },
716
- className: cn(
753
+ className: cn$1(
717
754
  "flex w-full flex-col items-start gap-0.5 rounded px-2 py-1.5 text-left hover:bg-muted",
718
755
  workflow.workflowKey === value && "bg-muted"
719
756
  ),
@@ -1006,7 +1043,7 @@ function NodeLoadOptionsSelect({
1006
1043
  disabled: loading,
1007
1044
  "aria-label": label ?? fieldName,
1008
1045
  "data-testid": testId,
1009
- className: cn(className, loading && "opacity-60"),
1046
+ className: cn$1(className, loading && "opacity-60"),
1010
1047
  children: [
1011
1048
  !multiple && /* @__PURE__ */ jsx("option", { value: "", children: loading ? tr(
1012
1049
  "fluidgrids.builder.nodeParamsForm.loadOptionsLoading",
@@ -1144,6 +1181,499 @@ function StructuredParameterInput({
1144
1181
  )
1145
1182
  ] });
1146
1183
  }
1184
+
1185
+ // src/builder/components/lib/expression-preview.ts
1186
+ var PURE_EXPRESSION_REGEX = /^\{\{(.*)\}\}$/s;
1187
+ var PURE_LEGACY_REGEX = /^\$\{(.*)\}$/s;
1188
+ function containsExpressionSyntax(str) {
1189
+ return typeof str === "string" && str.includes("{{");
1190
+ }
1191
+ function findEmbeddedExpressions(str) {
1192
+ const matches = [];
1193
+ let searchFrom = 0;
1194
+ while (searchFrom < str.length) {
1195
+ const start = str.indexOf("{{", searchFrom);
1196
+ if (start === -1) break;
1197
+ const end = str.indexOf("}}", start + 2);
1198
+ if (end === -1) break;
1199
+ matches.push({
1200
+ full: str.slice(start, end + 2),
1201
+ expression: str.slice(start + 2, end).trim()
1202
+ });
1203
+ searchFrom = end + 2;
1204
+ }
1205
+ return matches;
1206
+ }
1207
+ function toItems(value) {
1208
+ if (value === void 0 || value === null) return [];
1209
+ if (Array.isArray(value)) return value.map((entry) => ({ json: entry }));
1210
+ return [{ json: value }];
1211
+ }
1212
+ var SAFE_GLOBALS = {
1213
+ parseInt,
1214
+ parseFloat,
1215
+ isNaN,
1216
+ isFinite,
1217
+ Number,
1218
+ String,
1219
+ Boolean,
1220
+ JSON,
1221
+ Date,
1222
+ Math,
1223
+ Array,
1224
+ Object,
1225
+ console: {
1226
+ log: () => void 0,
1227
+ error: () => void 0,
1228
+ warn: () => void 0
1229
+ }
1230
+ };
1231
+ function buildExpressionSandbox(scope) {
1232
+ const inputItems = toItems(scope.input);
1233
+ const currentItem = inputItems[0] ?? { json: {} };
1234
+ const itemsForNode = (nodeName) => toItems(scope.nodeOutputs[nodeName]);
1235
+ const dollarNode = (nodeName) => {
1236
+ const items = itemsForNode(nodeName);
1237
+ const at = items[0] ?? { json: {} };
1238
+ return {
1239
+ item: at.json,
1240
+ all: () => items,
1241
+ first: () => items[0]?.json ?? {},
1242
+ last: () => items[items.length - 1]?.json ?? {}
1243
+ };
1244
+ };
1245
+ const nodeObject = {};
1246
+ for (const nodeName of Object.keys(scope.nodeOutputs)) {
1247
+ nodeObject[nodeName] = dollarNode(nodeName);
1248
+ }
1249
+ const now = /* @__PURE__ */ new Date();
1250
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1251
+ return {
1252
+ ...SAFE_GLOBALS,
1253
+ $json: currentItem.json,
1254
+ $node: nodeObject,
1255
+ $: dollarNode,
1256
+ $items: (nodeName) => nodeName ? itemsForNode(nodeName) : inputItems,
1257
+ $input: {
1258
+ item: currentItem.json,
1259
+ all: () => inputItems,
1260
+ first: () => inputItems[0]?.json ?? {},
1261
+ last: () => inputItems[inputItems.length - 1]?.json ?? {}
1262
+ },
1263
+ $workflow: {
1264
+ id: scope.workflowKey,
1265
+ name: scope.workflowKey,
1266
+ version: scope.version
1267
+ },
1268
+ $execution: { id: scope.runId },
1269
+ // Stubbed pending BOFF-7517 (Variables model) — mirrors the backend stub.
1270
+ $vars: {},
1271
+ // Never real env vars in the browser preview.
1272
+ $env: {},
1273
+ $now: now,
1274
+ $today: today
1275
+ };
1276
+ }
1277
+ function evaluateBare(expression, sandbox) {
1278
+ const paramNames = Object.keys(sandbox);
1279
+ const paramValues = Object.values(sandbox);
1280
+ const fn = new Function(
1281
+ ...paramNames,
1282
+ `"use strict"; return (${expression});`
1283
+ );
1284
+ return fn(...paramValues);
1285
+ }
1286
+ function stringifyForInterpolation(value) {
1287
+ if (value === null || value === void 0) return "";
1288
+ if (typeof value === "object") return JSON.stringify(value);
1289
+ return String(value);
1290
+ }
1291
+ function previewNewSyntax(str, scope) {
1292
+ const sandbox = buildExpressionSandbox(scope);
1293
+ const pure = PURE_EXPRESSION_REGEX.exec(str);
1294
+ try {
1295
+ if (pure) {
1296
+ const expression = pure[1]?.trim();
1297
+ if (!expression) {
1298
+ return { isTemplate: true, error: "Empty expression" };
1299
+ }
1300
+ return { isTemplate: true, value: evaluateBare(expression, sandbox) };
1301
+ }
1302
+ let result = str;
1303
+ for (const match of findEmbeddedExpressions(str)) {
1304
+ if (!match.expression) continue;
1305
+ const value = evaluateBare(match.expression, sandbox);
1306
+ result = result.replace(match.full, stringifyForInterpolation(value));
1307
+ }
1308
+ return { isTemplate: true, value: result };
1309
+ } catch (error) {
1310
+ return {
1311
+ isTemplate: true,
1312
+ error: error instanceof Error ? error.message : String(error)
1313
+ };
1314
+ }
1315
+ }
1316
+ function previewLegacySyntax(str, scope) {
1317
+ const sandbox = {
1318
+ ...SAFE_GLOBALS,
1319
+ output: scope.input,
1320
+ TriggerInput: {},
1321
+ WorkflowState: Object.fromEntries(
1322
+ Object.entries(scope.nodeOutputs).map(([nodeId, output]) => [
1323
+ nodeId,
1324
+ { output, status: "COMPLETED" }
1325
+ ])
1326
+ ),
1327
+ RunContext: {
1328
+ runId: scope.runId ?? "preview",
1329
+ workflowKey: scope.workflowKey ?? "preview",
1330
+ version: scope.version ?? "1.0.0"
1331
+ }
1332
+ };
1333
+ const pure = PURE_LEGACY_REGEX.exec(str);
1334
+ try {
1335
+ if (pure) {
1336
+ const expression = pure[1]?.trim();
1337
+ if (!expression) return { isTemplate: true, error: "Empty expression" };
1338
+ return { isTemplate: true, value: evaluateBare(expression, sandbox) };
1339
+ }
1340
+ if (!str.includes("${")) {
1341
+ return { isTemplate: false };
1342
+ }
1343
+ let result = str;
1344
+ const templateRegex = /\$\{([^}]+)\}/g;
1345
+ for (const match of str.matchAll(templateRegex)) {
1346
+ const expression = match[1]?.trim();
1347
+ if (!expression) continue;
1348
+ const value = evaluateBare(expression, sandbox);
1349
+ result = result.replace(match[0], stringifyForInterpolation(value));
1350
+ }
1351
+ return { isTemplate: true, value: result };
1352
+ } catch (error) {
1353
+ return {
1354
+ isTemplate: true,
1355
+ error: error instanceof Error ? error.message : String(error)
1356
+ };
1357
+ }
1358
+ }
1359
+ function evaluateExpressionPreview(str, scope) {
1360
+ if (typeof str !== "string" || str.trim() === "") {
1361
+ return { isTemplate: false };
1362
+ }
1363
+ if (containsExpressionSyntax(str)) {
1364
+ return previewNewSyntax(str, scope);
1365
+ }
1366
+ return previewLegacySyntax(str, scope);
1367
+ }
1368
+ var EXPRESSION_BUILTINS = [
1369
+ { insert: "$json", label: "$json", description: "Current item's data" },
1370
+ {
1371
+ insert: '$node["NodeName"]',
1372
+ label: '$node["NodeName"]',
1373
+ description: "Another node's current item"
1374
+ },
1375
+ {
1376
+ insert: '$("NodeName")',
1377
+ label: '$("NodeName")',
1378
+ description: "Lineage helper: .item / .all() / .first() / .last()"
1379
+ },
1380
+ {
1381
+ insert: "$items()",
1382
+ label: "$items()",
1383
+ description: "This node's input as a list of items"
1384
+ },
1385
+ {
1386
+ insert: "$input",
1387
+ label: "$input",
1388
+ description: "Input helper: .item / .all() / .first() / .last()"
1389
+ },
1390
+ {
1391
+ insert: "$workflow",
1392
+ label: "$workflow",
1393
+ description: "Workflow id / name / version"
1394
+ },
1395
+ {
1396
+ insert: "$execution",
1397
+ label: "$execution",
1398
+ description: "Current execution id"
1399
+ },
1400
+ {
1401
+ insert: "$vars",
1402
+ label: "$vars",
1403
+ description: "Workflow variables (coming soon)"
1404
+ },
1405
+ {
1406
+ insert: "$env",
1407
+ label: "$env",
1408
+ description: "Allowlisted environment values"
1409
+ },
1410
+ { insert: "$now", label: "$now", description: "Current date/time" },
1411
+ { insert: "$today", label: "$today", description: "Start of today" }
1412
+ ];
1413
+ var AUTOCOMPLETE_LOOKBACK = 40;
1414
+ function openExpressionPrefix(value, cursor) {
1415
+ const windowStart = Math.max(0, cursor - AUTOCOMPLETE_LOOKBACK);
1416
+ const before = value.slice(windowStart, cursor);
1417
+ const openIndex = before.lastIndexOf("{{");
1418
+ if (openIndex === -1) return null;
1419
+ const between = before.slice(openIndex + 2);
1420
+ if (between.includes("}}")) return null;
1421
+ return between.trimStart();
1422
+ }
1423
+ function ExpressionEditor({
1424
+ value,
1425
+ onChange,
1426
+ placeholder,
1427
+ className,
1428
+ rows = 4,
1429
+ label,
1430
+ ariaLabel,
1431
+ helpText,
1432
+ nodeId
1433
+ }) {
1434
+ const tr = useTr();
1435
+ const editorRef = useRef(null);
1436
+ const [isFocused, setIsFocused] = useState(false);
1437
+ const [suggestionsOpen, setSuggestionsOpen] = useState(false);
1438
+ const handleChange = useCallback(
1439
+ (e) => {
1440
+ onChange(e.target.value);
1441
+ const cursor = e.target.selectionStart;
1442
+ setSuggestionsOpen(openExpressionPrefix(e.target.value, cursor) !== null);
1443
+ },
1444
+ [onChange]
1445
+ );
1446
+ const insertTemplate = useCallback(
1447
+ (template) => {
1448
+ const textarea = editorRef.current;
1449
+ if (!textarea) return;
1450
+ const start = textarea.selectionStart;
1451
+ const end = textarea.selectionEnd;
1452
+ const newValue = value.substring(0, start) + template + value.substring(end);
1453
+ onChange(newValue);
1454
+ setTimeout(() => {
1455
+ textarea.focus();
1456
+ textarea.setSelectionRange(
1457
+ start + template.length,
1458
+ start + template.length
1459
+ );
1460
+ }, 0);
1461
+ },
1462
+ [value, onChange]
1463
+ );
1464
+ const applySuggestion = useCallback(
1465
+ (insert) => {
1466
+ const textarea = editorRef.current;
1467
+ if (!textarea) {
1468
+ insertTemplate(`{{ ${insert} }}`);
1469
+ return;
1470
+ }
1471
+ const cursor = textarea.selectionStart;
1472
+ const windowStart = Math.max(0, cursor - AUTOCOMPLETE_LOOKBACK);
1473
+ const before = value.slice(windowStart, cursor);
1474
+ const openIndex = before.lastIndexOf("{{");
1475
+ if (openIndex === -1) {
1476
+ insertTemplate(`{{ ${insert} }}`);
1477
+ return;
1478
+ }
1479
+ const replaceStart = windowStart + openIndex;
1480
+ const newValue = value.slice(0, replaceStart) + `{{ ${insert} }}` + value.slice(cursor);
1481
+ onChange(newValue);
1482
+ setSuggestionsOpen(false);
1483
+ const cursorAfter = replaceStart + `{{ ${insert} }}`.length;
1484
+ setTimeout(() => {
1485
+ textarea.focus();
1486
+ textarea.setSelectionRange(cursorAfter, cursorAfter);
1487
+ }, 0);
1488
+ },
1489
+ [value, onChange, insertTemplate]
1490
+ );
1491
+ const cursorPos = editorRef.current?.selectionStart ?? value.length;
1492
+ const filterText = suggestionsOpen ? openExpressionPrefix(value, cursorPos) ?? "" : "";
1493
+ const suggestions = useMemo(() => {
1494
+ if (!suggestionsOpen) return [];
1495
+ const needle = filterText.toLowerCase();
1496
+ return EXPRESSION_BUILTINS.filter(
1497
+ (b) => b.label.toLowerCase().startsWith(needle || "$")
1498
+ ).slice(0, 8);
1499
+ }, [suggestionsOpen, filterText]);
1500
+ const logs = useFlowStore((s) => s.execution.logs);
1501
+ const workflowKey = useFlowStore((s) => s.workflow.workflowKey);
1502
+ const workflowVersion = useFlowStore((s) => s.workflow.version);
1503
+ const previewScope = useMemo(() => {
1504
+ const nodeOutputs = {};
1505
+ let input = {};
1506
+ let runId;
1507
+ for (const log of logs) {
1508
+ if (log.status !== "completed" || !log.output) continue;
1509
+ nodeOutputs[log.nodeId] = log.output;
1510
+ if (log.nodeId === nodeId) {
1511
+ input = log.inputArgs ?? {};
1512
+ runId = log.runId;
1513
+ }
1514
+ }
1515
+ return { input, nodeOutputs, workflowKey, version: workflowVersion, runId };
1516
+ }, [logs, nodeId, workflowKey, workflowVersion]);
1517
+ const hasPreviewData = Object.keys(previewScope.input).length > 0;
1518
+ const [preview, setPreview] = useState(null);
1519
+ useEffect(() => {
1520
+ if (!nodeId || !value.trim()) {
1521
+ setPreview(null);
1522
+ return;
1523
+ }
1524
+ const timer = setTimeout(() => {
1525
+ setPreview(evaluateExpressionPreview(value, previewScope));
1526
+ }, 200);
1527
+ return () => clearTimeout(timer);
1528
+ }, [value, previewScope, nodeId]);
1529
+ const previewText = useMemo(() => {
1530
+ if (!preview) return null;
1531
+ if (!preview.isTemplate) return null;
1532
+ if (preview.error) return null;
1533
+ if (preview.value === void 0) return "undefined";
1534
+ if (typeof preview.value === "string") return preview.value;
1535
+ try {
1536
+ return JSON.stringify(preview.value);
1537
+ } catch {
1538
+ return String(preview.value);
1539
+ }
1540
+ }, [preview]);
1541
+ return /* @__PURE__ */ jsxs("div", { className: cn$1("space-y-2", className), children: [
1542
+ label && /* @__PURE__ */ jsx("label", { className: "text-sm font-medium text-foreground", children: label }),
1543
+ /* @__PURE__ */ jsxs(
1544
+ Card,
1545
+ {
1546
+ className: cn$1(
1547
+ "relative overflow-hidden transition-all",
1548
+ isFocused && "ring-2 ring-primary ring-offset-1"
1549
+ ),
1550
+ children: [
1551
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 px-2 py-1.5 border-b bg-muted/30", children: [
1552
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground mr-2", children: tr("fluidgrids.builder.expressionEditor.insertLabel", "Insert:") }),
1553
+ /* @__PURE__ */ jsx(
1554
+ "button",
1555
+ {
1556
+ type: "button",
1557
+ onClick: () => insertTemplate("${TriggerInput.}"),
1558
+ className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
1559
+ children: "${TriggerInput}"
1560
+ }
1561
+ ),
1562
+ /* @__PURE__ */ jsx(
1563
+ "button",
1564
+ {
1565
+ type: "button",
1566
+ onClick: () => insertTemplate("${output.}"),
1567
+ className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
1568
+ children: "${output}"
1569
+ }
1570
+ ),
1571
+ /* @__PURE__ */ jsx(
1572
+ "button",
1573
+ {
1574
+ type: "button",
1575
+ onClick: () => insertTemplate("${WorkflowState[''].output}"),
1576
+ className: "text-xs px-2 py-0.5 rounded bg-primary/10 text-primary hover:bg-primary/20 transition-colors",
1577
+ children: "${WorkflowState}"
1578
+ }
1579
+ ),
1580
+ /* @__PURE__ */ jsx("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": "true" }),
1581
+ /* @__PURE__ */ jsx(
1582
+ "button",
1583
+ {
1584
+ type: "button",
1585
+ onClick: () => insertTemplate("{{ $json. }}"),
1586
+ className: "text-xs px-2 py-0.5 rounded bg-secondary/40 text-secondary-foreground hover:bg-secondary/60 transition-colors",
1587
+ children: "{{ $json }}"
1588
+ }
1589
+ )
1590
+ ] }),
1591
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1592
+ /* @__PURE__ */ jsx(
1593
+ "textarea",
1594
+ {
1595
+ ref: editorRef,
1596
+ "aria-label": ariaLabel,
1597
+ value,
1598
+ onChange: handleChange,
1599
+ onKeyUp: (e) => {
1600
+ const target = e.currentTarget;
1601
+ setSuggestionsOpen(
1602
+ openExpressionPrefix(target.value, target.selectionStart) !== null
1603
+ );
1604
+ },
1605
+ onFocus: () => setIsFocused(true),
1606
+ onBlur: () => {
1607
+ setIsFocused(false);
1608
+ setTimeout(() => setSuggestionsOpen(false), 150);
1609
+ },
1610
+ placeholder: placeholder ?? tr(
1611
+ "fluidgrids.builder.expressionEditor.defaultPlaceholder",
1612
+ "Enter expression..."
1613
+ ),
1614
+ rows,
1615
+ spellCheck: false,
1616
+ className: cn$1(
1617
+ "w-full p-3 font-mono text-sm bg-background resize-none",
1618
+ "focus:outline-none",
1619
+ "placeholder:text-muted-foreground/50"
1620
+ )
1621
+ }
1622
+ ),
1623
+ suggestionsOpen && suggestions.length > 0 && /* @__PURE__ */ jsx(
1624
+ "div",
1625
+ {
1626
+ className: "absolute z-20 left-2 right-2 top-full mt-1 rounded-md border bg-popover shadow-md overflow-hidden",
1627
+ role: "listbox",
1628
+ "aria-label": tr(
1629
+ "fluidgrids.builder.expressionEditor.autocompleteLabel",
1630
+ "Expression variable suggestions"
1631
+ ),
1632
+ children: suggestions.map((s) => /* @__PURE__ */ jsxs(
1633
+ "button",
1634
+ {
1635
+ type: "button",
1636
+ role: "option",
1637
+ "aria-selected": false,
1638
+ onMouseDown: (e) => {
1639
+ e.preventDefault();
1640
+ applySuggestion(s.insert);
1641
+ },
1642
+ className: "flex w-full items-baseline gap-2 px-3 py-1.5 text-left hover:bg-accent",
1643
+ children: [
1644
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-primary", children: s.label }),
1645
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground truncate", children: s.description })
1646
+ ]
1647
+ },
1648
+ s.insert
1649
+ ))
1650
+ }
1651
+ )
1652
+ ] }),
1653
+ nodeId && value.trim() && /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/20 px-3 py-1.5 text-xs", children: preview?.error ? /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
1654
+ tr(
1655
+ "fluidgrids.builder.expressionEditor.previewError",
1656
+ "Preview error:"
1657
+ ),
1658
+ " ",
1659
+ preview.error
1660
+ ] }) : previewText !== null ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1661
+ tr(
1662
+ "fluidgrids.builder.expressionEditor.previewLabel",
1663
+ "Preview:"
1664
+ ),
1665
+ " ",
1666
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground", children: previewText })
1667
+ ] }) : !hasPreviewData ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 italic", children: tr(
1668
+ "fluidgrids.builder.expressionEditor.previewNoData",
1669
+ 'Run "Execute step" on this node to preview real values.'
1670
+ ) }) : null })
1671
+ ]
1672
+ }
1673
+ ),
1674
+ helpText && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: helpText })
1675
+ ] });
1676
+ }
1147
1677
  function JsonMappingInput({
1148
1678
  name,
1149
1679
  label,
@@ -1610,7 +2140,7 @@ function CronExpressionField({
1610
2140
  "aria-invalid": invalid,
1611
2141
  spellCheck: false,
1612
2142
  "data-testid": testId,
1613
- className: cn(className, "font-mono", invalid && "border-destructive")
2143
+ className: cn$1(className, "font-mono", invalid && "border-destructive")
1614
2144
  }
1615
2145
  ),
1616
2146
  description && /* @__PURE__ */ jsx(
@@ -1863,7 +2393,7 @@ function ScheduleRulesEditor({
1863
2393
  return /* @__PURE__ */ jsxs(
1864
2394
  "label",
1865
2395
  {
1866
- className: cn(
2396
+ className: cn$1(
1867
2397
  "flex flex-col gap-1",
1868
2398
  (isCron || spec.type === "multiOptions") && "sm:col-span-2"
1869
2399
  ),
@@ -1877,7 +2407,7 @@ function ScheduleRulesEditor({
1877
2407
  {
1878
2408
  value: stringValue(row[spec.name]),
1879
2409
  onChange: (value) => patchRow(index, spec.name, value),
1880
- className: cn(rowInput, "w-full"),
2410
+ className: cn$1(rowInput, "w-full"),
1881
2411
  testId,
1882
2412
  ariaLabel: label
1883
2413
  }
@@ -1937,7 +2467,7 @@ function ScheduleRulesEditor({
1937
2467
  ),
1938
2468
  "aria-label": label,
1939
2469
  "data-testid": testId,
1940
- className: cn(rowInput, selectCx),
2470
+ className: cn$1(rowInput, selectCx),
1941
2471
  children: spec.options.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: spec.optionLabels?.[option] ?? option }, option))
1942
2472
  }
1943
2473
  ) : spec.type === "integer" || spec.type === "number" ? /* @__PURE__ */ jsx(
@@ -2107,7 +2637,7 @@ function AssertRulesEditor({
2107
2637
  Array.isArray(value) ? readAssertRulesSetting(event.target.value) ?? value : event.target.value
2108
2638
  );
2109
2639
  },
2110
- className: cn(rowInput2, "w-full font-mono"),
2640
+ className: cn$1(rowInput2, "w-full font-mono"),
2111
2641
  "data-testid": `${testIdPrefix}-assert-rules-json`
2112
2642
  }
2113
2643
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -2142,7 +2672,7 @@ function AssertRulesEditor({
2142
2672
  "Path"
2143
2673
  ),
2144
2674
  onChange: (event) => setRow(index, { path: event.target.value }),
2145
- className: cn(rowInput2, "min-w-0 flex-1 font-mono")
2675
+ className: cn$1(rowInput2, "min-w-0 flex-1 font-mono")
2146
2676
  }
2147
2677
  ),
2148
2678
  /* @__PURE__ */ jsx(
@@ -2192,7 +2722,7 @@ function AssertRulesEditor({
2192
2722
  "Expected"
2193
2723
  ),
2194
2724
  onChange: (event) => setRow(index, { expected: event.target.value }),
2195
- className: cn(rowInput2, "mt-1.5 w-full font-mono")
2725
+ className: cn$1(rowInput2, "mt-1.5 w-full font-mono")
2196
2726
  }
2197
2727
  ),
2198
2728
  operand === "range" && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex gap-1.5", children: [
@@ -2211,7 +2741,7 @@ function AssertRulesEditor({
2211
2741
  "min"
2212
2742
  ),
2213
2743
  onChange: (event) => setRow(index, { min: event.target.value }),
2214
- className: cn(rowInput2, "w-full")
2744
+ className: cn$1(rowInput2, "w-full")
2215
2745
  }
2216
2746
  ),
2217
2747
  /* @__PURE__ */ jsx(
@@ -2229,7 +2759,7 @@ function AssertRulesEditor({
2229
2759
  "max"
2230
2760
  ),
2231
2761
  onChange: (event) => setRow(index, { max: event.target.value }),
2232
- className: cn(rowInput2, "w-full")
2762
+ className: cn$1(rowInput2, "w-full")
2233
2763
  }
2234
2764
  )
2235
2765
  ] }),
@@ -2247,7 +2777,7 @@ function AssertRulesEditor({
2247
2777
  "Message"
2248
2778
  ),
2249
2779
  onChange: (event) => setRow(index, { message: event.target.value }),
2250
- className: cn(rowInput2, "mt-1.5 w-full")
2780
+ className: cn$1(rowInput2, "mt-1.5 w-full")
2251
2781
  }
2252
2782
  )
2253
2783
  ]
@@ -2559,7 +3089,7 @@ function TimezoneSelect({
2559
3089
  "Timezone"
2560
3090
  ),
2561
3091
  "data-testid": testId,
2562
- className: cn(
3092
+ className: cn$1(
2563
3093
  className,
2564
3094
  "flex items-center justify-between gap-2 text-left"
2565
3095
  ),
@@ -2640,7 +3170,7 @@ function TimezoneSelect({
2640
3170
  onClick: () => select(zone),
2641
3171
  "data-testid": testId ? `${testId}-option` : void 0,
2642
3172
  "data-value": zone,
2643
- className: cn(
3173
+ className: cn$1(
2644
3174
  "flex w-full cursor-pointer items-center justify-between gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted",
2645
3175
  zone === current && "bg-muted/60"
2646
3176
  ),
@@ -2813,12 +3343,12 @@ function PlatformEventSourceSelect({
2813
3343
  "aria-haspopup": "listbox",
2814
3344
  "aria-label": fieldLabel,
2815
3345
  "data-testid": testId,
2816
- className: cn(
3346
+ className: cn$1(
2817
3347
  className,
2818
3348
  "flex items-center justify-between gap-2 text-left"
2819
3349
  ),
2820
3350
  children: [
2821
- /* @__PURE__ */ jsx("span", { className: cn("truncate", !value && "text-muted-foreground"), children: value || emptyText }),
3351
+ /* @__PURE__ */ jsx("span", { className: cn$1("truncate", !value && "text-muted-foreground"), children: value || emptyText }),
2822
3352
  /* @__PURE__ */ jsx(
2823
3353
  Icon,
2824
3354
  {
@@ -2884,7 +3414,7 @@ function PlatformEventSourceSelect({
2884
3414
  type: "button",
2885
3415
  onClick: () => select(""),
2886
3416
  "data-testid": testId ? `${testId}-any` : void 0,
2887
- className: cn(
3417
+ className: cn$1(
2888
3418
  "flex w-full cursor-pointer items-center justify-between gap-2 px-3 py-1.5 text-left text-sm italic text-muted-foreground hover:bg-muted",
2889
3419
  value === "" && "bg-muted/60"
2890
3420
  ),
@@ -2911,7 +3441,7 @@ function PlatformEventSourceSelect({
2911
3441
  onClick: () => select(name),
2912
3442
  "data-testid": testId ? `${testId}-option` : void 0,
2913
3443
  "data-value": name,
2914
- className: cn(
3444
+ className: cn$1(
2915
3445
  "flex w-full cursor-pointer items-center justify-between gap-2 px-3 py-1.5 text-left text-sm hover:bg-muted",
2916
3446
  name === value && "bg-muted/60"
2917
3447
  ),
@@ -3125,7 +3655,7 @@ function PickRow({
3125
3655
  disabled,
3126
3656
  "data-testid": testId,
3127
3657
  "data-value": value,
3128
- className: cn(ROW_CX, picked && "bg-muted/60", className),
3658
+ className: cn$1(ROW_CX, picked && "bg-muted/60", className),
3129
3659
  children: [
3130
3660
  /* @__PURE__ */ jsx(
3131
3661
  Icon,
@@ -3203,7 +3733,7 @@ function TimeCampusTriggerSelect({
3203
3733
  "aria-controls": listId,
3204
3734
  "aria-label": fieldLabel,
3205
3735
  "data-testid": testId,
3206
- className: cn(
3736
+ className: cn$1(
3207
3737
  className,
3208
3738
  "flex items-center justify-between gap-2 text-left"
3209
3739
  ),
@@ -3211,7 +3741,7 @@ function TimeCampusTriggerSelect({
3211
3741
  /* @__PURE__ */ jsx(
3212
3742
  "span",
3213
3743
  {
3214
- className: cn(
3744
+ className: cn$1(
3215
3745
  "truncate",
3216
3746
  selected.length === 0 && "text-muted-foreground"
3217
3747
  ),
@@ -3342,6 +3872,735 @@ function TimeCampusTriggerSelect({
3342
3872
  notice && /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-snug text-muted-foreground", children: notice })
3343
3873
  ] });
3344
3874
  }
3875
+ function useGlobalStoreClient() {
3876
+ const context = useContext(FluidGridsContext);
3877
+ if (context?.globalClient) {
3878
+ return context.globalClient;
3879
+ }
3880
+ const singletonClient = getGlobalStoreClient();
3881
+ if (singletonClient) {
3882
+ return singletonClient;
3883
+ }
3884
+ throw new Error(
3885
+ "useGlobalStoreClient: No global client available. Either wrap your component tree with <FluidGridsProvider globalClient={...}> or call setGlobalStoreClient(client) before rendering."
3886
+ );
3887
+ }
3888
+
3889
+ // src/modules/channels/hooks/useChannels.ts
3890
+ var CHANNEL_SUMMARY_FIELDS = gql`
3891
+ fragment ChannelSummaryFields on Channel {
3892
+ id
3893
+ name
3894
+ platform
3895
+ channelType
3896
+ status
3897
+ connectionHealth
3898
+ isConnected
3899
+ description
3900
+ adapterType
3901
+ createdAt
3902
+ }
3903
+ `;
3904
+ var CHANNEL_DETAIL_FIELDS = gql`
3905
+ fragment ChannelDetailFields on Channel {
3906
+ id
3907
+ name
3908
+ platform
3909
+ channelType
3910
+ status
3911
+ connectionHealth
3912
+ isConnected
3913
+ description
3914
+ adapterType
3915
+ messageCount24h
3916
+ config {
3917
+ enableThreading
3918
+ enableReactions
3919
+ enablePresence
3920
+ rateLimitPerMinute
3921
+ }
3922
+ createdAt
3923
+ updatedAt
3924
+ }
3925
+ `;
3926
+ var GET_CHANNELS = gql`
3927
+ ${CHANNEL_SUMMARY_FIELDS}
3928
+ query GetChannels(
3929
+ $filter: ChannelFilterInput
3930
+ $pagination: ChannelPaginationInput
3931
+ ) {
3932
+ getChannels(filter: $filter, pagination: $pagination) {
3933
+ items {
3934
+ ...ChannelSummaryFields
3935
+ }
3936
+ total
3937
+ hasMore
3938
+ }
3939
+ }
3940
+ `;
3941
+ var GET_CHANNEL = gql`
3942
+ ${CHANNEL_DETAIL_FIELDS}
3943
+ query GetChannel($id: ID!) {
3944
+ getChannel(id: $id) {
3945
+ ...ChannelDetailFields
3946
+ }
3947
+ }
3948
+ `;
3949
+ var PLATFORM_TO_PROVIDER_KEY = {
3950
+ SLACK: "slack",
3951
+ TEAMS: "microsoft-teams",
3952
+ DISCORD: "discord",
3953
+ EMAIL: "email",
3954
+ WHATSAPP: "whatsapp",
3955
+ WEBCHAT: "webchat",
3956
+ WEBHOOK: "webhook",
3957
+ SMS: "sms",
3958
+ PUSH: "push"
3959
+ };
3960
+ var PLATFORM_ICONS = {
3961
+ SLACK: "mdi:slack",
3962
+ TEAMS: "mdi:microsoft-teams",
3963
+ DISCORD: "mdi:discord",
3964
+ EMAIL: "mdi:email",
3965
+ SMS: "mdi:message-text",
3966
+ PUSH: "mdi:bell-ring",
3967
+ WEBCHAT: "mdi:chat",
3968
+ WEBHOOK: "mdi:webhook",
3969
+ WHATSAPP: "mdi:whatsapp"
3970
+ };
3971
+ var STATUS_COLORS = {
3972
+ ACTIVE: "text-[var(--color-status-success-text)]",
3973
+ INACTIVE: "text-muted-foreground",
3974
+ ERROR: "text-[var(--color-status-error-text)]",
3975
+ ARCHIVED: "text-muted-foreground",
3976
+ SUSPENDED: "text-[var(--color-status-warning-text)]"
3977
+ };
3978
+ var HEALTH_COLORS = {
3979
+ HEALTHY: "text-[var(--color-status-success-text)]",
3980
+ DEGRADED: "text-[var(--color-status-warning-text)]",
3981
+ UNHEALTHY: "text-[var(--color-status-error-text)]",
3982
+ UNKNOWN: "text-muted-foreground"
3983
+ };
3984
+ var HEALTH_ICONS = {
3985
+ HEALTHY: "mdi:check-circle",
3986
+ DEGRADED: "mdi:alert-circle",
3987
+ UNHEALTHY: "mdi:close-circle",
3988
+ UNKNOWN: "mdi:help-circle"
3989
+ };
3990
+ function useChannels(filter) {
3991
+ const apolloClient = useGlobalStoreClient();
3992
+ const [channels, setChannels] = useState([]);
3993
+ const [total, setTotal] = useState(0);
3994
+ const [hasMore, setHasMore] = useState(false);
3995
+ const [loading, setLoading] = useState(true);
3996
+ const [error, setError] = useState(null);
3997
+ const filterKey = JSON.stringify(filter);
3998
+ const fetchData = useCallback(async () => {
3999
+ setLoading(true);
4000
+ setError(null);
4001
+ try {
4002
+ const parsedFilter = filterKey ? JSON.parse(filterKey) : void 0;
4003
+ const PAGE_SIZE = 100;
4004
+ let offset = 0;
4005
+ let allItems = [];
4006
+ let totalCount = 0;
4007
+ let more = true;
4008
+ while (more) {
4009
+ const result = await apolloClient.query({
4010
+ query: GET_CHANNELS,
4011
+ variables: {
4012
+ filter: parsedFilter,
4013
+ pagination: { limit: PAGE_SIZE, offset }
4014
+ },
4015
+ fetchPolicy: "network-only"
4016
+ });
4017
+ const page = result.data?.getChannels;
4018
+ const pageItems = page?.items ?? [];
4019
+ totalCount = page?.total ?? totalCount;
4020
+ allItems = allItems.concat(pageItems);
4021
+ more = (page?.hasMore ?? false) && pageItems.length === PAGE_SIZE;
4022
+ offset += PAGE_SIZE;
4023
+ }
4024
+ setChannels(allItems);
4025
+ setTotal(totalCount);
4026
+ setHasMore(false);
4027
+ } catch (err) {
4028
+ setError(err);
4029
+ } finally {
4030
+ setLoading(false);
4031
+ }
4032
+ }, [apolloClient, filterKey]);
4033
+ useEffect(() => {
4034
+ fetchData();
4035
+ }, [fetchData]);
4036
+ return {
4037
+ channels,
4038
+ total,
4039
+ hasMore,
4040
+ loading,
4041
+ error,
4042
+ refetch: fetchData
4043
+ };
4044
+ }
4045
+ function useChannel(channelId) {
4046
+ const apolloClient = useGlobalStoreClient();
4047
+ const [channel, setChannel] = useState(null);
4048
+ const [loading, setLoading] = useState(true);
4049
+ const [error, setError] = useState(null);
4050
+ const fetchData = useCallback(
4051
+ async (policy = "cache-first") => {
4052
+ if (!channelId) return void 0;
4053
+ try {
4054
+ return await apolloClient.query({
4055
+ query: GET_CHANNEL,
4056
+ variables: { id: channelId },
4057
+ fetchPolicy: policy
4058
+ });
4059
+ } catch (err) {
4060
+ return err;
4061
+ }
4062
+ },
4063
+ [apolloClient, channelId]
4064
+ );
4065
+ const applyResult = useCallback(
4066
+ (result) => {
4067
+ if (result instanceof Error) {
4068
+ setError(result);
4069
+ } else if (result) {
4070
+ setChannel(result.data?.getChannel ?? null);
4071
+ } else {
4072
+ setChannel(null);
4073
+ }
4074
+ setLoading(false);
4075
+ },
4076
+ []
4077
+ );
4078
+ useEffect(() => {
4079
+ let cancelled = false;
4080
+ setLoading(true);
4081
+ setError(null);
4082
+ fetchData().then((result) => {
4083
+ if (cancelled) return;
4084
+ applyResult(result);
4085
+ });
4086
+ return () => {
4087
+ cancelled = true;
4088
+ };
4089
+ }, [fetchData, applyResult]);
4090
+ const refetch = useCallback(async () => {
4091
+ setLoading(true);
4092
+ setError(null);
4093
+ const result = await fetchData("network-only");
4094
+ applyResult(result);
4095
+ }, [fetchData, applyResult]);
4096
+ return {
4097
+ channel,
4098
+ loading,
4099
+ error,
4100
+ refetch
4101
+ };
4102
+ }
4103
+
4104
+ // src/builder/components/ndv/channel-fields.ts
4105
+ var CHANNEL_ID_FIELD = "channelId";
4106
+ var CHANNEL_PLATFORM_FIELD = "platform";
4107
+ var CHANNEL_INTEGRATION_FIELD = "integrationId";
4108
+ var CHANNEL_NODE_TYPE = /(?:^|[:/])(?:integration\/channel|channel_node)(?:@[\w.-]+)?$/;
4109
+ function isChannelNodeType(nodeType) {
4110
+ return !!nodeType && CHANNEL_NODE_TYPE.test(nodeType);
4111
+ }
4112
+ function isChannelPickerField(nodeType, fieldName) {
4113
+ return fieldName === CHANNEL_ID_FIELD && isChannelNodeType(nodeType);
4114
+ }
4115
+ function channelSelectionWrites(channel, declaredFieldNames) {
4116
+ const writes = [[CHANNEL_ID_FIELD, channel.id]];
4117
+ if (channel.platform && declaredFieldNames.includes(CHANNEL_PLATFORM_FIELD)) {
4118
+ writes.push([CHANNEL_PLATFORM_FIELD, channel.platform]);
4119
+ }
4120
+ return writes;
4121
+ }
4122
+ function channelSelectionHandler(declaredFieldNames, write) {
4123
+ return (channel) => {
4124
+ for (const [field, value] of channelSelectionWrites(
4125
+ channel,
4126
+ declaredFieldNames
4127
+ )) {
4128
+ write(field, value);
4129
+ }
4130
+ };
4131
+ }
4132
+ function channelLinkState(argsState, declaredFieldNames) {
4133
+ const integration = argsState[CHANNEL_INTEGRATION_FIELD];
4134
+ return {
4135
+ integrationId: typeof integration === "string" ? integration : void 0,
4136
+ linkable: declaredFieldNames.includes(CHANNEL_INTEGRATION_FIELD)
4137
+ };
4138
+ }
4139
+ function channelAuthUrl(providerKey, returnTo, nodeId) {
4140
+ const params = new URLSearchParams({ providerKey, returnTo });
4141
+ if (nodeId) params.set("channelNodeId", nodeId);
4142
+ return `/integrations/connections/add?${params.toString()}`;
4143
+ }
4144
+ var LINK_INTEGRATION_MUTATION = gql`
4145
+ mutation LinkIntegrationConnection(
4146
+ $integrationConnectionId: ID!
4147
+ $channelId: ID!
4148
+ ) {
4149
+ linkIntegrationConnection(
4150
+ integrationConnectionId: $integrationConnectionId
4151
+ channelId: $channelId
4152
+ ) {
4153
+ id
4154
+ }
4155
+ }
4156
+ `;
4157
+ function platformLabel(platform) {
4158
+ return platform.charAt(0) + platform.slice(1).toLowerCase();
4159
+ }
4160
+ function ChannelPickerField({
4161
+ value,
4162
+ onSelect,
4163
+ integrationId,
4164
+ linkable,
4165
+ nodeId,
4166
+ label,
4167
+ className,
4168
+ testId
4169
+ }) {
4170
+ const tr = useTr();
4171
+ const [open, setOpen] = useState(false);
4172
+ const { channels, loading, error } = useChannels({
4173
+ channelType: "COMMUNICATION"
4174
+ });
4175
+ const listed = channels.find((c) => c.id === value);
4176
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-testid": testId, children: [
4177
+ /* @__PURE__ */ jsxs(DropdownMenu, { open, onOpenChange: setOpen, children: [
4178
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
4179
+ "button",
4180
+ {
4181
+ type: "button",
4182
+ "aria-label": label,
4183
+ className: cn$1(
4184
+ className,
4185
+ "flex items-center justify-between gap-2 text-left"
4186
+ ),
4187
+ "data-testid": testId ? `${testId}-trigger` : void 0,
4188
+ children: [
4189
+ listed ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [
4190
+ /* @__PURE__ */ jsx(
4191
+ Icon,
4192
+ {
4193
+ icon: PLATFORM_ICONS[listed.platform] || "mdi:antenna",
4194
+ className: "size-4 shrink-0"
4195
+ }
4196
+ ),
4197
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: listed.name }),
4198
+ /* @__PURE__ */ jsx(
4199
+ Icon,
4200
+ {
4201
+ icon: HEALTH_ICONS[listed.connectionHealth],
4202
+ className: cn$1(
4203
+ "size-3.5 shrink-0",
4204
+ HEALTH_COLORS[listed.connectionHealth]
4205
+ )
4206
+ }
4207
+ )
4208
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-2 text-muted-foreground", children: [
4209
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:antenna", className: "size-4 shrink-0" }),
4210
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: value || tr(
4211
+ "fluidgrids.builder.channelSelector.selectPlaceholder",
4212
+ "Select channel"
4213
+ ) })
4214
+ ] }),
4215
+ /* @__PURE__ */ jsx(
4216
+ Icon,
4217
+ {
4218
+ icon: "mdi:chevron-down",
4219
+ className: "size-4 shrink-0 text-muted-foreground"
4220
+ }
4221
+ )
4222
+ ]
4223
+ }
4224
+ ) }),
4225
+ /* @__PURE__ */ jsx(
4226
+ DropdownMenuContent,
4227
+ {
4228
+ align: "start",
4229
+ className: "max-h-[350px] w-[260px] overflow-y-auto",
4230
+ children: loading ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center text-sm text-muted-foreground", children: [
4231
+ /* @__PURE__ */ jsx(
4232
+ Icon,
4233
+ {
4234
+ icon: "mdi:loading",
4235
+ className: "mx-auto mb-2 size-5 animate-spin"
4236
+ }
4237
+ ),
4238
+ /* @__PURE__ */ jsx("p", { children: tr(
4239
+ "fluidgrids.builder.channelSelector.loading",
4240
+ "Loading channels..."
4241
+ ) })
4242
+ ] }) : error ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center text-sm text-muted-foreground", children: [
4243
+ /* @__PURE__ */ jsx(
4244
+ Icon,
4245
+ {
4246
+ icon: "mdi:alert-circle",
4247
+ className: "mx-auto mb-2 size-5 text-[var(--color-status-error-text)]"
4248
+ }
4249
+ ),
4250
+ /* @__PURE__ */ jsx("p", { children: tr(
4251
+ "fluidgrids.builder.channelSelector.loadError",
4252
+ "Failed to load channels"
4253
+ ) }),
4254
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs", children: error.message })
4255
+ ] }) : channels.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center text-sm text-muted-foreground", children: [
4256
+ /* @__PURE__ */ jsx(
4257
+ Icon,
4258
+ {
4259
+ icon: "mdi:antenna-off",
4260
+ className: "mx-auto mb-2 size-6 opacity-50"
4261
+ }
4262
+ ),
4263
+ /* @__PURE__ */ jsx("p", { children: tr(
4264
+ "fluidgrids.builder.channelSelector.emptyTitle",
4265
+ "No channels found"
4266
+ ) }),
4267
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs", children: tr(
4268
+ "fluidgrids.builder.channelSelector.emptyHint",
4269
+ "Create one in the Channels settings"
4270
+ ) })
4271
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4272
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: tr(
4273
+ "fluidgrids.builder.channelSelector.availableChannelsHeading",
4274
+ "Available Channels"
4275
+ ) }),
4276
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
4277
+ channels.map((channel) => {
4278
+ const isSelected = channel.id === value;
4279
+ return /* @__PURE__ */ jsxs(
4280
+ DropdownMenuItem,
4281
+ {
4282
+ onClick: () => {
4283
+ onSelect(channel);
4284
+ setOpen(false);
4285
+ },
4286
+ className: cn$1(
4287
+ "flex cursor-pointer items-center justify-between",
4288
+ isSelected && "bg-primary/10"
4289
+ ),
4290
+ "data-testid": testId ? `${testId}-option-${channel.id}` : void 0,
4291
+ children: [
4292
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
4293
+ /* @__PURE__ */ jsx(
4294
+ Icon,
4295
+ {
4296
+ icon: PLATFORM_ICONS[channel.platform] || "mdi:antenna",
4297
+ className: "size-4 shrink-0"
4298
+ }
4299
+ ),
4300
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
4301
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: channel.name }),
4302
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
4303
+ /* @__PURE__ */ jsx("span", { className: STATUS_COLORS[channel.status], children: channel.status.toLowerCase() }),
4304
+ /* @__PURE__ */ jsx("span", { children: "\xB7" }),
4305
+ /* @__PURE__ */ jsx("span", { children: channel.platform.toLowerCase() })
4306
+ ] })
4307
+ ] })
4308
+ ] }),
4309
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
4310
+ /* @__PURE__ */ jsx(
4311
+ Icon,
4312
+ {
4313
+ icon: HEALTH_ICONS[channel.connectionHealth],
4314
+ className: cn$1(
4315
+ "size-3.5",
4316
+ HEALTH_COLORS[channel.connectionHealth]
4317
+ )
4318
+ }
4319
+ ),
4320
+ isSelected && /* @__PURE__ */ jsx(
4321
+ Icon,
4322
+ {
4323
+ icon: "mdi:check",
4324
+ className: "size-4 text-primary"
4325
+ }
4326
+ )
4327
+ ] })
4328
+ ]
4329
+ },
4330
+ channel.id
4331
+ );
4332
+ })
4333
+ ] })
4334
+ }
4335
+ )
4336
+ ] }),
4337
+ value && /* @__PURE__ */ jsx(
4338
+ ChannelSummary,
4339
+ {
4340
+ channelId: value,
4341
+ integrationId,
4342
+ linkable,
4343
+ nodeId,
4344
+ testId
4345
+ },
4346
+ value
4347
+ )
4348
+ ] });
4349
+ }
4350
+ function ChannelSummary({
4351
+ channelId,
4352
+ integrationId,
4353
+ linkable,
4354
+ nodeId,
4355
+ testId
4356
+ }) {
4357
+ const tr = useTr();
4358
+ const { channel, loading, error } = useChannel(channelId);
4359
+ if (loading) {
4360
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border border-border p-3 text-xs text-muted-foreground", children: [
4361
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:loading", className: "size-4 animate-spin" }),
4362
+ tr(
4363
+ "fluidgrids.builder.channelDetails.loading",
4364
+ "Loading channel details\u2026"
4365
+ )
4366
+ ] });
4367
+ }
4368
+ if (error || !channel) {
4369
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bg-subtle)] p-3 text-xs text-[var(--color-status-error-text)]", children: [
4370
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:alert-circle", className: "size-4" }),
4371
+ error ? error.message : tr(
4372
+ "fluidgrids.builder.channelDetails.notFound",
4373
+ "Channel not found"
4374
+ )
4375
+ ] });
4376
+ }
4377
+ return /* @__PURE__ */ jsxs(
4378
+ "div",
4379
+ {
4380
+ className: "flex flex-col gap-2.5 rounded-md border border-border p-3",
4381
+ "data-testid": testId ? `${testId}-details` : void 0,
4382
+ children: [
4383
+ /* @__PURE__ */ jsx(ChannelStatus, { channel }),
4384
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 border-t border-border pt-2", children: [
4385
+ /* @__PURE__ */ jsx(ChannelAuthButton, { channel, nodeId, testId }),
4386
+ linkable && /* @__PURE__ */ jsx(
4387
+ ChannelLinkAction,
4388
+ {
4389
+ channelId: channel.id,
4390
+ integrationId,
4391
+ testId
4392
+ }
4393
+ )
4394
+ ] })
4395
+ ]
4396
+ }
4397
+ );
4398
+ }
4399
+ function ChannelStatus({ channel }) {
4400
+ const tr = useTr();
4401
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4402
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4403
+ /* @__PURE__ */ jsx("div", { className: "flex size-8 items-center justify-center rounded-md bg-primary/10", children: /* @__PURE__ */ jsx(
4404
+ Icon,
4405
+ {
4406
+ icon: PLATFORM_ICONS[channel.platform] || "mdi:antenna",
4407
+ className: "size-5 text-primary"
4408
+ }
4409
+ ) }),
4410
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
4411
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: channel.name }),
4412
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
4413
+ channel.platform.toLowerCase(),
4414
+ " \xB7",
4415
+ " ",
4416
+ channel.channelType.toLowerCase()
4417
+ ] })
4418
+ ] })
4419
+ ] }),
4420
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 text-xs", children: [
4421
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
4422
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.channelDetails.statusLabel", "Status") }),
4423
+ /* @__PURE__ */ jsx("span", { className: cn$1("font-medium", STATUS_COLORS[channel.status]), children: channel.status.toLowerCase() })
4424
+ ] }),
4425
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
4426
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: tr("fluidgrids.builder.channelDetails.healthLabel", "Health") }),
4427
+ /* @__PURE__ */ jsxs(
4428
+ "span",
4429
+ {
4430
+ className: cn$1(
4431
+ "flex items-center gap-1 font-medium",
4432
+ HEALTH_COLORS[channel.connectionHealth]
4433
+ ),
4434
+ children: [
4435
+ /* @__PURE__ */ jsx(
4436
+ Icon,
4437
+ {
4438
+ icon: HEALTH_ICONS[channel.connectionHealth],
4439
+ className: "size-3.5"
4440
+ }
4441
+ ),
4442
+ channel.connectionHealth.toLowerCase()
4443
+ ]
4444
+ }
4445
+ )
4446
+ ] })
4447
+ ] }),
4448
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-t border-border pt-2 text-xs text-muted-foreground", children: [
4449
+ channel.isConnected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-[var(--color-status-success-text)]", children: [
4450
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:link", className: "size-3.5" }),
4451
+ tr("fluidgrids.builder.channelDetails.connected", "Connected")
4452
+ ] }) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4453
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:link-off", className: "size-3.5" }),
4454
+ tr(
4455
+ "fluidgrids.builder.channelDetails.notConnected",
4456
+ "Not connected"
4457
+ )
4458
+ ] }),
4459
+ channel.messageCount24h !== void 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4460
+ /* @__PURE__ */ jsx(Icon, { icon: "mdi:message-text", className: "size-3.5" }),
4461
+ tr(
4462
+ "fluidgrids.builder.channelDetails.messageCount",
4463
+ "{{count}} messages in 24h",
4464
+ { count: channel.messageCount24h }
4465
+ )
4466
+ ] })
4467
+ ] }),
4468
+ channel.description && /* @__PURE__ */ jsx("p", { className: "border-t border-border pt-2 text-xs text-muted-foreground", children: channel.description })
4469
+ ] });
4470
+ }
4471
+ function ChannelAuthButton({
4472
+ channel,
4473
+ nodeId,
4474
+ testId
4475
+ }) {
4476
+ const tr = useTr();
4477
+ const providerKey = PLATFORM_TO_PROVIDER_KEY[channel.platform] || channel.platform.toLowerCase();
4478
+ return /* @__PURE__ */ jsxs(
4479
+ Button,
4480
+ {
4481
+ type: "button",
4482
+ variant: "outline",
4483
+ size: "sm",
4484
+ className: "w-full justify-start gap-2",
4485
+ onClick: () => window.open(
4486
+ channelAuthUrl(providerKey, window.location.pathname, nodeId),
4487
+ "_blank",
4488
+ "noopener,noreferrer"
4489
+ ),
4490
+ "data-testid": testId ? `${testId}-authenticate` : void 0,
4491
+ children: [
4492
+ /* @__PURE__ */ jsx(
4493
+ Icon,
4494
+ {
4495
+ icon: PLATFORM_ICONS[channel.platform] || "mdi:key",
4496
+ className: "size-4"
4497
+ }
4498
+ ),
4499
+ /* @__PURE__ */ jsxs("span", { children: [
4500
+ tr(
4501
+ "fluidgrids.builder.channelAuth.authenticateWithPrefix",
4502
+ "Authenticate with"
4503
+ ),
4504
+ " ",
4505
+ platformLabel(channel.platform)
4506
+ ] }),
4507
+ /* @__PURE__ */ jsx(
4508
+ Icon,
4509
+ {
4510
+ icon: "mdi:open-in-new",
4511
+ className: "ml-auto size-3.5 text-muted-foreground"
4512
+ }
4513
+ )
4514
+ ]
4515
+ }
4516
+ );
4517
+ }
4518
+ function useChannelLink(channelId) {
4519
+ const tr = useTr();
4520
+ const globalClient = useGlobalStoreClient();
4521
+ const [state, setState] = useState({ kind: "idle" });
4522
+ const link = async (integrationConnectionId) => {
4523
+ setState({ kind: "linking" });
4524
+ try {
4525
+ throwIfResultError(
4526
+ await globalClient.mutate({
4527
+ mutation: LINK_INTEGRATION_MUTATION,
4528
+ variables: { integrationConnectionId, channelId },
4529
+ refetchQueries: [
4530
+ { query: GET_CHANNEL, variables: { id: channelId } }
4531
+ ],
4532
+ awaitRefetchQueries: true
4533
+ })
4534
+ );
4535
+ setState({ kind: "linked" });
4536
+ } catch (err) {
4537
+ setState({
4538
+ kind: "failed",
4539
+ message: err instanceof Error ? err.message : tr(
4540
+ "fluidgrids.builder.channelLink.failed",
4541
+ "The channel could not be linked."
4542
+ )
4543
+ });
4544
+ }
4545
+ };
4546
+ return { state, link };
4547
+ }
4548
+ var LINK_ICON = {
4549
+ idle: "mdi:link-variant",
4550
+ failed: "mdi:link-variant",
4551
+ linking: "mdi:loading",
4552
+ linked: "mdi:check-circle"
4553
+ };
4554
+ function ChannelLinkAction({
4555
+ channelId,
4556
+ integrationId,
4557
+ testId
4558
+ }) {
4559
+ const tr = useTr();
4560
+ const { state, link } = useChannelLink(channelId);
4561
+ const integration = integrationId?.trim() ?? "";
4562
+ const linking = state.kind === "linking";
4563
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4564
+ /* @__PURE__ */ jsxs(
4565
+ Button,
4566
+ {
4567
+ type: "button",
4568
+ variant: "outline",
4569
+ size: "sm",
4570
+ className: "w-full justify-start gap-2",
4571
+ onClick: () => void link(integration),
4572
+ disabled: !integration || linking,
4573
+ title: integration ? void 0 : tr(
4574
+ "fluidgrids.builder.channelLink.needsIntegration",
4575
+ "Enter an integration connection ID first."
4576
+ ),
4577
+ "data-testid": testId ? `${testId}-link` : void 0,
4578
+ children: [
4579
+ /* @__PURE__ */ jsx(
4580
+ Icon,
4581
+ {
4582
+ icon: LINK_ICON[state.kind],
4583
+ className: cn$1("size-4", linking && "animate-spin")
4584
+ }
4585
+ ),
4586
+ /* @__PURE__ */ jsx("span", { children: linking ? tr("fluidgrids.builder.channelLink.linking", "Linking\u2026") : tr(
4587
+ "fluidgrids.builder.channelLink.linkButton",
4588
+ "Link integration to channel"
4589
+ ) })
4590
+ ]
4591
+ }
4592
+ ),
4593
+ state.kind === "linked" && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-[var(--color-status-success-text)]", children: tr(
4594
+ "fluidgrids.builder.channelLink.linked",
4595
+ "Linked. A welcome message was sent."
4596
+ ) }),
4597
+ state.kind === "failed" && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-destructive", children: state.message }),
4598
+ !integration && /* @__PURE__ */ jsx("p", { className: "text-[11px] leading-snug text-muted-foreground", children: tr(
4599
+ "fluidgrids.builder.channelLink.integrationHint",
4600
+ "Complete the OAuth flow, then enter the integration connection ID below to link it."
4601
+ ) })
4602
+ ] });
4603
+ }
3345
4604
  var SECTION_LABEL3 = "text-[11px] font-semibold uppercase tracking-[0.12em] text-muted-foreground";
3346
4605
  function NodeParamsForm({
3347
4606
  config,
@@ -3621,10 +4880,11 @@ function NodeParamsForm({
3621
4880
  const optionList = Boolean(p.loadOptions) && isStringOptionList(p.schema ?? p.type);
3622
4881
  const booleanChecked = p.type === "boolean" && booleanParameterValue(fixedValue) === true;
3623
4882
  const isCodeParam = p.type === "code" || nodeType === "internal:code" && p.name === "code";
4883
+ const isChannelPicker = isChannelPickerField(nodeType, p.name);
3624
4884
  const highlight = dragField === p.name;
3625
4885
  const controlId = `${formId}-param-${p.name}`;
3626
- const nativeControl = mode !== "expression" && !isCodeParam && !(structuredType && !optionList) && !p.loadOptions && p.type !== PLATFORM_EVENT_MODULE_FIELD && p.type !== PLATFORM_EVENT_RECORD_TYPE_FIELD && p.type !== TIMECAMPUS_TIME_TRIGGER_FIELD;
3627
- const inputCx = cn(
4886
+ const nativeControl = mode !== "expression" && !isCodeParam && !(structuredType && !optionList) && !p.loadOptions && !isChannelPicker && p.type !== PLATFORM_EVENT_MODULE_FIELD && p.type !== PLATFORM_EVENT_RECORD_TYPE_FIELD && p.type !== TIMECAMPUS_TIME_TRIGGER_FIELD;
4887
+ const inputCx = cn$1(
3628
4888
  "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none transition-colors disabled:opacity-50",
3629
4889
  highlight ? "border-primary ring-1 ring-primary" : "border-input focus:border-primary"
3630
4890
  );
@@ -3662,7 +4922,7 @@ function NodeParamsForm({
3662
4922
  "Switch to an expression"
3663
4923
  ),
3664
4924
  "data-testid": `${testIdPrefix}-mode-${p.name}`,
3665
- className: cn(
4925
+ className: cn$1(
3666
4926
  "ml-1 rounded p-0.5 hover:text-foreground",
3667
4927
  mode === "expression" ? "text-primary" : "text-muted-foreground"
3668
4928
  ),
@@ -3680,7 +4940,7 @@ function NodeParamsForm({
3680
4940
  "div",
3681
4941
  {
3682
4942
  "data-testid": `${testIdPrefix}-param-${p.name}`,
3683
- className: cn(
4943
+ className: cn$1(
3684
4944
  !currentEdge && "pointer-events-none opacity-50"
3685
4945
  ),
3686
4946
  children: /* @__PURE__ */ jsx(
@@ -3722,7 +4982,7 @@ function NodeParamsForm({
3722
4982
  schema: p.schema ?? p.type,
3723
4983
  value: fixedValue,
3724
4984
  onChange: (value) => writeFixed(p.name, value),
3725
- className: cn(inputCx, "font-mono"),
4985
+ className: cn$1(inputCx, "font-mono"),
3726
4986
  testId: `${testIdPrefix}-param-${p.name}`
3727
4987
  },
3728
4988
  `${config.node?.id}:${config.currentOp?.name}:${p.name}`
@@ -3756,6 +5016,23 @@ function NodeParamsForm({
3756
5016
  className: inputCx,
3757
5017
  testId: `${testIdPrefix}-param-${p.name}`
3758
5018
  }
5019
+ ) : isChannelPicker ? /* @__PURE__ */ jsx(
5020
+ ChannelPickerField,
5021
+ {
5022
+ value: typeof fixedValue === "string" && fixedValue ? fixedValue : void 0,
5023
+ onSelect: channelSelectionHandler(
5024
+ params.map((f) => f.name),
5025
+ writeFixed
5026
+ ),
5027
+ ...channelLinkState(
5028
+ argsState,
5029
+ params.map((f) => f.name)
5030
+ ),
5031
+ nodeId: config.node?.id,
5032
+ label: p.label ?? p.name,
5033
+ className: inputCx,
5034
+ testId: `${testIdPrefix}-param-${p.name}`
5035
+ }
3759
5036
  ) : p.type === "boolean" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3760
5037
  /* @__PURE__ */ jsxs("label", { className: "flex w-fit cursor-pointer items-center gap-2 text-sm", children: [
3761
5038
  /* @__PURE__ */ jsx(
@@ -3901,7 +5178,7 @@ function NodeParamsForm({
3901
5178
  "aria-label": p.label ?? p.name,
3902
5179
  id: controlId,
3903
5180
  "data-testid": `${testIdPrefix}-param-${p.name}`,
3904
- className: cn(
5181
+ className: cn$1(
3905
5182
  inputCx,
3906
5183
  "scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary"
3907
5184
  ),
@@ -3942,7 +5219,7 @@ function NodeParamsForm({
3942
5219
  "aria-label": p.label ?? p.name,
3943
5220
  id: controlId,
3944
5221
  "data-testid": `${testIdPrefix}-param-${p.name}`,
3945
- className: cn(inputCx, "min-h-24 resize-y")
5222
+ className: cn$1(inputCx, "min-h-24 resize-y")
3946
5223
  }
3947
5224
  ) : /* @__PURE__ */ jsx(
3948
5225
  "input",
@@ -3970,21 +5247,6 @@ function NodeParamsForm({
3970
5247
  })
3971
5248
  ] });
3972
5249
  }
3973
- function ReadOnlyFieldset({
3974
- className,
3975
- children
3976
- }) {
3977
- const readOnly = useBuilderReadOnly();
3978
- return /* @__PURE__ */ jsx(
3979
- "fieldset",
3980
- {
3981
- disabled: readOnly,
3982
- "data-readonly": readOnly ? "true" : void 0,
3983
- className: cn("m-0 min-w-0 border-0 p-0", className),
3984
- children
3985
- }
3986
- );
3987
- }
3988
5250
 
3989
5251
  // src/builder/lib/continuation.ts
3990
5252
  var BLOCKED = {
@@ -4244,7 +5506,7 @@ function NodeExecutionActions({
4244
5506
  return /* @__PURE__ */ jsxs(
4245
5507
  "section",
4246
5508
  {
4247
- className: cn(
5509
+ className: cn$1(
4248
5510
  "shrink-0 border-t border-border bg-muted/20 px-3 py-2",
4249
5511
  className
4250
5512
  ),
@@ -4329,4 +5591,4 @@ function NodeExecutionActions({
4329
5591
  );
4330
5592
  }
4331
5593
 
4332
- export { NDV_Z_INDEX, NodeExecutionActions, NodeParamsForm, ReadOnlyFieldset };
5594
+ export { NDV_Z_INDEX, NodeExecutionActions, NodeParamsForm, Popover, PopoverContent, PopoverTrigger, ReadOnlyFieldset, useGlobalStoreClient };