diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a4af320..c8b6d3d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,8 +1,11 @@ { "permissions": { "allow": [ - "Bash(mkdir:*)" + "Bash(mkdir:*)", + "Bash(yarn dev:*)", + "Bash(yarn lint:*)", + "Bash(yarn build:*)" ], "deny": [] } -} \ No newline at end of file +} diff --git a/components/process-flow/ProcessFlowCanvas.vue b/components/process-flow/ProcessFlowCanvas.vue index 3aba1b0..cf6cc47 100644 --- a/components/process-flow/ProcessFlowCanvas.vue +++ b/components/process-flow/ProcessFlowCanvas.vue @@ -14,6 +14,7 @@ import { Background } from "@vue-flow/background"; import { Controls } from "@vue-flow/controls"; import { MiniMap } from "@vue-flow/minimap"; import InteractiveArrowEdge from "./InteractiveArrowEdge.vue"; +import { useNodeValidation } from '~/composables/useNodeValidation'; // Import all file-based custom node components import StartNode from "~/components/process-flow/custom/StartNode.vue"; import EndNode from "~/components/process-flow/custom/EndNode.vue"; @@ -96,6 +97,16 @@ const emit = defineEmits([ // Get the flow instance const { flowInstance } = useVueFlow(); +// Initialize validation system +const { + validateProcess, + validationResults, + isValidating, + overallValidationStatus, + validationSummary, + clearValidation +} = useNodeValidation(); + // Initialize Vue Flow const { nodes, @@ -734,6 +745,27 @@ onEdgesChange((changes) => { emit("edgesChange", changes, edges.value); }); +// Watch for changes to nodes and edges to trigger validation +watch( + () => [nodes.value, edges.value], + ([currentNodes, currentEdges]) => { + if (currentNodes && currentNodes.length > 0) { + // Debounce validation to avoid excessive re-computation + clearTimeout(validationTimeout.value); + validationTimeout.value = setTimeout(() => { + validateProcess(currentNodes, currentEdges || []); + }, 300); + } else { + // Clear validation if no nodes + clearValidation(); + } + }, + { deep: true } +); + +// Timeout for debouncing validation +const validationTimeout = ref(null); + // Handle new connections const handleConnect = (connection) => { if (!connection.source || !connection.target) return; @@ -1928,6 +1960,55 @@ function fromObject(flowObject) { + + + +
+
+

Process Validation

+
+ + + {{ overallValidationStatus === 'error' ? 'Errors' : + overallValidationStatus === 'warning' ? 'Warnings' : 'Valid' }} + +
+
+ +
+
+ + {{ validationSummary.errors }} error{{ validationSummary.errors > 1 ? 's' : '' }} + + + {{ validationSummary.warnings }} warning{{ validationSummary.warnings > 1 ? 's' : '' }} + + + {{ validationSummary.infos }} info{{ validationSummary.infos > 1 ? 's' : '' }} + +
+
+ +
+ + Process validation passed +
+ +
+ Add nodes to validate process +
+ +
+ + Validating... +
+
+
@@ -2313,4 +2394,104 @@ function fromObject(flowObject) { .help-list li:last-child { margin-bottom: 0; } + +/* Validation Panel Styles */ +.validation-panel { + pointer-events: all; +} + +.validation-panel-content { + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + padding: 12px; + min-width: 220px; + max-width: 280px; +} + +.validation-title { + font-size: 12px; + font-weight: 600; + color: #374151; + margin: 0; +} + +.validation-status { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; + font-weight: 500; + padding: 2px 6px; + border-radius: 4px; +} + +.validation-status.error { + color: #dc2626; + background: #fef2f2; +} + +.validation-status.warning { + color: #d97706; + background: #fffbeb; +} + +.validation-status.valid { + color: #059669; + background: #f0fdf4; +} + +.validation-summary { + margin-top: 8px; +} + +.summary-stats { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.stat-item { + font-size: 10px; + padding: 2px 6px; + border-radius: 3px; + font-weight: 500; +} + +.stat-item.error { + color: #dc2626; + background: #fef2f2; +} + +.stat-item.warning { + color: #d97706; + background: #fffbeb; +} + +.stat-item.info { + color: #2563eb; + background: #eff6ff; +} + +.validation-success { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} + +.validation-empty { + margin-top: 8px; +} + +.validation-loading { + display: flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} + +.status-text { + font-size: 10px; +} diff --git a/components/process-flow/ValidationIndicator.vue b/components/process-flow/ValidationIndicator.vue new file mode 100644 index 0000000..daabd14 --- /dev/null +++ b/components/process-flow/ValidationIndicator.vue @@ -0,0 +1,175 @@ + + + + + \ No newline at end of file diff --git a/components/process-flow/ValidationTooltip.vue b/components/process-flow/ValidationTooltip.vue new file mode 100644 index 0000000..a6734bc --- /dev/null +++ b/components/process-flow/ValidationTooltip.vue @@ -0,0 +1,160 @@ + + + + + \ No newline at end of file diff --git a/components/process-flow/custom/ApiNode.vue b/components/process-flow/custom/ApiNode.vue index ce66b3e..8c52544 100644 --- a/components/process-flow/custom/ApiNode.vue +++ b/components/process-flow/custom/ApiNode.vue @@ -1,5 +1,7 @@