From 236158d4a49b12d689707a95502ca2d05d23783a Mon Sep 17 00:00:00 2001 From: Molecule AI Core-FE Date: Wed, 22 Apr 2026 17:40:43 +0000 Subject: [PATCH] fix(canvas/a11y): add aria-hidden to decorative SVGs + MissingKeysModal semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DeleteCascadeConfirmDialog: aria-hidden on warning triangle SVG (button already has adjacent text content; icon is purely decorative) - Toolbar: aria-hidden on 4 decorative SVGs (stop-all, restart-pending, search, help) — buttons all have aria-label/aria-expanded/text - MissingKeysModal: role="dialog" aria-modal="true" aria-labelledby on container, id="missing-keys-title" on heading, requestAnimationFrame focus management via useRef (replaces autoFocus={index===0}) - CreateWorkspaceDialog: remove redundant aria-describedby={undefined} WCAG 2.1 SC 1.1.1 — screen readers skip purely-presentational icons. Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/CreateWorkspaceDialog.tsx | 1 - .../components/DeleteCascadeConfirmDialog.tsx | 2 +- canvas/src/components/MissingKeysModal.tsx | 23 +++++++++++++++---- canvas/src/components/Toolbar.tsx | 8 +++---- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/canvas/src/components/CreateWorkspaceDialog.tsx b/canvas/src/components/CreateWorkspaceDialog.tsx index 37e1231d..09975a03 100644 --- a/canvas/src/components/CreateWorkspaceDialog.tsx +++ b/canvas/src/components/CreateWorkspaceDialog.tsx @@ -166,7 +166,6 @@ export function CreateWorkspaceButton() { Create Workspace diff --git a/canvas/src/components/DeleteCascadeConfirmDialog.tsx b/canvas/src/components/DeleteCascadeConfirmDialog.tsx index e31114b7..b51eef60 100644 --- a/canvas/src/components/DeleteCascadeConfirmDialog.tsx +++ b/canvas/src/components/DeleteCascadeConfirmDialog.tsx @@ -101,7 +101,7 @@ export function DeleteCascadeConfirmDialog({ {/* Warning */}
- + diff --git a/canvas/src/components/MissingKeysModal.tsx b/canvas/src/components/MissingKeysModal.tsx index 31f3bb2d..8444b7c9 100644 --- a/canvas/src/components/MissingKeysModal.tsx +++ b/canvas/src/components/MissingKeysModal.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { api } from "@/lib/api"; import { getKeyLabel } from "@/lib/deploy-preflight"; @@ -38,6 +38,7 @@ export function MissingKeysModal({ }: Props) { const [entries, setEntries] = useState([]); const [globalError, setGlobalError] = useState(null); + const firstInputRef = useRef(null); // Initialize entries when modal opens or missingKeys change useEffect(() => { @@ -55,7 +56,14 @@ export function MissingKeysModal({ setGlobalError(null); }, [open, missingKeys]); - // Keyboard handler + // Focus first input when modal opens + useEffect(() => { + if (!open) return; + const raf = requestAnimationFrame(() => { + firstInputRef.current?.focus(); + }); + return () => cancelAnimationFrame(raf); + }, [open]); useEffect(() => { if (!open) return; const handler = (e: KeyboardEvent) => { @@ -134,7 +142,12 @@ export function MissingKeysModal({ /> {/* Dialog */} -
+
{/* Header */}
@@ -150,7 +163,7 @@ export function MissingKeysModal({
-

+

Missing API Keys

@@ -193,7 +206,7 @@ export function MissingKeysModal({ onChange={(e) => updateEntry(index, { value: e.target.value.trimStart() })} placeholder={entry.key.includes("API_KEY") ? "sk-..." : "Enter value"} type="password" - autoFocus={index === 0} + ref={index === 0 ? firstInputRef : undefined} onKeyDown={(e) => { if (e.key === "Enter" && entry.value.trim()) { handleSaveKey(index); diff --git a/canvas/src/components/Toolbar.tsx b/canvas/src/components/Toolbar.tsx index 63684204..f994c75b 100644 --- a/canvas/src/components/Toolbar.tsx +++ b/canvas/src/components/Toolbar.tsx @@ -159,7 +159,7 @@ export function Toolbar() { title={`Stop all running tasks (${counts.activeTasks} active)`} aria-label={stopping ? "Stopping all running tasks" : `Stop all running tasks (${counts.activeTasks} active)`} > - + @@ -177,7 +177,7 @@ export function Toolbar() { title={`Restart ${needsRestartNodes.length} workspace${needsRestartNodes.length === 1 ? "" : "s"} that need to pick up config or secret changes`} aria-label={restartingAll ? "Restarting workspaces" : `Restart ${needsRestartNodes.length} workspace${needsRestartNodes.length === 1 ? "" : "s"} pending config or secret changes`} > - + @@ -253,7 +253,7 @@ export function Toolbar() { onClick={() => useCanvasStore.getState().setSearchOpen(true)} className="flex items-center gap-1.5 px-2.5 py-1 bg-zinc-800/50 hover:bg-zinc-700/50 border border-zinc-700/40 rounded-lg transition-colors" > - + @@ -269,7 +269,7 @@ export function Toolbar() { aria-expanded={helpOpen} aria-label="Open quick help" > - +