From 8124d8587dbd7d8e1ee8d5470fe975f58a02126f Mon Sep 17 00:00:00 2001 From: shrimbly Date: Wed, 15 Apr 2026 19:56:57 +1200 Subject: [PATCH] fix: accessibility, progress monotonicity, speed guard, and flaky test - AnnotationNode: add aria-label and focus-visible styles to download btn - AudioInputNode: same accessibility fix for download button - useStitchVideos: fix non-monotonic progress (8 -> 11 after 10) - useTypewriter: guard speed with Math.max(16, speed) minimum - loopEdge test: replace setTimeout race with vi.waitFor polling Co-Authored-By: Claude Opus 4.6 --- src/components/nodes/AnnotationNode.tsx | 3 ++- src/components/nodes/AudioInputNode.tsx | 3 ++- src/hooks/useStitchVideos.ts | 2 +- src/hooks/useTypewriter.ts | 5 +++-- src/store/__tests__/loopEdge.integration.test.ts | 10 ++++++---- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/nodes/AnnotationNode.tsx b/src/components/nodes/AnnotationNode.tsx index cb8cbc73..20f8d2a0 100644 --- a/src/components/nodes/AnnotationNode.tsx +++ b/src/components/nodes/AnnotationNode.tsx @@ -136,7 +136,8 @@ export function AnnotationNode({ id, data, selected }: NodeProps diff --git a/src/components/nodes/AudioInputNode.tsx b/src/components/nodes/AudioInputNode.tsx index 806ab8b9..7fe9f525 100644 --- a/src/components/nodes/AudioInputNode.tsx +++ b/src/components/nodes/AudioInputNode.tsx @@ -217,7 +217,8 @@ export function AudioInputNode({ id, data, selected }: NodeProps downloadMedia(nodeData.audioFile!, "audio")} - className="absolute top-1 right-7 w-5 h-5 bg-black/60 hover:bg-black/80 text-white rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center" + aria-label="Download audio" + className="absolute top-1 right-7 w-5 h-5 bg-black/60 hover:bg-black/80 text-white rounded text-xs opacity-0 group-hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-1 focus-visible:ring-white transition-opacity flex items-center justify-center" title="Download audio" > diff --git a/src/hooks/useStitchVideos.ts b/src/hooks/useStitchVideos.ts index c41b2b31..cce63fbe 100644 --- a/src/hooks/useStitchVideos.ts +++ b/src/hooks/useStitchVideos.ts @@ -295,7 +295,7 @@ export async function stitchVideosAsync( let outputStarted = false; if (effectiveAudioData) { - updateProgress('processing', 'Detecting supported audio codec...', 8); + updateProgress('processing', 'Detecting supported audio codec...', 11); // Detect the best supported audio codec for MP4 // Try common codecs in order of preference: aac, mp3 (no opus - Twitter doesn't support it) diff --git a/src/hooks/useTypewriter.ts b/src/hooks/useTypewriter.ts index 4b4d1b3c..c90e48c2 100644 --- a/src/hooks/useTypewriter.ts +++ b/src/hooks/useTypewriter.ts @@ -13,6 +13,7 @@ interface UseTypewriterResult { * @returns Object with displayedText (current partial text) and isComplete (true when done) */ export function useTypewriter(text: string, speed: number = 50): UseTypewriterResult { + const safeSpeed = Math.max(16, speed); const [displayedText, setDisplayedText] = useState(""); const [currentIndex, setCurrentIndex] = useState(0); @@ -38,10 +39,10 @@ export function useTypewriter(text: string, speed: number = 50): UseTypewriterRe } return nextIndex; }); - }, speed); + }, safeSpeed); return () => clearInterval(timer); - }, [text, speed, currentIndex]); + }, [text, safeSpeed, currentIndex]); return { displayedText, diff --git a/src/store/__tests__/loopEdge.integration.test.ts b/src/store/__tests__/loopEdge.integration.test.ts index 3cccca45..89b71dba 100644 --- a/src/store/__tests__/loopEdge.integration.test.ts +++ b/src/store/__tests__/loopEdge.integration.test.ts @@ -214,10 +214,12 @@ describe("executeWorkflow with loop edges", () => { // Start execution (don't await) const executionPromise = useWorkflowStore.getState().executeWorkflow(); - // Abort after a short delay - setTimeout(() => { - useWorkflowStore.getState().stopWorkflow(); - }, 10); + // Poll until execution has actually started before stopping + await vi.waitFor(() => { + expect(useWorkflowStore.getState().isRunning).toBe(true); + }, { timeout: 1000 }); + + useWorkflowStore.getState().stopWorkflow(); await executionPromise;