Browse Source

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 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
18c01177ee
  1. 4
      src/app/api/list-workflows/route.ts
  2. 15
      src/components/GroupsOverlay.tsx
  3. 3
      src/components/WorkflowBrowserModal.tsx
  4. 1
      src/components/__tests__/GroupsOverlay.test.tsx
  5. 4
      src/components/nodes/AudioInputNode.tsx
  6. 4
      src/components/quickstart/WorkflowBrowserView.tsx

4
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 {

15
src/components/GroupsOverlay.tsx

@ -76,6 +76,13 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) {
const colorPickerRef = useRef<HTMLDivElement>(null);
const menuRef = useRef<HTMLDivElement>(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) {
<span
className="text-xs font-medium text-white truncate"
style={{ maxWidth: 200 }}
onDoubleClick={(e) => { e.stopPropagation(); setIsEditing(true); }}
onDoubleClick={(e) => { e.stopPropagation(); if (!group.locked) setIsEditing(true); }}
>
{group.name}
</span>

3
src/components/WorkflowBrowserModal.tsx

@ -23,6 +23,9 @@ export function WorkflowBrowserModal({
onClick={onClose}
>
<div
role="dialog"
aria-modal="true"
aria-labelledby="workflow-browser-title"
className="w-full max-w-2xl mx-4 bg-neutral-800 rounded-xl border border-neutral-700 shadow-2xl overflow-clip max-h-[85vh] flex flex-col"
onClick={(e) => e.stopPropagation()}
>

1
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();
});
});

4
src/components/nodes/AudioInputNode.tsx

@ -225,7 +225,11 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
</div>
) : (
<div
role="button"
tabIndex={0}
aria-label="Upload audio file"
onClick={() => 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" : ""}`}

4
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({
/>
</svg>
</div>
<h2 className="text-lg font-medium text-neutral-200">
<h2 id="workflow-browser-title" className="text-lg font-medium text-neutral-200">
Your Workflows
</h2>
</div>

Loading…
Cancel
Save