Skip to content
Closed
4 changes: 4 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@
## 2024-07-01 - Testing components with focusable disabled button wrappers
**Learning:** When native disabled buttons are wrapped in a focusable `span` to provide accessible tooltips, tests that previously found and clicked the `button` (by temporarily removing the `disabled` attribute) may fail or become overly complex. It is cleaner and more accurate to query the wrapper element (e.g. via its `title`) and fire events on it, reflecting the actual accessible DOM structure.
**Action:** When testing UI components that wrap disabled buttons in a focusable span for accessibility (e.g., using a tooltip/title), use `screen.getByTitle(...)` to query the wrapper element for interactions like `fireEvent.click` rather than `screen.getByRole('button')`.

## 2024-05-24 - Avoid nesting native buttons with ARIA role button on wrappers
**Learning:** Adding `role="button"` to a `span` or `div` wrapper that contains a native `<button>` element inside violates ARIA specifications. Interactive roles (like `button`) must not contain other interactive elements (even if the inner element is disabled or has `aria-hidden`), as this causes invalid/redundant accessibility trees and screen reader confusion.
**Action:** Always verify wrappers used to implement tooltips for disabled buttons are standard elements (e.g., `<span tabIndex={0} title="...">`) but *do not* assign `role="button"` to the wrapper itself.
2 changes: 1 addition & 1 deletion apps/desktop/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,6 @@ describe("App", () => {
render(<App />);
const settingsSpan = screen.getByTitle("Settings coming soon");
expect(settingsSpan).toHaveAttribute("tabIndex", "0");
expect(settingsSpan).toHaveAttribute("role", "button");
expect(settingsSpan).not.toHaveAttribute("role", "button");
});
});
6 changes: 3 additions & 3 deletions apps/desktop/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -535,13 +535,13 @@ export function App() {
</div>

<div className="flex items-center justify-between text-slate-400">
<span tabIndex={0} role="button" aria-disabled="true" title="Settings coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title="Settings coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span className="sr-only">Settings coming soon</span>
<button type="button" disabled aria-hidden="true" className="pointer-events-none rounded-xl p-2 text-slate-600 transition">
<Settings className="size-5" aria-hidden="true" />
</button>
</span>
<span tabIndex={0} role="button" aria-disabled="true" title="Help coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title="Help coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span className="sr-only">Help coming soon</span>
<button type="button" disabled aria-hidden="true" className="pointer-events-none rounded-xl p-2 text-slate-600 transition">
<CircleHelp className="size-5" aria-hidden="true" />
Expand Down Expand Up @@ -646,7 +646,7 @@ export function App() {
Save Project
</Button>
) : (
<span tabIndex={0} role="button" aria-disabled="true" title="Analyze a song to enable saving" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title="Analyze a song to enable saving" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<Button
disabled
variant="outline"
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/features/workspace/RoleSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function RoleSwitcher({ roles, activeRole, onRoleChange }: RoleSwitcherPr
return (
<div className="flex flex-col gap-4 py-2 sm:flex-row sm:items-center">
<div className="flex whitespace-nowrap text-sm font-semibold text-slate-200">
<Users className="mr-2 size-4 text-cyan-300" />
<Users className="mr-2 size-4 text-cyan-300" aria-hidden="true" />
{t("roleSwitcherTitle")}
</div>
<Tabs
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/features/workspace/SectionRoadmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,14 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma

{role.setupNote && (
<div className="flex items-start gap-2 rounded-md border border-amber-300/20 bg-amber-300/[0.08] p-2 text-xs font-medium text-amber-100">
<Lightbulb className="mt-0.5 size-3.5 shrink-0" />
<Lightbulb className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
<span className="leading-snug">{role.setupNote}</span>
</div>
)}

{role.simplification && (
<div className="flex items-start gap-2 rounded-md border border-indigo-300/20 bg-indigo-300/[0.08] p-2 text-xs font-medium text-indigo-100">
<Wand2 className="mt-0.5 size-3.5 shrink-0" />
<Wand2 className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
<span className="leading-snug">{role.simplification}</span>
</div>
)}
Expand All @@ -200,7 +200,7 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma
<div className="mt-2 space-y-1.5">
{role.overlapWarnings.map((warning, wIdx) => (
<div key={wIdx} className="flex items-start gap-2 rounded-md border border-rose-300/20 bg-rose-300/[0.08] p-2 text-xs font-medium text-rose-100">
<AlertCircle className="mt-0.5 size-3.5 shrink-0" />
<AlertCircle className="mt-0.5 size-3.5 shrink-0" aria-hidden="true" />
<span className="leading-snug">{warning}</span>
</div>
))}
Expand Down
14 changes: 7 additions & 7 deletions apps/desktop/src/features/workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
onClick={handleExportCueSheet}
className="min-h-10 border-cyan-300/30 bg-cyan-300/10 font-semibold text-cyan-50 shadow-[0_10px_30px_rgba(34,211,238,0.16)] hover:bg-cyan-300/20 hover:text-white"
>
<Download className="mr-2 size-4 text-cyan-200" />
<Download className="mr-2 size-4 text-cyan-200" aria-hidden="true" />
Export Cue Sheet (CSV)
</Button>
<Button
Expand All @@ -231,7 +231,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
onClick={handleExportChart}
className="min-h-10 border-white/10 bg-white/5 font-semibold text-slate-100 shadow-sm hover:bg-white/10 hover:text-white"
>
<Download className="mr-2 size-4 text-slate-300" />
<Download className="mr-2 size-4 text-slate-300" aria-hidden="true" />
Export Chart (JSON)
</Button>
<Button
Expand All @@ -240,7 +240,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
onClick={handleExportHandoff}
className="min-h-10 border-teal-300/25 bg-teal-300/10 font-semibold text-teal-50 shadow-sm hover:bg-teal-300/20 hover:text-white"
>
<Download className="mr-2 size-4 text-teal-200" />
<Download className="mr-2 size-4 text-teal-200" aria-hidden="true" />
Export Handoff (JSON)
</Button>
</div>
Expand Down Expand Up @@ -311,13 +311,13 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
<p className="text-xs font-black uppercase tracking-[0.24em] text-emerald-200">Stem Player</p>
<p className="mt-1 text-sm font-semibold text-slate-100">{activeRoleDetails?.name ?? activeRole}</p>
<div className="mt-3 flex flex-wrap gap-2">
<span tabIndex={0} role="button" aria-disabled="true" title="Coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title="Coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<Button type="button" disabled variant="outline" className="min-h-11 border-white/10 bg-white/5 text-slate-400">Play stem</Button>
</span>
<span tabIndex={0} role="button" aria-disabled="true" title="Coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title="Coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<Button type="button" disabled variant="outline" className="min-h-11 border-white/10 bg-white/5 text-slate-400">Loop section</Button>
</span>
<span tabIndex={0} role="button" aria-disabled="true" title="Coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title="Coming soon" className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<Button type="button" disabled variant="outline" className="min-h-11 border-white/10 bg-white/5 text-slate-400">Solo / mute others</Button>
</span>
{canTranscribeBass ? (
Expand All @@ -330,7 +330,7 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp
Transcribe Bass
</Button>
) : (
<span tabIndex={0} role="button" aria-disabled="true" title={`${activeRoleDetails?.name ?? "This role"} transcription is coming soon. Bass is ready first.`} className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<span tabIndex={0} title={`${activeRoleDetails?.name ?? "This role"} transcription is coming soon. Bass is ready first.`} className="inline-block cursor-not-allowed rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300">
<Button
type="button"
disabled
Expand Down
Loading