fix(canvas): WCAG focus-visible + aria-hidden decorative elements [CLOSED - subset of #1340] #1391

Closed
core-fe wants to merge 1 commits from fix/canvas-wcag-focus-visible-2 into main
12 changed files with 43 additions and 20 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ export function BatchActionBar() {
title="Clear selection (Escape)"
className="p-1.5 rounded-lg text-[12px] text-ink-mid hover:text-ink hover:bg-surface-card/50 transition-colors disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
>
<span aria-hidden="true"></span>
</button>
</div>
);
@@ -217,7 +217,11 @@ export function CommunicationOverlay() {
}
return (
<div className="fixed top-16 right-4 z-30 w-[320px] max-h-[400px] bg-surface-sunken/95 border border-line/50 rounded-xl shadow-xl shadow-black/30 backdrop-blur-sm overflow-hidden">
<div
role="complementary"
aria-label={`Communications panel — ${comms.length} message${comms.length !== 1 ? "s" : ""}`}
className="fixed top-16 right-4 z-30 w-[320px] max-h-[400px] bg-surface-sunken/95 border border-line/50 rounded-xl shadow-xl shadow-black/30 backdrop-blur-sm overflow-hidden"
>
<div className="flex items-center justify-between px-3 py-2 border-b border-line/60">
<div className="text-[10px] font-semibold text-ink-mid uppercase tracking-wider">
<span aria-hidden="true"> </span>Communications ({comms.length})
@@ -125,7 +125,7 @@ export function ConversationTraceModal({ open, workspaceId: _workspaceId, onClos
aria-label="Close conversation trace"
className="text-ink-mid hover:text-ink-mid text-lg px-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
>
<span aria-hidden="true"></span>
</button>
</Dialog.Close>
</div>
@@ -406,7 +406,7 @@ function StrictEnvRow({
{envKey}
</code>
{configured ? (
<span className="text-[10px] text-good"> set</span>
<span aria-hidden="true" className="text-[10px] text-good"> set</span>
) : (
<>
<input
@@ -498,7 +498,7 @@ function AnyOfEnvGroup({
{m}
</code>
{isConfigured ? (
<span className="text-[10px] text-good"> set</span>
<span aria-hidden="true" className="text-[10px] text-good"> set</span>
) : (
<>
<input
+1 -1
View File
@@ -323,7 +323,7 @@ export function WorkspaceNode({ id, data }: NodeProps<Node<WorkspaceNodeData>>)
}}
className="flex items-center gap-1.5 mt-1 w-full bg-accent/10 px-2 py-1 rounded-md border border-accent/40 hover:bg-accent/20 transition-colors text-left focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:outline-none"
>
<span className="text-[10px] text-accent"></span>
<span aria-hidden="true" className="text-[10px] text-accent"></span>
<span className="text-[10px] text-accent">Restart to apply changes</span>
</button>
)}
+3 -3
View File
@@ -145,7 +145,7 @@ export function ActivityTab({ workspaceId }: Props) {
: "text-ink-mid hover:text-ink-mid hover:bg-surface-card/60"
}`}
>
<span className="mr-0.5 opacity-60">{f.icon}</span> {f.label}
<span aria-hidden="true" className="mr-0.5 opacity-60">{f.icon}</span> {f.label}
</button>
))}
<div className="ml-auto flex items-center gap-2">
@@ -260,7 +260,7 @@ function ActivityRow({
</span>
)}
<span className={`text-[9px] ml-auto shrink-0 ${statusStyle.color}`}>
<span aria-hidden="true" className={`text-[9px] ml-auto shrink-0 ${statusStyle.color}`}>
{statusStyle.icon}
</span>
@@ -274,7 +274,7 @@ function ActivityRow({
{formatTime(entry.created_at)}
</span>
<span className="text-[9px] text-ink-mid">
<span aria-hidden="true" className="text-[9px] text-ink-mid">
{expanded ? "▼" : "▶"}
</span>
</div>
+2 -2
View File
@@ -383,7 +383,7 @@ function MyChatPanel({ workspaceId, data }: Props) {
// ignore — user will see no change and can retry
}
}}
className="px-2 py-0.5 text-[10px] font-medium bg-accent/10 hover:bg-accent/20 text-accent rounded border border-accent/30 transition-colors shrink-0"
className="px-2 py-0.5 text-[10px] font-medium bg-accent/10 hover:bg-accent/20 text-accent rounded border border-accent/30 transition-colors shrink-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-zinc-900"
>
Enable
</button>
@@ -582,7 +582,7 @@ function MyChatPanel({ workspaceId, data }: Props) {
<div className="mt-1.5 text-[9px] text-ink-mid space-y-0.5">
<div className="text-ink-mid">Processing with {runtimeDisplayName(data.runtime)}...</div>
{activityLog.map((line, i) => (
<div key={line + i} className="pl-2 border-l border-line"> {line}</div>
<div key={line + i} className="pl-2 border-l border-line"><span aria-hidden="true"></span> {line}</div>
))}
</div>
)}
@@ -35,7 +35,7 @@ export function FileEditor({
return (
<div className="flex-1 flex items-center justify-center">
<div className="text-center">
<div className="text-2xl opacity-20 mb-2">📄</div>
<div aria-hidden="true" className="text-2xl opacity-20 mb-2">📄</div>
<p className="text-[10px] text-ink-mid">Select a file to edit</p>
</div>
</div>
@@ -47,7 +47,7 @@ export function FileEditor({
{/* File header */}
<div className="flex items-center justify-between px-3 py-1.5 border-b border-line/40 bg-surface-sunken/20">
<div className="flex items-center gap-1.5 min-w-0">
<span className="text-[10px] opacity-50">{getIcon(selectedFile, false)}</span>
<span aria-hidden="true" className="text-[10px] opacity-50">{getIcon(selectedFile, false)}</span>
<span className="text-[10px] font-mono text-ink-mid truncate">{selectedFile}</span>
{isDirty && <span className="text-[9px] text-warm">modified</span>}
</div>
@@ -199,6 +199,9 @@ function TreeItem({
return (
<div>
<div
role="button"
tabIndex={0}
aria-label={`${node.name}${isDropTarget ? " (drop target)" : ""}`}
className={`group w-full flex items-center gap-1 px-2 py-0.5 text-left transition-colors cursor-pointer ${
isDropTarget
? "bg-accent/20 outline outline-1 outline-accent/60"
@@ -206,11 +209,17 @@ function TreeItem({
}`}
style={{ paddingLeft: `${depth * 12 + 8}px` }}
onClick={() => onToggleDir(node.path)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onToggleDir(node.path);
}
}}
onContextMenu={(e) => openContextMenu(e, node)}
{...dragProps}
>
<span className="text-[9px] text-ink-mid w-3">{isLoading ? "…" : expanded ? "▼" : "▶"}</span>
<span className="text-[10px]">📁</span>
<span aria-hidden="true" className="text-[9px] text-ink-mid w-3">{isLoading ? "…" : expanded ? "▼" : "▶"}</span>
<span aria-hidden="true" className="text-[10px]">📁</span>
<span className="text-[10px] text-ink-mid flex-1">{node.name}</span>
<button
aria-label={`Delete ${node.name}`}
@@ -244,14 +253,23 @@ function TreeItem({
return (
<div
role="button"
tabIndex={0}
aria-label={node.name}
className={`group flex items-center gap-1 px-2 py-0.5 cursor-pointer transition-colors ${
isSelected ? "bg-blue-900/30 text-ink" : "hover:bg-surface-card/40 text-ink-mid"
}`}
style={{ paddingLeft: `${depth * 12 + 20}px` }}
onClick={() => onSelect(node.path)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onSelect(node.path);
}
}}
onContextMenu={(e) => openContextMenu(e, node)}
>
<span className="text-[9px]">{getIcon(node.name, false)}</span>
<span aria-hidden="true" className="text-[9px]">{getIcon(node.name, false)}</span>
<span className="text-[10px] flex-1 truncate font-mono">{node.name}</span>
<button
aria-label={`Delete ${node.name}`}
+1 -1
View File
@@ -368,7 +368,7 @@ export function MemoryTab({ workspaceId }: Props) {
TTL {new Date(entry.expires_at).toLocaleString()}
</span>
)}
<span className="text-[10px] text-ink-mid">
<span aria-hidden="true" className="text-[10px] text-ink-mid">
{expanded === entry.key ? "▼" : "▶"}
</span>
</div>
+1 -1
View File
@@ -313,7 +313,7 @@ export function ScheduleTab({ workspaceId }: Props) {
<div className="flex-1 overflow-y-auto">
{schedules.length === 0 && !showForm ? (
<div className="p-6 text-center">
<div className="text-2xl mb-2"></div>
<div aria-hidden="true" className="text-2xl mb-2"></div>
<div className="text-[10px] text-ink-mid mb-1">No schedules yet</div>
<div className="text-[9px] text-ink-mid">
Add a schedule to run tasks automatically daily scans, periodic reports, standup reminders.
@@ -33,7 +33,7 @@ export function PendingAttachmentPill({
<button
onClick={onRemove}
aria-label={`Remove ${file.name}`}
className="ml-0.5 text-ink-mid hover:text-ink transition-colors shrink-0"
className="ml-0.5 text-ink-mid hover:text-ink transition-colors shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
<svg width="10" height="10" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
@@ -63,7 +63,8 @@ export function AttachmentChip({
<button
onClick={() => onDownload(attachment)}
title={`Download ${attachment.name}`}
className={`flex items-center gap-1.5 rounded-md border px-2 py-1 text-[10px] transition-colors max-w-full ${toneClasses}`}
aria-label={`Download ${attachment.name}`}
className={`flex items-center gap-1.5 rounded-md border px-2 py-1 text-[10px] transition-colors max-w-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 ${toneClasses}`}
>
<FileGlyph className="shrink-0 opacity-70" />
<span className="truncate">{attachment.name}</span>