diff --git a/.Jules/palette.md b/.Jules/palette.md
index b6f5d5bf..ef81abe8 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -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-07-02 - Accessible tooltips for disabled native buttons
+**Learning:** Wrapped natively disabled buttons in a `span role="button"` to attach a tooltip and bypass the pointer-event block causes invalid HTML and creates nested interactive elements, which harms screen reader accessibility and violates ARIA standards.
+**Action:** To make disabled buttons accessible and their tooltips functional, do NOT wrap them in a `span role="button"` or apply `aria-hidden="true"` to the nested button. Instead, use the native `
) : (
-
-
-
- Save Project
-
-
+ e.preventDefault()}
+ className="min-h-11 cursor-not-allowed border-white/10 bg-white/5 font-semibold text-slate-100 opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 aria-disabled:hover:bg-white/5 aria-disabled:hover:text-slate-100"
+ aria-label="Save Project"
+ >
+
+ Save Project
+
)}
{
fireEvent.click(screen.getByRole("tab", { name: "Bass Guitar" }));
const transcribeButton = screen.getByRole("button", { name: "Transcribe Bass" }) as HTMLButtonElement;
- expect(transcribeButton.disabled).toBe(false);
+ expect(transcribeButton.hasAttribute("aria-disabled")).toBe(false);
expect(transcribeButton.title).toBe("Transcribe part");
});
diff --git a/apps/desktop/src/features/workspace/Workspace.tsx b/apps/desktop/src/features/workspace/Workspace.tsx
index 2f990eec..6f836181 100644
--- a/apps/desktop/src/features/workspace/Workspace.tsx
+++ b/apps/desktop/src/features/workspace/Workspace.tsx
@@ -311,15 +311,36 @@ export function Workspace({ song, sourceBootstrap = null, onSongUpdate }: Worksp