@burdenoff/fluidgrids-fe-sdk 2026.1111.12 → 2026.1111.14

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 (33) hide show
  1. package/dist/{ExecutionDetailPage-CDkJeeu2.js → ExecutionDetailPage-itor_whl.js} +4 -4
  2. package/dist/{FluidGridsProvider-CDZjKmfD.js → FluidGridsProvider-B30VcLdX.js} +236 -17
  3. package/dist/{HistoryPage-DnVGN_72.js → HistoryPage-DueEld8g.js} +1 -1
  4. package/dist/{InsightsPage-CGRpZtGu.js → InsightsPage-DQ7KCR9h.js} +2 -2
  5. package/dist/{ManagedAiPolicyAuthority-DbJBledZ.js → ManagedAiPolicyAuthority-C0YPTJMS.js} +2 -2
  6. package/dist/{MissingNodesInstallModal-YI57ImRx.js → MissingNodesInstallModal-Cj31oMDX.js} +1 -1
  7. package/dist/{TemplateConfigurationModal-BimyvVH6.js → TemplateConfigurationModal-BAPCG8y-.js} +1 -1
  8. package/dist/{TemplateDetailPanel-CkwB6vrW.js → TemplateDetailPanel-CZ1uxEQL.js} +1 -1
  9. package/dist/{TemplatesPage-ZK6cMWur.js → TemplatesPage-DWdgZnIH.js} +4 -4
  10. package/dist/{WorkflowSearchRedirect-BHtdF64q.js → WorkflowSearchRedirect-C8vhSZK9.js} +32 -49
  11. package/dist/{WorkflowShareDialog-ByTSWwwR.js → WorkflowShareDialog-DnBQA3V6.js} +1 -1
  12. package/dist/{WorkflowsList-B9F2e8M6.js → WorkflowsList-74jjJFsJ.js} +4 -4
  13. package/dist/{agentTools-Dgrdql-W.js → agentTools-D5AW7zHn.js} +2 -2
  14. package/dist/canvasNoteTools-BqQTTCll.js +136 -0
  15. package/dist/executions.js +1 -1
  16. package/dist/{flow-builder-CukxgO65.js → flow-builder-BR8p5tXG.js} +520 -65
  17. package/dist/history.js +1 -1
  18. package/dist/{index-BES5GtJJ.d.ts → index-CLqh2u28.d.ts} +73 -2
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +440 -19
  21. package/dist/{node-detail-view-DRk8Ln5L.js → node-detail-view-Dnhqdn8C.js} +3 -3
  22. package/dist/observability.d.ts +1 -1
  23. package/dist/observability.js +1 -1
  24. package/dist/{quick-config-panel-40_1utiL.js → quick-config-panel-C4tSpCrt.js} +2 -2
  25. package/dist/settings.js +1 -1
  26. package/dist/templates.js +1 -1
  27. package/dist/{useExecutions-BqoD7XWi.js → useExecutions-CNEN1ENQ.js} +1 -1
  28. package/dist/{useObservability-Cs-5_NHh.js → useObservability-CmFFXmKb.js} +1 -1
  29. package/dist/{useRunHistory-CEL-CIz-.js → useRunHistory-DhU_mrzF.js} +1 -1
  30. package/dist/{useWorkflows-Bwpvfdgv.js → useWorkflows-D01LC39Y.js} +1 -1
  31. package/dist/workflow-builder.js +2 -2
  32. package/dist/workflows-list.js +1 -1
  33. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { A as nodeAccent, B as NODE_TYPE_DRAG_DATA_FORMAT, G as isTriggerOperation, H as useFlowStore, M as isImageIcon, T as resolveInputHandles, U as useIsValidConnection, V as STICKY_NOTE_TYPE, g as toCodeMirrorDiagnostics, j as NodeTypeIcon, r as CODE_WORKFLOW_NODE_TYPE, z as NODE_OPERATION_DRAG_DATA_FORMAT } from "./node-operation-selection--oy-yEq_.js";
3
- import { $ as resolveLoadOptionsSpec, $n as useWorkflowTestsEnabled, $t as useNodeExecution, A as UninstallWorkflowNodeDocument, At as bannerOf, B as coerceNumber, Bn as FLUIDGRIDS_FLAGS, Bt as forgetSessionForReload, C as InstallOrganizationPrivateNodeDocument, Cn as RUNS_QUOTA_NAME, D as OrganizationPrivateNodesDocument, Dn as isQuotaExhaustedFailure, Dt as useSaveStatusStore, Et as saveIndicatorOf, F as useInsertNode, Ft as phaseAfterTakeOver, G as paramsFromOperation, Gt as AUTOSAVE_DEBOUNCE_MS, H as nodeSubtitle, Ht as sessionBeforeReload, I as MissingParametersDialog, It as releasesOnClose, Jn as useFluidGridsFlag, Kn as useDurableFunctionNodesEnabled, Kt as AUTOSAVE_RETRY_DELAYS_MS, L as useRunGuard, Lt as shouldReleaseForIdle, M as getNodeMetadata, Mt as leaseCallQueue, N as buildOperationSeed, Nt as leaseCallSequence, On as useCanvasEdges, Ot as useEditLeaseLocked, Pt as phaseAfterAcquire, Q as optionListValues, Qt as workflowFingerprint, R as workflowParameterIssues, Rt as tickAction, S as GetNodeInstallableVersionsDocument, Sn as BILLING_PLANS_PATH, T as InstallWorkflowNodeDocument, Tn as collectExtensions, Tt as refusalOfQuota, U as nodeTitle, Ut as rememberOwnStatusChange, Vt as rememberSessionForReload, W as optionText, Wt as rememberRevision, X as isStringOptionList, Xn as usePaletteGates, Xt as inAppLeaveTarget, Z as optionListText, Zn as usePhaseCFlags, Zt as saveFailureActionOf, _n as emitWorkflowCreated, _t as watchLeavingLosesChanges, a as NODES_METADATA, an as structuredParameterType, ar as getNodeDetail, b as CreateStoreOrderDocument, bt as useSaveWorkflow, c as ensureNodeConfigsLoaded, cn as isParamVisible, cr as useBuilderReadOnly, ct as useGuardedNavigate, d as onNodeTypesUpdate, dn as emitNodeInstalled, dt as departingSave, er as connectionsForAdapter, et as ConditionTreeBuilder, f as refetchStoreNodes, ft as editStateFor, g as scheduleNodePaletteLoad, gn as emitUpgradeClicked, gt as touchesSavedFields, hn as emitTemplateUsed, ht as savedEverythingBeforeLeaving, i as AVAILABLE_NODES, in as parseParameterValue, ir as replaceAdapterConnections, it as parseMappingValue, jn as loadDefinitionJson, jt as isLocked, k as UninstallOrganizationPrivateNodeDocument, kn as useNodeRegistryRevision, kt as useEditLeaseStore, l as ensureNodeTypesLoaded, ln as useFluidGridsAnalytics, lt as useManualSave, m as registerWorkflowNodeTypes, mt as leavingLosesChanges, nr as matchesAdapterProvider, nt as casesFromRules, o as NODE_CONFIGS, or as buildStickyNode, ot as boundFieldsOf, pt as editingLeaseLocked, q as useNodeConfig, qn as useDurableParityEnabled, qt as canAutosave, rn as formatParameterValue, rr as newConnectionProviderKey, rt as FieldTree, s as NODE_TYPES, sr as BuilderReadOnlyProvider, st as missingRequiredParameters, t as FluidGridsContext, tr as isOptionalAdapter, tt as SwitchRulesEditor, u as isNodeConfigsInitialized, un as openRunQuotaUpgradePrompt, ur as getGlobalStoreClient, ut as autosaveAllowedFor, v as BrowseWorkflowNodesDocument, vt as isFlowComplete, w as InstallPurchasedNodeDocument, x as GetMyNodeInstallationsDocument, y as BrowseWorkflowTemplatesDocument, yt as useFlowValidator, z as asRecord, zt as EDIT_SESSION_ID } from "./FluidGridsProvider-CDZjKmfD.js";
3
+ import { $ as isStringOptionList, $n as usePaletteGates, $t as inAppLeaveTarget, A as StoreNodeProductsByTypeDocument, An as isQuotaExhaustedFailure, At as useSaveStatusStore, B as useRunGuard, Bt as shouldReleaseForIdle, C as GetMyNodeInstallationsDocument, Ct as useSaveWorkflow, D as InstallWorkflowNodeDocument, E as InstallPurchasedNodeDocument, En as RUNS_QUOTA_NAME, F as getNodeMetadata, Ft as leaseCallQueue, G as nodeSubtitle, Gt as sessionBeforeReload, H as asRecord, Ht as EDIT_SESSION_ID, I as buildOperationSeed, It as leaseCallSequence, J as paramsFromOperation, Jt as AUTOSAVE_DEBOUNCE_MS, K as nodeTitle, Kt as rememberOwnStatusChange, Lt as phaseAfterAcquire, M as UninstallOrganizationPrivateNodeDocument, Mn as useNodeRegistryRevision, Mt as useEditLeaseStore, N as UninstallWorkflowNodeDocument, Nt as bannerOf, On as collectExtensions, Ot as refusalOfQuota, Pn as loadDefinitionJson, Pt as isLocked, R as useInsertNode, Rt as phaseAfterTakeOver, S as CreateStoreOrderDocument, St as useFlowValidator, T as InstallOrganizationPrivateNodeDocument, Tn as BILLING_PLANS_PATH, U as coerceNumber, Un as FLUIDGRIDS_FLAGS, Ut as forgetSessionForReload, V as workflowParameterIssues, Vt as tickAction, Wt as rememberSessionForReload, X as useNodeConfig, Xn as useDurableParityEnabled, Xt as canAutosave, Yn as useDurableFunctionNodesEnabled, Yt as AUTOSAVE_RETRY_DELAYS_MS, Zn as useFluidGridsFlag, _t as leavingLosesChanges, a as NODES_METADATA, ar as matchesAdapterProvider, at as casesFromRules, b as BrowseWorkflowNodesDocument, bn as emitWorkflowCreated, bt as watchLeavingLosesChanges, c as ensureInstalledNodeTypesLoaded, cn as structuredParameterType, cr as getNodeDetail, d as ensureRegistryNodeTypesLoaded, dn as isParamVisible, dr as useBuilderReadOnly, dt as useGuardedNavigate, en as saveFailureActionOf, er as usePhaseCFlags, et as optionListText, f as isNodeConfigsInitialized, fn as useFluidGridsAnalytics, ft as useManualSave, g as registerWorkflowNodeTypes, gt as editingLeaseLocked, ht as editStateFor, i as AVAILABLE_NODES, ir as isOptionalAdapter, it as SwitchRulesEditor, jn as useCanvasEdges, jt as useEditLeaseLocked, k as OrganizationPrivateNodesDocument, kt as saveIndicatorOf, l as ensureNodeConfigsLoaded, lr as buildStickyNode, lt as boundFieldsOf, m as refetchStoreNodes, mn as emitNodeInstalled, mt as departingSave, nn as useNodeExecution, nr as useWorkflowTestsEnabled, nt as resolveLoadOptionsSpec, o as NODE_CONFIGS, on as formatParameterValue, or as newConnectionProviderKey, ot as FieldTree, p as onNodeTypesUpdate, pn as openRunQuotaUpgradePrompt, pr as getGlobalStoreClient, pt as autosaveAllowedFor, q as optionText, qt as rememberRevision, rr as connectionsForAdapter, rt as ConditionTreeBuilder, s as NODE_TYPES, sn as parseParameterValue, sr as replaceAdapterConnections, st as parseMappingValue, t as FluidGridsContext, tn as workflowFingerprint, tt as optionListValues, ur as BuilderReadOnlyProvider, ut as missingRequiredParameters, v as scheduleNodePaletteLoad, vn as emitTemplateUsed, vt as savedEverythingBeforeLeaving, w as GetNodeInstallableVersionsDocument, x as BrowseWorkflowTemplatesDocument, xt as isFlowComplete, yn as emitUpgradeClicked, yt as touchesSavedFields, z as MissingParametersDialog, zt as releasesOnClose } from "./FluidGridsProvider-B30VcLdX.js";
4
4
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
5
5
  import { Bi as describeOperationError, Br as WorkflowGitRepositoryDocument, Ct as LinkWorkflowGitRepositoryDocument, Dr as WorkflowDefinitionDocument, Ei as WorkspaceRunsQuotaDocument, Ht as PreviewScheduleDocument, Ir as WorkflowGenerationTemplatesDocument, J as DeleteWorkflowRolloutDocument, Kt as PushWorkflowToGitDocument, Li as emitFluidGridsEvent, Lr as WorkflowGitCommitsDocument, Mt as NodeSchemaIntrospectionDocument, Ni as waitForFluidGridsClient, Nr as WorkflowEnvironmentsDocument, Oi as getFluidGridsClient, Pt as NodeUsageStatusDocument, Qt as RemoveNodeFromWorkflowsDocument, Rr as WorkflowGitDiffDocument, Si as WorkflowVersioningDocument, Ui as throwIfResultError, Vn as TakeOverWorkflowEditLeaseDocument, Wr as WorkflowPromotionRequestsDocument, Xn as UnlinkWorkflowGitRepositoryDocument, Yt as RejectWorkflowPromotionDocument, Z as DeployWorkflowVersionToDevDocument, Zt as ReleaseWorkflowEditLeaseDocument, cr as UpdateWorkflowEnvironmentPolicyDocument, i as ActivateWorkflowVersionCheckedDocument, jn as SetWorkflowRolloutDocument, jr as WorkflowEditLeaseDocument, ki as getFluidGridsClientSafe, mr as WebhookTestListenerDocument, n as AcquireWorkflowEditLeaseDocument, nn as RequestWorkflowPromotionDocument, oa as __exportAll, on as RestoreWorkflowFromGitDocument, qi as getFluidGridsConfig, u as ApproveWorkflowPromotionDocument, wr as WorkflowCallInputsDocument, wt as ListenForWebhookTestEventDocument, x as CancelWorkflowPromotionDocument, xi as WorkflowVersionDrainageDocument, yr as WorkflowAuthoringDocument, zn as StopWebhookTestListenerDocument, zr as WorkflowGitPushesDocument } from "./node-endpoint-configs-DHFGA5MQ.js";
6
6
  import { D as describeSchedule, H as pauseSchedule, _ as useResource, c as FieldError, d as NativeSelect, et as triggerSchedule, nt as unpauseSchedule, p as StatusPill, r as describeRule, s as isAgentNodeType, v as backfillSchedule } from "./agent-form-BvMWhQ42.js";
@@ -9,9 +9,9 @@ import { a as useConnections, r as useAgentsUsingConnection, t as useAgentDefini
9
9
  import { F as normaliseAssertRules, I as operatorSpec, L as readAssertRulesSetting, M as ASSERT_OPERATORS, N as describeAssertRuleProblem, P as emptyAssertRuleRow, R as rowsFromRules, _ as buildNodeDisplayMap, a as TEST_STATUS_TONE_CLASS, b as EntityTagPicker, c as draftToInput, d as isTestRunOpen, f as nextSortOrder, g as testStatusTone, h as testStatusLabel, i as useWorkflowTests, l as emptyTestCaseDraft, m as testRunSummaryText, n as useWorkflowProposal, p as pinnedOutputsFromCanvas, s as draftFromTestCase, t as WorkflowProposalCard, u as enabledCaseIds, z as ruleFromRow } from "./WorkflowProposalCard-8Vr56kQa.js";
10
10
  import { t as cn$1 } from "./cn-QqidFIgr.js";
11
11
  import { i as Button$2, t as PublishCustomNodeAction } from "./PublishCustomNodeAction-D2F7oN0S.js";
12
- import { O as CreateWorkflowRefusalDialog, b as lineOfNode, h as fileBaseName, i as useWorkflowCodeExport, n as downloadWorkflowCodeExport, t as WorkflowShareDialog, v as isLosslessReport, w as sortFidelityItems, x as orderedExportFiles, y as isLossyFidelityItem } from "./WorkflowShareDialog-ByTSWwwR.js";
12
+ import { O as CreateWorkflowRefusalDialog, b as lineOfNode, h as fileBaseName, i as useWorkflowCodeExport, n as downloadWorkflowCodeExport, t as WorkflowShareDialog, v as isLosslessReport, w as sortFidelityItems, x as orderedExportFiles, y as isLossyFidelityItem } from "./WorkflowShareDialog-DnBQA3V6.js";
13
13
  import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
14
- import { a as useDeactivateWorkflow, d as useRestoreWorkflowVersion, o as useDeleteWorkflow, t as useActivateWorkflow, v as useWorkflowVersions } from "./useWorkflows-Bwpvfdgv.js";
14
+ import { a as useDeactivateWorkflow, d as useRestoreWorkflowVersion, o as useDeleteWorkflow, t as useActivateWorkflow, v as useWorkflowVersions } from "./useWorkflows-D01LC39Y.js";
15
15
  import { n as VersionLifecycleBar, r as countLifecycle, t as VersionDiffDialog } from "./VersionDiffDialog-Dq2xlPFI.js";
16
16
  import { n as useRequestedProposal } from "./useRequestedProposal-B7uPXfZB.js";
17
17
  import { t as AiCreditBalanceSlot } from "./aiCreditsBridge-BuE92tKo.js";
@@ -21,7 +21,7 @@ import { Link, useNavigate, useParams } from "react-router";
21
21
  import { useMutation, useQuery } from "@apollo/client/react";
22
22
  import { gql } from "@apollo/client/core";
23
23
  import { create } from "zustand";
24
- import { Background, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, Panel, Position, ReactFlow, ReactFlowProvider, addEdge, getBezierPath, useEdgesState, useNodes, useNodesState, useOnSelectionChange, useReactFlow, useStore } from "@xyflow/react";
24
+ import { Background, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, NodeToolbar, Panel, Position, ReactFlow, ReactFlowProvider, ViewportPortal, addEdge, getBezierPath, useEdgesState, useInternalNode, useNodes, useNodesState, useOnSelectionChange, useReactFlow, useStore } from "@xyflow/react";
25
25
  import { Icon } from "@iconify/react";
26
26
  import ReactMarkdown from "react-markdown";
27
27
  import { cn } from "@burdenoff/fe-libs/utils";
@@ -10395,6 +10395,19 @@ function useImportWorkflowTemplate() {
10395
10395
 
10396
10396
  // src/utils/workflow-node-check.ts
10397
10397
  /**
10398
+ * Workflow node dependency checking.
10399
+ *
10400
+ * Shared logic for detecting node types used by a workflow that are not
10401
+ * installed in the current workspace, resolving them to store products, and
10402
+ * classifying them as installed / missing (free installable) / missingPaid /
10403
+ * unavailable (not offered by the store, so nothing can install them).
10404
+ *
10405
+ * Used by:
10406
+ * - Template import flow (desktop-sidebar-fragment)
10407
+ * - Shared-workflow copy flow (WorkflowItem)
10408
+ * - AI-assistant-created workflow open flow (WorkflowItem)
10409
+ */
10410
+ /**
10398
10411
  * Canvas-only node types: they live on the React Flow canvas but are never
10399
10412
  * executed, never registered in NODE_CONFIGS and never published to the store.
10400
10413
  *
@@ -10418,50 +10431,22 @@ function getWorkflowNodeTypes(nodes) {
10418
10431
  return [...new Set(nodes.map((n) => n.type).filter((t) => Boolean(t)).filter((t) => !CANVAS_ONLY_NODE_TYPES.has(t)))];
10419
10432
  }
10420
10433
  /**
10421
- * Page size for walking the node catalog. global-store-svc clamps
10422
- * `browseStore` pages to 100 rows (MAX_PAGINATION_LIMIT), so asking for more
10423
- * returns 100 anyway. The old single request for 200 rows silently dropped
10424
- * every node past the first 100 (BOFF-11402).
10434
+ * Node types per store lookup: global-store-svc answers for at most 100 at a
10435
+ * time. A workflow misses far fewer; more are asked in parallel requests.
10425
10436
  */
10426
- const STORE_NODE_PAGE_SIZE = 100;
10437
+ const STORE_NODE_LOOKUP_BATCH_SIZE = 100;
10438
+ /** The store context of FluidGrids node products, as the Node Store browses them. */
10439
+ const STORE_NODE_PRODUCT_CONTEXT = "fluidgrids";
10427
10440
  /**
10428
- * Safety net for the paging loop: 50 pages of 100 is far beyond the catalog
10429
- * (about 175 node products in 2026-11). The loop normally ends on `hasMore`.
10441
+ * The store products that offer any of `nodeTypes`, at most
10442
+ * `STORE_NODE_LOOKUP_BATCH_SIZE` types per request, the requests in parallel.
10443
+ * Blank and repeated types are asked once.
10430
10444
  */
10431
- const STORE_NODE_MAX_PAGES = 50;
10432
- /** The browseStore variables for one page of FluidGrids node products. */
10433
- function storeNodePageVariables(offset) {
10434
- return { input: {
10435
- filter: {
10436
- type: "NODE",
10437
- compatibleWith: "fluidgrids"
10438
- },
10439
- sortBy: "DOWNLOADS",
10440
- pagination: {
10441
- limit: 100,
10442
- offset
10443
- }
10444
- } };
10445
- }
10446
- /**
10447
- * Fetch every FluidGrids-compatible node product from the store.
10448
- *
10449
- * `browseStore` pages by `offset`; its `cursor` input is ignored by the
10450
- * server. The offset advances by the number of rows actually returned so a
10451
- * lower server cap still walks the whole catalog. Rows are de-duplicated by
10452
- * product id in case a page boundary moves while the catalog changes.
10453
- */
10454
- async function fetchAllStoreNodes(fetchPage) {
10455
- const byId = /* @__PURE__ */ new Map();
10456
- let offset = 0;
10457
- for (let page = 0; page < 50; page++) {
10458
- const browse = await fetchPage(storeNodePageVariables(offset));
10459
- const products = browse?.products ?? [];
10460
- for (const product of products) if (!byId.has(product.id)) byId.set(product.id, product);
10461
- if (!browse?.hasMore || products.length === 0) break;
10462
- offset += products.length;
10463
- }
10464
- return [...byId.values()];
10445
+ async function fetchStoreNodeProducts(nodeTypes, fetchProducts) {
10446
+ const wanted = [...new Set(nodeTypes.filter(Boolean))];
10447
+ const batches = [];
10448
+ for (let i = 0; i < wanted.length; i += 100) batches.push(wanted.slice(i, i + 100));
10449
+ return (await Promise.all(batches.map(fetchProducts))).flatMap((products) => [...products ?? []]);
10465
10450
  }
10466
10451
  function publishedAtMs(product) {
10467
10452
  const ms = product.publishedAt ? Date.parse(product.publishedAt) : NaN;
@@ -10503,18 +10488,22 @@ function buildStoreNodeTypeMap(products) {
10503
10488
  return nodeTypeToProduct;
10504
10489
  }
10505
10490
  /**
10506
- * Query the global store for every FluidGrids-compatible node product and map
10507
- * nodeType string -> StoreProduct using manifest.nodeType.
10491
+ * Ask the global store which products offer `nodeTypes` and map each type to the
10492
+ * best one (see compareStoreNodeCandidates). Only the asked types travel: the
10493
+ * check used to page through the whole node catalog for them (BOFF-11754).
10508
10494
  */
10509
- async function fetchStoreNodeTypeMap() {
10495
+ async function fetchStoreNodeTypeMap(nodeTypes) {
10510
10496
  const storeClient = getGlobalStoreClient();
10511
- if (!storeClient) return /* @__PURE__ */ new Map();
10512
- return buildStoreNodeTypeMap(await fetchAllStoreNodes(async (variables) => {
10497
+ if (!storeClient || nodeTypes.length === 0) return /* @__PURE__ */ new Map();
10498
+ return buildStoreNodeTypeMap(await fetchStoreNodeProducts(nodeTypes, async (batch) => {
10513
10499
  return (await storeClient.query({
10514
- query: BrowseWorkflowNodesDocument,
10515
- variables,
10500
+ query: StoreNodeProductsByTypeDocument,
10501
+ variables: {
10502
+ nodeTypes: batch,
10503
+ compatibleWith: STORE_NODE_PRODUCT_CONTEXT
10504
+ },
10516
10505
  fetchPolicy: "cache-first"
10517
- })).data?.browseStore;
10506
+ })).data?.storeNodeProductsByType;
10518
10507
  }));
10519
10508
  }
10520
10509
  /**
@@ -10551,10 +10540,11 @@ function requiredNodeEntry(type, registered, product) {
10551
10540
  async function buildRequiredNodeTypes(nodes) {
10552
10541
  const workflowNodeTypes = getWorkflowNodeTypes(nodes);
10553
10542
  if (workflowNodeTypes.length === 0) return [];
10554
- await ensureNodeTypesLoaded(workflowNodeTypes);
10555
- const missingTypes = workflowNodeTypes.filter((type) => !NODE_CONFIGS.some((c) => c.type === type));
10543
+ const isRegistered = (type) => NODE_CONFIGS.some((c) => c.type === type);
10544
+ await ensureRegistryNodeTypesLoaded(workflowNodeTypes);
10545
+ const missingTypes = workflowNodeTypes.filter((type) => !isRegistered(type));
10556
10546
  let nodeTypeToProduct = /* @__PURE__ */ new Map();
10557
- if (missingTypes.length > 0) nodeTypeToProduct = await fetchStoreNodeTypeMap();
10547
+ if (missingTypes.length > 0) [, nodeTypeToProduct] = await Promise.all([ensureInstalledNodeTypesLoaded(missingTypes), fetchStoreNodeTypeMap(missingTypes)]);
10558
10548
  return workflowNodeTypes.map((type) => {
10559
10549
  const registered = NODE_CONFIGS.find((c) => c.type === type);
10560
10550
  return requiredNodeEntry(type, registered ? { version: registered.defaultData.version } : void 0, nodeTypeToProduct.get(type));
@@ -10582,7 +10572,7 @@ function buildNodeCheck(requiredNodeTypes, installedAppIds) {
10582
10572
  }
10583
10573
 
10584
10574
  // src/hooks/store/useTemplateImportFlow.tsx
10585
- const LazyTemplateConfigurationModal = lazy(() => import("./TemplateConfigurationModal-BimyvVH6.js").then((m) => ({ default: m.TemplateConfigurationModal })));
10575
+ const LazyTemplateConfigurationModal = lazy(() => import("./TemplateConfigurationModal-BAPCG8y-.js").then((m) => ({ default: m.TemplateConfigurationModal })));
10586
10576
  /**
10587
10577
  * "Use this template", the same everywhere it is offered: the builder's
10588
10578
  * template panel, the Templates page and the template preview page
@@ -12759,7 +12749,7 @@ function TemplatesGrid({ templates, loading, hasMore, onLoadMore, onTemplateClic
12759
12749
  }
12760
12750
 
12761
12751
  // src/builder/components/flow-builder/components/templates/TemplatesModal.tsx
12762
- const LazyTemplateDetailPanel = lazy(() => import("./TemplateDetailPanel-CkwB6vrW.js").then((m) => ({ default: m.TemplateDetailPanel })));
12752
+ const LazyTemplateDetailPanel = lazy(() => import("./TemplateDetailPanel-CZ1uxEQL.js").then((m) => ({ default: m.TemplateDetailPanel })));
12763
12753
  /**
12764
12754
  * Modal for browsing and selecting workflow templates
12765
12755
  * Features:
@@ -20702,6 +20692,448 @@ function WebhookRunFollower() {
20702
20692
  return null;
20703
20693
  }
20704
20694
 
20695
+ // src/agentMarks/plainText.ts
20696
+ /**
20697
+ * ★ BOFF-11584: the text of a callout or note is model output, and the model
20698
+ * has read workflow content people wrote. It is shown as plain text only:
20699
+ * React renders it as a text node (never `dangerouslySetInnerHTML`, never a
20700
+ * markdown renderer), so markup, links and images in it stay inert characters.
20701
+ *
20702
+ * This keeps it short and printable as well: control characters (other than
20703
+ * line breaks) and bidirectional controls, which can disguise what a line
20704
+ * says, are removed; runs of blank lines collapse; the result is clamped.
20705
+ */
20706
+ const BIDI_CONTROLS = /* @__PURE__ */ new Set([
20707
+ 1564,
20708
+ 8206,
20709
+ 8207,
20710
+ 8234,
20711
+ 8235,
20712
+ 8236,
20713
+ 8237,
20714
+ 8238,
20715
+ 8294,
20716
+ 8295,
20717
+ 8296,
20718
+ 8297
20719
+ ]);
20720
+ function isDroppedCharacter(code) {
20721
+ if (code === 10) return false;
20722
+ if (code <= 31) return true;
20723
+ if (code >= 127 && code <= 159) return true;
20724
+ return BIDI_CONTROLS.has(code);
20725
+ }
20726
+ function toPlainText(text, max) {
20727
+ let kept = "";
20728
+ for (const character of text.replace(/\r\n?/g, "\n").replace(/\t/g, " ")) if (!isDroppedCharacter(character.codePointAt(0) ?? 0)) kept += character;
20729
+ const cleaned = kept.replace(/ {2,}/g, " ").replace(/ *\n */g, "\n").replace(/\n{3,}/g, "\n\n").trim();
20730
+ if (cleaned.length <= max) return cleaned;
20731
+ return `${cleaned.slice(0, max - 1).trimEnd()}…`;
20732
+ }
20733
+
20734
+ // src/agentMarks/marksStore.ts
20735
+ /** The text of a mark is model output: kept as plain text, never markup. */
20736
+ const CALLOUT_LABEL_MAX = 200;
20737
+ const NOTE_MESSAGE_MAX = 280;
20738
+ /** A workflow holds at most this many notes, resolved ones included. */
20739
+ const MAX_NOTES_PER_WORKFLOW = 20;
20740
+ let sequence = 0;
20741
+ const nextId = (prefix) => {
20742
+ sequence += 1;
20743
+ return `${prefix}${sequence}`;
20744
+ };
20745
+ const useAgentMarksStore = create()((set, get) => ({
20746
+ callouts: [],
20747
+ notes: [],
20748
+ nextNumber: {},
20749
+ openNoteId: null,
20750
+ flash: null,
20751
+ actions: {
20752
+ addCallout: ({ label, target, element, route }) => {
20753
+ const callout = {
20754
+ id: nextId("c"),
20755
+ label: toPlainText(label, 200),
20756
+ target,
20757
+ route,
20758
+ element: element ? new WeakRef(element) : null
20759
+ };
20760
+ set((state) => ({ callouts: [...state.callouts, callout].slice(-3) }));
20761
+ return callout;
20762
+ },
20763
+ rebindCallout: (id, element) => set((state) => ({ callouts: state.callouts.map((callout) => callout.id === id ? {
20764
+ ...callout,
20765
+ element: new WeakRef(element)
20766
+ } : callout) })),
20767
+ removeCallout: (id) => set((state) => ({ callouts: state.callouts.filter((callout) => callout.id !== id) })),
20768
+ clearCallouts: () => {
20769
+ const count = get().callouts.length;
20770
+ if (count > 0) set({ callouts: [] });
20771
+ return count;
20772
+ },
20773
+ dropCalloutsOffRoute: (route) => {
20774
+ const { callouts } = get();
20775
+ const kept = callouts.filter((callout) => callout.route === route);
20776
+ if (kept.length !== callouts.length) set({ callouts: kept });
20777
+ return callouts.length - kept.length;
20778
+ },
20779
+ addNote: (input) => {
20780
+ const { notes, nextNumber } = get();
20781
+ if (notes.filter((note) => note.workflowKey === input.workflowKey).length >= 20) return null;
20782
+ const number = nextNumber[input.workflowKey] ?? 1;
20783
+ const note = {
20784
+ id: nextId("n"),
20785
+ number,
20786
+ workflowKey: input.workflowKey,
20787
+ nodeId: input.nodeId,
20788
+ nodeLabel: toPlainText(input.nodeLabel, 80),
20789
+ paramKey: input.paramKey ?? null,
20790
+ kind: input.kind,
20791
+ message: toPlainText(input.message, 280),
20792
+ resolved: false
20793
+ };
20794
+ set({
20795
+ notes: [...notes, note],
20796
+ nextNumber: {
20797
+ ...nextNumber,
20798
+ [input.workflowKey]: number + 1
20799
+ }
20800
+ });
20801
+ return note;
20802
+ },
20803
+ setResolved: (id, resolved) => set((state) => ({ notes: state.notes.map((note) => note.id === id ? {
20804
+ ...note,
20805
+ resolved
20806
+ } : note) })),
20807
+ removeNote: (id) => set((state) => ({
20808
+ notes: state.notes.filter((note) => note.id !== id),
20809
+ openNoteId: state.openNoteId === id ? null : state.openNoteId
20810
+ })),
20811
+ clearNotes: (workflowKey) => {
20812
+ const { notes, openNoteId } = get();
20813
+ const kept = workflowKey === void 0 ? [] : notes.filter((note) => note.workflowKey !== workflowKey);
20814
+ const removed = notes.length - kept.length;
20815
+ if (removed > 0) set({
20816
+ notes: kept,
20817
+ openNoteId: kept.some((note) => note.id === openNoteId) ? openNoteId : null
20818
+ });
20819
+ return removed;
20820
+ },
20821
+ openNote: (id) => set({ openNoteId: id }),
20822
+ flashNode: (nodeId) => {
20823
+ sequence += 1;
20824
+ set({ flash: {
20825
+ nodeId,
20826
+ token: sequence
20827
+ } });
20828
+ }
20829
+ }
20830
+ }));
20831
+ /** Reset everything (tests, and sign-out if a host wants it). */
20832
+ function resetAgentMarks() {
20833
+ useAgentMarksStore.setState({
20834
+ callouts: [],
20835
+ notes: [],
20836
+ nextNumber: {},
20837
+ openNoteId: null,
20838
+ flash: null
20839
+ });
20840
+ }
20841
+
20842
+ // src/builder/agentNotes/notesKey.ts
20843
+ /**
20844
+ * ★ BOFF-11584: which workflow a canvas note belongs to. A saved workflow is
20845
+ * known by its key across versions; a new one has no key until its first
20846
+ * save, so its in-memory id stands in.
20847
+ */
20848
+ function notesKeyOf(workflow) {
20849
+ return workflow.workflowKey || workflow.id || "unsaved";
20850
+ }
20851
+
20852
+ // src/builder/agentNotes/CanvasNotesLayer.tsx
20853
+ /**
20854
+ * ★ BOFF-11584: the assistant's notes on the builder canvas
20855
+ * (`fg_canvas_annotate`). Rendered as a child of `<ReactFlow>`, outside every
20856
+ * node component: the nodes know nothing about notes.
20857
+ *
20858
+ * - Numbered pins sit above their node (`NodeToolbar`), so they follow it
20859
+ * through drags, pans and zooms. A pin opens the note's card: the message,
20860
+ * the parameter it is about, "Open node" and "Mark resolved".
20861
+ * - A dashed ring marks each node with an open note (`ViewportPortal`, in
20862
+ * canvas coordinates), and a brief ring shows the node the assistant just
20863
+ * brought into view.
20864
+ * - The notes list (top left; the FluidGrids mark holds the top right)
20865
+ * holds every note, including orphaned ones whose node was deleted: those
20866
+ * are never moved onto another node.
20867
+ *
20868
+ * Everything here is `data-agent="deny"`: an agent cannot read or press its
20869
+ * own notes. Messages render as React text, never as markup.
20870
+ */
20871
+ const FLASH_MS = 1800;
20872
+ const RING_PAD = 8;
20873
+ function pinClass(note, open) {
20874
+ return cn("flex size-6 items-center justify-center rounded-full text-xs font-semibold shadow-md transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", note.resolved ? "bg-muted text-muted-foreground" : note.kind === "fix" ? "bg-destructive text-destructive-foreground" : "bg-primary text-primary-foreground", open && "scale-110 ring-2 ring-ring ring-offset-1 ring-offset-background");
20875
+ }
20876
+ /** A ring around a node, drawn in canvas coordinates. */
20877
+ const NodeRing = ({ nodeId, tone }) => {
20878
+ const node = useInternalNode(nodeId);
20879
+ const width = node?.measured.width ?? 0;
20880
+ const height = node?.measured.height ?? 0;
20881
+ if (!node || width === 0 || height === 0) return null;
20882
+ const { x, y } = node.internals.positionAbsolute;
20883
+ return /* @__PURE__ */ jsx("div", {
20884
+ "aria-hidden": "true",
20885
+ "data-testid": `fg-canvas-note-ring-${tone}`,
20886
+ className: cn("pointer-events-none absolute left-0 top-0 rounded-2xl border-2", tone === "fix" && "border-dashed border-destructive", tone === "info" && "border-dashed border-primary", tone === "flash" && "border-primary ring-4 ring-primary/30 motion-safe:animate-pulse"),
20887
+ style: {
20888
+ transform: `translate(${x - RING_PAD}px, ${y - RING_PAD}px)`,
20889
+ width: width + 16,
20890
+ height: height + 16
20891
+ }
20892
+ });
20893
+ };
20894
+ /** The node the assistant just brought into view, for a moment. */
20895
+ const FlashRing = () => {
20896
+ const flash = useAgentMarksStore((state) => state.flash);
20897
+ const [shown, setShown] = useState(null);
20898
+ useEffect(() => {
20899
+ if (!flash) return;
20900
+ setShown(flash.token);
20901
+ const timer = setTimeout(() => setShown(null), FLASH_MS);
20902
+ return () => clearTimeout(timer);
20903
+ }, [flash]);
20904
+ if (!flash || shown !== flash.token) return null;
20905
+ return /* @__PURE__ */ jsx(NodeRing, {
20906
+ nodeId: flash.nodeId,
20907
+ tone: "flash"
20908
+ });
20909
+ };
20910
+ const CanvasNoteCard = ({ note, nodeOnCanvas }) => {
20911
+ const tr = useTr();
20912
+ const { openNote, setResolved } = useAgentMarksStore((state) => state.actions);
20913
+ const openNodePanel = () => {
20914
+ const { workflow, actions } = useFlowStore.getState();
20915
+ const node = workflow.nodes.find((candidate) => candidate.id === note.nodeId);
20916
+ if (!node?.type) return;
20917
+ actions.ndv.open({
20918
+ nodeId: node.id,
20919
+ type: node.type
20920
+ });
20921
+ openNote(null);
20922
+ };
20923
+ return /* @__PURE__ */ jsxs("div", {
20924
+ "data-testid": "fg-canvas-note-card",
20925
+ className: "nowheel w-64 cursor-default rounded-lg border border-border bg-popover p-3 text-left text-xs text-foreground shadow-lg",
20926
+ children: [
20927
+ /* @__PURE__ */ jsxs("div", {
20928
+ className: "mb-1.5 flex items-center justify-between gap-2",
20929
+ children: [/* @__PURE__ */ jsxs("span", {
20930
+ className: "flex items-center gap-1.5 font-semibold",
20931
+ children: [/* @__PURE__ */ jsx(Sparkles, {
20932
+ "aria-hidden": "true",
20933
+ className: "size-3.5 text-primary"
20934
+ }), note.kind === "fix" ? tr("fluidgrids.agentMarks.noteFixTitle", "Note {{number}} · To fix", { number: note.number }) : tr("fluidgrids.agentMarks.noteInfoTitle", "Note {{number}}", { number: note.number })]
20935
+ }), /* @__PURE__ */ jsx("button", {
20936
+ type: "button",
20937
+ "aria-label": tr("fluidgrids.agentMarks.closeNote", "Close note"),
20938
+ onClick: () => openNote(null),
20939
+ className: "-m-1 rounded p-1 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
20940
+ children: /* @__PURE__ */ jsx(X, {
20941
+ "aria-hidden": "true",
20942
+ className: "size-3.5"
20943
+ })
20944
+ })]
20945
+ }),
20946
+ /* @__PURE__ */ jsxs("p", {
20947
+ className: "mb-1 truncate text-muted-foreground",
20948
+ children: [note.nodeLabel, note.paramKey ? ` · ${tr("fluidgrids.agentMarks.noteParam", "Field: {{param}}", { param: note.paramKey })}` : ""]
20949
+ }),
20950
+ /* @__PURE__ */ jsx("p", {
20951
+ "data-testid": "fg-canvas-note-message",
20952
+ className: "whitespace-pre-line break-words text-sm text-foreground",
20953
+ children: note.message
20954
+ }),
20955
+ !nodeOnCanvas && /* @__PURE__ */ jsx("p", {
20956
+ className: "mt-1.5 text-muted-foreground",
20957
+ children: tr("fluidgrids.agentMarks.noteOrphaned", "This node was removed from the workflow.")
20958
+ }),
20959
+ /* @__PURE__ */ jsxs("div", {
20960
+ className: "mt-2.5 flex justify-end gap-2",
20961
+ children: [nodeOnCanvas && /* @__PURE__ */ jsx("button", {
20962
+ type: "button",
20963
+ onClick: openNodePanel,
20964
+ className: "rounded-md border border-border px-2 py-1 font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
20965
+ children: tr("fluidgrids.agentMarks.openNode", "Open node")
20966
+ }), /* @__PURE__ */ jsx("button", {
20967
+ type: "button",
20968
+ "data-testid": "fg-canvas-note-resolve",
20969
+ onClick: () => setResolved(note.id, !note.resolved),
20970
+ className: "rounded-md bg-primary px-2 py-1 font-medium text-primary-foreground hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
20971
+ children: note.resolved ? tr("fluidgrids.agentMarks.reopenNote", "Reopen") : tr("fluidgrids.agentMarks.resolveNote", "Mark resolved")
20972
+ })]
20973
+ })
20974
+ ]
20975
+ });
20976
+ };
20977
+ /** The pins above one node, and the open card. */
20978
+ const NodePins = ({ nodeId, notes }) => {
20979
+ const tr = useTr();
20980
+ const openNoteId = useAgentMarksStore((state) => state.openNoteId);
20981
+ const openNote = useAgentMarksStore((state) => state.actions.openNote);
20982
+ const open = notes.find((note) => note.id === openNoteId);
20983
+ return /* @__PURE__ */ jsx(NodeToolbar, {
20984
+ nodeId,
20985
+ isVisible: true,
20986
+ position: Position.Top,
20987
+ align: "end",
20988
+ offset: 6,
20989
+ "data-agent": "deny",
20990
+ "data-testid": "fg-canvas-note-pins",
20991
+ className: "nodrag nopan",
20992
+ children: /* @__PURE__ */ jsxs("div", {
20993
+ className: "flex flex-col items-end gap-1.5",
20994
+ children: [open && /* @__PURE__ */ jsx(CanvasNoteCard, {
20995
+ note: open,
20996
+ nodeOnCanvas: true
20997
+ }), /* @__PURE__ */ jsx("div", {
20998
+ className: "flex gap-1",
20999
+ children: notes.map((note) => /* @__PURE__ */ jsx("button", {
21000
+ type: "button",
21001
+ "data-testid": "fg-canvas-note-pin",
21002
+ "aria-expanded": note.id === openNoteId,
21003
+ "aria-label": tr("fluidgrids.agentMarks.notePin", "Assistant note {{number}}", { number: note.number }),
21004
+ onClick: () => openNote(note.id === openNoteId ? null : note.id),
21005
+ className: pinClass(note, note.id === openNoteId),
21006
+ children: note.resolved ? /* @__PURE__ */ jsx(Check, {
21007
+ "aria-hidden": "true",
21008
+ className: "size-3.5"
21009
+ }) : note.number
21010
+ }, note.id))
21011
+ })]
21012
+ })
21013
+ });
21014
+ };
21015
+ /** Every note on this workflow, orphaned ones included. */
21016
+ const NotesList = ({ notes, live }) => {
21017
+ const tr = useTr();
21018
+ const [expanded, setExpanded] = useState(false);
21019
+ const { fitView } = useReactFlow();
21020
+ const { openNote, removeNote, flashNode } = useAgentMarksStore((state) => state.actions);
21021
+ const openCount = notes.filter((note) => !note.resolved).length;
21022
+ const orphaned = notes.filter((note) => !live.has(note.nodeId)).length;
21023
+ const show = (note) => {
21024
+ if (!live.has(note.nodeId)) return;
21025
+ fitView({
21026
+ nodes: [{ id: note.nodeId }],
21027
+ duration: 400,
21028
+ padding: .6,
21029
+ maxZoom: 1.25
21030
+ }).then((done) => {
21031
+ if (done) flashNode(note.nodeId);
21032
+ });
21033
+ openNote(note.id);
21034
+ };
21035
+ return /* @__PURE__ */ jsx(Panel, {
21036
+ position: "top-left",
21037
+ "data-agent": "deny",
21038
+ "data-testid": "fg-canvas-notes-list",
21039
+ className: "nodrag nopan nowheel",
21040
+ children: /* @__PURE__ */ jsxs("div", {
21041
+ className: "w-64 rounded-lg border border-border bg-popover text-xs text-foreground shadow-md",
21042
+ children: [/* @__PURE__ */ jsxs("button", {
21043
+ type: "button",
21044
+ "aria-expanded": expanded,
21045
+ onClick: () => setExpanded((value) => !value),
21046
+ className: "flex w-full items-center justify-between gap-2 rounded-lg px-3 py-2 font-medium hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
21047
+ children: [/* @__PURE__ */ jsxs("span", {
21048
+ className: "flex items-center gap-1.5",
21049
+ children: [/* @__PURE__ */ jsx(Sparkles, {
21050
+ "aria-hidden": "true",
21051
+ className: "size-3.5 text-primary"
21052
+ }), tr("fluidgrids.agentMarks.notesHeading", "Assistant notes")]
21053
+ }), /* @__PURE__ */ jsxs("span", {
21054
+ className: "text-muted-foreground",
21055
+ children: [tr("fluidgrids.agentMarks.notesOpenCount", "{{count}} open", { count: openCount }), orphaned > 0 ? ` · ${tr("fluidgrids.agentMarks.notesOrphanedCount", "{{count}} removed", { count: orphaned })}` : ""]
21056
+ })]
21057
+ }), expanded && /* @__PURE__ */ jsx("ul", {
21058
+ className: "max-h-64 overflow-y-auto border-t border-border py-1",
21059
+ children: notes.map((note) => {
21060
+ const onCanvas = live.has(note.nodeId);
21061
+ return /* @__PURE__ */ jsxs("li", {
21062
+ "data-testid": "fg-canvas-notes-item",
21063
+ className: "flex items-start gap-2 px-3 py-1.5",
21064
+ children: [
21065
+ /* @__PURE__ */ jsx("span", {
21066
+ className: cn(pinClass(note, false), "size-5 shrink-0"),
21067
+ children: note.resolved ? /* @__PURE__ */ jsx(Check, {
21068
+ "aria-hidden": "true",
21069
+ className: "size-3"
21070
+ }) : note.number
21071
+ }),
21072
+ /* @__PURE__ */ jsxs("button", {
21073
+ type: "button",
21074
+ disabled: !onCanvas,
21075
+ onClick: () => show(note),
21076
+ className: "min-w-0 flex-1 text-left disabled:cursor-default",
21077
+ children: [/* @__PURE__ */ jsx("span", {
21078
+ className: "block truncate font-medium",
21079
+ children: note.nodeLabel
21080
+ }), /* @__PURE__ */ jsx("span", {
21081
+ className: cn("block truncate text-muted-foreground", note.resolved && "line-through"),
21082
+ children: onCanvas ? note.message : tr("fluidgrids.agentMarks.noteOrphaned", "This node was removed from the workflow.")
21083
+ })]
21084
+ }),
21085
+ !onCanvas && /* @__PURE__ */ jsx("button", {
21086
+ type: "button",
21087
+ "aria-label": tr("fluidgrids.agentMarks.removeNote", "Remove note"),
21088
+ onClick: () => removeNote(note.id),
21089
+ className: "shrink-0 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
21090
+ children: /* @__PURE__ */ jsx(X, {
21091
+ "aria-hidden": "true",
21092
+ className: "size-3.5"
21093
+ })
21094
+ })
21095
+ ]
21096
+ }, note.id);
21097
+ })
21098
+ })]
21099
+ })
21100
+ });
21101
+ };
21102
+ const CanvasNotesLayer = () => {
21103
+ const workflowKey = useFlowStore((state) => notesKeyOf(state.workflow));
21104
+ const nodeIds = useFlowStore(useShallow((state) => state.workflow.nodes.map((node) => node.id)));
21105
+ const allNotes = useAgentMarksStore((state) => state.notes);
21106
+ const notes = useMemo(() => allNotes.filter((note) => note.workflowKey === workflowKey), [allNotes, workflowKey]);
21107
+ const live = useMemo(() => new Set(nodeIds), [nodeIds]);
21108
+ const byNode = useMemo(() => {
21109
+ const groups = /* @__PURE__ */ new Map();
21110
+ for (const note of notes) {
21111
+ if (!live.has(note.nodeId)) continue;
21112
+ groups.set(note.nodeId, [...groups.get(note.nodeId) ?? [], note]);
21113
+ }
21114
+ return [...groups.entries()];
21115
+ }, [notes, live]);
21116
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
21117
+ /* @__PURE__ */ jsxs(ViewportPortal, { children: [byNode.map(([nodeId, nodeNotes]) => {
21118
+ const openNotes = nodeNotes.filter((note) => !note.resolved);
21119
+ if (openNotes.length === 0) return null;
21120
+ const tone = openNotes.some((note) => note.kind === "fix") ? "fix" : "info";
21121
+ return /* @__PURE__ */ jsx(NodeRing, {
21122
+ nodeId,
21123
+ tone
21124
+ }, nodeId);
21125
+ }), /* @__PURE__ */ jsx(FlashRing, {})] }),
21126
+ byNode.map(([nodeId, nodeNotes]) => /* @__PURE__ */ jsx(NodePins, {
21127
+ nodeId,
21128
+ notes: nodeNotes
21129
+ }, nodeId)),
21130
+ notes.length > 0 && /* @__PURE__ */ jsx(NotesList, {
21131
+ notes,
21132
+ live
21133
+ })
21134
+ ] });
21135
+ };
21136
+
20705
21137
  // src/builder/lib/autosave-runner.ts
20706
21138
  function createAutosaveRunner(deps) {
20707
21139
  let timer;
@@ -27143,8 +27575,8 @@ function EditLeaseBanner() {
27143
27575
  }
27144
27576
 
27145
27577
  // src/builder/components/flow-builder/flow-builder.tsx
27146
- const LazyNodeDetailView = lazy(() => import("./node-detail-view-DRk8Ln5L.js").then((m) => ({ default: m.NodeDetailView })));
27147
- const LazyQuickConfigPanel = lazy(() => import("./quick-config-panel-40_1utiL.js").then((m) => ({ default: m.QuickConfigPanel })));
27578
+ const LazyNodeDetailView = lazy(() => import("./node-detail-view-Dnhqdn8C.js").then((m) => ({ default: m.NodeDetailView })));
27579
+ const LazyQuickConfigPanel = lazy(() => import("./quick-config-panel-C4tSpCrt.js").then((m) => ({ default: m.QuickConfigPanel })));
27148
27580
  const edgeTypes = { deletable: CustomDeletableEdge };
27149
27581
  const edgeOptions = {
27150
27582
  type: "deletable",
@@ -27204,7 +27636,7 @@ const FlowBuilder = ({ readOnly = false }) => {
27204
27636
  const [agentTools, setAgentTools] = useState([]);
27205
27637
  useEffect(() => {
27206
27638
  let cancelled = false;
27207
- import("./agentTools-Dgrdql-W.js").then(({ createBuilderAgentTools }) => {
27639
+ import("./agentTools-D5AW7zHn.js").then(({ createBuilderAgentTools }) => {
27208
27640
  if (cancelled) return;
27209
27641
  setAgentTools(createBuilderAgentTools({ durableFunctionsEnabled: () => paletteGatesRef.current }));
27210
27642
  }).catch((error) => {
@@ -27251,7 +27683,30 @@ const FlowBuilder = ({ readOnly = false }) => {
27251
27683
  }
27252
27684
  return merged;
27253
27685
  }, [nodeTypesVersion, nodes]);
27254
- const { getNodes } = useReactFlow();
27686
+ const { getNodes, fitView } = useReactFlow();
27687
+ const fitViewRef = useRef(fitView);
27688
+ useEffect(() => {
27689
+ fitViewRef.current = fitView;
27690
+ }, [fitView]);
27691
+ const [noteTools, setNoteTools] = useState([]);
27692
+ useEffect(() => {
27693
+ let cancelled = false;
27694
+ import("./canvasNoteTools-BqQTTCll.js").then(({ createCanvasNoteTools }) => {
27695
+ if (cancelled) return;
27696
+ setNoteTools(createCanvasNoteTools({ focusNode: (nodeId) => fitViewRef.current({
27697
+ nodes: [{ id: nodeId }],
27698
+ duration: 400,
27699
+ padding: .6,
27700
+ maxZoom: 1.25
27701
+ }) }));
27702
+ }).catch((error) => {
27703
+ console.error("[FlowBuilder] Assistant note tools failed to load:", error);
27704
+ });
27705
+ return () => {
27706
+ cancelled = true;
27707
+ };
27708
+ }, []);
27709
+ useAgentTools(noteTools);
27255
27710
  const { handleOnEdgeDropConnectEnd, floatingMenuWrapperRef, handleAddConnectedNode } = useAddNodeOnEdgeDrop();
27256
27711
  const deleteKeyCode = useDeleteKeyCode();
27257
27712
  useNodeSelectedEvent();
@@ -27321,7 +27776,7 @@ const FlowBuilder = ({ readOnly = false }) => {
27321
27776
  className: "relative flex-1 rounded-b-xl border border-card-foreground/10 border-t-0 overflow-hidden mx-4 mb-1",
27322
27777
  children: [
27323
27778
  /* @__PURE__ */ jsx(CanvasControls, {}),
27324
- /* @__PURE__ */ jsx(ReactFlow, {
27779
+ /* @__PURE__ */ jsxs(ReactFlow, {
27325
27780
  proOptions: { hideAttribution: true },
27326
27781
  onInit: ({ fitView }) => fitView({
27327
27782
  minZoom: 1,
@@ -27354,7 +27809,7 @@ const FlowBuilder = ({ readOnly = false }) => {
27354
27809
  maxZoom: 1
27355
27810
  },
27356
27811
  defaultEdgeOptions: edgeOptions,
27357
- children: /* @__PURE__ */ jsx(Background, { gap: 24 })
27812
+ children: [/* @__PURE__ */ jsx(Background, { gap: 24 }), /* @__PURE__ */ jsx(CanvasNotesLayer, {})]
27358
27813
  }),
27359
27814
  /* @__PURE__ */ jsx(FluidGridsMark, {}),
27360
27815
  nodes.length === 0 && !editingBlocked && /* @__PURE__ */ jsx("div", {
@@ -27544,4 +27999,4 @@ const FlowBuilderPage = ({ workflow, readOnly = false }) => {
27544
27999
  ] });
27545
28000
  };
27546
28001
 
27547
- export { PopoverTrigger$1 as $, DialogTitle$1 as A, NodeEditorTabBody as B, DialogClose as C, isDurableFunctionNodeType as Ct, DialogHeader$1 as D, DialogFooter$1 as E, buildRequiredNodeTypes as F, runnerInstallCommand as G, RunOnSection as H, useImportWorkflowTemplate as I, NodeParamsForm as J, useRunnerActions as K, useWorkflowTemplates as L, computeAttachPosition as M, useTemplateImportFlow as N, DialogOverlay as O, buildNodeCheck as P, PopoverContent$1 as Q, useInstalledNodes as R, Dialog$1 as S, filterPaletteNodes as St, DialogDescription$1 as T, isRunControlNodeType as Tt, RunnerStatusPill as U, NodeSettingsPanel as V, RUNNER_INSTALL_SCRIPT_URL as W, NDV_Z_INDEX as X, useGlobalStoreClient as Y, Popover$1 as Z, TableView as _, CardTitle$1 as _t, useAiWorkflowTemplates as a, SCHEDULE_NODE_TYPE as at, Badge$1 as b, RUN_CONTROL_NODE_TYPES as bt, RunsUsageMeter as c, TabButton as ct, readRunsQuota as d, FluidGridsMark as dt, CodeEditorField as et, runsQuotaLevel as f, Card$1 as ft, JsonView as g, CardHeader$1 as gt, useWorkflowReloadStore as h, CardFooter as ht, enrichDraftNodes as i, useNodeEditorTab as it, DialogTrigger$1 as j, DialogPortal as k, RunsUsageMeterView as l, NodeInputFields as lt, releaseLeaseKeptForReload as m, CardDescription$1 as mt, AI_DRAFT_MAX_PROMPT_LENGTH as n, ConnectionFormDialog as nt, BUILDER_FULLSCREEN_ATTRIBUTE as o, SchedulePanel as ot, summarizeRunsQuota as p, CardContent$1 as pt, useRunners as q, draftToCanvas as r, ReadOnlyFieldset as rt, BUILDER_FULLSCREEN_EVENT as s, NodeEditorTabs as st, FlowBuilderPage as t, ConnectionSelector as tt, useRunsQuota as u, useNodeSelectedEvent as ut, TemplateCard as v, Input$1 as vt, DialogContent$1 as w, isNodeTypeHiddenFromPalette as wt, badgeVariants as x, WORKFLOW_TEST_NODE_TYPES as xt, templatePreview as y, DURABLE_FUNCTION_NODE_TYPES as yt, NodeExecutionActions as z };
28002
+ export { NodeParamsForm as $, DialogFooter$1 as A, isRunControlNodeType as At, getWorkflowNodeTypes as B, templatePreview as C, Input$1 as Ct, DialogClose as D, filterPaletteNodes as Dt, Dialog$1 as E, WORKFLOW_TEST_NODE_TYPES as Et, DialogTrigger$1 as F, NodeEditorTabBody as G, useWorkflowTemplates as H, computeAttachPosition as I, RunnerStatusPill as J, NodeSettingsPanel as K, useTemplateImportFlow as L, DialogOverlay as M, DialogPortal as N, DialogContent$1 as O, isDurableFunctionNodeType as Ot, DialogTitle$1 as P, useRunners as Q, buildNodeCheck as R, TemplateCard as S, CardTitle$1 as St, badgeVariants as T, RUN_CONTROL_NODE_TYPES as Tt, useInstalledNodes as U, useImportWorkflowTemplate as V, NodeExecutionActions as W, runnerInstallCommand as X, RUNNER_INSTALL_SCRIPT_URL as Y, useRunnerActions as Z, resetAgentMarks as _, Card$1 as _t, useAiWorkflowTemplates as a, CodeEditorField as at, JsonView as b, CardFooter as bt, RunsUsageMeter as c, ReadOnlyFieldset as ct, readRunsQuota as d, SchedulePanel as dt, useGlobalStoreClient as et, runsQuotaLevel as f, NodeEditorTabs as ft, notesKeyOf as g, FluidGridsMark as gt, useWorkflowReloadStore as h, useNodeSelectedEvent as ht, enrichDraftNodes as i, PopoverTrigger$1 as it, DialogHeader$1 as j, DialogDescription$1 as k, isNodeTypeHiddenFromPalette as kt, RunsUsageMeterView as l, useNodeEditorTab as lt, releaseLeaseKeptForReload as m, NodeInputFields as mt, AI_DRAFT_MAX_PROMPT_LENGTH as n, Popover$1 as nt, BUILDER_FULLSCREEN_ATTRIBUTE as o, ConnectionSelector as ot, summarizeRunsQuota as p, TabButton as pt, RunOnSection as q, draftToCanvas as r, PopoverContent$1 as rt, BUILDER_FULLSCREEN_EVENT as s, ConnectionFormDialog as st, FlowBuilderPage as t, NDV_Z_INDEX as tt, useRunsQuota as u, SCHEDULE_NODE_TYPE as ut, useAgentMarksStore as v, CardContent$1 as vt, Badge$1 as w, DURABLE_FUNCTION_NODE_TYPES as wt, TableView as x, CardHeader$1 as xt, toPlainText as y, CardDescription$1 as yt, buildRequiredNodeTypes as z };