@open-mercato/core 0.6.4-develop.3944.1.4100aa7fbe → 0.6.4-develop.3949.1.adc3d0b3b1

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 2634 entry points
1
+ [build:core] found 2636 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 172 entry points
4
4
  [build:core:generated] built successfully
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=global.d.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -1,193 +1,36 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useCallback, useMemo, useEffect, useState } from "react";
4
- import {
5
- ReactFlow,
6
- Controls,
7
- Background,
8
- BackgroundVariant,
9
- MiniMap,
10
- Panel,
11
- useNodesState,
12
- useEdgesState,
13
- addEdge,
14
- ConnectionMode,
15
- MarkerType
16
- } from "@xyflow/react";
17
- import { StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode } from "./nodes/index.js";
18
- import { WorkflowTransitionEdge } from "./WorkflowTransitionEdge.js";
19
- import { STATUS_COLORS } from "../lib/status-colors.js";
20
- import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
21
- import { Edit3 } from "lucide-react";
22
- import { useTheme } from "@open-mercato/ui/theme";
23
- import { useT } from "@open-mercato/shared/lib/i18n/context";
24
- function WorkflowGraph({
25
- initialNodes = [],
26
- initialEdges = [],
27
- onNodesChange: onNodesChangeProp,
28
- onEdgesChange: onEdgesChangeProp,
29
- onNodeClick: onNodeClickProp,
30
- onEdgeClick: onEdgeClickProp,
31
- onConnect: onConnectProp,
32
- editable = false,
33
- className = "",
34
- height = "600px"
35
- }) {
36
- const t = useT();
37
- const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
38
- const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
39
- const { resolvedTheme } = useTheme();
40
- const isDark = resolvedTheme === "dark";
41
- const backgroundDotColor = isDark ? "#374151" : "#e5e7eb";
42
- const [isCompactViewport, setIsCompactViewport] = useState(false);
43
- useEffect(() => {
44
- if (typeof window === "undefined") return;
45
- const mediaQuery = window.matchMedia("(max-width: 1279px)");
46
- const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches);
47
- updateViewportMode();
48
- mediaQuery.addEventListener("change", updateViewportMode);
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import dynamic from "next/dynamic";
5
+ import { Spinner } from "@open-mercato/ui/primitives/spinner";
6
+ const WorkflowGraphImpl = dynamic(() => import("./WorkflowGraphImpl.js"), {
7
+ ssr: false,
8
+ loading: () => null
9
+ });
10
+ function WorkflowGraphPlaceholder({ height }) {
11
+ return /* @__PURE__ */ jsx(
12
+ "div",
13
+ {
14
+ className: "workflow-graph-container flex items-center justify-center rounded-lg border border-border bg-muted/30",
15
+ style: { height },
16
+ children: /* @__PURE__ */ jsx(Spinner, { className: "h-6 w-6 text-muted-foreground" })
17
+ }
18
+ );
19
+ }
20
+ function WorkflowGraph(props) {
21
+ const { height = "600px" } = props;
22
+ const [isImplReady, setIsImplReady] = React.useState(false);
23
+ React.useEffect(() => {
24
+ let cancelled = false;
25
+ void import("./WorkflowGraphImpl.js").then(() => {
26
+ if (!cancelled) setIsImplReady(true);
27
+ });
49
28
  return () => {
50
- mediaQuery.removeEventListener("change", updateViewportMode);
29
+ cancelled = true;
51
30
  };
52
31
  }, []);
53
- useEffect(() => {
54
- setNodes(initialNodes);
55
- }, [initialNodes, setNodes]);
56
- useEffect(() => {
57
- setEdges(initialEdges);
58
- }, [initialEdges, setEdges]);
59
- const onConnect = useCallback(
60
- (connection) => {
61
- if (onConnectProp) {
62
- onConnectProp(connection);
63
- } else {
64
- const newEdge = {
65
- ...connection,
66
- type: "workflowTransition",
67
- animated: false,
68
- markerEnd: {
69
- type: MarkerType.ArrowClosed,
70
- width: 16,
71
- height: 16,
72
- color: "#9ca3af"
73
- }
74
- };
75
- setEdges((eds) => addEdge(newEdge, eds));
76
- }
77
- },
78
- [setEdges, onConnectProp]
79
- );
80
- const handleNodesChange = useCallback(
81
- (changes) => {
82
- onNodesChange(changes);
83
- if (onNodesChangeProp) {
84
- onNodesChangeProp(changes);
85
- }
86
- },
87
- [onNodesChange, onNodesChangeProp]
88
- );
89
- const handleEdgesChange = useCallback(
90
- (changes) => {
91
- onEdgesChange(changes);
92
- if (onEdgesChangeProp) {
93
- onEdgesChangeProp(changes);
94
- }
95
- },
96
- [onEdgesChange, onEdgesChangeProp]
97
- );
98
- const nodeTypes = useMemo(
99
- () => ({
100
- start: StartNode,
101
- end: EndNode,
102
- userTask: UserTaskNode,
103
- automated: AutomatedNode,
104
- subWorkflow: SubWorkflowNode,
105
- waitForSignal: WaitForSignalNode,
106
- waitForTimer: WaitForTimerNode
107
- }),
108
- []
109
- );
110
- const edgeTypes = useMemo(
111
- () => ({
112
- workflowTransition: WorkflowTransitionEdge
113
- }),
114
- []
115
- );
116
- return /* @__PURE__ */ jsx("div", { className: `workflow-graph-container ${className}`, style: { height }, children: /* @__PURE__ */ jsxs(
117
- ReactFlow,
118
- {
119
- nodes,
120
- edges,
121
- nodeTypes,
122
- edgeTypes,
123
- onNodesChange: handleNodesChange,
124
- onEdgesChange: handleEdgesChange,
125
- onConnect: editable ? onConnect : void 0,
126
- onNodeClick: onNodeClickProp,
127
- onEdgeClick: onEdgeClickProp,
128
- connectionMode: ConnectionMode.Loose,
129
- fitView: true,
130
- fitViewOptions: {
131
- padding: 0.2,
132
- maxZoom: isCompactViewport ? 0.9 : 1
133
- },
134
- minZoom: 0.1,
135
- maxZoom: 2,
136
- defaultEdgeOptions: {
137
- type: "workflowTransition",
138
- animated: false,
139
- markerEnd: {
140
- type: MarkerType.ArrowClosed,
141
- width: 16,
142
- height: 16,
143
- color: "#9ca3af"
144
- }
145
- },
146
- nodesDraggable: editable,
147
- nodesConnectable: editable,
148
- elementsSelectable: editable,
149
- proOptions: { hideAttribution: true },
150
- children: [
151
- /* @__PURE__ */ jsx(
152
- Background,
153
- {
154
- variant: BackgroundVariant.Dots,
155
- gap: 16,
156
- size: 1,
157
- color: backgroundDotColor
158
- }
159
- ),
160
- /* @__PURE__ */ jsx(
161
- Controls,
162
- {
163
- showZoom: true,
164
- showFitView: true,
165
- showInteractive: false,
166
- position: isCompactViewport ? "bottom-right" : "top-right",
167
- className: `!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? "scale-90 origin-bottom-right" : ""}`
168
- }
169
- ),
170
- !isCompactViewport && /* @__PURE__ */ jsx(
171
- MiniMap,
172
- {
173
- nodeStrokeWidth: 3,
174
- nodeColor: (node) => {
175
- const status = node.data?.status || "not_started";
176
- return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex;
177
- },
178
- maskColor: "rgba(0, 0, 0, 0.1)",
179
- position: "bottom-left",
180
- className: "!bg-card !border !border-border !rounded-lg"
181
- }
182
- ),
183
- !editable && !isCompactViewport && /* @__PURE__ */ jsx(Panel, { position: "top-left", style: { margin: 10 }, children: /* @__PURE__ */ jsx("div", { className: "bg-card rounded-lg shadow-sm border border-border px-4 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground font-medium", children: t("workflows.graph.visualization") }) }) }),
184
- editable && !isCompactViewport && /* @__PURE__ */ jsx(Panel, { position: "top-left", style: { margin: 10 }, children: /* @__PURE__ */ jsxs(Alert, { variant: "info", className: "max-w-sm", children: [
185
- /* @__PURE__ */ jsx(Edit3, { className: "size-4" }),
186
- /* @__PURE__ */ jsx(AlertDescription, { className: "font-medium", children: t("workflows.graph.editModeInfo") })
187
- ] }) })
188
- ]
189
- }
190
- ) });
32
+ if (!isImplReady) return /* @__PURE__ */ jsx(WorkflowGraphPlaceholder, { height });
33
+ return /* @__PURE__ */ jsx(WorkflowGraphImpl, { ...props });
191
34
  }
192
35
  function WorkflowGraphReadOnly({
193
36
  nodes,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/workflows/components/WorkflowGraph.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport { useCallback, useMemo, useEffect, useState } from 'react'\nimport {\n ReactFlow,\n Node,\n Edge,\n Controls,\n Background,\n BackgroundVariant,\n MiniMap,\n Panel,\n useNodesState,\n useEdgesState,\n addEdge,\n Connection,\n ConnectionMode,\n MarkerType,\n} from '@xyflow/react'\nimport {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'\nimport { WorkflowTransitionEdge } from './WorkflowTransitionEdge'\nimport { STATUS_COLORS } from '../lib/status-colors'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Edit3 } from 'lucide-react'\nimport { useTheme } from '@open-mercato/ui/theme'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n// NOTE: ReactFlow styles should be imported in the page that uses this component\n// or in a global CSS file. Import: '@xyflow/react/dist/style.css'\n\nexport interface WorkflowGraphProps {\n initialNodes?: Node[]\n initialEdges?: Edge[]\n onNodesChange?: (changes: any[]) => void\n onEdgesChange?: (changes: any[]) => void\n onNodeClick?: (event: React.MouseEvent, node: Node) => void\n onEdgeClick?: (event: React.MouseEvent, edge: Edge) => void\n onConnect?: (connection: Connection) => void\n editable?: boolean\n className?: string\n height?: string\n}\n\n/**\n * WorkflowGraph - ReactFlow wrapper component for workflow visualization\n *\n * Provides a graph-based view of workflow definitions with:\n * - Pan and zoom controls\n * - Background grid\n * - Mini-map for navigation\n * - Optional editing capabilities\n */\nexport function WorkflowGraph({\n initialNodes = [],\n initialEdges = [],\n onNodesChange: onNodesChangeProp,\n onEdgesChange: onEdgesChangeProp,\n onNodeClick: onNodeClickProp,\n onEdgeClick: onEdgeClickProp,\n onConnect: onConnectProp,\n editable = false,\n className = '',\n height = '600px',\n}: WorkflowGraphProps) {\n const t = useT()\n // Use ReactFlow hooks for node and edge state management\n const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)\n const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)\n\n // Get theme for dark mode support\n const { resolvedTheme } = useTheme()\n const isDark = resolvedTheme === 'dark'\n const backgroundDotColor = isDark ? '#374151' : '#e5e7eb'\n const [isCompactViewport, setIsCompactViewport] = useState(false)\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n const mediaQuery = window.matchMedia('(max-width: 1279px)')\n const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches)\n\n updateViewportMode()\n mediaQuery.addEventListener('change', updateViewportMode)\n\n return () => {\n mediaQuery.removeEventListener('change', updateViewportMode)\n }\n }, [])\n\n // Sync internal state when external state changes (e.g., when parent adds nodes)\n useEffect(() => {\n setNodes(initialNodes)\n }, [initialNodes, setNodes])\n\n useEffect(() => {\n setEdges(initialEdges)\n }, [initialEdges, setEdges])\n\n // Handle connection between nodes (when user drags from one node to another)\n const onConnect = useCallback(\n (connection: Connection) => {\n if (onConnectProp) {\n // Let parent handle the connection\n onConnectProp(connection)\n } else {\n // Fallback: handle internally if no parent callback\n const newEdge = {\n ...connection,\n type: 'workflowTransition',\n animated: false,\n markerEnd: {\n type: MarkerType.ArrowClosed,\n width: 16,\n height: 16,\n color: '#9ca3af',\n },\n }\n setEdges((eds) => addEdge(newEdge, eds))\n }\n },\n [setEdges, onConnectProp]\n )\n\n // Notify parent when nodes change\n const handleNodesChange = useCallback(\n (changes: any) => {\n onNodesChange(changes)\n if (onNodesChangeProp) {\n onNodesChangeProp(changes)\n }\n },\n [onNodesChange, onNodesChangeProp]\n )\n\n // Notify parent when edges change\n const handleEdgesChange = useCallback(\n (changes: any) => {\n onEdgesChange(changes)\n if (onEdgesChangeProp) {\n onEdgesChangeProp(changes)\n }\n },\n [onEdgesChange, onEdgesChangeProp]\n )\n\n // Register custom node types\n const nodeTypes = useMemo(\n () => ({\n start: StartNode,\n end: EndNode,\n userTask: UserTaskNode,\n automated: AutomatedNode,\n subWorkflow: SubWorkflowNode,\n waitForSignal: WaitForSignalNode,\n waitForTimer: WaitForTimerNode,\n }),\n []\n )\n\n // Register custom edge types\n const edgeTypes = useMemo(\n () => ({\n workflowTransition: WorkflowTransitionEdge,\n }),\n []\n )\n\n return (\n <div className={`workflow-graph-container ${className}`} style={{ height }}>\n <ReactFlow\n nodes={nodes}\n edges={edges}\n nodeTypes={nodeTypes}\n edgeTypes={edgeTypes}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={editable ? onConnect : undefined}\n onNodeClick={onNodeClickProp}\n onEdgeClick={onEdgeClickProp}\n connectionMode={ConnectionMode.Loose}\n fitView\n fitViewOptions={{\n padding: 0.2,\n maxZoom: isCompactViewport ? 0.9 : 1,\n }}\n minZoom={0.1}\n maxZoom={2}\n defaultEdgeOptions={{\n type: 'workflowTransition',\n animated: false,\n markerEnd: {\n type: MarkerType.ArrowClosed,\n width: 16,\n height: 16,\n color: '#9ca3af',\n },\n }}\n nodesDraggable={editable}\n nodesConnectable={editable}\n elementsSelectable={editable}\n proOptions={{ hideAttribution: true }}\n >\n {/* Background grid for visual reference */}\n <Background\n variant={BackgroundVariant.Dots}\n gap={16}\n size={1}\n color={backgroundDotColor}\n />\n\n {/* Zoom and pan controls */}\n <Controls\n showZoom={true}\n showFitView={true}\n showInteractive={false}\n position={isCompactViewport ? 'bottom-right' : 'top-right'}\n className={`!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? 'scale-90 origin-bottom-right' : ''}`}\n />\n\n {/* Mini-map for navigation in large workflows */}\n {!isCompactViewport && (\n <MiniMap\n nodeStrokeWidth={3}\n nodeColor={(node) => {\n // Color nodes by status - using status-based colors\n const status = (node.data?.status || 'not_started') as keyof typeof STATUS_COLORS\n return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex\n }}\n maskColor=\"rgba(0, 0, 0, 0.1)\"\n position=\"bottom-left\"\n className=\"!bg-card !border !border-border !rounded-lg\"\n />\n )}\n\n {/* Info panel */}\n {!editable && !isCompactViewport && (\n <Panel position=\"top-left\" style={{ margin: 10 }}>\n <div className=\"bg-card rounded-lg shadow-sm border border-border px-4 py-2\">\n <p className=\"text-sm text-muted-foreground font-medium\">\n {t('workflows.graph.visualization')}\n </p>\n </div>\n </Panel>\n )}\n\n {editable && !isCompactViewport && (\n <Panel position=\"top-left\" style={{ margin: 10 }}>\n <Alert variant=\"info\" className=\"max-w-sm\">\n <Edit3 className=\"size-4\" />\n <AlertDescription className=\"font-medium\">\n {t('workflows.graph.editModeInfo')}\n </AlertDescription>\n </Alert>\n </Panel>\n )}\n </ReactFlow>\n </div>\n )\n}\n\n/**\n * WorkflowGraphReadOnly - Read-only version for viewing workflow execution\n */\nexport function WorkflowGraphReadOnly({\n nodes,\n edges,\n className = '',\n height = '500px',\n}: {\n nodes: Node[]\n edges: Edge[]\n className?: string\n height?: string\n}) {\n return (\n <WorkflowGraph\n initialNodes={nodes}\n initialEdges={edges}\n editable={false}\n className={className}\n height={height}\n />\n )\n}\n"],
5
- "mappings": ";AA0MQ,cA4CI,YA5CJ;AAxMR,SAAS,aAAa,SAAS,WAAW,gBAAgB;AAC1D;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP,SAAQ,WAAW,SAAS,cAAc,eAAe,iBAAiB,mBAAmB,wBAAuB;AACpH,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,OAAO,wBAAwB;AACxC,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB,SAAS,YAAY;AA2Bd,SAAS,cAAc;AAAA,EAC5B,eAAe,CAAC;AAAA,EAChB,eAAe,CAAC;AAAA,EAChB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,aAAa;AAAA,EACb,aAAa;AAAA,EACb,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,SAAS;AACX,GAAuB;AACrB,QAAM,IAAI,KAAK;AAEf,QAAM,CAAC,OAAO,UAAU,aAAa,IAAI,cAAc,YAAY;AACnE,QAAM,CAAC,OAAO,UAAU,aAAa,IAAI,cAAc,YAAY;AAGnE,QAAM,EAAE,cAAc,IAAI,SAAS;AACnC,QAAM,SAAS,kBAAkB;AACjC,QAAM,qBAAqB,SAAS,YAAY;AAChD,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAEhE,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AACnC,UAAM,aAAa,OAAO,WAAW,qBAAqB;AAC1D,UAAM,qBAAqB,MAAM,qBAAqB,WAAW,OAAO;AAExE,uBAAmB;AACnB,eAAW,iBAAiB,UAAU,kBAAkB;AAExD,WAAO,MAAM;AACX,iBAAW,oBAAoB,UAAU,kBAAkB;AAAA,IAC7D;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,YAAU,MAAM;AACd,aAAS,YAAY;AAAA,EACvB,GAAG,CAAC,cAAc,QAAQ,CAAC;AAE3B,YAAU,MAAM;AACd,aAAS,YAAY;AAAA,EACvB,GAAG,CAAC,cAAc,QAAQ,CAAC;AAG3B,QAAM,YAAY;AAAA,IAChB,CAAC,eAA2B;AAC1B,UAAI,eAAe;AAEjB,sBAAc,UAAU;AAAA,MAC1B,OAAO;AAEL,cAAM,UAAU;AAAA,UACd,GAAG;AAAA,UACH,MAAM;AAAA,UACN,UAAU;AAAA,UACV,WAAW;AAAA,YACT,MAAM,WAAW;AAAA,YACjB,OAAO;AAAA,YACP,QAAQ;AAAA,YACR,OAAO;AAAA,UACT;AAAA,QACF;AACA,iBAAS,CAAC,QAAQ,QAAQ,SAAS,GAAG,CAAC;AAAA,MACzC;AAAA,IACF;AAAA,IACA,CAAC,UAAU,aAAa;AAAA,EAC1B;AAGA,QAAM,oBAAoB;AAAA,IACxB,CAAC,YAAiB;AAChB,oBAAc,OAAO;AACrB,UAAI,mBAAmB;AACrB,0BAAkB,OAAO;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,CAAC,eAAe,iBAAiB;AAAA,EACnC;AAGA,QAAM,oBAAoB;AAAA,IACxB,CAAC,YAAiB;AAChB,oBAAc,OAAO;AACrB,UAAI,mBAAmB;AACrB,0BAAkB,OAAO;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,CAAC,eAAe,iBAAiB;AAAA,EACnC;AAGA,QAAM,YAAY;AAAA,IAChB,OAAO;AAAA,MACL,OAAO;AAAA,MACP,KAAK;AAAA,MACL,UAAU;AAAA,MACV,WAAW;AAAA,MACX,aAAa;AAAA,MACb,eAAe;AAAA,MACf,cAAc;AAAA,IAChB;AAAA,IACA,CAAC;AAAA,EACH;AAGA,QAAM,YAAY;AAAA,IAChB,OAAO;AAAA,MACL,oBAAoB;AAAA,IACtB;AAAA,IACA,CAAC;AAAA,EACH;AAEA,SACE,oBAAC,SAAI,WAAW,4BAA4B,SAAS,IAAI,OAAO,EAAE,OAAO,GACvE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe;AAAA,MACf,eAAe;AAAA,MACf,WAAW,WAAW,YAAY;AAAA,MAClC,aAAa;AAAA,MACb,aAAa;AAAA,MACb,gBAAgB,eAAe;AAAA,MAC/B,SAAO;AAAA,MACP,gBAAgB;AAAA,QACd,SAAS;AAAA,QACT,SAAS,oBAAoB,MAAM;AAAA,MACrC;AAAA,MACA,SAAS;AAAA,MACT,SAAS;AAAA,MACT,oBAAoB;AAAA,QAClB,MAAM;AAAA,QACN,UAAU;AAAA,QACV,WAAW;AAAA,UACT,MAAM,WAAW;AAAA,UACjB,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,OAAO;AAAA,QACT;AAAA,MACF;AAAA,MACA,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,MACpB,YAAY,EAAE,iBAAiB,KAAK;AAAA,MAGpC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,kBAAkB;AAAA,YAC3B,KAAK;AAAA,YACL,MAAM;AAAA,YACN,OAAO;AAAA;AAAA,QACT;AAAA,QAGA;AAAA,UAAC;AAAA;AAAA,YACC,UAAU;AAAA,YACV,aAAa;AAAA,YACb,iBAAiB;AAAA,YACjB,UAAU,oBAAoB,iBAAiB;AAAA,YAC/C,WAAW,2IAA2I,oBAAoB,iCAAiC,EAAE;AAAA;AAAA,QAC/M;AAAA,QAGC,CAAC,qBACA;AAAA,UAAC;AAAA;AAAA,YACC,iBAAiB;AAAA,YACjB,WAAW,CAAC,SAAS;AAEnB,oBAAM,SAAU,KAAK,MAAM,UAAU;AACrC,qBAAO,cAAc,MAAM,GAAG,OAAO,cAAc,YAAY;AAAA,YACjE;AAAA,YACA,WAAU;AAAA,YACV,UAAS;AAAA,YACT,WAAU;AAAA;AAAA,QACZ;AAAA,QAID,CAAC,YAAY,CAAC,qBACb,oBAAC,SAAM,UAAS,YAAW,OAAO,EAAE,QAAQ,GAAG,GAC7C,8BAAC,SAAI,WAAU,+DACb,8BAAC,OAAE,WAAU,6CACV,YAAE,+BAA+B,GACpC,GACF,GACF;AAAA,QAGD,YAAY,CAAC,qBACZ,oBAAC,SAAM,UAAS,YAAW,OAAO,EAAE,QAAQ,GAAG,GAC7C,+BAAC,SAAM,SAAQ,QAAO,WAAU,YAC9B;AAAA,8BAAC,SAAM,WAAU,UAAS;AAAA,UAC1B,oBAAC,oBAAiB,WAAU,eACzB,YAAE,8BAA8B,GACnC;AAAA,WACF,GACF;AAAA;AAAA;AAAA,EAEJ,GACF;AAEJ;AAKO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,SAAS;AACX,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAc;AAAA,MACd,cAAc;AAAA,MACd,UAAU;AAAA,MACV;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport type { Node, Edge, Connection } from '@xyflow/react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\n\nexport interface WorkflowGraphProps {\n initialNodes?: Node[]\n initialEdges?: Edge[]\n onNodesChange?: (changes: any[]) => void\n onEdgesChange?: (changes: any[]) => void\n onNodeClick?: (event: React.MouseEvent, node: Node) => void\n onEdgeClick?: (event: React.MouseEvent, edge: Edge) => void\n onConnect?: (connection: Connection) => void\n editable?: boolean\n className?: string\n height?: string\n}\n\nconst WorkflowGraphImpl = dynamic(() => import('./WorkflowGraphImpl'), {\n ssr: false,\n loading: () => null,\n})\n\nfunction WorkflowGraphPlaceholder({ height }: { height: string }) {\n return (\n <div\n className=\"workflow-graph-container flex items-center justify-center rounded-lg border border-border bg-muted/30\"\n style={{ height }}\n >\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\n </div>\n )\n}\n\n/**\n * WorkflowGraph \u2014 lazy-loaded ReactFlow wrapper.\n *\n * @xyflow/react is loaded via next/dynamic({ ssr: false }) so the ~12 MB\n * package only enters the Turbopack module graph when this component\n * actually renders.\n */\nexport function WorkflowGraph(props: WorkflowGraphProps) {\n const { height = '600px' } = props\n // Track impl-chunk readiness so the loading placeholder respects the\n // caller's `height` prop (next/dynamic's `loading` cannot access props).\n // The browser caches the module, so the duplicate `import()` is free.\n const [isImplReady, setIsImplReady] = React.useState(false)\n React.useEffect(() => {\n let cancelled = false\n void import('./WorkflowGraphImpl').then(() => {\n if (!cancelled) setIsImplReady(true)\n })\n return () => {\n cancelled = true\n }\n }, [])\n\n if (!isImplReady) return <WorkflowGraphPlaceholder height={height} />\n return <WorkflowGraphImpl {...props} />\n}\n\n/**\n * WorkflowGraphReadOnly \u2014 read-only viewer that reuses WorkflowGraph.\n */\nexport function WorkflowGraphReadOnly({\n nodes,\n edges,\n className = '',\n height = '500px',\n}: {\n nodes: Node[]\n edges: Edge[]\n className?: string\n height?: string\n}) {\n return (\n <WorkflowGraph\n initialNodes={nodes}\n initialEdges={edges}\n editable={false}\n className={className}\n height={height}\n />\n )\n}\n"],
5
+ "mappings": ";AA+BM;AA7BN,YAAY,WAAW;AACvB,OAAO,aAAa;AAEpB,SAAS,eAAe;AAexB,MAAM,oBAAoB,QAAQ,MAAM,OAAO,qBAAqB,GAAG;AAAA,EACrE,KAAK;AAAA,EACL,SAAS,MAAM;AACjB,CAAC;AAED,SAAS,yBAAyB,EAAE,OAAO,GAAuB;AAChE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO,EAAE,OAAO;AAAA,MAEhB,8BAAC,WAAQ,WAAU,iCAAgC;AAAA;AAAA,EACrD;AAEJ;AASO,SAAS,cAAc,OAA2B;AACvD,QAAM,EAAE,SAAS,QAAQ,IAAI;AAI7B,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,SAAK,OAAO,qBAAqB,EAAE,KAAK,MAAM;AAC5C,UAAI,CAAC,UAAW,gBAAe,IAAI;AAAA,IACrC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,YAAa,QAAO,oBAAC,4BAAyB,QAAgB;AACnE,SAAO,oBAAC,qBAAmB,GAAG,OAAO;AACvC;AAKO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,SAAS;AACX,GAKG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,cAAc;AAAA,MACd,cAAc;AAAA,MACd,UAAU;AAAA,MACV;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,196 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import "@xyflow/react/dist/style.css";
4
+ import { useCallback, useMemo, useEffect, useState } from "react";
5
+ import {
6
+ ReactFlow,
7
+ Controls,
8
+ Background,
9
+ BackgroundVariant,
10
+ MiniMap,
11
+ Panel,
12
+ useNodesState,
13
+ useEdgesState,
14
+ addEdge,
15
+ ConnectionMode,
16
+ MarkerType
17
+ } from "@xyflow/react";
18
+ import { StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode } from "./nodes/index.js";
19
+ import { WorkflowTransitionEdge } from "./WorkflowTransitionEdge.js";
20
+ import { STATUS_COLORS } from "../lib/status-colors.js";
21
+ import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
22
+ import { Edit3 } from "lucide-react";
23
+ import { useTheme } from "@open-mercato/ui/theme";
24
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
25
+ function WorkflowGraphImpl({
26
+ initialNodes = [],
27
+ initialEdges = [],
28
+ onNodesChange: onNodesChangeProp,
29
+ onEdgesChange: onEdgesChangeProp,
30
+ onNodeClick: onNodeClickProp,
31
+ onEdgeClick: onEdgeClickProp,
32
+ onConnect: onConnectProp,
33
+ editable = false,
34
+ className = "",
35
+ height = "600px"
36
+ }) {
37
+ const t = useT();
38
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
39
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
40
+ const { resolvedTheme } = useTheme();
41
+ const isDark = resolvedTheme === "dark";
42
+ const backgroundDotColor = isDark ? "#374151" : "#e5e7eb";
43
+ const [isCompactViewport, setIsCompactViewport] = useState(false);
44
+ useEffect(() => {
45
+ if (typeof window === "undefined") return;
46
+ const mediaQuery = window.matchMedia("(max-width: 1279px)");
47
+ const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches);
48
+ updateViewportMode();
49
+ mediaQuery.addEventListener("change", updateViewportMode);
50
+ return () => {
51
+ mediaQuery.removeEventListener("change", updateViewportMode);
52
+ };
53
+ }, []);
54
+ useEffect(() => {
55
+ setNodes(initialNodes);
56
+ }, [initialNodes, setNodes]);
57
+ useEffect(() => {
58
+ setEdges(initialEdges);
59
+ }, [initialEdges, setEdges]);
60
+ const onConnect = useCallback(
61
+ (connection) => {
62
+ if (onConnectProp) {
63
+ onConnectProp(connection);
64
+ } else {
65
+ const newEdge = {
66
+ ...connection,
67
+ type: "workflowTransition",
68
+ animated: false,
69
+ markerEnd: {
70
+ type: MarkerType.ArrowClosed,
71
+ width: 16,
72
+ height: 16,
73
+ color: "#9ca3af"
74
+ }
75
+ };
76
+ setEdges((eds) => addEdge(newEdge, eds));
77
+ }
78
+ },
79
+ [setEdges, onConnectProp]
80
+ );
81
+ const handleNodesChange = useCallback(
82
+ (changes) => {
83
+ onNodesChange(changes);
84
+ if (onNodesChangeProp) {
85
+ onNodesChangeProp(changes);
86
+ }
87
+ },
88
+ [onNodesChange, onNodesChangeProp]
89
+ );
90
+ const handleEdgesChange = useCallback(
91
+ (changes) => {
92
+ onEdgesChange(changes);
93
+ if (onEdgesChangeProp) {
94
+ onEdgesChangeProp(changes);
95
+ }
96
+ },
97
+ [onEdgesChange, onEdgesChangeProp]
98
+ );
99
+ const nodeTypes = useMemo(
100
+ () => ({
101
+ start: StartNode,
102
+ end: EndNode,
103
+ userTask: UserTaskNode,
104
+ automated: AutomatedNode,
105
+ subWorkflow: SubWorkflowNode,
106
+ waitForSignal: WaitForSignalNode,
107
+ waitForTimer: WaitForTimerNode
108
+ }),
109
+ []
110
+ );
111
+ const edgeTypes = useMemo(
112
+ () => ({
113
+ workflowTransition: WorkflowTransitionEdge
114
+ }),
115
+ []
116
+ );
117
+ return /* @__PURE__ */ jsx("div", { className: `workflow-graph-container ${className}`, style: { height }, children: /* @__PURE__ */ jsxs(
118
+ ReactFlow,
119
+ {
120
+ nodes,
121
+ edges,
122
+ nodeTypes,
123
+ edgeTypes,
124
+ onNodesChange: handleNodesChange,
125
+ onEdgesChange: handleEdgesChange,
126
+ onConnect: editable ? onConnect : void 0,
127
+ onNodeClick: onNodeClickProp,
128
+ onEdgeClick: onEdgeClickProp,
129
+ connectionMode: ConnectionMode.Loose,
130
+ fitView: true,
131
+ fitViewOptions: {
132
+ padding: 0.2,
133
+ maxZoom: isCompactViewport ? 0.9 : 1
134
+ },
135
+ minZoom: 0.1,
136
+ maxZoom: 2,
137
+ defaultEdgeOptions: {
138
+ type: "workflowTransition",
139
+ animated: false,
140
+ markerEnd: {
141
+ type: MarkerType.ArrowClosed,
142
+ width: 16,
143
+ height: 16,
144
+ color: "#9ca3af"
145
+ }
146
+ },
147
+ nodesDraggable: editable,
148
+ nodesConnectable: editable,
149
+ elementsSelectable: editable,
150
+ proOptions: { hideAttribution: true },
151
+ children: [
152
+ /* @__PURE__ */ jsx(
153
+ Background,
154
+ {
155
+ variant: BackgroundVariant.Dots,
156
+ gap: 16,
157
+ size: 1,
158
+ color: backgroundDotColor
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsx(
162
+ Controls,
163
+ {
164
+ showZoom: true,
165
+ showFitView: true,
166
+ showInteractive: false,
167
+ position: isCompactViewport ? "bottom-right" : "top-right",
168
+ className: `!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? "scale-90 origin-bottom-right" : ""}`
169
+ }
170
+ ),
171
+ !isCompactViewport && /* @__PURE__ */ jsx(
172
+ MiniMap,
173
+ {
174
+ nodeStrokeWidth: 3,
175
+ nodeColor: (node) => {
176
+ const status = node.data?.status || "not_started";
177
+ return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex;
178
+ },
179
+ maskColor: "rgba(0, 0, 0, 0.1)",
180
+ position: "bottom-left",
181
+ className: "!bg-card !border !border-border !rounded-lg"
182
+ }
183
+ ),
184
+ !editable && !isCompactViewport && /* @__PURE__ */ jsx(Panel, { position: "top-left", style: { margin: 10 }, children: /* @__PURE__ */ jsx("div", { className: "bg-card rounded-lg shadow-sm border border-border px-4 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground font-medium", children: t("workflows.graph.visualization") }) }) }),
185
+ editable && !isCompactViewport && /* @__PURE__ */ jsx(Panel, { position: "top-left", style: { margin: 10 }, children: /* @__PURE__ */ jsxs(Alert, { variant: "info", className: "max-w-sm", children: [
186
+ /* @__PURE__ */ jsx(Edit3, { className: "size-4" }),
187
+ /* @__PURE__ */ jsx(AlertDescription, { className: "font-medium", children: t("workflows.graph.editModeInfo") })
188
+ ] }) })
189
+ ]
190
+ }
191
+ ) });
192
+ }
193
+ export {
194
+ WorkflowGraphImpl as default
195
+ };
196
+ //# sourceMappingURL=WorkflowGraphImpl.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/workflows/components/WorkflowGraphImpl.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport '@xyflow/react/dist/style.css'\n\nimport { useCallback, useMemo, useEffect, useState } from 'react'\nimport {\n ReactFlow,\n Node,\n Edge,\n Controls,\n Background,\n BackgroundVariant,\n MiniMap,\n Panel,\n useNodesState,\n useEdgesState,\n addEdge,\n Connection,\n ConnectionMode,\n MarkerType,\n} from '@xyflow/react'\nimport {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'\nimport { WorkflowTransitionEdge } from './WorkflowTransitionEdge'\nimport { STATUS_COLORS } from '../lib/status-colors'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Edit3 } from 'lucide-react'\nimport { useTheme } from '@open-mercato/ui/theme'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport interface WorkflowGraphImplProps {\n initialNodes?: Node[]\n initialEdges?: Edge[]\n onNodesChange?: (changes: any[]) => void\n onEdgesChange?: (changes: any[]) => void\n onNodeClick?: (event: React.MouseEvent, node: Node) => void\n onEdgeClick?: (event: React.MouseEvent, edge: Edge) => void\n onConnect?: (connection: Connection) => void\n editable?: boolean\n className?: string\n height?: string\n}\n\nexport default function WorkflowGraphImpl({\n initialNodes = [],\n initialEdges = [],\n onNodesChange: onNodesChangeProp,\n onEdgesChange: onEdgesChangeProp,\n onNodeClick: onNodeClickProp,\n onEdgeClick: onEdgeClickProp,\n onConnect: onConnectProp,\n editable = false,\n className = '',\n height = '600px',\n}: WorkflowGraphImplProps) {\n const t = useT()\n const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)\n const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)\n\n const { resolvedTheme } = useTheme()\n const isDark = resolvedTheme === 'dark'\n const backgroundDotColor = isDark ? '#374151' : '#e5e7eb'\n const [isCompactViewport, setIsCompactViewport] = useState(false)\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n const mediaQuery = window.matchMedia('(max-width: 1279px)')\n const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches)\n\n updateViewportMode()\n mediaQuery.addEventListener('change', updateViewportMode)\n\n return () => {\n mediaQuery.removeEventListener('change', updateViewportMode)\n }\n }, [])\n\n useEffect(() => {\n setNodes(initialNodes)\n }, [initialNodes, setNodes])\n\n useEffect(() => {\n setEdges(initialEdges)\n }, [initialEdges, setEdges])\n\n const onConnect = useCallback(\n (connection: Connection) => {\n if (onConnectProp) {\n onConnectProp(connection)\n } else {\n const newEdge = {\n ...connection,\n type: 'workflowTransition',\n animated: false,\n markerEnd: {\n type: MarkerType.ArrowClosed,\n width: 16,\n height: 16,\n color: '#9ca3af',\n },\n }\n setEdges((eds) => addEdge(newEdge, eds))\n }\n },\n [setEdges, onConnectProp]\n )\n\n const handleNodesChange = useCallback(\n (changes: any) => {\n onNodesChange(changes)\n if (onNodesChangeProp) {\n onNodesChangeProp(changes)\n }\n },\n [onNodesChange, onNodesChangeProp]\n )\n\n const handleEdgesChange = useCallback(\n (changes: any) => {\n onEdgesChange(changes)\n if (onEdgesChangeProp) {\n onEdgesChangeProp(changes)\n }\n },\n [onEdgesChange, onEdgesChangeProp]\n )\n\n const nodeTypes = useMemo(\n () => ({\n start: StartNode,\n end: EndNode,\n userTask: UserTaskNode,\n automated: AutomatedNode,\n subWorkflow: SubWorkflowNode,\n waitForSignal: WaitForSignalNode,\n waitForTimer: WaitForTimerNode,\n }),\n []\n )\n\n const edgeTypes = useMemo(\n () => ({\n workflowTransition: WorkflowTransitionEdge,\n }),\n []\n )\n\n return (\n <div className={`workflow-graph-container ${className}`} style={{ height }}>\n <ReactFlow\n nodes={nodes}\n edges={edges}\n nodeTypes={nodeTypes}\n edgeTypes={edgeTypes}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={editable ? onConnect : undefined}\n onNodeClick={onNodeClickProp}\n onEdgeClick={onEdgeClickProp}\n connectionMode={ConnectionMode.Loose}\n fitView\n fitViewOptions={{\n padding: 0.2,\n maxZoom: isCompactViewport ? 0.9 : 1,\n }}\n minZoom={0.1}\n maxZoom={2}\n defaultEdgeOptions={{\n type: 'workflowTransition',\n animated: false,\n markerEnd: {\n type: MarkerType.ArrowClosed,\n width: 16,\n height: 16,\n color: '#9ca3af',\n },\n }}\n nodesDraggable={editable}\n nodesConnectable={editable}\n elementsSelectable={editable}\n proOptions={{ hideAttribution: true }}\n >\n <Background\n variant={BackgroundVariant.Dots}\n gap={16}\n size={1}\n color={backgroundDotColor}\n />\n\n <Controls\n showZoom={true}\n showFitView={true}\n showInteractive={false}\n position={isCompactViewport ? 'bottom-right' : 'top-right'}\n className={`!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? 'scale-90 origin-bottom-right' : ''}`}\n />\n\n {!isCompactViewport && (\n <MiniMap\n nodeStrokeWidth={3}\n nodeColor={(node) => {\n const status = (node.data?.status || 'not_started') as keyof typeof STATUS_COLORS\n return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex\n }}\n maskColor=\"rgba(0, 0, 0, 0.1)\"\n position=\"bottom-left\"\n className=\"!bg-card !border !border-border !rounded-lg\"\n />\n )}\n\n {!editable && !isCompactViewport && (\n <Panel position=\"top-left\" style={{ margin: 10 }}>\n <div className=\"bg-card rounded-lg shadow-sm border border-border px-4 py-2\">\n <p className=\"text-sm text-muted-foreground font-medium\">\n {t('workflows.graph.visualization')}\n </p>\n </div>\n </Panel>\n )}\n\n {editable && !isCompactViewport && (\n <Panel position=\"top-left\" style={{ margin: 10 }}>\n <Alert variant=\"info\" className=\"max-w-sm\">\n <Edit3 className=\"size-4\" />\n <AlertDescription className=\"font-medium\">\n {t('workflows.graph.editModeInfo')}\n </AlertDescription>\n </Alert>\n </Panel>\n )}\n </ReactFlow>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.4-develop.3944.1.4100aa7fbe",
3
+ "version": "0.6.4-develop.3949.1.adc3d0b3b1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -243,16 +243,16 @@
243
243
  "zod": "^4.4.3"
244
244
  },
245
245
  "peerDependencies": {
246
- "@open-mercato/ai-assistant": "0.6.4-develop.3944.1.4100aa7fbe",
247
- "@open-mercato/shared": "0.6.4-develop.3944.1.4100aa7fbe",
248
- "@open-mercato/ui": "0.6.4-develop.3944.1.4100aa7fbe",
246
+ "@open-mercato/ai-assistant": "0.6.4-develop.3949.1.adc3d0b3b1",
247
+ "@open-mercato/shared": "0.6.4-develop.3949.1.adc3d0b3b1",
248
+ "@open-mercato/ui": "0.6.4-develop.3949.1.adc3d0b3b1",
249
249
  "react": "^19.0.0",
250
250
  "react-dom": "^19.0.0"
251
251
  },
252
252
  "devDependencies": {
253
- "@open-mercato/ai-assistant": "0.6.4-develop.3944.1.4100aa7fbe",
254
- "@open-mercato/shared": "0.6.4-develop.3944.1.4100aa7fbe",
255
- "@open-mercato/ui": "0.6.4-develop.3944.1.4100aa7fbe",
253
+ "@open-mercato/ai-assistant": "0.6.4-develop.3949.1.adc3d0b3b1",
254
+ "@open-mercato/shared": "0.6.4-develop.3949.1.adc3d0b3b1",
255
+ "@open-mercato/ui": "0.6.4-develop.3949.1.adc3d0b3b1",
256
256
  "@testing-library/dom": "^10.4.1",
257
257
  "@testing-library/jest-dom": "^6.9.1",
258
258
  "@testing-library/react": "^16.3.1",
@@ -0,0 +1,9 @@
1
+ // Ambient module declarations for the @open-mercato/core TypeScript context.
2
+ //
3
+ // Next.js apps get these declarations via `next-env.d.ts`, but workspace
4
+ // packages need them locally because they may import CSS side-effects from
5
+ // .tsx files (e.g. `import 'pkg/dist/style.css'` inside a lazy-loaded
6
+ // client component).
7
+
8
+ declare module '*.css'
9
+ declare module '*.scss'
@@ -1,32 +1,9 @@
1
1
  'use client'
2
2
 
3
- import { useCallback, useMemo, useEffect, useState } from 'react'
4
- import {
5
- ReactFlow,
6
- Node,
7
- Edge,
8
- Controls,
9
- Background,
10
- BackgroundVariant,
11
- MiniMap,
12
- Panel,
13
- useNodesState,
14
- useEdgesState,
15
- addEdge,
16
- Connection,
17
- ConnectionMode,
18
- MarkerType,
19
- } from '@xyflow/react'
20
- import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'
21
- import { WorkflowTransitionEdge } from './WorkflowTransitionEdge'
22
- import { STATUS_COLORS } from '../lib/status-colors'
23
- import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
24
- import { Edit3 } from 'lucide-react'
25
- import { useTheme } from '@open-mercato/ui/theme'
26
- import { useT } from '@open-mercato/shared/lib/i18n/context'
27
-
28
- // NOTE: ReactFlow styles should be imported in the page that uses this component
29
- // or in a global CSS file. Import: '@xyflow/react/dist/style.css'
3
+ import * as React from 'react'
4
+ import dynamic from 'next/dynamic'
5
+ import type { Node, Edge, Connection } from '@xyflow/react'
6
+ import { Spinner } from '@open-mercato/ui/primitives/spinner'
30
7
 
31
8
  export interface WorkflowGraphProps {
32
9
  initialNodes?: Node[]
@@ -41,224 +18,51 @@ export interface WorkflowGraphProps {
41
18
  height?: string
42
19
  }
43
20
 
21
+ const WorkflowGraphImpl = dynamic(() => import('./WorkflowGraphImpl'), {
22
+ ssr: false,
23
+ loading: () => null,
24
+ })
25
+
26
+ function WorkflowGraphPlaceholder({ height }: { height: string }) {
27
+ return (
28
+ <div
29
+ className="workflow-graph-container flex items-center justify-center rounded-lg border border-border bg-muted/30"
30
+ style={{ height }}
31
+ >
32
+ <Spinner className="h-6 w-6 text-muted-foreground" />
33
+ </div>
34
+ )
35
+ }
36
+
44
37
  /**
45
- * WorkflowGraph - ReactFlow wrapper component for workflow visualization
38
+ * WorkflowGraph — lazy-loaded ReactFlow wrapper.
46
39
  *
47
- * Provides a graph-based view of workflow definitions with:
48
- * - Pan and zoom controls
49
- * - Background grid
50
- * - Mini-map for navigation
51
- * - Optional editing capabilities
40
+ * @xyflow/react is loaded via next/dynamic({ ssr: false }) so the ~12 MB
41
+ * package only enters the Turbopack module graph when this component
42
+ * actually renders.
52
43
  */
53
- export function WorkflowGraph({
54
- initialNodes = [],
55
- initialEdges = [],
56
- onNodesChange: onNodesChangeProp,
57
- onEdgesChange: onEdgesChangeProp,
58
- onNodeClick: onNodeClickProp,
59
- onEdgeClick: onEdgeClickProp,
60
- onConnect: onConnectProp,
61
- editable = false,
62
- className = '',
63
- height = '600px',
64
- }: WorkflowGraphProps) {
65
- const t = useT()
66
- // Use ReactFlow hooks for node and edge state management
67
- const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
68
- const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
69
-
70
- // Get theme for dark mode support
71
- const { resolvedTheme } = useTheme()
72
- const isDark = resolvedTheme === 'dark'
73
- const backgroundDotColor = isDark ? '#374151' : '#e5e7eb'
74
- const [isCompactViewport, setIsCompactViewport] = useState(false)
75
-
76
- useEffect(() => {
77
- if (typeof window === 'undefined') return
78
- const mediaQuery = window.matchMedia('(max-width: 1279px)')
79
- const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches)
80
-
81
- updateViewportMode()
82
- mediaQuery.addEventListener('change', updateViewportMode)
83
-
44
+ export function WorkflowGraph(props: WorkflowGraphProps) {
45
+ const { height = '600px' } = props
46
+ // Track impl-chunk readiness so the loading placeholder respects the
47
+ // caller's `height` prop (next/dynamic's `loading` cannot access props).
48
+ // The browser caches the module, so the duplicate `import()` is free.
49
+ const [isImplReady, setIsImplReady] = React.useState(false)
50
+ React.useEffect(() => {
51
+ let cancelled = false
52
+ void import('./WorkflowGraphImpl').then(() => {
53
+ if (!cancelled) setIsImplReady(true)
54
+ })
84
55
  return () => {
85
- mediaQuery.removeEventListener('change', updateViewportMode)
56
+ cancelled = true
86
57
  }
87
58
  }, [])
88
59
 
89
- // Sync internal state when external state changes (e.g., when parent adds nodes)
90
- useEffect(() => {
91
- setNodes(initialNodes)
92
- }, [initialNodes, setNodes])
93
-
94
- useEffect(() => {
95
- setEdges(initialEdges)
96
- }, [initialEdges, setEdges])
97
-
98
- // Handle connection between nodes (when user drags from one node to another)
99
- const onConnect = useCallback(
100
- (connection: Connection) => {
101
- if (onConnectProp) {
102
- // Let parent handle the connection
103
- onConnectProp(connection)
104
- } else {
105
- // Fallback: handle internally if no parent callback
106
- const newEdge = {
107
- ...connection,
108
- type: 'workflowTransition',
109
- animated: false,
110
- markerEnd: {
111
- type: MarkerType.ArrowClosed,
112
- width: 16,
113
- height: 16,
114
- color: '#9ca3af',
115
- },
116
- }
117
- setEdges((eds) => addEdge(newEdge, eds))
118
- }
119
- },
120
- [setEdges, onConnectProp]
121
- )
122
-
123
- // Notify parent when nodes change
124
- const handleNodesChange = useCallback(
125
- (changes: any) => {
126
- onNodesChange(changes)
127
- if (onNodesChangeProp) {
128
- onNodesChangeProp(changes)
129
- }
130
- },
131
- [onNodesChange, onNodesChangeProp]
132
- )
133
-
134
- // Notify parent when edges change
135
- const handleEdgesChange = useCallback(
136
- (changes: any) => {
137
- onEdgesChange(changes)
138
- if (onEdgesChangeProp) {
139
- onEdgesChangeProp(changes)
140
- }
141
- },
142
- [onEdgesChange, onEdgesChangeProp]
143
- )
144
-
145
- // Register custom node types
146
- const nodeTypes = useMemo(
147
- () => ({
148
- start: StartNode,
149
- end: EndNode,
150
- userTask: UserTaskNode,
151
- automated: AutomatedNode,
152
- subWorkflow: SubWorkflowNode,
153
- waitForSignal: WaitForSignalNode,
154
- waitForTimer: WaitForTimerNode,
155
- }),
156
- []
157
- )
158
-
159
- // Register custom edge types
160
- const edgeTypes = useMemo(
161
- () => ({
162
- workflowTransition: WorkflowTransitionEdge,
163
- }),
164
- []
165
- )
166
-
167
- return (
168
- <div className={`workflow-graph-container ${className}`} style={{ height }}>
169
- <ReactFlow
170
- nodes={nodes}
171
- edges={edges}
172
- nodeTypes={nodeTypes}
173
- edgeTypes={edgeTypes}
174
- onNodesChange={handleNodesChange}
175
- onEdgesChange={handleEdgesChange}
176
- onConnect={editable ? onConnect : undefined}
177
- onNodeClick={onNodeClickProp}
178
- onEdgeClick={onEdgeClickProp}
179
- connectionMode={ConnectionMode.Loose}
180
- fitView
181
- fitViewOptions={{
182
- padding: 0.2,
183
- maxZoom: isCompactViewport ? 0.9 : 1,
184
- }}
185
- minZoom={0.1}
186
- maxZoom={2}
187
- defaultEdgeOptions={{
188
- type: 'workflowTransition',
189
- animated: false,
190
- markerEnd: {
191
- type: MarkerType.ArrowClosed,
192
- width: 16,
193
- height: 16,
194
- color: '#9ca3af',
195
- },
196
- }}
197
- nodesDraggable={editable}
198
- nodesConnectable={editable}
199
- elementsSelectable={editable}
200
- proOptions={{ hideAttribution: true }}
201
- >
202
- {/* Background grid for visual reference */}
203
- <Background
204
- variant={BackgroundVariant.Dots}
205
- gap={16}
206
- size={1}
207
- color={backgroundDotColor}
208
- />
209
-
210
- {/* Zoom and pan controls */}
211
- <Controls
212
- showZoom={true}
213
- showFitView={true}
214
- showInteractive={false}
215
- position={isCompactViewport ? 'bottom-right' : 'top-right'}
216
- className={`!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? 'scale-90 origin-bottom-right' : ''}`}
217
- />
218
-
219
- {/* Mini-map for navigation in large workflows */}
220
- {!isCompactViewport && (
221
- <MiniMap
222
- nodeStrokeWidth={3}
223
- nodeColor={(node) => {
224
- // Color nodes by status - using status-based colors
225
- const status = (node.data?.status || 'not_started') as keyof typeof STATUS_COLORS
226
- return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex
227
- }}
228
- maskColor="rgba(0, 0, 0, 0.1)"
229
- position="bottom-left"
230
- className="!bg-card !border !border-border !rounded-lg"
231
- />
232
- )}
233
-
234
- {/* Info panel */}
235
- {!editable && !isCompactViewport && (
236
- <Panel position="top-left" style={{ margin: 10 }}>
237
- <div className="bg-card rounded-lg shadow-sm border border-border px-4 py-2">
238
- <p className="text-sm text-muted-foreground font-medium">
239
- {t('workflows.graph.visualization')}
240
- </p>
241
- </div>
242
- </Panel>
243
- )}
244
-
245
- {editable && !isCompactViewport && (
246
- <Panel position="top-left" style={{ margin: 10 }}>
247
- <Alert variant="info" className="max-w-sm">
248
- <Edit3 className="size-4" />
249
- <AlertDescription className="font-medium">
250
- {t('workflows.graph.editModeInfo')}
251
- </AlertDescription>
252
- </Alert>
253
- </Panel>
254
- )}
255
- </ReactFlow>
256
- </div>
257
- )
60
+ if (!isImplReady) return <WorkflowGraphPlaceholder height={height} />
61
+ return <WorkflowGraphImpl {...props} />
258
62
  }
259
63
 
260
64
  /**
261
- * WorkflowGraphReadOnly - Read-only version for viewing workflow execution
65
+ * WorkflowGraphReadOnly read-only viewer that reuses WorkflowGraph.
262
66
  */
263
67
  export function WorkflowGraphReadOnly({
264
68
  nodes,
@@ -0,0 +1,233 @@
1
+ 'use client'
2
+
3
+ import '@xyflow/react/dist/style.css'
4
+
5
+ import { useCallback, useMemo, useEffect, useState } from 'react'
6
+ import {
7
+ ReactFlow,
8
+ Node,
9
+ Edge,
10
+ Controls,
11
+ Background,
12
+ BackgroundVariant,
13
+ MiniMap,
14
+ Panel,
15
+ useNodesState,
16
+ useEdgesState,
17
+ addEdge,
18
+ Connection,
19
+ ConnectionMode,
20
+ MarkerType,
21
+ } from '@xyflow/react'
22
+ import {StartNode, EndNode, UserTaskNode, AutomatedNode, SubWorkflowNode, WaitForSignalNode, WaitForTimerNode} from './nodes'
23
+ import { WorkflowTransitionEdge } from './WorkflowTransitionEdge'
24
+ import { STATUS_COLORS } from '../lib/status-colors'
25
+ import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
26
+ import { Edit3 } from 'lucide-react'
27
+ import { useTheme } from '@open-mercato/ui/theme'
28
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
29
+
30
+ export interface WorkflowGraphImplProps {
31
+ initialNodes?: Node[]
32
+ initialEdges?: Edge[]
33
+ onNodesChange?: (changes: any[]) => void
34
+ onEdgesChange?: (changes: any[]) => void
35
+ onNodeClick?: (event: React.MouseEvent, node: Node) => void
36
+ onEdgeClick?: (event: React.MouseEvent, edge: Edge) => void
37
+ onConnect?: (connection: Connection) => void
38
+ editable?: boolean
39
+ className?: string
40
+ height?: string
41
+ }
42
+
43
+ export default function WorkflowGraphImpl({
44
+ initialNodes = [],
45
+ initialEdges = [],
46
+ onNodesChange: onNodesChangeProp,
47
+ onEdgesChange: onEdgesChangeProp,
48
+ onNodeClick: onNodeClickProp,
49
+ onEdgeClick: onEdgeClickProp,
50
+ onConnect: onConnectProp,
51
+ editable = false,
52
+ className = '',
53
+ height = '600px',
54
+ }: WorkflowGraphImplProps) {
55
+ const t = useT()
56
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes)
57
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges)
58
+
59
+ const { resolvedTheme } = useTheme()
60
+ const isDark = resolvedTheme === 'dark'
61
+ const backgroundDotColor = isDark ? '#374151' : '#e5e7eb'
62
+ const [isCompactViewport, setIsCompactViewport] = useState(false)
63
+
64
+ useEffect(() => {
65
+ if (typeof window === 'undefined') return
66
+ const mediaQuery = window.matchMedia('(max-width: 1279px)')
67
+ const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches)
68
+
69
+ updateViewportMode()
70
+ mediaQuery.addEventListener('change', updateViewportMode)
71
+
72
+ return () => {
73
+ mediaQuery.removeEventListener('change', updateViewportMode)
74
+ }
75
+ }, [])
76
+
77
+ useEffect(() => {
78
+ setNodes(initialNodes)
79
+ }, [initialNodes, setNodes])
80
+
81
+ useEffect(() => {
82
+ setEdges(initialEdges)
83
+ }, [initialEdges, setEdges])
84
+
85
+ const onConnect = useCallback(
86
+ (connection: Connection) => {
87
+ if (onConnectProp) {
88
+ onConnectProp(connection)
89
+ } else {
90
+ const newEdge = {
91
+ ...connection,
92
+ type: 'workflowTransition',
93
+ animated: false,
94
+ markerEnd: {
95
+ type: MarkerType.ArrowClosed,
96
+ width: 16,
97
+ height: 16,
98
+ color: '#9ca3af',
99
+ },
100
+ }
101
+ setEdges((eds) => addEdge(newEdge, eds))
102
+ }
103
+ },
104
+ [setEdges, onConnectProp]
105
+ )
106
+
107
+ const handleNodesChange = useCallback(
108
+ (changes: any) => {
109
+ onNodesChange(changes)
110
+ if (onNodesChangeProp) {
111
+ onNodesChangeProp(changes)
112
+ }
113
+ },
114
+ [onNodesChange, onNodesChangeProp]
115
+ )
116
+
117
+ const handleEdgesChange = useCallback(
118
+ (changes: any) => {
119
+ onEdgesChange(changes)
120
+ if (onEdgesChangeProp) {
121
+ onEdgesChangeProp(changes)
122
+ }
123
+ },
124
+ [onEdgesChange, onEdgesChangeProp]
125
+ )
126
+
127
+ const nodeTypes = useMemo(
128
+ () => ({
129
+ start: StartNode,
130
+ end: EndNode,
131
+ userTask: UserTaskNode,
132
+ automated: AutomatedNode,
133
+ subWorkflow: SubWorkflowNode,
134
+ waitForSignal: WaitForSignalNode,
135
+ waitForTimer: WaitForTimerNode,
136
+ }),
137
+ []
138
+ )
139
+
140
+ const edgeTypes = useMemo(
141
+ () => ({
142
+ workflowTransition: WorkflowTransitionEdge,
143
+ }),
144
+ []
145
+ )
146
+
147
+ return (
148
+ <div className={`workflow-graph-container ${className}`} style={{ height }}>
149
+ <ReactFlow
150
+ nodes={nodes}
151
+ edges={edges}
152
+ nodeTypes={nodeTypes}
153
+ edgeTypes={edgeTypes}
154
+ onNodesChange={handleNodesChange}
155
+ onEdgesChange={handleEdgesChange}
156
+ onConnect={editable ? onConnect : undefined}
157
+ onNodeClick={onNodeClickProp}
158
+ onEdgeClick={onEdgeClickProp}
159
+ connectionMode={ConnectionMode.Loose}
160
+ fitView
161
+ fitViewOptions={{
162
+ padding: 0.2,
163
+ maxZoom: isCompactViewport ? 0.9 : 1,
164
+ }}
165
+ minZoom={0.1}
166
+ maxZoom={2}
167
+ defaultEdgeOptions={{
168
+ type: 'workflowTransition',
169
+ animated: false,
170
+ markerEnd: {
171
+ type: MarkerType.ArrowClosed,
172
+ width: 16,
173
+ height: 16,
174
+ color: '#9ca3af',
175
+ },
176
+ }}
177
+ nodesDraggable={editable}
178
+ nodesConnectable={editable}
179
+ elementsSelectable={editable}
180
+ proOptions={{ hideAttribution: true }}
181
+ >
182
+ <Background
183
+ variant={BackgroundVariant.Dots}
184
+ gap={16}
185
+ size={1}
186
+ color={backgroundDotColor}
187
+ />
188
+
189
+ <Controls
190
+ showZoom={true}
191
+ showFitView={true}
192
+ showInteractive={false}
193
+ position={isCompactViewport ? 'bottom-right' : 'top-right'}
194
+ className={`!bg-card !border-border !shadow-md [&>button]:!bg-card [&>button]:!border-border [&>button]:!fill-foreground [&>button:hover]:!bg-muted ${isCompactViewport ? 'scale-90 origin-bottom-right' : ''}`}
195
+ />
196
+
197
+ {!isCompactViewport && (
198
+ <MiniMap
199
+ nodeStrokeWidth={3}
200
+ nodeColor={(node) => {
201
+ const status = (node.data?.status || 'not_started') as keyof typeof STATUS_COLORS
202
+ return STATUS_COLORS[status]?.hex || STATUS_COLORS.not_started.hex
203
+ }}
204
+ maskColor="rgba(0, 0, 0, 0.1)"
205
+ position="bottom-left"
206
+ className="!bg-card !border !border-border !rounded-lg"
207
+ />
208
+ )}
209
+
210
+ {!editable && !isCompactViewport && (
211
+ <Panel position="top-left" style={{ margin: 10 }}>
212
+ <div className="bg-card rounded-lg shadow-sm border border-border px-4 py-2">
213
+ <p className="text-sm text-muted-foreground font-medium">
214
+ {t('workflows.graph.visualization')}
215
+ </p>
216
+ </div>
217
+ </Panel>
218
+ )}
219
+
220
+ {editable && !isCompactViewport && (
221
+ <Panel position="top-left" style={{ margin: 10 }}>
222
+ <Alert variant="info" className="max-w-sm">
223
+ <Edit3 className="size-4" />
224
+ <AlertDescription className="font-medium">
225
+ {t('workflows.graph.editModeInfo')}
226
+ </AlertDescription>
227
+ </Alert>
228
+ </Panel>
229
+ )}
230
+ </ReactFlow>
231
+ </div>
232
+ )
233
+ }