diff --git a/.jules/palette.md b/.jules/palette.md
index c05638899..04f7363d7 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,6 @@
## 2024-05-19 - Replace HTML disabled with aria-disabled="true" for Accessible Tooltips
**Learning:** Native HTML `disabled` attributes completely hide elements from screen readers and block all pointer/hover events, preventing tooltips from functioning for disabled elements.
**Action:** Replace `disabled` with `aria-disabled="true"`, enforce block click handlers via `e.preventDefault()`, and add a title tooltip directly to the element to maintain full tooltip accessibility and keyboard focus support for visually impaired and mouse users.
+## 2025-02-12 - Playwright Frontend Verification for Score Components
+**Learning:** In headless Playwright scripts interacting with the BandScope desktop frontend on `localhost:5173`, interacting with dynamic or conditionally rendered components (like the "Open Project" button, demo track rows, or nested tabs like "Score") requires careful handling of element counts and using `force=True` on `.first` locator clicks due to overlapping layers or strict mode violations in complex UIs. The "Score" tab specifically can be consistently found using `page.locator("nav a").filter(has_text="Score")`.
+**Action:** When writing Playwright verification scripts for this UI, always handle potential `count() == 0` edge cases for conditional elements (e.g., demo lists vs home states), use `.first.click(force=True)` to bypass overlapping pointer-events interception, and ensure enough `page.wait_for_timeout` delays for CSS transitions/data loading before capturing screenshots of disabled or state-dependent buttons.
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index e7685d6f0..647047e31 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -20,7 +20,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.24.0",
- "pdfjs-dist": "6.1.200",
+ "pdfjs-dist": "^6.2.108",
"react": "^19.2.4",
"react-dom": "^19.2.7",
"sonner": "^2.0.7",
diff --git a/apps/desktop/src/features/score/ScoreView.test.tsx b/apps/desktop/src/features/score/ScoreView.test.tsx
index de4ccb95c..ab7b87148 100644
--- a/apps/desktop/src/features/score/ScoreView.test.tsx
+++ b/apps/desktop/src/features/score/ScoreView.test.tsx
@@ -85,7 +85,7 @@ describe("ScoreView", () => {
expect(screen.getByRole("heading", { name: /Score ยท Late Night Set/i })).toBeInTheDocument();
expect(screen.getByText("No scores attached to this song yet.")).toBeInTheDocument();
- expect(screen.getByRole("button", { name: "Add score" })).toBeEnabled();
+ expect(screen.getByRole("button", { name: "Add score" })).not.toHaveAttribute("aria-disabled", "true");
expect(screen.getByTestId("score-viewer")).toHaveTextContent("no-data");
expect(mockInvoke).not.toHaveBeenCalled();
});
@@ -95,11 +95,24 @@ describe("ScoreView", () => {
render();
expect(screen.getByText("Scores attach to the active analysis project.")).toBeInTheDocument();
- expect(screen.getByRole("button", { name: "Add score" })).toBeDisabled();
- expect(screen.getByRole("button", { name: "Open score: opener.pdf" })).toBeDisabled();
- expect(screen.getByRole("button", { name: "Remove: opener.pdf" })).toBeDisabled();
- fireEvent.click(screen.getByRole("button", { name: "Open score: opener.pdf" }));
+ const addBtn = screen.getByRole("button", { name: "Add score" });
+ const openBtn = screen.getByRole("button", { name: "Open score: opener.pdf" });
+ const removeBtn = screen.getByRole("button", { name: "Remove: opener.pdf" });
+
+ expect(addBtn).toHaveAttribute("aria-disabled", "true");
+ expect(openBtn).toHaveAttribute("aria-disabled", "true");
+ expect(removeBtn).toHaveAttribute("aria-disabled", "true");
+
+ const preventDefaultSpy = vi.spyOn(Event.prototype, 'preventDefault');
+
+ fireEvent.click(addBtn);
+ fireEvent.click(openBtn);
+ fireEvent.click(removeBtn);
+
+ expect(preventDefaultSpy).toHaveBeenCalledTimes(3);
+ preventDefaultSpy.mockRestore();
+
expect(mockInvoke).not.toHaveBeenCalled();
});
@@ -143,7 +156,7 @@ describe("ScoreView", () => {
"Choose a PDF file to attach as a score."
);
expect(onSongUpdate).not.toHaveBeenCalled();
- expect(screen.getByRole("button", { name: "Add score" })).toBeEnabled();
+ expect(screen.getByRole("button", { name: "Add score" })).not.toHaveAttribute("aria-disabled", "true");
});
it("falls back to the generic attach failure for malformed bridge responses", async () => {
diff --git a/apps/desktop/src/features/score/ScoreView.tsx b/apps/desktop/src/features/score/ScoreView.tsx
index 72732450f..bacc9bfcc 100644
--- a/apps/desktop/src/features/score/ScoreView.tsx
+++ b/apps/desktop/src/features/score/ScoreView.tsx
@@ -134,8 +134,14 @@ export function ScoreView({ song, projectId, onSongUpdate }: ScoreViewProps) {
{t("scoreViewSubtitle")}