From 856f930bc9f86cba056626b2b988f6e8c787d481 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sat, 8 Aug 2026 04:11:54 +0000
Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=95=84?=
=?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=A0=84=EC=9A=A9=20=EB=B2=84=ED=8A=BC?=
=?UTF-8?q?=EC=97=90=20=ED=88=B4=ED=8C=81(title)=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.jules/palette.md | 3 +++
apps/desktop/src/features/score/ScoreView.test.tsx | 1 +
apps/desktop/src/features/score/ScoreView.tsx | 1 +
apps/desktop/src/features/score/ScoreViewer.test.tsx | 4 ++++
apps/desktop/src/features/score/ScoreViewer.tsx | 4 ++++
5 files changed, 13 insertions(+)
diff --git a/.jules/palette.md b/.jules/palette.md
index c0563889..771a35d4 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.
+## 2026-08-08 - Adding Tooltips to Icon-Only Buttons
+**Learning:** Icon-only buttons lacking `title` attributes may not provide sufficient visual cues on hover, making navigation less intuitive for mouse users. Relying purely on `aria-label` ensures screen reader accessibility but omits visual feedback.
+**Action:** When adding `aria-label` to icon-only buttons, always accompany it with a matching `title` attribute to show native browser tooltips on hover.
diff --git a/apps/desktop/src/features/score/ScoreView.test.tsx b/apps/desktop/src/features/score/ScoreView.test.tsx
index de4ccb95..c8bd72f9 100644
--- a/apps/desktop/src/features/score/ScoreView.test.tsx
+++ b/apps/desktop/src/features/score/ScoreView.test.tsx
@@ -98,6 +98,7 @@ describe("ScoreView", () => {
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();
+ expect(screen.getByRole("button", { name: "Remove: opener.pdf" })).toHaveAttribute("title", "Remove: opener.pdf");
fireEvent.click(screen.getByRole("button", { name: "Open score: opener.pdf" }));
expect(mockInvoke).not.toHaveBeenCalled();
diff --git a/apps/desktop/src/features/score/ScoreView.tsx b/apps/desktop/src/features/score/ScoreView.tsx
index 72732450..53cab32f 100644
--- a/apps/desktop/src/features/score/ScoreView.tsx
+++ b/apps/desktop/src/features/score/ScoreView.tsx
@@ -198,6 +198,7 @@ export function ScoreView({ song, projectId, onSongUpdate }: ScoreViewProps) {
onClick={projectId ? () => void handleRemove(projectId, attachment) : undefined}
disabled={!projectId}
aria-label={`${t("scoreRemove")}: ${attachment.fileName}`}
+ title={`${t("scoreRemove")}: ${attachment.fileName}`}
className="size-10 border-rose-300/25 text-rose-200 hover:bg-rose-400/10"
>
diff --git a/apps/desktop/src/features/score/ScoreViewer.test.tsx b/apps/desktop/src/features/score/ScoreViewer.test.tsx
index 3ac2dd60..fa9bebaf 100644
--- a/apps/desktop/src/features/score/ScoreViewer.test.tsx
+++ b/apps/desktop/src/features/score/ScoreViewer.test.tsx
@@ -173,7 +173,9 @@ describe("ScoreViewer", () => {
expect(await screen.findByText("Page 1 of 3")).toBeInTheDocument();
const previousButton = screen.getByRole("button", { name: "Previous page" });
+ expect(previousButton).toHaveAttribute("title", "Previous page");
const nextButton = screen.getByRole("button", { name: "Next page" });
+ expect(nextButton).toHaveAttribute("title", "Next page");
expect(previousButton).toBeDisabled();
fireEvent.click(nextButton);
@@ -202,7 +204,9 @@ describe("ScoreViewer", () => {
expect(await screen.findByText("Page 1 of 1")).toBeInTheDocument();
const zoomInButton = screen.getByRole("button", { name: "Zoom in" });
+ expect(zoomInButton).toHaveAttribute("title", "Zoom in");
const zoomOutButton = screen.getByRole("button", { name: "Zoom out" });
+ expect(zoomOutButton).toHaveAttribute("title", "Zoom out");
const fitWidthButton = screen.getByRole("button", { name: "Fit width" });
expect(fitWidthButton).toHaveAttribute("aria-pressed", "true");
diff --git a/apps/desktop/src/features/score/ScoreViewer.tsx b/apps/desktop/src/features/score/ScoreViewer.tsx
index 82692469..35819e41 100644
--- a/apps/desktop/src/features/score/ScoreViewer.tsx
+++ b/apps/desktop/src/features/score/ScoreViewer.tsx
@@ -258,6 +258,7 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
size="icon-lg"
className="size-12"
aria-label={t("scoreViewerZoomOut")}
+ title={t("scoreViewerZoomOut")}
onClick={zoomOut}
>
@@ -267,6 +268,7 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
size="icon-lg"
className="size-12"
aria-label={t("scoreViewerZoomIn")}
+ title={t("scoreViewerZoomIn")}
onClick={zoomIn}
>
@@ -292,6 +294,7 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
size="icon-lg"
className="size-14"
aria-label={t("scoreViewerPrevPage")}
+ title={t("scoreViewerPrevPage")}
disabled={pageNumber <= 1}
onClick={goToPreviousPage}
>
@@ -305,6 +308,7 @@ export function ScoreViewer({ data, fileName, onStatusChange }: ScoreViewerProps
size="icon-lg"
className="size-14"
aria-label={t("scoreViewerNextPage")}
+ title={t("scoreViewerNextPage")}
disabled={pageNumber >= pageCount}
onClick={goToNextPage}
>
From db6d44cb260cf7eeb710eebf34ae298da746c34e Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sat, 8 Aug 2026 04:34:30 +0000
Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=95=84?=
=?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=A0=84=EC=9A=A9=20=EB=B2=84=ED=8A=BC?=
=?UTF-8?q?=EC=97=90=20=ED=88=B4=ED=8C=81(title)=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
chore(deps): update pdfjs-dist, fix typecheck
---
apps/desktop/package.json | 2 +-
package-lock.json | 48 +++++++++++++--------------------------
package.json | 3 +++
3 files changed, 20 insertions(+), 33 deletions(-)
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index e7685d6f..647047e3 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/package-lock.json b/package-lock.json
index cf1c991c..057430f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,9 @@
"apps/*",
"packages/*"
],
+ "dependencies": {
+ "@types/react-dom": "^19.2.4"
+ },
"devDependencies": {
"@eslint/js": "^10.0.1",
"eslint-plugin-jsdoc": "^63.0.13",
@@ -32,7 +35,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",
@@ -955,7 +958,6 @@
"os": [
"aix"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -973,7 +975,6 @@
"os": [
"android"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -991,7 +992,6 @@
"os": [
"android"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1009,7 +1009,6 @@
"os": [
"android"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1027,7 +1026,6 @@
"os": [
"darwin"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1045,7 +1043,6 @@
"os": [
"darwin"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1063,7 +1060,6 @@
"os": [
"freebsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1081,7 +1077,6 @@
"os": [
"freebsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1099,7 +1094,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1117,7 +1111,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1135,7 +1128,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1153,7 +1145,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1171,7 +1162,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1189,7 +1179,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1207,7 +1196,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1225,7 +1213,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1243,7 +1230,6 @@
"os": [
"linux"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1261,7 +1247,6 @@
"os": [
"netbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1279,7 +1264,6 @@
"os": [
"netbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1297,7 +1281,6 @@
"os": [
"openbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1315,7 +1298,6 @@
"os": [
"openbsd"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1333,7 +1315,6 @@
"os": [
"openharmony"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1351,7 +1332,6 @@
"os": [
"sunos"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1369,7 +1349,6 @@
"os": [
"win32"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1387,7 +1366,6 @@
"os": [
"win32"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1405,7 +1383,6 @@
"os": [
"win32"
],
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -3894,12 +3871,20 @@
"version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
+ "node_modules/@types/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
"node_modules/@types/resolve": {
"version": "1.20.6",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz",
@@ -4617,7 +4602,6 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/data-urls": {
@@ -6368,9 +6352,9 @@
}
},
"node_modules/pdfjs-dist": {
- "version": "6.1.200",
- "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.1.200.tgz",
- "integrity": "sha512-o8MolyzirkkLrcdsae/HEOiIcXWI7DS5zGpvqW8xTC2YUsW30rltFw2bDGvw/fskUdEMrQm2br68jzDS5BH2vw==",
+ "version": "6.2.108",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-6.2.108.tgz",
+ "integrity": "sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=22.13.0 || >=24"
diff --git a/package.json b/package.json
index a71236ed..64845e4c 100644
--- a/package.json
+++ b/package.json
@@ -38,5 +38,8 @@
"overrides": {
"brace-expansion": "5.0.9",
"postcss": "8.5.25"
+ },
+ "dependencies": {
+ "@types/react-dom": "^19.2.4"
}
}
From 5573cf0afd579f8ed715e217a68cdfc816138b21 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sat, 8 Aug 2026 04:44:26 +0000
Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=95=84?=
=?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=A0=84=EC=9A=A9=20=EB=B2=84=ED=8A=BC?=
=?UTF-8?q?=EC=97=90=20=ED=88=B4=ED=8C=81(title)=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
chore(deps): update pdfjs-dist, fix typecheck
---
package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 057430f8..cd4a8cd0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6059,9 +6059,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.16",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
- "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true,
"funding": [
{
@@ -7163,9 +7163,9 @@
}
},
"node_modules/undici": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
- "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
"dev": true,
"license": "MIT",
"engines": {