From 338a006e64ee29ea0533da02735b354a5704a42c Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Thu, 5 Mar 2026 11:56:00 +0000 Subject: [PATCH 1/3] fix: sanitize component key input in pipeline editor Automatically convert spaces to underscores and strip invalid characters as users type component keys. Adds helper text showing the allowed format. Prevents cryptic server-side validation errors from Vector's identifier requirements. --- src/components/flow/detail-panel.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/flow/detail-panel.tsx b/src/components/flow/detail-panel.tsx index 46f5824..e5f5360 100644 --- a/src/components/flow/detail-panel.tsx +++ b/src/components/flow/detail-panel.tsx @@ -140,9 +140,13 @@ export function DetailPanel() { ); const handleKeyChange = useCallback( - (key: string) => { + (raw: string) => { if (selectedNodeId) { - updateNodeKey(selectedNodeId, key); + const sanitized = raw + .replace(/\s+/g, "_") + .replace(/[^a-zA-Z0-9_]/g, "") + .replace(/^(\d+)/, "_$1"); + updateNodeKey(selectedNodeId, sanitized); } }, [selectedNodeId, updateNodeKey], @@ -266,6 +270,9 @@ export function DetailPanel() { onChange={(e) => handleKeyChange(e.target.value)} disabled={isSystemLocked} /> +

+ Letters, numbers, and underscores only (e.g. traefik_logs) +

{/* Enabled toggle */} From 86add35aa9fcbef9980157484a64576b05d832b7 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Thu, 5 Mar 2026 12:33:40 +0000 Subject: [PATCH 2/3] fix: address greptile review findings --- src/components/flow/detail-panel.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/flow/detail-panel.tsx b/src/components/flow/detail-panel.tsx index e5f5360..204330d 100644 --- a/src/components/flow/detail-panel.tsx +++ b/src/components/flow/detail-panel.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useParams } from "next/navigation"; import { Copy, Trash2, Lock, Info } from "lucide-react"; import { useFlowStore } from "@/stores/flow-store"; @@ -122,6 +122,13 @@ export function DetailPanel() { ? nodes.find((n) => n.id === selectedNodeId) : null; + const storeKey = (selectedNode?.data as { componentKey?: string })?.componentKey ?? ""; + const [displayKey, setDisplayKey] = useState(storeKey); + + useEffect(() => { + setDisplayKey(storeKey); + }, [storeKey]); + const upstream = useMemo( () => selectedNodeId @@ -141,12 +148,13 @@ export function DetailPanel() { const handleKeyChange = useCallback( (raw: string) => { + setDisplayKey(raw); if (selectedNodeId) { const sanitized = raw .replace(/\s+/g, "_") .replace(/[^a-zA-Z0-9_]/g, "") .replace(/^(\d+)/, "_$1"); - updateNodeKey(selectedNodeId, sanitized); + if (sanitized) updateNodeKey(selectedNodeId, sanitized); } }, [selectedNodeId, updateNodeKey], @@ -266,7 +274,7 @@ export function DetailPanel() { handleKeyChange(e.target.value)} disabled={isSystemLocked} /> From 79a4e710fc2be2fc960892ec70086d6c0416ee38 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Thu, 5 Mar 2026 12:40:50 +0000 Subject: [PATCH 3/3] fix: reset displayKey to storeKey when sanitized input is empty --- src/components/flow/detail-panel.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/flow/detail-panel.tsx b/src/components/flow/detail-panel.tsx index 204330d..e93a1ad 100644 --- a/src/components/flow/detail-panel.tsx +++ b/src/components/flow/detail-panel.tsx @@ -148,16 +148,20 @@ export function DetailPanel() { const handleKeyChange = useCallback( (raw: string) => { - setDisplayKey(raw); if (selectedNodeId) { const sanitized = raw .replace(/\s+/g, "_") .replace(/[^a-zA-Z0-9_]/g, "") .replace(/^(\d+)/, "_$1"); - if (sanitized) updateNodeKey(selectedNodeId, sanitized); + if (sanitized) { + setDisplayKey(raw); + updateNodeKey(selectedNodeId, sanitized); + } else { + setDisplayKey(storeKey); + } } }, - [selectedNodeId, updateNodeKey], + [selectedNodeId, updateNodeKey, storeKey], ); const handleDelete = useCallback(() => {