@burdenoff/fluidgrids-fe-sdk 2026.1112.7 → 2026.1112.9

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 (86) hide show
  1. package/dist/{AiStudioPage-QGTFaVpx.js → AiStudioPage-DfoV1Olc.js} +5 -5
  2. package/dist/ApprovalInboxPage-Bb8H9ruw.js +1317 -0
  3. package/dist/{DataSinkDashboardPage-kyzcjpzL.js → DataSinkDashboardPage-Dt3xTBb8.js} +1 -1
  4. package/dist/{DataTablesPage-0U89KNBt.js → DataTablesPage-Zz23QZQp.js} +6 -4
  5. package/dist/Disclosure-DQiUKrqd.js +41 -0
  6. package/dist/{EntityTagPicker-dxfzv8aO.js → EntityTagPicker-CuZAMby_.js} +1 -1
  7. package/dist/{ExecutionDetailPage-D4Mdbzb2.js → ExecutionDetailPage-CcUxFexZ.js} +15 -14
  8. package/dist/{ExecutionsListPage-g_SPxAGX.js → ExecutionsListPage-JpByw6nA.js} +11 -9
  9. package/dist/{FailureTriagePanel-D4lJ4zqQ.js → FailureTriagePanel-BjatYyAa.js} +1 -1
  10. package/dist/{FluidGridsProvider-B1hgCbxG.js → FluidGridsProvider-TQ-aDPtn.js} +3 -3
  11. package/dist/{HistoryPage-C7NwkVNC.js → HistoryPage-s9pl5gyl.js} +2 -2
  12. package/dist/{InsightsPage-D9B_qW8e.js → InsightsPage-CtjJCky-.js} +4 -4
  13. package/dist/{MissingNodesInstallModal-BkkgeFeN.js → MissingNodesInstallModal-olEc52p2.js} +1 -1
  14. package/dist/{NodesPage-BEeUbIts.js → NodesPage-BSNpOUMX.js} +2 -2
  15. package/dist/OpenTemplateAsCodeButton-Dh3ee2OR.js +94 -0
  16. package/dist/PaidTemplateConfirm-DjmVi_m-.js +28 -0
  17. package/dist/{RunActionsMenu-DmO14hEE.js → RunActionsMenu-BE_1r3bF.js} +1 -50
  18. package/dist/{RunnersPage-CvAPHdfU.js → RunnersPage-CAt4YDhB.js} +2 -1
  19. package/dist/{SettingsPage-k21044-Z.js → SettingsPage-ywyjIAL8.js} +6 -6
  20. package/dist/{TagFilterSelect-CqhxcDjh.js → TagFilterSelect-DcusTt1p.js} +1 -1
  21. package/dist/{TaskQueuesPage-B86p51gH.js → TaskQueuesPage-C2dkenVF.js} +2 -2
  22. package/dist/TechnicalDetails-D4xGy201.js +19 -0
  23. package/dist/{TemplateCard-DanOe65C.js → TemplateCard--WYJZlA_.js} +293 -36
  24. package/dist/{TemplateConfigurationModal-CeMpPEhT.js → TemplateConfigurationModal-cd0-QVMZ.js} +20 -11
  25. package/dist/{TemplateDetailPanel-DnoA6mC3.js → TemplateDetailPanel-C-2JuXg6.js} +1 -1
  26. package/dist/TemplatePreview-BCnGjMuN.js +646 -0
  27. package/dist/{TemplatesPage-C7f2xq12.js → TemplatesPage-u7bXJYfv.js} +377 -138
  28. package/dist/{VariablesPage-BtoLjmUA.js → VariablesPage-CPL34WBc.js} +2 -358
  29. package/dist/{VersionDiffDialog-pLpG_I1r.js → VersionDiffDialog-c27yet_-.js} +1 -1
  30. package/dist/{WorkflowBuilder-Aslfa2vj.js → WorkflowBuilder-BWxBmBoP.js} +3 -3
  31. package/dist/{WorkflowBuilder-DY1_KJiV.js → WorkflowBuilder-DxAAPHsn.js} +8 -8
  32. package/dist/{WorkflowProposalCard-BjYAZH3q.js → WorkflowProposalCard-BvmJ5ea8.js} +1 -1
  33. package/dist/{WorkflowSearchRedirect-iPQBL6WO.js → WorkflowSearchRedirect-BGKduhfZ.js} +1 -1
  34. package/dist/{WorkflowShareDialog-DB-qJLB2.js → WorkflowShareDialog-BRwTF9QB.js} +1 -1
  35. package/dist/{WorkflowsList-rOxlI49U.js → WorkflowsList-Bocag4TX.js} +9 -8
  36. package/dist/{agentTools-B8gnHFVM.js → agentTools-JHtYGgd7.js} +3 -3
  37. package/dist/{api-JpNuowX4.js → api-Dw8nfVRD.js} +1 -1
  38. package/dist/{api-Dvitj1aT.js → api-m8hX_l2k.js} +1 -1
  39. package/dist/{canvasNoteTools-Cj7MvVDH.js → canvasNoteTools-B2ouyIbj.js} +2 -2
  40. package/dist/{HumanRequestCard-lbWDpWd7.js → channel-CZ70oI81.js} +5 -228
  41. package/dist/{code-editor-field-yJZEDJBi.js → code-editor-field-CWGZyNGM.js} +1 -1
  42. package/dist/executions.js +2 -2
  43. package/dist/{feature-flags-DM5myWaD.js → feature-flags-B_b8PJ6Z.js} +63 -3
  44. package/dist/flags.d.ts +1 -1
  45. package/dist/flags.js +1 -1
  46. package/dist/{flow-builder-dvqpKs8d.js → flow-builder-DzKPrW2r.js} +535 -313
  47. package/dist/{graphql-operation-types-z2oXfQvM.js → graphql-operation-types-DZVhCBEi.js} +250 -1
  48. package/dist/history.js +1 -1
  49. package/dist/{index-BcdtKBM-.d.ts → index-B4g88tCa.d.ts} +15 -1
  50. package/dist/{index-DNo9aFUT.d.ts → index-CGCAcRKo.d.ts} +32 -2
  51. package/dist/index.d.ts +3 -3
  52. package/dist/index.js +38 -38
  53. package/dist/{node-detail-view-ZnJ3Hvvo.js → node-detail-view-DNilUGcx.js} +9 -9
  54. package/dist/{node-editor-tab-body-BuiIr7VD.js → node-editor-tab-body-XBedxkpN.js} +3 -3
  55. package/dist/{node-endpoint-configs-typvqw2c.js → node-endpoint-configs-BI7zc8eM.js} +1 -1
  56. package/dist/nodes.js +1 -1
  57. package/dist/observability.d.ts +1 -1
  58. package/dist/observability.js +2 -2
  59. package/dist/{popover-mexhIFP3.js → popover-BC4r84K0.js} +3 -3
  60. package/dist/{quick-config-panel-EV66lJOM.js → quick-config-panel-BPmrhIOb.js} +3 -3
  61. package/dist/{schedule-panel-BeLfDNFh.js → schedule-panel-DJN1TDQH.js} +8 -8
  62. package/dist/settings.js +1 -1
  63. package/dist/{sidebar-node-editor-D85Moj5d.js → sidebar-node-editor-DKdfcd2r.js} +3 -3
  64. package/dist/templates.js +2 -2
  65. package/dist/{useAgentDefinitions-Uy6nhpKN.js → useAgentDefinitions-B4sw7bLw.js} +1 -1
  66. package/dist/{useDurableParity-04-5PTk2.js → useDurableParity-B3h8jqP0.js} +2 -2
  67. package/dist/{useEntityTags-D5C0tdLs.js → useEntityTags-BpDeLhzV.js} +1 -1
  68. package/dist/{useExecutions-DJXtbA15.js → useExecutions-BbkBSYpS.js} +2 -2
  69. package/dist/{useObservability-Dfgh4yjv.js → useObservability-BASo2sA3.js} +1 -1
  70. package/dist/{useRunHistory-BZQ3t68Q.js → useRunHistory-C3Pv_SJW.js} +2 -2
  71. package/dist/useVariables-cI8uLwye.js +361 -0
  72. package/dist/useWorkflowCodeExport-k5Emm4ZS.js +226 -0
  73. package/dist/{useWorkflows-CP-d3ygY.js → useWorkflows-CRUlQBd3.js} +2 -2
  74. package/dist/workflow-builder.js +4 -4
  75. package/dist/workflow-code-transfer-Cshsgf2H.js +375 -0
  76. package/dist/workflow-host.d.ts +1 -1
  77. package/dist/workflow-host.js +1 -1
  78. package/dist/workflow-pages.d.ts +1 -1
  79. package/dist/workflow-pages.js +19 -19
  80. package/dist/workflow-transfer-CQnQC-ke.js +104 -0
  81. package/dist/workflows-list.js +1 -1
  82. package/package.json +1 -1
  83. package/dist/ApprovalInboxPage-QAJim81n.js +0 -559
  84. package/dist/QuotaExhaustedDialog-Bj7AG_0y.js +0 -55
  85. package/dist/TemplatePreview-DY4GxnfC.js +0 -231
  86. package/dist/useWorkflowCodeExport-D0zapXXm.js +0 -643
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { _ as throwIfResultError, n as getFluidGridsClient, x as getFluidGridsConfig } from "./client-C9XSzSXr.js";
3
3
  import { i as getGlobalStoreClient } from "./node-registry-refresh-Dy0uRg9f.js";
4
- import { A as StoreNodeProductsByTypeDocument, C as GetMyNodeInstallationsDocument, D as InstallWorkflowNodeDocument, c as ensureInstalledNodeTypesLoaded, d as ensureRegistryNodeTypesLoaded, i as AVAILABLE_NODES, o as NODE_CONFIGS, t as FluidGridsContext, vn as useFluidGridsAnalytics, x as BrowseWorkflowTemplatesDocument } from "./FluidGridsProvider-B1hgCbxG.js";
4
+ import { A as StoreNodeProductsByTypeDocument, C as GetMyNodeInstallationsDocument, D as InstallWorkflowNodeDocument, c as ensureInstalledNodeTypesLoaded, d as ensureRegistryNodeTypesLoaded, i as AVAILABLE_NODES, o as NODE_CONFIGS, t as FluidGridsContext, vn as useFluidGridsAnalytics, x as BrowseWorkflowTemplatesDocument } from "./FluidGridsProvider-TQ-aDPtn.js";
5
5
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
6
+ import { h as usePhaseDFlags } from "./feature-flags-B_b8PJ6Z.js";
6
7
  import { c as isQuotaExhaustedFailure } from "./quota-Cdt8yz7N.js";
7
8
  import { a as emitTemplateUsed, s as emitWorkflowCreated, t as emitNodeInstalled } from "./fluidgridsEvents-Dm5CaHtr.js";
8
9
  import { t as cn } from "./cn-QqidFIgr.js";
9
10
  import { t as Button } from "./button-DG7y_FVJ.js";
11
+ import { b as utf8Length } from "./workflow-code-transfer-Cshsgf2H.js";
10
12
  import { i as SdkPermissionButton, t as FLUIDGRIDS_PERMISSIONS } from "./constants-dcxg6zG6.js";
13
+ import { n as useNavigationV2 } from "./useNavigationV2-C2WeqN7d.js";
11
14
  import React, { Suspense, forwardRef, lazy, useCallback, useContext, useMemo, useState } from "react";
12
15
  import { useQuery } from "@apollo/client/react";
13
16
  import { gql } from "@apollo/client/core";
@@ -17,6 +20,7 @@ import { toast } from "sonner";
17
20
  import { useEventBus } from "@burdenoff/fe-libs/shared/events";
18
21
  import { cva } from "class-variance-authority";
19
22
  import { Download, Eye, Sparkles, Star } from "lucide-react";
23
+ import JSZip from "jszip";
20
24
 
21
25
  // src/feature-flags/durable-nodes.ts
22
26
  /**
@@ -567,7 +571,7 @@ function replaceVariables(str, inputs) {
567
571
  }
568
572
 
569
573
  // src/utils/template-import-flow.ts
570
- function isRecord(value) {
574
+ function isRecord$1(value) {
571
575
  return typeof value === "object" && value !== null && !Array.isArray(value);
572
576
  }
573
577
  function graphOf(value) {
@@ -585,13 +589,13 @@ function graphOf(value) {
585
589
  * manifest holds neither.
586
590
  */
587
591
  function manifestTemplateGraph(manifest) {
588
- if (!isRecord(manifest)) return null;
592
+ if (!isRecord$1(manifest)) return null;
589
593
  const direct = graphOf(manifest);
590
594
  if (direct) return direct;
591
595
  if (typeof manifest.jsonContent !== "string") return null;
592
596
  try {
593
597
  const parsed = JSON.parse(manifest.jsonContent);
594
- return isRecord(parsed) ? graphOf(parsed) : null;
598
+ return isRecord$1(parsed) ? graphOf(parsed) : null;
595
599
  } catch {
596
600
  return null;
597
601
  }
@@ -630,9 +634,9 @@ async function resolveTemplateContent(product, requiredNodeTypesOf) {
630
634
  */
631
635
  function unwrapTemplateDefinition(definitionJson) {
632
636
  const data = definitionJson.data;
633
- if (!isRecord(data)) return definitionJson;
634
- const nested = isRecord(data.workflowDefinition) ? data.workflowDefinition.definitionJson : void 0;
635
- return isRecord(nested) ? nested : definitionJson;
637
+ if (!isRecord$1(data)) return definitionJson;
638
+ const nested = isRecord$1(data.workflowDefinition) ? data.workflowDefinition.definitionJson : void 0;
639
+ return isRecord$1(nested) ? nested : definitionJson;
636
640
  }
637
641
  /**
638
642
  * Whether "Use this template" must stop at the configuration dialog before
@@ -673,6 +677,201 @@ async function installNodesInOrder(nodes, install, onProgress, onFailure) {
673
677
  return installed;
674
678
  }
675
679
 
680
+ // src/utils/template-code.ts
681
+ /**
682
+ * Templates as code (BOFF-9907, PLAN-D D2 and C-TEMPLATE).
683
+ *
684
+ * A store WORKFLOW template may carry its project files in `content.sources`
685
+ * next to the `definitionJson` every client installs. These helpers:
686
+ *
687
+ * - read a template's code defensively (store content is free JSON);
688
+ * - package it as a project zip for *Open as code*;
689
+ * - shape it as the `source` of `importWorkflowTemplate` for a tier-2
690
+ * install;
691
+ * - check code before it is published, with the same rules global-store-svc
692
+ * applies on every write (DD3 size guard, path rules, DD7), so a refusal is
693
+ * explained before anything is sent.
694
+ *
695
+ * Nothing here lifts or lowers code: the service does that.
696
+ */
697
+ /** DD3: the largest `content.sources` the store accepts, as UTF-8 JSON. */
698
+ const TEMPLATE_SOURCES_MAX_BYTES = 1048576;
699
+ /** DD3: the most files `content.sources` may hold. */
700
+ const TEMPLATE_SOURCES_MAX_FILES = 64;
701
+ /** The only package a tier-2 template's sources may import (C-TEMPLATE). */
702
+ const TEMPLATE_RUNTIME_PACKAGE = "@fluidgrids/workflows";
703
+ var TemplateCodeError = class extends Error {
704
+ problem;
705
+ constructor(problem) {
706
+ super(`Template code refused: ${problem.code}`);
707
+ this.problem = problem;
708
+ this.name = "TemplateCodeError";
709
+ }
710
+ };
711
+ function isRecord(value) {
712
+ return typeof value === "object" && value !== null && !Array.isArray(value);
713
+ }
714
+ /**
715
+ * Whether a source path is a plain relative POSIX path, as global-store-svc
716
+ * requires: not empty, not absolute, no drive letter, no backslash, no `.`,
717
+ * `..` or empty segment, and no control character.
718
+ */
719
+ function isSafeTemplateSourcePath(path) {
720
+ if (!path || path.startsWith("/") || path.includes("\\")) return false;
721
+ if (/^[A-Za-z]:/.test(path)) return false;
722
+ for (const character of path) {
723
+ const code = character.charCodeAt(0);
724
+ if (code < 32 || code === 127) return false;
725
+ }
726
+ return path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
727
+ }
728
+ /** Bytes the store counts for a sources list: its UTF-8 JSON. */
729
+ function templateSourcesBytes(sources) {
730
+ return utf8Length(JSON.stringify(sources));
731
+ }
732
+ /**
733
+ * Module specifiers a source imports: static and dynamic `import`, `export
734
+ * … from` and `require`. Comments are not stripped, so a commented-out import
735
+ * is counted too; that only makes the check stricter.
736
+ */
737
+ function importedSpecifiers(source) {
738
+ const found = [];
739
+ for (const pattern of [
740
+ /\b(?:import|export)\s[^'"`;]*?\bfrom\s*['"]([^'"]+)['"]/g,
741
+ /\bimport\s*['"]([^'"]+)['"]/g,
742
+ /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
743
+ /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g
744
+ ]) for (const match of source.matchAll(pattern)) if (match[1] !== void 0) found.push(match[1]);
745
+ return found;
746
+ }
747
+ /** A specifier a tier-2 source may use: a project file or the SDK itself. */
748
+ function isAllowedSpecifier(specifier) {
749
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier === "@fluidgrids/workflows";
750
+ }
751
+ /**
752
+ * Checks sources against the store's rules (and, for tier 2, the import
753
+ * rule). Throws {@link TemplateCodeError} with the first problem found.
754
+ */
755
+ function assertTemplateSources(sources, tier) {
756
+ if (sources.length === 0) throw new TemplateCodeError({ code: "no-files" });
757
+ if (sources.length > 64) throw new TemplateCodeError({
758
+ code: "too-many-files",
759
+ limit: 64
760
+ });
761
+ const seen = /* @__PURE__ */ new Set();
762
+ for (const file of sources) {
763
+ if (!isSafeTemplateSourcePath(file.path)) throw new TemplateCodeError({
764
+ code: "unsafe-path",
765
+ path: file.path
766
+ });
767
+ const folded = file.path.toLowerCase();
768
+ if (seen.has(folded)) throw new TemplateCodeError({
769
+ code: "duplicate-path",
770
+ path: file.path
771
+ });
772
+ seen.add(folded);
773
+ if (tier === "tier2" && /\.[cm]?[jt]s$/.test(file.path)) {
774
+ const foreign = importedSpecifiers(file.content).find((specifier) => !isAllowedSpecifier(specifier));
775
+ if (foreign !== void 0) throw new TemplateCodeError({
776
+ code: "foreign-import",
777
+ path: file.path,
778
+ specifier: foreign
779
+ });
780
+ }
781
+ }
782
+ if (templateSourcesBytes(sources) > 1048576) throw new TemplateCodeError({
783
+ code: "too-large",
784
+ limitBytes: TEMPLATE_SOURCES_MAX_BYTES
785
+ });
786
+ }
787
+ /**
788
+ * A template's code, or null when it has none or what it has is not usable
789
+ * (not a list of files, unsafe paths, an unknown format or tier). The store
790
+ * refuses such content on write; reading defensively keeps a template written
791
+ * before the guard, or by another client, from breaking the page.
792
+ */
793
+ function readTemplateCode(content) {
794
+ if (!content || !Array.isArray(content.sources)) return null;
795
+ const files = [];
796
+ for (const entry of content.sources) {
797
+ if (!isRecord(entry) || typeof entry.path !== "string" || typeof entry.content !== "string") return null;
798
+ files.push({
799
+ path: entry.path,
800
+ contentType: typeof entry.contentType === "string" ? entry.contentType : contentTypeOf(entry.path),
801
+ content: entry.content
802
+ });
803
+ }
804
+ const format = content.sourceFormat ?? "TYPESCRIPT";
805
+ if (format !== "TYPESCRIPT" && format !== "PYTHON") return null;
806
+ const tier = content.codeTier ?? "tier1";
807
+ if (tier !== "tier1" && tier !== "tier2") return null;
808
+ try {
809
+ assertTemplateSources(files, "tier1");
810
+ } catch {
811
+ return null;
812
+ }
813
+ return {
814
+ files,
815
+ format,
816
+ tier,
817
+ irHash: typeof content.sourceIrHash === "string" ? content.sourceIrHash : null
818
+ };
819
+ }
820
+ /** The content type a project file is stored with, by its extension. */
821
+ function contentTypeOf(path) {
822
+ if (/\.[cm]?ts$/.test(path)) return "text/typescript";
823
+ if (/\.[cm]?js$/.test(path)) return "text/javascript";
824
+ if (path.endsWith(".py")) return "text/x-python";
825
+ if (path.endsWith(".json")) return "application/json";
826
+ return "text/plain";
827
+ }
828
+ /** A file name segment that is safe on every OS. */
829
+ function safeSegment(value) {
830
+ return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
831
+ }
832
+ /** The zip *Open as code* downloads for a template. */
833
+ function templateArchiveName(templateName) {
834
+ return `${safeSegment(templateName) || "template"}.code.zip`;
835
+ }
836
+ /**
837
+ * *Open as code*: the template's project files as one zip that keeps their
838
+ * paths, so unpacking it gives a FluidGrids project (`fluidgrids push`
839
+ * reads it as it is).
840
+ */
841
+ async function buildTemplateCodeDownload(templateName, code) {
842
+ const zip = new JSZip();
843
+ for (const file of code.files) zip.file(file.path, file.content);
844
+ return {
845
+ filename: templateArchiveName(templateName),
846
+ blob: await zip.generateAsync({
847
+ type: "blob",
848
+ mimeType: "application/zip"
849
+ })
850
+ };
851
+ }
852
+ /** The `source` of an `importWorkflowTemplate` for a tier-2 install. */
853
+ function templateImportSource(code) {
854
+ return {
855
+ format: code.format,
856
+ files: code.files.map((file) => ({
857
+ path: file.path,
858
+ content: file.content
859
+ }))
860
+ };
861
+ }
862
+ /**
863
+ * What `importWorkflowTemplate` installs a template from (PLAN-D D2): its
864
+ * sources for a tier-2 TypeScript template while code-first, durable-functions and
865
+ * code-templates are on (`codeTemplatesDurable`), else null, and the caller
866
+ * sends `definitionJson` exactly as before (tier 1, Python, no code, or a
867
+ * gate off).
868
+ */
869
+ function templateInstallSource(content, codeTemplatesDurable) {
870
+ if (!codeTemplatesDurable) return null;
871
+ const code = readTemplateCode(content);
872
+ return code?.tier === "tier2" && code.format === "TYPESCRIPT" ? templateImportSource(code) : null;
873
+ }
874
+
676
875
  // src/hooks/store/useImportWorkflowTemplate.ts
677
876
  const IMPORT_WORKFLOW_TEMPLATE = gql`
678
877
  mutation ImportWorkflowTemplate($input: ImportWorkflowTemplateInput!) {
@@ -739,6 +938,7 @@ function useImportWorkflowTemplate() {
739
938
  const context = useContext(FluidGridsContext);
740
939
  const workspaceId = context?.workspaceId ?? getFluidGridsConfig().workspaceId;
741
940
  const bus = useEventBus();
941
+ const { codeTemplatesDurable } = usePhaseDFlags();
742
942
  const getTemplateDetail = useCallback(async (templateId) => {
743
943
  return fetchTemplateDetail(templateId);
744
944
  }, [fetchTemplateDetail]);
@@ -771,8 +971,9 @@ function useImportWorkflowTemplate() {
771
971
  try {
772
972
  const content = options?.content ?? parseTemplateContent(template);
773
973
  if (!content) throw new Error("Template does not contain valid workflow definition data");
974
+ const installSource = templateInstallSource(content, codeTemplatesDurable);
774
975
  let definitionJson = unwrapTemplateDefinition(deepClone(content.definitionJson));
775
- if (options?.templateInputs) definitionJson = applyTemplateInputs(definitionJson, options.templateInputs);
976
+ if (options?.templateInputs && !installSource) definitionJson = applyTemplateInputs(definitionJson, options.templateInputs);
776
977
  const config = getFluidGridsConfig();
777
978
  const importContext = {
778
979
  ...options?.context || {},
@@ -783,7 +984,7 @@ function useImportWorkflowTemplate() {
783
984
  const importResult = throwIfResultError(await client.mutate({
784
985
  mutation: IMPORT_WORKFLOW_TEMPLATE,
785
986
  variables: { input: {
786
- definitionJson,
987
+ ...installSource ? { source: installSource } : { definitionJson },
787
988
  name: options?.name || content.defaultWorkflowName || template.name,
788
989
  description: options?.description || template.description,
789
990
  sourceTemplateId: template.id,
@@ -830,7 +1031,7 @@ function useImportWorkflowTemplate() {
830
1031
  workspaceId,
831
1032
  workflowId: importResult.workflow.id,
832
1033
  source: "template",
833
- definition: definitionJson
1034
+ definition: installSource ? importResult.workflow.definitionJson : definitionJson
834
1035
  });
835
1036
  emitTemplateUsed(bus, {
836
1037
  workspaceId,
@@ -864,7 +1065,8 @@ function useImportWorkflowTemplate() {
864
1065
  globalClient,
865
1066
  workspaceId,
866
1067
  context?.tenantId,
867
- bus
1068
+ bus,
1069
+ codeTemplatesDurable
868
1070
  ]),
869
1071
  /** Whether we're fetching template detail */
870
1072
  fetchingDetail,
@@ -1080,7 +1282,7 @@ async function dismissToastWhenShown(id) {
1080
1282
  }
1081
1283
 
1082
1284
  // src/hooks/store/useTemplateImportFlow.tsx
1083
- const LazyTemplateConfigurationModal = lazy(() => import("./TemplateConfigurationModal-CeMpPEhT.js").then((m) => ({ default: m.TemplateConfigurationModal })));
1285
+ const LazyTemplateConfigurationModal = lazy(() => import("./TemplateConfigurationModal-cd0-QVMZ.js").then((m) => ({ default: m.TemplateConfigurationModal })));
1084
1286
  /**
1085
1287
  * "Use this template", the same everywhere it is offered: the builder's
1086
1288
  * template panel, the Templates page and the template preview page
@@ -1139,10 +1341,10 @@ function useTemplateImportFlow({ onImported, onQuotaExhausted }) {
1139
1341
  if (result.success && result.workflow) {
1140
1342
  toast.success(tr("fluidgrids.builder.desktopSidebar.workflowCreatedToast", "Workflow created from template!"), {
1141
1343
  id: "template-import",
1142
- description: `"${result.workflow.name}" is ready to edit`
1344
+ description: tr("fluidgrids.pages.templatePreview.importSuccessDescription", "\"{{name}}\" is ready to edit", { name: result.workflow.name })
1143
1345
  });
1144
1346
  if ((content.requiredCredentialTypes?.length ?? 0) > 0) toast.info(tr("fluidgrids.builder.desktopSidebar.credentialsNeededToast", "Credentials needed"), {
1145
- description: `Configure ${content.requiredCredentialTypes?.join(", ")} in the Credentials page`,
1347
+ description: tr("fluidgrids.builder.desktopSidebar.credentialsNeededDescription", "Configure {{types}} in the Credentials page", { types: (content.requiredCredentialTypes ?? []).join(", ") }),
1146
1348
  duration: 6e3
1147
1349
  });
1148
1350
  onImported(result.workflow);
@@ -1286,12 +1488,78 @@ function templatePreview(template) {
1286
1488
  };
1287
1489
  }
1288
1490
 
1491
+ // src/utils/template-price.ts
1492
+ /**
1493
+ * The price shown on a store template card and in the preview.
1494
+ *
1495
+ * USD (and a template with no currency) keeps the plain "$12" form the cards
1496
+ * have always shown; any other currency is written with its own symbol, so a
1497
+ * template priced in rupees or euros is not shown with a dollar sign.
1498
+ */
1499
+ const formatters = /* @__PURE__ */ new Map();
1500
+ function formatterFor(currency) {
1501
+ const cached = formatters.get(currency);
1502
+ if (cached) return cached;
1503
+ try {
1504
+ const created = new Intl.NumberFormat(void 0, {
1505
+ style: "currency",
1506
+ currency,
1507
+ maximumFractionDigits: 2
1508
+ });
1509
+ formatters.set(currency, created);
1510
+ return created;
1511
+ } catch {
1512
+ return null;
1513
+ }
1514
+ }
1515
+ function formatTemplatePrice(price, currency) {
1516
+ const code = currency?.trim().toUpperCase() || "USD";
1517
+ if (code === "USD") return `$${price?.toString() || "0"}`;
1518
+ const formatter = formatterFor(code);
1519
+ return formatter ? formatter.format(price ?? 0) : `${price?.toString() || "0"} ${code}`;
1520
+ }
1521
+ /**
1522
+ * A template that costs money and has not been bought yet: "Use this
1523
+ * template" asks first, naming the price (Navigation v2, BOFF-11848).
1524
+ */
1525
+ function isPaidTemplate(template) {
1526
+ return !!template.pricingModel && template.pricingModel !== "FREE" && (template.price ?? 0) > 0 && template.isPurchased !== true;
1527
+ }
1528
+
1289
1529
  // src/builder/components/flow-builder/components/templates/TemplateCard.tsx
1290
1530
  function formatNumber(count) {
1291
1531
  if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
1292
1532
  if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
1293
1533
  return count.toString();
1294
1534
  }
1535
+ function PriceBadge({ template }) {
1536
+ const tr = useTr();
1537
+ return template.pricingModel === "FREE" ? /* @__PURE__ */ jsx(Badge, {
1538
+ variant: "secondary",
1539
+ className: "bg-status-success-bg text-action-primary-text border-0",
1540
+ children: tr("fluidgrids.builder.templateCard.freeBadge", "Free")
1541
+ }) : /* @__PURE__ */ jsx(Badge, {
1542
+ variant: "default",
1543
+ className: "bg-status-info-bg text-action-primary-text border-0",
1544
+ children: formatTemplatePrice(template.price, template.currency)
1545
+ });
1546
+ }
1547
+ /** Downloads and views (today's card; Navigation v2 shows them on the preview). */
1548
+ function UsageCounts({ template }) {
1549
+ const tr = useTr();
1550
+ return /* @__PURE__ */ jsxs("div", {
1551
+ className: "flex items-center gap-3",
1552
+ children: [/* @__PURE__ */ jsxs("div", {
1553
+ className: "flex items-center gap-1",
1554
+ title: tr("fluidgrids.builder.templateCard.downloadsTooltip", "Downloads"),
1555
+ children: [/* @__PURE__ */ jsx(Download, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: formatNumber(template.downloads || 0) })]
1556
+ }), /* @__PURE__ */ jsxs("div", {
1557
+ className: "flex items-center gap-1",
1558
+ title: tr("fluidgrids.builder.templateCard.viewsTooltip", "Views"),
1559
+ children: [/* @__PURE__ */ jsx(Eye, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: formatNumber(template.viewCount || 0) })]
1560
+ })]
1561
+ });
1562
+ }
1295
1563
  /**
1296
1564
  * Template card component inspired by ComfyUI's template showcase
1297
1565
  * Features:
@@ -1301,9 +1569,14 @@ function formatNumber(count) {
1301
1569
  * - Stats (downloads, views, rating)
1302
1570
  * - Featured badge
1303
1571
  * - Category/tags
1572
+ *
1573
+ * Navigation v2 (BOFF-11848, the Templates gallery only): the download and
1574
+ * view counts move to the template preview. The builder's template picker
1575
+ * turns the switch off for its cards (see TemplatesGrid), so it stays as is.
1304
1576
  */
1305
1577
  function TemplateCard({ template, onClick, onUseTemplate }) {
1306
1578
  const tr = useTr();
1579
+ const v2 = useNavigationV2();
1307
1580
  const [imageError, setImageError] = React.useState(false);
1308
1581
  const preview = templatePreview(template);
1309
1582
  const previewImage = preview.kind === "image" ? preview.src : void 0;
@@ -1346,15 +1619,7 @@ function TemplateCard({ template, onClick, onUseTemplate }) {
1346
1619
  }),
1347
1620
  /* @__PURE__ */ jsx("div", {
1348
1621
  className: "absolute top-2 left-2",
1349
- children: template.pricingModel === "FREE" ? /* @__PURE__ */ jsx(Badge, {
1350
- variant: "secondary",
1351
- className: "bg-status-success-bg text-action-primary-text border-0",
1352
- children: tr("fluidgrids.builder.templateCard.freeBadge", "Free")
1353
- }) : /* @__PURE__ */ jsxs(Badge, {
1354
- variant: "default",
1355
- className: "bg-status-info-bg text-action-primary-text border-0",
1356
- children: ["$", template.price?.toString() || "0"]
1357
- })
1622
+ children: /* @__PURE__ */ jsx(PriceBadge, { template })
1358
1623
  }),
1359
1624
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-bg-overlay via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" })
1360
1625
  ]
@@ -1377,6 +1642,9 @@ function TemplateCard({ template, onClick, onUseTemplate }) {
1377
1642
  }), template.publisher?.isVerified && /* @__PURE__ */ jsx(Badge, {
1378
1643
  variant: "secondary",
1379
1644
  className: "text-xs px-1.5 py-0",
1645
+ role: "img",
1646
+ "aria-label": tr("fluidgrids.builder.templateCard.verifiedLabel", "Verified publisher"),
1647
+ title: tr("fluidgrids.builder.templateCard.verifiedLabel", "Verified publisher"),
1380
1648
  children: "✓"
1381
1649
  })]
1382
1650
  }),
@@ -1402,18 +1670,7 @@ function TemplateCard({ template, onClick, onUseTemplate }) {
1402
1670
  })
1403
1671
  ]
1404
1672
  })
1405
- }), /* @__PURE__ */ jsxs("div", {
1406
- className: "flex items-center gap-3",
1407
- children: [/* @__PURE__ */ jsxs("div", {
1408
- className: "flex items-center gap-1",
1409
- title: tr("fluidgrids.builder.templateCard.downloadsTooltip", "Downloads"),
1410
- children: [/* @__PURE__ */ jsx(Download, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: formatNumber(template.downloads || 0) })]
1411
- }), /* @__PURE__ */ jsxs("div", {
1412
- className: "flex items-center gap-1",
1413
- title: tr("fluidgrids.builder.templateCard.viewsTooltip", "Views"),
1414
- children: [/* @__PURE__ */ jsx(Eye, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: formatNumber(template.viewCount || 0) })]
1415
- })]
1416
- })]
1673
+ }), !v2 && /* @__PURE__ */ jsx(UsageCounts, { template })]
1417
1674
  }),
1418
1675
  (template.category || template.tags?.length > 0) && /* @__PURE__ */ jsxs("div", {
1419
1676
  className: "flex flex-wrap gap-1",
@@ -1450,4 +1707,4 @@ function TemplateCard({ template, onClick, onUseTemplate }) {
1450
1707
  });
1451
1708
  }
1452
1709
 
1453
- export { isDurableFunctionNodeType as A, CardFooter as C, RUN_CONTROL_NODE_TYPES as D, DURABLE_FUNCTION_NODE_TYPES as E, isRunControlNodeType as M, WORKFLOW_TEST_NODE_TYPES as O, CardDescription as S, CardTitle as T, storeNodeInstallVersion as _, useTemplateImportFlow as a, Card as b, getWorkflowNodeTypes as c, filterStoreNodesForPalette as d, getStoreNodeAvailability as f, storeNodeDisplayVersion as g, isStoreNodeUpdateAvailable as h, badgeVariants as i, isNodeTypeHiddenFromPalette as j, filterPaletteNodes as k, useImportWorkflowTemplate as l, getStoreNodeType as m, templatePreview as n, buildNodeCheck as o, getStoreNodeIcon as p, Badge as r, buildRequiredNodeTypes as s, TemplateCard as t, useWorkflowTemplates as u, useInstalledNodes as v, CardHeader as w, CardContent as x, useGlobalStoreClient as y };
1710
+ export { DURABLE_FUNCTION_NODE_TYPES as A, useGlobalStoreClient as C, CardFooter as D, CardDescription as E, isNodeTypeHiddenFromPalette as F, isRunControlNodeType as I, WORKFLOW_TEST_NODE_TYPES as M, filterPaletteNodes as N, CardHeader as O, isDurableFunctionNodeType as P, useInstalledNodes as S, CardContent as T, getStoreNodeIcon as _, Badge as a, storeNodeDisplayVersion as b, buildNodeCheck as c, useImportWorkflowTemplate as d, buildTemplateCodeDownload as f, getStoreNodeAvailability as g, filterStoreNodesForPalette as h, templatePreview as i, RUN_CONTROL_NODE_TYPES as j, CardTitle as k, buildRequiredNodeTypes as l, useWorkflowTemplates as m, formatTemplatePrice as n, badgeVariants as o, readTemplateCode as p, isPaidTemplate as r, useTemplateImportFlow as s, TemplateCard as t, getWorkflowNodeTypes as u, getStoreNodeType as v, Card as w, storeNodeInstallVersion as x, isStoreNodeUpdateAvailable as y };
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  import { a as DialogFooter, d as Input, i as DialogDescription, l as DialogTitle, o as DialogHeader, r as DialogContent, t as Dialog } from "./dialog-Cjb4lB4D.js";
3
3
  import { t as Button } from "./button-DG7y_FVJ.js";
4
- import { r as Badge } from "./TemplateCard-DanOe65C.js";
4
+ import { a as Badge } from "./TemplateCard--WYJZlA_.js";
5
5
  import { t as Label } from "./label-CDy86rIZ.js";
6
6
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
7
+ import { t as OpenTemplateAsCodeButton } from "./OpenTemplateAsCodeButton-Dh3ee2OR.js";
7
8
  import { useEffect, useMemo, useState } from "react";
8
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
10
  import { AlertTriangle, CheckCircle2, Download, ExternalLink, KeyRound, Loader2, ShoppingCart, Sparkles } from "lucide-react";
@@ -246,16 +247,24 @@ function TemplateConfigurationModal({ open, onOpenChange, template, content, nod
246
247
  })]
247
248
  }), /* @__PURE__ */ jsxs("div", {
248
249
  className: "flex w-full justify-end gap-2",
249
- children: [/* @__PURE__ */ jsx(Button, {
250
- variant: "outline",
251
- onClick: () => onOpenChange(false),
252
- disabled: importing,
253
- children: tr("fluidgrids.builder.templateConfigModal.cancelButton", "Cancel")
254
- }), /* @__PURE__ */ jsx(Button, {
255
- onClick: handleConfirm,
256
- disabled: !isValid || importing || hasPaidBlockers,
257
- children: importing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Loader2, { className: "size-4 mr-2 animate-spin" }), buttonLabel] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Sparkles, { className: "size-4 mr-2" }), buttonLabel] })
258
- })]
250
+ children: [
251
+ /* @__PURE__ */ jsx(OpenTemplateAsCodeButton, {
252
+ templateName: template.name,
253
+ content,
254
+ className: "mr-auto"
255
+ }),
256
+ /* @__PURE__ */ jsx(Button, {
257
+ variant: "outline",
258
+ onClick: () => onOpenChange(false),
259
+ disabled: importing,
260
+ children: tr("fluidgrids.builder.templateConfigModal.cancelButton", "Cancel")
261
+ }),
262
+ /* @__PURE__ */ jsx(Button, {
263
+ onClick: handleConfirm,
264
+ disabled: !isValid || importing || hasPaidBlockers,
265
+ children: importing ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Loader2, { className: "size-4 mr-2 animate-spin" }), buttonLabel] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Sparkles, { className: "size-4 mr-2" }), buttonLabel] })
266
+ })
267
+ ]
259
268
  })]
260
269
  })
261
270
  ]
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { t as Button } from "./button-DG7y_FVJ.js";
3
- import { n as templatePreview, r as Badge } from "./TemplateCard-DanOe65C.js";
3
+ import { a as Badge, i as templatePreview } from "./TemplateCard--WYJZlA_.js";
4
4
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
5
5
  import { i as SdkPermissionButton, t as FLUIDGRIDS_PERMISSIONS } from "./constants-dcxg6zG6.js";
6
6
  import { Icon } from "@iconify/react";