Legend
diff --git a/canvas/src/components/MemoryInspectorPanel.tsx b/canvas/src/components/MemoryInspectorPanel.tsx
index 6358f802..6655ad37 100644
--- a/canvas/src/components/MemoryInspectorPanel.tsx
+++ b/canvas/src/components/MemoryInspectorPanel.tsx
@@ -360,7 +360,7 @@ export function MemoryInspectorPanel({ workspaceId }: Props) {
setDebouncedQuery('');
}}
aria-label="Clear search"
- className="absolute right-2 text-ink-mid hover:text-ink transition-colors text-sm leading-none"
+ className="absolute right-2 text-ink-mid hover:text-ink transition-colors text-sm leading-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
×
@@ -381,7 +381,7 @@ export function MemoryInspectorPanel({ workspaceId }: Props) {
type="button"
onClick={loadEntries}
disabled={pluginUnavailable}
- className="px-2 py-1 text-[11px] bg-surface-card hover:bg-surface-card text-ink-mid rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ className="px-2 py-1 text-[11px] bg-surface-card hover:bg-surface-card text-ink-mid rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
aria-label="Refresh memories"
>
↻ Refresh
@@ -515,7 +515,7 @@ function MemoryEntryRow({ entry, onDelete }: MemoryEntryRowProps) {
{/* Header row */}
diff --git a/canvas/src/components/MissingKeysModal.tsx b/canvas/src/components/MissingKeysModal.tsx
index 80231043..c9dbc90d 100644
--- a/canvas/src/components/MissingKeysModal.tsx
+++ b/canvas/src/components/MissingKeysModal.tsx
@@ -706,7 +706,7 @@ function AllKeysModal({
type="button"
onClick={() => handleSaveKey(index)}
disabled={!entry.value.trim() || entry.saving}
- className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-[11px] rounded text-white disabled:opacity-30 transition-colors shrink-0"
+ className="px-3 py-1.5 bg-accent-strong hover:bg-accent text-[11px] rounded text-white disabled:opacity-30 transition-colors shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{entry.saving ? "..." : "Save"}
@@ -730,7 +730,7 @@ function AllKeysModal({
@@ -740,7 +740,7 @@ function AllKeysModal({
@@ -748,7 +748,7 @@ function AllKeysModal({
type="button"
onClick={handleAddKeysAndDeploy}
disabled={!allSaved || anySaving}
- className="px-3.5 py-1.5 text-[12px] bg-accent-strong hover:bg-accent text-white rounded-lg transition-colors disabled:opacity-40"
+ className="px-3.5 py-1.5 text-[12px] bg-accent-strong hover:bg-accent text-white rounded-lg transition-colors disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{anySaving ? "Saving..." : allSaved ? "Deploy" : "Add Keys"}
diff --git a/canvas/src/components/OnboardingWizard.tsx b/canvas/src/components/OnboardingWizard.tsx
index b513636b..5485f5b7 100644
--- a/canvas/src/components/OnboardingWizard.tsx
+++ b/canvas/src/components/OnboardingWizard.tsx
@@ -210,7 +210,7 @@ export function OnboardingWizard() {
// Was hover:bg-surface-card on top of bg-surface-card —
// silent no-op hover. Lift to surface-elevated, matching
// the Cancel pattern in ConfirmDialog.
- className="px-3 py-1.5 bg-surface-card hover:bg-surface-elevated hover:text-ink rounded-lg text-[11px] text-ink-mid transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 focus-visible:ring-offset-2 focus-visible:ring-offset-surface-sunken"
+ className="px-3 py-1.5 bg-surface-card hover:bg-surface-elevated hover:text-ink rounded-lg text-[11px] text-ink-mid transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Next
diff --git a/canvas/src/components/OrgImportPreflightModal.tsx b/canvas/src/components/OrgImportPreflightModal.tsx
index 048ad054..3a1b22ad 100644
--- a/canvas/src/components/OrgImportPreflightModal.tsx
+++ b/canvas/src/components/OrgImportPreflightModal.tsx
@@ -308,7 +308,7 @@ export function OrgImportPreflightModal({
type="button"
onClick={onProceed}
disabled={!canProceed}
- className="px-4 py-1.5 text-[11px] font-semibold rounded bg-accent hover:bg-accent-strong text-white disabled:bg-surface-card disabled:text-white-soft disabled:cursor-not-allowed"
+ className="px-4 py-1.5 text-[11px] font-semibold rounded bg-accent hover:bg-accent-strong text-white disabled:bg-surface-card disabled:text-white-soft disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Import
@@ -428,7 +428,7 @@ function StrictEnvRow({
type="button"
onClick={() => onSave(envKey)}
disabled={d?.saving || !d?.value.trim()}
- className="px-2 py-1 text-[10px] rounded bg-accent hover:bg-accent-strong text-white disabled:opacity-40 disabled:cursor-not-allowed"
+ className="px-2 py-1 text-[10px] rounded bg-accent hover:bg-accent-strong text-white disabled:opacity-40 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{d?.saving ? "…" : "Save"}
@@ -520,7 +520,7 @@ function AnyOfEnvGroup({
type="button"
onClick={() => onSave(m)}
disabled={d?.saving || !d?.value.trim()}
- className="px-2 py-1 text-[10px] rounded bg-accent hover:bg-accent-strong text-white disabled:opacity-40 disabled:cursor-not-allowed"
+ className="px-2 py-1 text-[10px] rounded bg-accent hover:bg-accent-strong text-white disabled:opacity-40 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{d?.saving ? "…" : "Save"}
diff --git a/canvas/src/components/PricingTable.tsx b/canvas/src/components/PricingTable.tsx
index 8bd58f93..5f3bc210 100644
--- a/canvas/src/components/PricingTable.tsx
+++ b/canvas/src/components/PricingTable.tsx
@@ -128,9 +128,9 @@ function PlanCard({
type="button"
onClick={onSelect}
disabled={loading}
- className={`mt-6 rounded-lg px-4 py-3 text-sm font-medium ${
+ className={`mt-6 rounded-lg px-4 py-3 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-surface ${
plan.highlighted
- ? "bg-accent-strong text-white hover:bg-accent disabled:bg-blue-900"
+ ? "bg-accent-strong text-white hover:bg-accent disabled:bg-zinc-700 disabled:text-zinc-500"
: "border border-line bg-surface-sunken text-ink hover:bg-surface-card disabled:opacity-50"
}`}
>
diff --git a/canvas/src/components/ProviderModelSelector.tsx b/canvas/src/components/ProviderModelSelector.tsx
index 4de96f7f..6620aa55 100644
--- a/canvas/src/components/ProviderModelSelector.tsx
+++ b/canvas/src/components/ProviderModelSelector.tsx
@@ -437,7 +437,7 @@ export function ProviderModelSelector({
handleModelChange(selected.models[0]?.id ?? "");
}
}}
- className="text-[9px] text-accent hover:text-accent mt-0.5"
+ className="text-[9px] text-accent hover:text-accent mt-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
← back to model list
diff --git a/canvas/src/components/ProvisioningTimeout.tsx b/canvas/src/components/ProvisioningTimeout.tsx
index 2602d9cb..de959922 100644
--- a/canvas/src/components/ProvisioningTimeout.tsx
+++ b/canvas/src/components/ProvisioningTimeout.tsx
@@ -321,7 +321,7 @@ export function ProvisioningTimeout({
onClick={() => handleDismiss(entry.workspaceId)}
aria-label="Dismiss provisioning timeout warning"
title="Dismiss — keep this workspace running without the warning"
- className="shrink-0 text-warm/60 hover:text-amber-200 transition-colors -mr-1"
+ className="shrink-0 text-warm/60 hover:text-amber-200 transition-colors -mr-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-400 focus-visible:ring-offset-1 focus-visible:ring-offset-amber-950"
>
)}
@@ -876,7 +876,7 @@ export function ConfigTab({ workspaceId }: Props) {
@@ -1016,7 +1016,7 @@ export function ConfigTab({ workspaceId }: Props) {
onClick={() => handleSave(true)}
disabled={!isDirty || saving}
// Same accent-LIGHTER fix shipped on every other tab.
- className="px-3 py-1.5 bg-accent hover:bg-accent-strong text-xs rounded text-white disabled:opacity-30 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
+ className="px-3 py-1.5 bg-accent hover:bg-accent-strong text-xs rounded text-white disabled:opacity-30 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
>
{saving ? "Restarting..." : "Save & Restart"}
@@ -1024,14 +1024,14 @@ export function ConfigTab({ workspaceId }: Props) {
type="button"
onClick={() => handleSave(false)}
disabled={!isDirty || saving}
- className="px-3 py-1.5 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid disabled:opacity-30 transition-colors"
+ className="px-3 py-1.5 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid disabled:opacity-30 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Save
diff --git a/canvas/src/components/tabs/DetailsTab.tsx b/canvas/src/components/tabs/DetailsTab.tsx
index 2677a2f6..8d659797 100644
--- a/canvas/src/components/tabs/DetailsTab.tsx
+++ b/canvas/src/components/tabs/DetailsTab.tsx
@@ -182,7 +182,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
setRole(data.role || "");
setTier(data.tier);
}}
- className="px-3 py-1 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid"
+ className="px-3 py-1 bg-surface-card hover:bg-surface-card text-xs rounded text-ink-mid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Cancel
@@ -211,7 +211,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
type="button"
onClick={handleRestart}
disabled={restarting}
- className="px-3 py-1 bg-green-700 hover:bg-green-600 text-xs rounded text-white disabled:opacity-50"
+ className="px-3 py-1 bg-green-700 hover:bg-green-600 text-xs rounded text-white disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{restarting ? "Restarting..." : data.status === "failed" ? "Retry" : "Restart"}
@@ -220,7 +220,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
@@ -247,7 +247,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
@@ -293,7 +293,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
key={p.id}
type="button"
onClick={() => selectNode(p.id)}
- className="w-full flex items-center gap-2 px-2 py-1 rounded hover:bg-surface-card text-left"
+ className="w-full flex items-center gap-2 px-2 py-1 rounded hover:bg-surface-card text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{p.name}
@@ -353,7 +353,7 @@ export function DetailsTab({ workspaceId, data }: Props) {
type="button"
ref={deleteButtonRef}
onClick={() => setConfirmDelete(true)}
- className="px-3 py-1 bg-surface-card hover:bg-red-900 border border-line hover:border-red-700 text-xs rounded text-ink-mid hover:text-bad transition-colors"
+ className="px-3 py-1 bg-surface-card hover:bg-red-900 border border-line hover:border-red-700 text-xs rounded text-ink-mid hover:text-bad transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1"
>
Delete Workspace
diff --git a/canvas/src/components/tabs/EventsTab.tsx b/canvas/src/components/tabs/EventsTab.tsx
index 44de3410..c239153e 100644
--- a/canvas/src/components/tabs/EventsTab.tsx
+++ b/canvas/src/components/tabs/EventsTab.tsx
@@ -75,7 +75,7 @@ export function EventsTab({ workspaceId }: Props) {
// Was hover:bg-surface-card on top of bg-surface-card — silent
// no-op hover. Lift to surface-elevated, matching the Cancel
// pattern from ConfirmDialog.
- className="px-2 py-1 bg-surface-card hover:bg-surface-elevated hover:text-ink text-[10px] rounded text-ink-mid transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
+ className="px-2 py-1 bg-surface-card hover:bg-surface-elevated hover:text-ink text-[10px] rounded text-ink-mid transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Refresh
@@ -106,7 +106,7 @@ export function EventsTab({ workspaceId }: Props) {
// toggles or what it controls.
aria-expanded={isOpen}
aria-controls={panelId}
- className="w-full flex items-center gap-2 px-3 py-2 text-left rounded-t hover:bg-surface-elevated/40 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent/50 transition-colors"
+ className="w-full flex items-center gap-2 px-3 py-2 text-left rounded-t hover:bg-surface-elevated/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 transition-colors"
>
{busy === "show" ? "Loading…" : "Show connection info"}
@@ -95,7 +95,7 @@ export function ExternalConnectionSection({ workspaceId }: Props) {
type="button"
onClick={() => setConfirmRotate(true)}
disabled={busy !== null}
- className="px-3 py-1.5 bg-red-900/30 hover:bg-red-900/50 border border-red-800/60 text-xs rounded text-bad disabled:opacity-30 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-red-600/60"
+ className="px-3 py-1.5 bg-red-900/30 hover:bg-red-900/50 border border-red-800/60 text-xs rounded text-bad disabled:opacity-30 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1"
>
{busy === "rotate" ? "Rotating…" : "Rotate credentials"}
@@ -124,14 +124,14 @@ export function ExternalConnectionSection({ workspaceId }: Props) {
diff --git a/canvas/src/components/tabs/FilesTab/FileTreeContextMenu.tsx b/canvas/src/components/tabs/FilesTab/FileTreeContextMenu.tsx
index 76704959..052ac52e 100644
--- a/canvas/src/components/tabs/FilesTab/FileTreeContextMenu.tsx
+++ b/canvas/src/components/tabs/FilesTab/FileTreeContextMenu.tsx
@@ -128,8 +128,8 @@ export function FileTreeContextMenu({ x, y, items, onClose }: Props) {
}}
className={
item.destructive
- ? "w-full text-left px-3 py-1 text-bad hover:bg-red-900/30 focus:bg-red-900/30 focus:outline-none disabled:opacity-40 disabled:pointer-events-none transition-colors"
- : "w-full text-left px-3 py-1 text-ink-mid hover:bg-surface-card hover:text-ink focus:bg-surface-card focus:text-ink focus:outline-none disabled:opacity-40 disabled:pointer-events-none transition-colors"
+ ? "w-full text-left px-3 py-1 text-bad hover:bg-red-900/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1 disabled:opacity-40 disabled:pointer-events-none transition-colors"
+ : "w-full text-left px-3 py-1 text-ink-mid hover:bg-surface-card hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 disabled:opacity-40 disabled:pointer-events-none transition-colors"
}
>
{item.icon && {item.icon}}
diff --git a/canvas/src/components/tabs/FilesTab/FilesToolbar.tsx b/canvas/src/components/tabs/FilesTab/FilesToolbar.tsx
index 492f571b..8b567e41 100644
--- a/canvas/src/components/tabs/FilesTab/FilesToolbar.tsx
+++ b/canvas/src/components/tabs/FilesTab/FilesToolbar.tsx
@@ -44,7 +44,7 @@ export function FilesToolbar({
{root === "/configs" && (
<>
-
diff --git a/canvas/src/components/tabs/FilesTab/tree.ts b/canvas/src/components/tabs/FilesTab/tree.ts
index 35e02c7b..9972d071 100644
--- a/canvas/src/components/tabs/FilesTab/tree.ts
+++ b/canvas/src/components/tabs/FilesTab/tree.ts
@@ -28,7 +28,7 @@ const FILE_ICONS: Record = {
export function getIcon(path: string, isDir: boolean): string {
if (isDir) return "📁";
- const ext = "." + path.split(".").pop();
+ const ext = "." + (path.split(".").pop() ?? "").toLowerCase();
return FILE_ICONS[ext] || "📄";
}
diff --git a/canvas/src/components/tabs/MemoryTab.tsx b/canvas/src/components/tabs/MemoryTab.tsx
index 3dfd7034..8e560801 100644
--- a/canvas/src/components/tabs/MemoryTab.tsx
+++ b/canvas/src/components/tabs/MemoryTab.tsx
@@ -205,14 +205,14 @@ export function MemoryTab({ workspaceId }: Props) {
setShowAwareness((prev) => !prev)}
- className="shrink-0 px-2 py-1 bg-surface-card hover:bg-surface-elevated text-[10px] rounded text-ink"
+ className="shrink-0 px-2 py-1 bg-surface-card hover:bg-surface-elevated text-[10px] rounded text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{showAwareness ? "Collapse" : "Expand"}
Open
@@ -245,7 +245,7 @@ export function MemoryTab({ workspaceId }: Props) {
setShowAwareness(true)}
- className="shrink-0 px-2 py-1 bg-accent hover:bg-accent-strong text-[10px] rounded text-white"
+ className="shrink-0 px-2 py-1 bg-accent hover:bg-accent-strong text-[10px] rounded text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Expand
@@ -280,21 +280,21 @@ export function MemoryTab({ workspaceId }: Props) {
setShowAdvanced((prev) => !prev)}
- className="px-2 py-1 bg-surface-card hover:bg-surface-elevated text-[10px] rounded text-ink-mid"
+ className="px-2 py-1 bg-surface-card hover:bg-surface-elevated text-[10px] rounded text-ink-mid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{showAdvanced ? "Hide Advanced" : "Advanced"}
Refresh
{ setShowAdd(!showAdd); if (!showAdd) setShowAdvanced(true); }}
- className="px-2 py-1 bg-accent hover:bg-accent-strong text-[10px] rounded text-white"
+ className="px-2 py-1 bg-accent hover:bg-accent-strong text-[10px] rounded text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
+ Add
@@ -330,7 +330,7 @@ export function MemoryTab({ workspaceId }: Props) {
Save
@@ -340,7 +340,7 @@ export function MemoryTab({ workspaceId }: Props) {
setShowAdd(false);
setError(null);
}}
- className="px-3 py-1 bg-surface-card hover:bg-surface-elevated text-xs rounded text-ink-mid"
+ className="px-3 py-1 bg-surface-card hover:bg-surface-elevated text-xs rounded text-ink-mid focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Cancel
@@ -358,7 +358,7 @@ export function MemoryTab({ workspaceId }: Props) {
setExpanded(expanded === entry.key ? null : entry.key)}
- className="w-full flex items-center justify-between px-3 py-2 text-left"
+ className="w-full flex items-center justify-between px-3 py-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
aria-expanded={expanded === entry.key}
>
{entry.key}
@@ -401,14 +401,14 @@ export function MemoryTab({ workspaceId }: Props) {
handleEditSave(entry)}
- className="px-3 py-1 bg-accent hover:bg-accent-strong text-xs rounded text-white"
+ className="px-3 py-1 bg-accent hover:bg-accent-strong text-xs rounded text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Save
Cancel
@@ -428,7 +428,7 @@ export function MemoryTab({ workspaceId }: Props) {
beginEdit(entry)}
- className="text-[10px] text-ink-mid hover:bg-surface-elevated rounded px-1 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60"
+ className="text-[10px] text-ink-mid hover:bg-surface-elevated rounded px-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Edit
@@ -436,7 +436,7 @@ export function MemoryTab({ workspaceId }: Props) {
handleDelete(entry.key)}
- className="text-[10px] text-bad hover:bg-red-950/40 rounded px-1 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500/60"
+ className="text-[10px] text-bad hover:bg-red-950/40 rounded px-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1"
>
Delete
@@ -459,7 +459,7 @@ export function MemoryTab({ workspaceId }: Props) {
setShowAdvanced(true)}
- className="shrink-0 px-2 py-1 bg-accent hover:bg-accent-strong text-[10px] rounded text-white"
+ className="shrink-0 px-2 py-1 bg-accent hover:bg-accent-strong text-[10px] rounded text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Show
diff --git a/canvas/src/components/tabs/ScheduleTab.tsx b/canvas/src/components/tabs/ScheduleTab.tsx
index 3772a940..f7ac5c3a 100644
--- a/canvas/src/components/tabs/ScheduleTab.tsx
+++ b/canvas/src/components/tabs/ScheduleTab.tsx
@@ -276,7 +276,7 @@ export function ScheduleTab({ workspaceId }: Props) {
// LIGHTER variant, so this hovered lighter on white text
// and dropped contrast below AA. Same trap fixed in
// OnboardingWizard, ConfirmDialog, ApprovalBanner.
- className="text-[11px] px-3 py-1 bg-accent text-white rounded hover:bg-accent-strong disabled:opacity-40 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
+ className="text-[11px] px-3 py-1 bg-accent text-white rounded hover:bg-accent-strong disabled:opacity-40 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
>
{editId ? "Update" : "Create"}
@@ -285,7 +285,7 @@ export function ScheduleTab({ workspaceId }: Props) {
onClick={resetForm}
// Was hover:bg-surface-card on top of bg-surface-card —
// silent no-op hover. Lift to surface-elevated.
- className="text-[11px] px-3 py-1 bg-surface-card text-ink-mid rounded hover:bg-surface-elevated hover:text-ink transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
+ className="text-[11px] px-3 py-1 bg-surface-card text-ink-mid rounded hover:bg-surface-elevated hover:text-ink transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface"
>
Cancel
diff --git a/canvas/src/components/tabs/SkillsTab.tsx b/canvas/src/components/tabs/SkillsTab.tsx
index f6917c43..563aff58 100644
--- a/canvas/src/components/tabs/SkillsTab.tsx
+++ b/canvas/src/components/tabs/SkillsTab.tsx
@@ -479,7 +479,7 @@ export function SkillsTab({ workspaceId, data }: Props) {
loadRegistry(true)}
- className="text-[10px] text-violet-300 hover:text-violet-200 underline-offset-2 hover:underline"
+ className="text-[10px] text-violet-300 hover:text-violet-200 underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
{registryLoading ? "Loading… click to retry" : "Retry"}
diff --git a/canvas/src/components/tabs/TracesTab.tsx b/canvas/src/components/tabs/TracesTab.tsx
index 6932ceed..84f79cd0 100644
--- a/canvas/src/components/tabs/TracesTab.tsx
+++ b/canvas/src/components/tabs/TracesTab.tsx
@@ -60,7 +60,7 @@ export function TracesTab({ workspaceId }: Props) {
onClick={loadTraces}
// Added focus-visible ring; previous version was hover-only,
// invisible to keyboard users.
- className="text-[10px] text-ink-mid hover:text-ink-mid rounded-sm px-1 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
+ className="text-[10px] text-ink-mid hover:text-ink-mid rounded-sm px-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Refresh
@@ -98,7 +98,7 @@ export function TracesTab({ workspaceId }: Props) {
// panel. Same pattern shipped on EventsTab.
aria-expanded={isOpen}
aria-controls={panelId}
- className="w-full px-3 py-2 flex items-center gap-2 text-left hover:bg-surface-card/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-accent/50 transition-colors"
+ className="w-full px-3 py-2 flex items-center gap-2 text-left hover:bg-surface-card/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 transition-colors"
>
{/* Status dot uses semantic bad/good tokens — was hardcoded
bg-red-400 / bg-emerald-400 which doesn't pin to the
diff --git a/canvas/src/components/tabs/chat/AgentCommsPanel.tsx b/canvas/src/components/tabs/chat/AgentCommsPanel.tsx
index 2c8b2858..b44ae1c0 100644
--- a/canvas/src/components/tabs/chat/AgentCommsPanel.tsx
+++ b/canvas/src/components/tabs/chat/AgentCommsPanel.tsx
@@ -827,14 +827,14 @@ function ErrorMessage({ msg }: { msg: CommMessage }) {
type="button"
onClick={handleRestart}
disabled={restarting}
- className="px-2 py-0.5 rounded bg-red-900/50 hover:bg-red-800/60 border border-red-700/40 text-[10px] text-red-200 disabled:opacity-50 transition-colors"
+ className="px-2 py-0.5 rounded bg-red-900/50 hover:bg-red-800/60 border border-red-700/40 text-[10px] text-red-200 disabled:opacity-50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1"
>
{restarting ? "Restarting…" : `Restart ${msg.peerName}`}
Open {msg.peerName}
diff --git a/canvas/src/components/tabs/chat/AttachmentImage.tsx b/canvas/src/components/tabs/chat/AttachmentImage.tsx
index ca4df242..a123856f 100644
--- a/canvas/src/components/tabs/chat/AttachmentImage.tsx
+++ b/canvas/src/components/tabs/chat/AttachmentImage.tsx
@@ -143,7 +143,7 @@ export function AttachmentImage({ workspaceId, attachment, onDownload, tone }: P
type="button"
onClick={() => setOpen(true)}
title={`Preview ${attachment.name}`}
- className={`group relative inline-block max-w-full rounded-lg overflow-hidden border focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 ${
+ className={`group relative inline-block max-w-full rounded-lg overflow-hidden border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 ${
tone === "user" ? "border-blue-400/30" : "border-line/50"
}`}
aria-label={`Open ${attachment.name} preview`}
diff --git a/canvas/src/components/tabs/chat/AttachmentTextPreview.tsx b/canvas/src/components/tabs/chat/AttachmentTextPreview.tsx
index 9b6eb6fd..80b53262 100644
--- a/canvas/src/components/tabs/chat/AttachmentTextPreview.tsx
+++ b/canvas/src/components/tabs/chat/AttachmentTextPreview.tsx
@@ -148,7 +148,7 @@ export function AttachmentTextPreview({ workspaceId, attachment, onDownload, ton
onDownload(attachment)}
- className="text-ink-mid hover:text-ink"
+ className="text-ink-mid hover:text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
title={`Download ${attachment.name}`}
aria-label={`Download ${attachment.name}`}
>
@@ -162,7 +162,7 @@ export function AttachmentTextPreview({ workspaceId, attachment, onDownload, ton
setExpanded(true)}
- className="block w-full text-center text-[10px] text-ink-mid hover:text-ink py-1 border-t border-line/40"
+ className="block w-full text-center text-[10px] text-ink-mid hover:text-ink py-1 border-t border-line/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
Show all {lines.length} lines
@@ -173,7 +173,7 @@ export function AttachmentTextPreview({ workspaceId, attachment, onDownload, ton
onDownload(attachment)}
- className="underline"
+ className="underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1"
>
download full file
diff --git a/canvas/src/components/tabs/chat/types.ts b/canvas/src/components/tabs/chat/types.ts
index a03cb459..15d98d26 100644
--- a/canvas/src/components/tabs/chat/types.ts
+++ b/canvas/src/components/tabs/chat/types.ts
@@ -26,13 +26,15 @@ export function createMessage(
content: string,
attachments?: ChatAttachment[],
): ChatMessage {
- return {
+ return Object.freeze({
id: crypto.randomUUID(),
role,
content,
- attachments: attachments && attachments.length > 0 ? attachments : undefined,
+ // Conditional spread avoids `attachments: undefined` appearing in
+ // Object.keys() when no attachments are provided.
+ ...(attachments?.length ? { attachments } : {}),
timestamp: new Date().toISOString(),
- };
+ });
}
// appendMessageDeduped adds a ChatMessage to `prev` unless the tail
diff --git a/canvas/src/components/tabs/config/form-inputs.tsx b/canvas/src/components/tabs/config/form-inputs.tsx
index 4110383e..0cf30e7c 100644
--- a/canvas/src/components/tabs/config/form-inputs.tsx
+++ b/canvas/src/components/tabs/config/form-inputs.tsx
@@ -102,7 +102,7 @@ export function TagList({ label, values, onChange, placeholder }: { label: strin
{values.map((v, i) => (
{v}
- onChange(values.filter((_, j) => j !== i))} className="text-ink-mid hover:text-bad">×
+ onChange(values.filter((_, j) => j !== i))} className="text-ink-mid hover:text-bad focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-1">×
))}
@@ -129,7 +129,7 @@ export function Section({ title, children, defaultOpen = true }: { title: string
const [open, setOpen] = useState(defaultOpen);
return (
@@ -131,7 +131,7 @@ function SecretRow({ label, secretKey, isSet, scope, globalMode, onSave, onDelet