From 3930b12c4139b40560afc8ebe10d6d24735fbeeb Mon Sep 17 00:00:00 2001 From: Orinks <38449772+Orinks@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:46:54 -0500 Subject: [PATCH] fix(a11y): Add missing ARIA attributes for screen reader accessibility (#634) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(a11y): add missing ARIA attributes for screen reader accessibility Add comprehensive ARIA attributes across frontend components: - aria-label on icon-only buttons (close, edit, delete, add, refresh) - aria-required on required form fields (description, title, phase) - role="alert" on validation error messages - aria-expanded/aria-controls on collapsible sections - role="radiogroup" on button groups acting as radio selects Components updated: - GitHubSetupModal: repo action buttons, owner/visibility selection - TaskCreationWizard: description, image removal, toggles - TaskEditDialog: description, advanced/images toggles - AddFeatureDialog: title, description, phase fields - AddProjectModal: action buttons, error messages - KanbanBoard: add task, archive buttons - TaskHeader, FeatureDetailPanel, RoadmapHeader: icon buttons - WelcomeScreen, Sidebar, ProjectTabBar: various buttons 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): address PR review feedback - Remove redundant aria-required from Select component (keep only on SelectTrigger) - Replace hardcoded aria-label strings with i18n translation keys - Add translation strings for en and fr locales 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): convert remaining hardcoded aria-labels to i18n - Add useTranslation to components missing i18n support - Convert all hardcoded aria-label strings to translation keys - Add accessibility translation keys to common and tasks namespaces - Add French translations for all new aria-label keys - Update radiogroup aria-labels in GitHubSetupModal to use i18n 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): add screen reader indication for external links - Add sr-only text indicating links open in new window - Add aria-hidden to decorative ExternalLink icons - Add aria-labels for external link buttons - Update SafeLink component in Insights for markdown links - Add translation keys for external link accessibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): improve CollapsibleSection and FileTreeItem accessibility CollapsibleSection: - Add type="button" to prevent form submission - Add aria-expanded and aria-controls for screen readers - Add aria-hidden to decorative chevron icons - Use React useId hook for unique content IDs FileTreeItem: - Add keyboard support (Enter/Space) for directory toggle - Add role="button" and tabIndex for keyboard focus - Add aria-expanded state for directories - Add aria-labels for expand/collapse actions with i18n - Add focus ring styling for keyboard navigation - Mark decorative icons as aria-hidden 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): add aria-labels to icon buttons in FileExplorer and IssueList - Add aria-label to refresh and close buttons in FileExplorerPanel - Add aria-label to refresh button in IssueListHeader - Mark decorative icons as aria-hidden 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): convert TaskHeader edit button strings to i18n - Replace hardcoded aria-label and tooltip text with translation keys - Add editTask and cannotEditWhileRunning keys to en/fr locales 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): convert remaining hardcoded strings to i18n - WelcomeScreen: convert project button aria-label to i18n - TaskCreationWizard: add useTranslation and convert remove image aria-label - TaskEditDialog: add useTranslation and convert paste hint to i18n - Insights: refactor SafeLink to use factory pattern with translated "opens in new window" text Added translation keys for en/fr: - welcome:recentProjects.openProjectAriaLabel - tasks:images.removeImageAriaLabel - tasks:images.pasteHint 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): make ClaudeCodeStatusBadge aria-label match visible text The aria-label was "Learn more (opens in new window)" but the visible text was "Learn more about Claude Code" - these didn't match. Added specific translation key navigation:claudeCode.learnMoreAriaLabel that includes the full visible text plus "(opens in new window)" suffix. Removed redundant sr-only span since aria-label now provides the complete accessible name. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): pass markdownComponents as prop to MessageBubble After moving markdownComponents inside the Insights component for i18n support, MessageBubble (defined outside Insights) couldn't access it. - Import Components type from react-markdown - Add markdownComponents prop to MessageBubbleProps - Update MessageBubble to use the prop instead of free variable - Pass markdownComponents from Insights when rendering MessageBubble 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): add fallback string to learnMoreAriaLabel translation Added fallback string to match the pattern used elsewhere in the file, ensuring the aria-label has a sensible default if translation is missing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): add aria-keyshortcuts to navigation sidebar buttons Screen readers can now announce keyboard shortcuts (K, A, G, etc.) when focusing on navigation items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * fix(a11y): clarify close tab button removes project from app Screen readers now announce "Close tab (removes project from app)" instead of just "Close tab" to match the confirmation dialog behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 * Merge develop --------- Co-authored-by: Claude Opus 4.5 --- .../renderer/components/AddFeatureDialog.tsx | 6 +- .../renderer/components/AddProjectModal.tsx | 6 +- .../components/ClaudeCodeStatusBadge.tsx | 3 +- .../components/CompetitorAnalysisViewer.tsx | 7 +- .../renderer/components/FileExplorerPanel.tsx | 9 ++- .../src/renderer/components/FileTreeItem.tsx | 32 ++++++-- .../renderer/components/GitHubSetupModal.tsx | 20 ++++- .../src/renderer/components/Insights.tsx | 78 ++++++++++--------- .../src/renderer/components/KanbanBoard.tsx | 3 +- .../src/renderer/components/ProjectTabBar.tsx | 5 +- .../renderer/components/RateLimitModal.tsx | 4 +- .../src/renderer/components/Sidebar.tsx | 2 + .../components/SortableProjectTab.tsx | 2 +- .../components/TaskCreationWizard.tsx | 17 +++- .../renderer/components/TaskEditDialog.tsx | 18 +++-- .../src/renderer/components/WelcomeScreen.tsx | 1 + .../components/IssueListHeader.tsx | 6 +- .../project-settings/CollapsibleSection.tsx | 13 +++- .../components/roadmap/FeatureDetailPanel.tsx | 5 +- .../components/roadmap/RoadmapHeader.tsx | 4 +- .../components/task-detail/TaskHeader.tsx | 8 +- .../src/shared/i18n/locales/en/common.json | 18 ++++- .../src/shared/i18n/locales/en/dialogs.json | 12 ++- .../shared/i18n/locales/en/navigation.json | 1 + .../src/shared/i18n/locales/en/tasks.json | 10 ++- .../src/shared/i18n/locales/en/welcome.json | 3 +- .../src/shared/i18n/locales/fr/common.json | 18 ++++- .../src/shared/i18n/locales/fr/dialogs.json | 12 ++- .../shared/i18n/locales/fr/navigation.json | 1 + .../src/shared/i18n/locales/fr/tasks.json | 10 ++- .../src/shared/i18n/locales/fr/welcome.json | 3 +- 31 files changed, 254 insertions(+), 83 deletions(-) diff --git a/apps/frontend/src/renderer/components/AddFeatureDialog.tsx b/apps/frontend/src/renderer/components/AddFeatureDialog.tsx index d29e2b97..d139298b 100644 --- a/apps/frontend/src/renderer/components/AddFeatureDialog.tsx +++ b/apps/frontend/src/renderer/components/AddFeatureDialog.tsx @@ -208,6 +208,7 @@ export function AddFeatureDialog({ value={title} onChange={(e) => setTitle(e.target.value)} disabled={isSaving} + aria-required="true" /> @@ -223,6 +224,7 @@ export function AddFeatureDialog({ onChange={(e) => setDescription(e.target.value)} rows={3} disabled={isSaving} + aria-required="true" /> @@ -253,7 +255,7 @@ export function AddFeatureDialog({ onValueChange={setPhaseId} disabled={isSaving} > - + @@ -338,7 +340,7 @@ export function AddFeatureDialog({ {/* Error */} {error && ( -
+
{error}
diff --git a/apps/frontend/src/renderer/components/AddProjectModal.tsx b/apps/frontend/src/renderer/components/AddProjectModal.tsx index fa8db82c..852f3feb 100644 --- a/apps/frontend/src/renderer/components/AddProjectModal.tsx +++ b/apps/frontend/src/renderer/components/AddProjectModal.tsx @@ -167,6 +167,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje 'bg-card hover:bg-accent hover:border-accent transition-all duration-200', 'text-left group' )} + aria-label={t('addProject.openExistingAriaLabel')} >
@@ -188,6 +189,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje 'bg-card hover:bg-accent hover:border-accent transition-all duration-200', 'text-left group' )} + aria-label={t('addProject.createNewAriaLabel')} >
@@ -203,7 +205,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
{error && ( -
+
{error}
)} @@ -272,7 +274,7 @@ export function AddProjectModal({ open, onOpenChange, onProjectAdded }: AddProje
{error && ( -
+
{error}
)} diff --git a/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx b/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx index 06744006..726982fa 100644 --- a/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx +++ b/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx @@ -294,9 +294,10 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) size="sm" className="w-full text-xs text-muted-foreground gap-1" onClick={() => window.electronAPI?.openExternal?.('https://claude.ai/code')} + aria-label={t('navigation:claudeCode.learnMoreAriaLabel', 'Learn more about Claude Code (opens in new window)')} > {t('navigation:claudeCode.learnMore', 'Learn more about Claude Code')} - +
diff --git a/apps/frontend/src/renderer/components/CompetitorAnalysisViewer.tsx b/apps/frontend/src/renderer/components/CompetitorAnalysisViewer.tsx index c71043d7..53f47767 100644 --- a/apps/frontend/src/renderer/components/CompetitorAnalysisViewer.tsx +++ b/apps/frontend/src/renderer/components/CompetitorAnalysisViewer.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'react-i18next'; import { TrendingUp, ExternalLink, AlertCircle } from 'lucide-react'; import { Dialog, @@ -21,6 +22,8 @@ export function CompetitorAnalysisViewer({ open, onOpenChange, }: CompetitorAnalysisViewerProps) { + const { t } = useTranslation('common'); + if (!analysis) return null; return ( @@ -66,9 +69,11 @@ export function CompetitorAnalysisViewer({ target="_blank" rel="noopener noreferrer" className="text-primary hover:underline flex items-center gap-1 text-sm ml-4" + aria-label={t('accessibility.visitExternalLink', { name: competitor.name })} > - +
diff --git a/apps/frontend/src/renderer/components/FileExplorerPanel.tsx b/apps/frontend/src/renderer/components/FileExplorerPanel.tsx index e7c0b980..5c598d84 100644 --- a/apps/frontend/src/renderer/components/FileExplorerPanel.tsx +++ b/apps/frontend/src/renderer/components/FileExplorerPanel.tsx @@ -1,4 +1,5 @@ import { motion, AnimatePresence } from 'motion/react'; +import { useTranslation } from 'react-i18next'; import { X, FolderTree, RefreshCw } from 'lucide-react'; import { Button } from './ui/button'; import { ScrollArea } from './ui/scroll-area'; @@ -34,6 +35,7 @@ const contentVariants = { }; export function FileExplorerPanel({ projectPath }: FileExplorerPanelProps) { + const { t } = useTranslation('common'); const { isOpen, close, clearCache, loadDirectory } = useFileExplorerStore(); const handleRefresh = () => { @@ -80,17 +82,18 @@ export function FileExplorerPanel({ projectPath }: FileExplorerPanelProps) { size="icon" className="h-6 w-6" onClick={handleRefresh} - title="Refresh" + aria-label={t('buttons.refresh')} > - +
diff --git a/apps/frontend/src/renderer/components/FileTreeItem.tsx b/apps/frontend/src/renderer/components/FileTreeItem.tsx index e0af5b94..d6273b5a 100644 --- a/apps/frontend/src/renderer/components/FileTreeItem.tsx +++ b/apps/frontend/src/renderer/components/FileTreeItem.tsx @@ -1,4 +1,5 @@ -import { useState, useRef, useEffect, type DragEvent } from 'react'; +import { useState, useRef, useEffect, type DragEvent, type KeyboardEvent } from 'react'; +import { useTranslation } from 'react-i18next'; import { ChevronRight, ChevronDown, Folder, File, FileCode, FileJson, FileText, FileImage, Loader2 } from 'lucide-react'; import { cn } from '../lib/utils'; import type { FileNode } from '../../shared/types'; @@ -70,6 +71,7 @@ export function FileTreeItem({ isLoading, onToggle, }: FileTreeItemProps) { + const { t } = useTranslation('common'); const [isDragging, setIsDragging] = useState(false); const dragImageRef = useRef(null); @@ -98,6 +100,16 @@ export function FileTreeItem({ } }; + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + e.stopPropagation(); + if (node.isDirectory) { + onToggle(); + } + } + }; + const handleDragStart = (e: DragEvent) => { e.stopPropagation(); setIsDragging(true); @@ -147,37 +159,47 @@ export function FileTreeItem({ return (
{/* Expand/collapse chevron for directories */} {node.isDirectory ? ( ) : ( - +
) : ( -
+
{/* Personal account */} {githubUsername && ( diff --git a/apps/frontend/src/renderer/components/Insights.tsx b/apps/frontend/src/renderer/components/Insights.tsx index 72e01a9a..3f3a9b5f 100644 --- a/apps/frontend/src/renderer/components/Insights.tsx +++ b/apps/frontend/src/renderer/components/Insights.tsx @@ -1,4 +1,5 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { MessageSquare, Send, @@ -15,7 +16,7 @@ import { PanelLeftClose, PanelLeft } from 'lucide-react'; -import ReactMarkdown from 'react-markdown'; +import ReactMarkdown, { type Components } from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Button } from './ui/button'; import { Textarea } from './ui/textarea'; @@ -46,42 +47,40 @@ import { TASK_COMPLEXITY_COLORS } from '../../shared/constants'; -// Safe link renderer for ReactMarkdown to prevent phishing and ensure external links open safely -const SafeLink = ({ href, children, ...props }: React.AnchorHTMLAttributes) => { - // Validate URL - only allow http, https, and relative links - const isValidUrl = href && ( - href.startsWith('http://') || - href.startsWith('https://') || - href.startsWith('/') || - href.startsWith('#') - ); +// createSafeLink - factory function that creates a SafeLink component with i18n support +const createSafeLink = (opensInNewWindowText: string) => { + return function SafeLink({ href, children, ...props }: React.AnchorHTMLAttributes) { + // Validate URL - only allow http, https, and relative links + const isValidUrl = href && ( + href.startsWith('http://') || + href.startsWith('https://') || + href.startsWith('/') || + href.startsWith('#') + ); - if (!isValidUrl) { - // For invalid or potentially malicious URLs, render as plain text - return {children}; - } + if (!isValidUrl) { + // For invalid or potentially malicious URLs, render as plain text + return {children}; + } - // External links get security attributes - const isExternal = href?.startsWith('http://') || href?.startsWith('https://'); + // External links get security attributes and accessibility indicator + const isExternal = href?.startsWith('http://') || href?.startsWith('https://'); - return ( - - {children} - - ); -}; - -// Markdown components with safe link rendering -const markdownComponents = { - a: SafeLink, + return ( + + {children} + {isExternal && {opensInNewWindowText}} + + ); + }; }; interface InsightsProps { @@ -89,6 +88,7 @@ interface InsightsProps { } export function Insights({ projectId }: InsightsProps) { + const { t } = useTranslation('common'); const session = useInsightsStore((state) => state.session); const sessions = useInsightsStore((state) => state.sessions); const status = useInsightsStore((state) => state.status); @@ -96,6 +96,11 @@ export function Insights({ projectId }: InsightsProps) { const currentTool = useInsightsStore((state) => state.currentTool); const isLoadingSessions = useInsightsStore((state) => state.isLoadingSessions); + // Create markdown components with translated accessibility text + const markdownComponents = useMemo(() => ({ + a: createSafeLink(t('accessibility.opensInNewWindow')), + }), [t]); + const [inputValue, setInputValue] = useState(''); const [creatingTask, setCreatingTask] = useState(null); const [taskCreated, setTaskCreated] = useState>(new Set()); @@ -295,6 +300,7 @@ export function Insights({ projectId }: InsightsProps) { handleCreateTask(message)} isCreatingTask={creatingTask === message.id} taskCreated={taskCreated.has(message.id)} @@ -387,6 +393,7 @@ export function Insights({ projectId }: InsightsProps) { interface MessageBubbleProps { message: InsightsChatMessage; + markdownComponents: Components; onCreateTask: () => void; isCreatingTask: boolean; taskCreated: boolean; @@ -394,6 +401,7 @@ interface MessageBubbleProps { function MessageBubble({ message, + markdownComponents, onCreateTask, isCreatingTask, taskCreated diff --git a/apps/frontend/src/renderer/components/KanbanBoard.tsx b/apps/frontend/src/renderer/components/KanbanBoard.tsx index 6541eede..05563d67 100644 --- a/apps/frontend/src/renderer/components/KanbanBoard.tsx +++ b/apps/frontend/src/renderer/components/KanbanBoard.tsx @@ -219,6 +219,7 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli size="icon" className="h-7 w-7 hover:bg-primary/10 hover:text-primary transition-colors" onClick={onAddClick} + aria-label={t('kanban.addTaskAriaLabel')} > @@ -229,7 +230,7 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli size="icon" className="h-7 w-7 hover:bg-muted-foreground/10 hover:text-muted-foreground transition-colors" onClick={onArchiveAll} - title={t('tooltips.archiveAllDone')} + aria-label={t('tooltips.archiveAllDone')} > diff --git a/apps/frontend/src/renderer/components/ProjectTabBar.tsx b/apps/frontend/src/renderer/components/ProjectTabBar.tsx index a3a9bc14..7836b8c7 100644 --- a/apps/frontend/src/renderer/components/ProjectTabBar.tsx +++ b/apps/frontend/src/renderer/components/ProjectTabBar.tsx @@ -1,4 +1,5 @@ import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { Plus } from 'lucide-react'; import { cn } from '../lib/utils'; import { Button } from './ui/button'; @@ -26,6 +27,8 @@ export function ProjectTabBar({ className, onSettingsClick }: ProjectTabBarProps) { + const { t } = useTranslation('common'); + // Keyboard shortcuts for tab navigation useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -115,7 +118,7 @@ export function ProjectTabBar({ size="icon" className="h-8 w-8" onClick={onAddProject} - title="Add Project" + aria-label={t('projectTab.addProjectAriaLabel')} > diff --git a/apps/frontend/src/renderer/components/RateLimitModal.tsx b/apps/frontend/src/renderer/components/RateLimitModal.tsx index a3640165..b19c842a 100644 --- a/apps/frontend/src/renderer/components/RateLimitModal.tsx +++ b/apps/frontend/src/renderer/components/RateLimitModal.tsx @@ -375,9 +375,11 @@ export function RateLimitModal() { size="sm" className="gap-2" onClick={handleUpgrade} + aria-label={t('accessibility.upgradeSubscriptionAriaLabel')} > - +
diff --git a/apps/frontend/src/renderer/components/Sidebar.tsx b/apps/frontend/src/renderer/components/Sidebar.tsx index ce405852..5114b549 100644 --- a/apps/frontend/src/renderer/components/Sidebar.tsx +++ b/apps/frontend/src/renderer/components/Sidebar.tsx @@ -276,6 +276,7 @@ export function Sidebar({ key={item.id} onClick={() => handleNavClick(item.id)} disabled={!selectedProjectId} + aria-keyshortcuts={item.shortcut} className={cn( 'flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-all duration-200', 'hover:bg-accent hover:text-accent-foreground', @@ -354,6 +355,7 @@ export function Sidebar({ variant="ghost" size="icon" onClick={() => window.open('https://github.com/AndyMik90/Auto-Claude/issues', '_blank')} + aria-label={t('tooltips.help')} > diff --git a/apps/frontend/src/renderer/components/SortableProjectTab.tsx b/apps/frontend/src/renderer/components/SortableProjectTab.tsx index e6a9d6fd..d57cf129 100644 --- a/apps/frontend/src/renderer/components/SortableProjectTab.tsx +++ b/apps/frontend/src/renderer/components/SortableProjectTab.tsx @@ -160,7 +160,7 @@ export function SortableProjectTab({ isActive && 'opacity-100' )} onClick={onClose} - aria-label={t('projectTab.closeTab')} + aria-label={t('projectTab.closeTabAriaLabel')} > diff --git a/apps/frontend/src/renderer/components/TaskCreationWizard.tsx b/apps/frontend/src/renderer/components/TaskCreationWizard.tsx index e2c4d6f5..4bbed28a 100644 --- a/apps/frontend/src/renderer/components/TaskCreationWizard.tsx +++ b/apps/frontend/src/renderer/components/TaskCreationWizard.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback, useRef, useMemo, type ClipboardEvent, type DragEvent } from 'react'; +import { useTranslation } from 'react-i18next'; import { Loader2, ChevronDown, ChevronUp, Image as ImageIcon, X, RotateCcw, FolderTree, GitBranch } from 'lucide-react'; import { Dialog, @@ -59,6 +60,7 @@ export function TaskCreationWizard({ open, onOpenChange }: TaskCreationWizardProps) { + const { t } = useTranslation('tasks'); // Get selected agent profile from settings const { settings } = useSettingsStore(); const selectedProfile = DEFAULT_AGENT_PROFILES.find( @@ -797,6 +799,8 @@ export function TaskCreationWizard({ onDrop={handleTextareaDrop} rows={5} disabled={isCreating} + aria-required="true" + aria-describedby="description-help" className={cn( "resize-y min-h-[120px] max-h-[400px] relative bg-transparent", // Visual feedback when dragging over textarea @@ -815,7 +819,7 @@ export function TaskCreationWizard({ /> )} -

+

Files and images can be copy/pasted or dragged & dropped into the description.

@@ -852,6 +856,7 @@ export function TaskCreationWizard({ e.stopPropagation(); setImages(prev => prev.filter(img => img.id !== image.id)); }} + aria-label={t('images.removeImageAriaLabel', { filename: image.filename })} > @@ -915,6 +920,8 @@ export function TaskCreationWizard({ 'w-full justify-between py-2 px-3 rounded-md hover:bg-muted/50' )} disabled={isCreating} + aria-expanded={showAdvanced} + aria-controls="advanced-options-section" > Classification (optional) {showAdvanced ? ( @@ -926,7 +933,7 @@ export function TaskCreationWizard({ {/* Advanced Options */} {showAdvanced && ( -
+
{/* Category */}
@@ -1058,6 +1065,8 @@ export function TaskCreationWizard({ 'w-full justify-between py-2 px-3 rounded-md hover:bg-muted/50' )} disabled={isCreating} + aria-expanded={showGitOptions} + aria-controls="git-options-section" > @@ -1077,7 +1086,7 @@ export function TaskCreationWizard({ {/* Git Options */} {showGitOptions && ( -
+