From 18c01177ee91cdd1f36972f5e37ba7fa4fbfd621 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Sun, 29 Mar 2026 19:41:41 +1300 Subject: [PATCH] fix: code review fixes for groups, a11y, regex, and dialog semantics - GroupsOverlay: reset color picker when menu closes, guard locked groups from drag/resize/rename - AudioInputNode: add keyboard accessibility to dropzone (role, tabIndex, onKeyDown) - list-workflows: fix name regex to handle escaped quotes - GroupsOverlay.test: add missing NBP Input menu item assertion - WorkflowBrowserView: make relativePath required to match API contract - WorkflowBrowserModal: add dialog role, aria-modal, aria-labelledby Co-Authored-By: Claude Opus 4.6 --- src/app/api/list-workflows/route.ts | 4 ++-- src/components/GroupsOverlay.tsx | 15 ++++++++++++--- src/components/WorkflowBrowserModal.tsx | 3 +++ src/components/__tests__/GroupsOverlay.test.tsx | 1 + src/components/nodes/AudioInputNode.tsx | 4 ++++ src/components/quickstart/WorkflowBrowserView.tsx | 4 ++-- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/app/api/list-workflows/route.ts b/src/app/api/list-workflows/route.ts index ddfc0f02..56630885 100644 --- a/src/app/api/list-workflows/route.ts +++ b/src/app/api/list-workflows/route.ts @@ -77,8 +77,8 @@ async function probeWorkflow( } // Extract name via regex — avoids JSON.parse on potentially huge files - const nameMatch = head.match(/"name"\s*:\s*"([^"]*)"/); - const name = nameMatch ? nameMatch[1] : dirName; + const nameMatch = head.match(/"name"\s*:\s*"((?:\\.|[^"\\])*)"/); + const name = nameMatch ? nameMatch[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\') : dirName; const stat = await fs.stat(filePath); return { diff --git a/src/components/GroupsOverlay.tsx b/src/components/GroupsOverlay.tsx index a5cac780..3dad28cb 100644 --- a/src/components/GroupsOverlay.tsx +++ b/src/components/GroupsOverlay.tsx @@ -76,6 +76,13 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { const colorPickerRef = useRef(null); const menuRef = useRef(null); + // Reset color picker when menu closes + useEffect(() => { + if (!showMenu) { + setShowColorPicker(false); + } + }, [showMenu]); + useEffect(() => { if (group?.name && !isEditing) { setEditName(group.name); @@ -145,6 +152,7 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { // Header drag handlers const handleHeaderMouseDown = useCallback( (e: React.MouseEvent) => { + if (group?.locked) return; if ( (e.target as HTMLElement).closest("button") || (e.target as HTMLElement).closest("input") @@ -156,12 +164,13 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { setIsDragging(true); dragStartRef.current = { x: e.clientX, y: e.clientY }; }, - [] + [group?.locked] ); // Resize handlers const handleResizeMouseDown = useCallback( (e: React.MouseEvent, handle: string) => { + if (group?.locked) return; e.stopPropagation(); e.preventDefault(); setIsResizing(true); @@ -175,7 +184,7 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { posY: group.position.y, }; }, - [group?.size, group?.position] + [group?.locked, group?.size, group?.position] ); useEffect(() => { @@ -325,7 +334,7 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { { e.stopPropagation(); setIsEditing(true); }} + onDoubleClick={(e) => { e.stopPropagation(); if (!group.locked) setIsEditing(true); }} > {group.name} diff --git a/src/components/WorkflowBrowserModal.tsx b/src/components/WorkflowBrowserModal.tsx index 077701bd..3b4693a3 100644 --- a/src/components/WorkflowBrowserModal.tsx +++ b/src/components/WorkflowBrowserModal.tsx @@ -23,6 +23,9 @@ export function WorkflowBrowserModal({ onClick={onClose} >
e.stopPropagation()} > diff --git a/src/components/__tests__/GroupsOverlay.test.tsx b/src/components/__tests__/GroupsOverlay.test.tsx index acd014d3..84cf1888 100644 --- a/src/components/__tests__/GroupsOverlay.test.tsx +++ b/src/components/__tests__/GroupsOverlay.test.tsx @@ -226,6 +226,7 @@ describe("GroupControlsOverlay", () => { expect(screen.getByText("Background")).toBeInTheDocument(); expect(screen.getByText("Lock")).toBeInTheDocument(); + expect(screen.getByText("NBP Input")).toBeInTheDocument(); expect(screen.getByText("Delete")).toBeInTheDocument(); }); }); diff --git a/src/components/nodes/AudioInputNode.tsx b/src/components/nodes/AudioInputNode.tsx index 98d42a6e..2bddc8d8 100644 --- a/src/components/nodes/AudioInputNode.tsx +++ b/src/components/nodes/AudioInputNode.tsx @@ -225,7 +225,11 @@ export function AudioInputNode({ id, data, selected }: NodeProps ) : (
fileInputRef.current?.click()} + onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fileInputRef.current?.click(); } }} onDrop={handleDrop} onDragOver={handleDragOver} className={`w-full h-full bg-neutral-900/40 flex flex-col items-center justify-center cursor-pointer hover:bg-neutral-800/60 transition-colors ${nodeData.isOptional ? "border-2 border-dashed border-neutral-600" : ""}`} diff --git a/src/components/quickstart/WorkflowBrowserView.tsx b/src/components/quickstart/WorkflowBrowserView.tsx index b6cf6785..52a461f1 100644 --- a/src/components/quickstart/WorkflowBrowserView.tsx +++ b/src/components/quickstart/WorkflowBrowserView.tsx @@ -11,7 +11,7 @@ import { interface WorkflowListEntry { name: string; directoryPath: string; - relativePath?: string; + relativePath: string; lastModified: number; } @@ -188,7 +188,7 @@ export function WorkflowBrowserView({ />
-

+

Your Workflows