Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-06-06 - Dynamic Accessibility Labels in React
**Learning:** Found a pattern where interactive elements (like the SpatialWorkspace delete buttons) use a two-step confirm process managed entirely via local React state without text labels. If aria-labels don't adapt to the current UI state, screen readers will hear "Delete" even when the visual UI says "Confirm?".
**Action:** Always bind `aria-label`s to the same state variable that drives the text or icon conditional rendering so accessibility state perfectly mirrors visual state.
1 change: 1 addition & 0 deletions ui/components/NodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,7 @@ function NodeEditor({
? "Unfreeze β€” allow auto-optimize"
: "Freeze β€” protect from auto-optimize"
}
aria-label={editFrozen ? "Unfreeze priority" : "Freeze priority"}
>
❄️
</button>
Expand Down
1 change: 1 addition & 0 deletions ui/components/NodeEditorExpanded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ export default function NodeEditorExpanded({
? "Unfreeze β€” auto-optimize priority"
: "Freeze β€” protect from auto-optimize"
}
aria-label={editFrozen ? "Unfreeze priority" : "Freeze priority"}
>
❄️
</button>
Expand Down
24 changes: 24 additions & 0 deletions ui/components/SpatialWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,7 @@ export default function SpatialWorkspace({
setEditingItemType("vault");
setEditValue(vault.name);
}}
aria-label={`Edit ${vault.name}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The aria-label directly exposes vault.name to screen readers. To prevent potential privacy leaks of redacted vault names, use the privacy-safe display label helper getPrivacyDisplayLabel.

Suggested change
aria-label={`Edit ${vault.name}`}
aria-label={`Edit ${getPrivacyDisplayLabel(vault.name, vaultEffectiveTier, isRedactedUnlocked)}`}

>
✏️
</button>
Expand All @@ -1459,6 +1460,11 @@ export default function SpatialWorkspace({
className={`spatial-card-action-btn ${deleteArmedId === vault.id ? "delete-armed" : ""}`}
onClick={(e) => handleArmDelete(e, vault.id)}
title="Click twice to delete vault card"
aria-label={
deleteArmedId === vault.id
? `Confirm delete ${vault.name}`
: `Delete ${vault.name}`
}
Comment on lines +1463 to +1467

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The delete button is rendered for all vaults, including those that are redacted and locked. Directly using vault.name in the aria-label exposes the confidential vault name to screen readers. Use getPrivacyDisplayLabel to ensure the name is properly redacted when locked.

Suggested change
aria-label={
deleteArmedId === vault.id
? `Confirm delete ${vault.name}`
: `Delete ${vault.name}`
}
aria-label={
deleteArmedId === vault.id
? `Confirm delete ${getPrivacyDisplayLabel(vault.name, vaultEffectiveTier, isRedactedUnlocked)}`
: `Delete ${getPrivacyDisplayLabel(vault.name, vaultEffectiveTier, isRedactedUnlocked)}`
}

>
{deleteArmedId === vault.id ? "Confirm?" : "πŸ—‘οΈ"}
</button>
Expand Down Expand Up @@ -1575,6 +1581,7 @@ export default function SpatialWorkspace({
setEditingItemType("node");
setEditValue(node.title);
}}
aria-label={`Edit ${node.title}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

To prevent potential privacy leaks of redacted node titles, use the privacy-safe display label helper getPrivacyDisplayLabel for the aria-label.

Suggested change
aria-label={`Edit ${node.title}`}
aria-label={`Edit ${getPrivacyDisplayLabel(node.title, nodeEffectiveTier, isRedactedUnlocked)}`}

>
✏️
</button>
Expand All @@ -1586,6 +1593,11 @@ export default function SpatialWorkspace({
e.stopPropagation();
handleArmDelete(e, node.id);
}}
aria-label={
deleteArmedId === node.id
? `Confirm delete ${node.title}`
: `Delete ${node.title}`
}
Comment on lines +1596 to +1600

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Directly using node.title in the aria-label can expose redacted node titles to screen readers. Use getPrivacyDisplayLabel to ensure the title is properly redacted when locked.

Suggested change
aria-label={
deleteArmedId === node.id
? `Confirm delete ${node.title}`
: `Delete ${node.title}`
}
aria-label={
deleteArmedId === node.id
? `Confirm delete ${getPrivacyDisplayLabel(node.title, nodeEffectiveTier, isRedactedUnlocked)}`
: `Delete ${getPrivacyDisplayLabel(node.title, nodeEffectiveTier, isRedactedUnlocked)}`
}

>
{deleteArmedId === node.id ? "Ok?" : "πŸ—‘οΈ"}
</button>
Expand Down Expand Up @@ -1657,6 +1669,7 @@ export default function SpatialWorkspace({
setEditingItemType("subvault");
setEditValue(subvault.name);
}}
aria-label={`Edit ${subvault.name}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

To prevent potential privacy leaks of redacted subvault names, use the privacy-safe display label helper getPrivacyDisplayLabel for the aria-label.

Suggested change
aria-label={`Edit ${subvault.name}`}
aria-label={`Edit ${getPrivacyDisplayLabel(subvault.name, subvaultEffectiveTier, isRedactedUnlocked)}`}

>
✏️
</button>
Expand All @@ -1668,6 +1681,11 @@ export default function SpatialWorkspace({
e.stopPropagation();
handleArmDelete(e, subvault.id);
}}
aria-label={
deleteArmedId === subvault.id
? `Confirm delete ${subvault.name}`
: `Delete ${subvault.name}`
}
Comment on lines +1684 to +1688

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Directly using subvault.name in the aria-label can expose redacted subvault names to screen readers. Use getPrivacyDisplayLabel to ensure the name is properly redacted when locked.

Suggested change
aria-label={
deleteArmedId === subvault.id
? `Confirm delete ${subvault.name}`
: `Delete ${subvault.name}`
}
aria-label={
deleteArmedId === subvault.id
? `Confirm delete ${getPrivacyDisplayLabel(subvault.name, subvaultEffectiveTier, isRedactedUnlocked)}`
: `Delete ${getPrivacyDisplayLabel(subvault.name, subvaultEffectiveTier, isRedactedUnlocked)}`
}

>
{deleteArmedId === subvault.id ? "Ok?" : "πŸ—‘οΈ"}
</button>
Expand Down Expand Up @@ -1753,6 +1771,7 @@ export default function SpatialWorkspace({
setEditingItemType("node");
setEditValue(node.title);
}}
aria-label={`Edit ${node.title}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

To prevent potential privacy leaks of redacted node titles, use the privacy-safe display label helper getPrivacyDisplayLabel for the aria-label.

Suggested change
aria-label={`Edit ${node.title}`}
aria-label={`Edit ${getPrivacyDisplayLabel(node.title, nodeEffectiveTier, isRedactedUnlocked)}`}

>
✏️
</button>
Expand All @@ -1764,6 +1783,11 @@ export default function SpatialWorkspace({
e.stopPropagation();
handleArmDelete(e, node.id);
}}
aria-label={
deleteArmedId === node.id
? `Confirm delete ${node.title}`
: `Delete ${node.title}`
}
Comment on lines +1786 to +1790

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Directly using node.title in the aria-label can expose redacted node titles to screen readers. Use getPrivacyDisplayLabel to ensure the title is properly redacted when locked.

Suggested change
aria-label={
deleteArmedId === node.id
? `Confirm delete ${node.title}`
: `Delete ${node.title}`
}
aria-label={
deleteArmedId === node.id
? `Confirm delete ${getPrivacyDisplayLabel(node.title, nodeEffectiveTier, isRedactedUnlocked)}`
: `Delete ${getPrivacyDisplayLabel(node.title, nodeEffectiveTier, isRedactedUnlocked)}`
}

>
{deleteArmedId === node.id ? "Ok?" : "πŸ—‘οΈ"}
</button>
Expand Down