From 8b20d8362586742a8fbe07a1743073d88f23cba2 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 13:58:34 +0000 Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/features/workspace/SectionRoadmap.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index 834d1e8f0..f9685bb66 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -155,7 +155,7 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma From ac67328fdf0bfe456d5802d3c5b9ccd596c214f1 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:10:02 +0000 Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 715f21f03ce980796921f147df9eaf490c93d46c Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 14:32:40 +0000 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 32f63be41aeb2080e35dd4b8d6d46faf71a31b23 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 16:06:59 +0000 Subject: [PATCH 04/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=EA=B0=9C=EC=84=A0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=88=B4=ED=8C=81=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 45ca66c6c1d0cff2d95a77bc4a5cc432b48f7df7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 02:15:07 +0900 Subject: [PATCH 05/15] fix(ui): explain unavailable chord editing --- apps/desktop/src/locales/en/common.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop/src/locales/en/common.json b/apps/desktop/src/locales/en/common.json index d803a765e..db3ad16c1 100644 --- a/apps/desktop/src/locales/en/common.json +++ b/apps/desktop/src/locales/en/common.json @@ -59,6 +59,7 @@ "chordEditAriaLabel": "Edit chord for {roleName} in {sectionLabel}, current {chord}", "chordEditPrompt": "Enter new chord:", "chordEditTitle": "Click to edit chord", + "chordEditUnavailable": "Chord editing is unavailable in this view", "harmonySourceUserBadge": "User", "roleSwitcherTitle": "Role-specific View", "allRoles": "All Roles", From a84df832d5908853d88ee427b4d5ad7a79ccc9ee Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 02:15:37 +0900 Subject: [PATCH 06/15] fix(ui): localize unavailable chord editing --- apps/desktop/src/locales/ko/common.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop/src/locales/ko/common.json b/apps/desktop/src/locales/ko/common.json index 0f6c6c66d..d63b2fc9a 100644 --- a/apps/desktop/src/locales/ko/common.json +++ b/apps/desktop/src/locales/ko/common.json @@ -59,6 +59,7 @@ "chordEditAriaLabel": "{roleName}의 {sectionLabel} 코드 수정, 현재 {chord}", "chordEditPrompt": "새 코드 입력:", "chordEditTitle": "코드 수정", + "chordEditUnavailable": "이 화면에서는 코드를 수정할 수 없습니다", "harmonySourceUserBadge": "사용자", "roleSwitcherTitle": "악기/보컬 역할", "allRoles": "전체 보기", From b792dfc7ef0fde2e4902b602f2bf94ec538b4a3f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 02:16:09 +0900 Subject: [PATCH 07/15] fix(ui): explain disabled chord edit control --- apps/desktop/src/features/workspace/SectionRoadmap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index f9685bb66..4e0ab5d9a 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -168,7 +168,7 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma if (!onSongUpdate) return; handleChordEdit(section.id, role); }} - title={onSongUpdate ? t("chordEditTitle") : undefined} + title={onSongUpdate ? t("chordEditTitle") : t("chordEditUnavailable")} aria-disabled={!onSongUpdate ? "true" : undefined} > {role.harmony.chord} From d854d60b013106ecb4babd510b62dfd1d88f5acc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 02:16:35 +0900 Subject: [PATCH 08/15] test(ui): cover unavailable chord editing --- .../features/workspace/SectionRoadmap.test.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index 5b32019d2..f4b5b6997 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,6 +36,21 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); + it("keeps unavailable chord edits inert and explains the disabled state", () => { + setNavigatorLanguage("ko-KR"); + const song = createDemoRehearsalSong(); + const promptSpy = vi.spyOn(window, "prompt"); + + render(); + + const button = screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" }); + expect(button.getAttribute("aria-disabled")).toBe("true"); + expect(button.getAttribute("title")).toBe("이 화면에서는 코드를 수정할 수 없습니다"); + + fireEvent.click(button); + expect(promptSpy).not.toHaveBeenCalled(); + }); + it("omits the range row when both notes are unnamed", () => { setNavigatorLanguage("en-US"); const song = createDemoRehearsalSong(); From f6822f7f2c010cbc162ad8201c234fc32e0ef0ea Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 18:28:48 +0000 Subject: [PATCH 09/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=ED=88=B4=ED=8C=81=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=90=EC=B2=B4=20=EB=B0=8F=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EC=A7=80=EC=9B=90=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workspace/SectionRoadmap.test.tsx | 16 ------- .../src/features/workspace/SectionRoadmap.tsx | 47 ++++++++++--------- apps/desktop/src/locales/en/common.json | 2 +- apps/desktop/src/locales/ko/common.json | 2 +- 4 files changed, 28 insertions(+), 39 deletions(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index f4b5b6997..b1888ee4d 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,21 +36,6 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); - it("keeps unavailable chord edits inert and explains the disabled state", () => { - setNavigatorLanguage("ko-KR"); - const song = createDemoRehearsalSong(); - const promptSpy = vi.spyOn(window, "prompt"); - - render(); - - const button = screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" }); - expect(button.getAttribute("aria-disabled")).toBe("true"); - expect(button.getAttribute("title")).toBe("이 화면에서는 코드를 수정할 수 없습니다"); - - fireEvent.click(button); - expect(promptSpy).not.toHaveBeenCalled(); - }); - it("omits the range row when both notes are unnamed", () => { setNavigatorLanguage("en-US"); const song = createDemoRehearsalSong(); @@ -105,7 +90,6 @@ describe("SectionRoadmap", () => { fireEvent.click(screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" })); expect(promptSpy).toHaveBeenCalledWith("새 코드 입력:", "C#m7"); - expect(screen.getAllByTitle("코드 수정").length).toBeGreaterThan(0); expect(onSongUpdate).toHaveBeenCalledTimes(1); }); diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index 4e0ab5d9a..35c3b0b86 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -6,6 +6,7 @@ import { fillRangeCopy, playableRange } from "./firstRangeSqueeze"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { AlertCircle, CheckCircle2, Music2, Wand2, Lightbulb, Info } from "lucide-react"; interface SectionRoadmapProps { @@ -152,27 +153,31 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma
{t("sectionChordLabel")} - + + { + if (!onSongUpdate) return; + handleChordEdit(section.id, role); + }} + aria-disabled={!onSongUpdate ? "true" : undefined} + > + {role.harmony.chord} + + + {onSongUpdate ? t("chordEditTitle") : t("chordEditDisabledExplanation")} + + {role.harmony.source === "user" && ( {t("harmonySourceUserBadge")} diff --git a/apps/desktop/src/locales/en/common.json b/apps/desktop/src/locales/en/common.json index db3ad16c1..e03e5f2bf 100644 --- a/apps/desktop/src/locales/en/common.json +++ b/apps/desktop/src/locales/en/common.json @@ -59,7 +59,7 @@ "chordEditAriaLabel": "Edit chord for {roleName} in {sectionLabel}, current {chord}", "chordEditPrompt": "Enter new chord:", "chordEditTitle": "Click to edit chord", - "chordEditUnavailable": "Chord editing is unavailable in this view", + "chordEditDisabledExplanation": "Chord editing is not available while analysis is running or without an active project.", "harmonySourceUserBadge": "User", "roleSwitcherTitle": "Role-specific View", "allRoles": "All Roles", diff --git a/apps/desktop/src/locales/ko/common.json b/apps/desktop/src/locales/ko/common.json index d63b2fc9a..f4f25f69d 100644 --- a/apps/desktop/src/locales/ko/common.json +++ b/apps/desktop/src/locales/ko/common.json @@ -59,7 +59,7 @@ "chordEditAriaLabel": "{roleName}의 {sectionLabel} 코드 수정, 현재 {chord}", "chordEditPrompt": "새 코드 입력:", "chordEditTitle": "코드 수정", - "chordEditUnavailable": "이 화면에서는 코드를 수정할 수 없습니다", + "chordEditDisabledExplanation": "분석이 진행 중이거나 활성화된 프로젝트가 없을 때는 코드를 수정할 수 없습니다.", "harmonySourceUserBadge": "사용자", "roleSwitcherTitle": "악기/보컬 역할", "allRoles": "전체 보기", From cb7eaeb6bbc4d4be7e460c59b94e85d346e83b45 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 18:53:21 +0000 Subject: [PATCH 10/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=ED=88=B4=ED=8C=81=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=90=EC=B2=B4=20=EB=B0=8F=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EC=A7=80=EC=9B=90=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6f2350776c1a49166027e7003461fbe10e8fcaf7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 04:25:12 +0900 Subject: [PATCH 11/15] fix(ui): keep unavailable chord edit semantics factual and tested --- .../features/workspace/SectionRoadmap.test.tsx | 16 ++++++++++++++++ apps/desktop/src/locales/en/common.json | 2 +- apps/desktop/src/locales/ko/common.json | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index b1888ee4d..3b8351651 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,6 +36,22 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); + it("keeps unavailable chord edits focusable, semantically disabled, and inert", () => { + setNavigatorLanguage("ko-KR"); + const song = createDemoRehearsalSong(); + const promptSpy = vi.spyOn(window, "prompt"); + + render(); + + const button = screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" }); + expect(button.getAttribute("aria-disabled")).toBe("true"); + expect(button.hasAttribute("disabled")).toBe(false); + expect(button.tabIndex).toBe(0); + + fireEvent.click(button); + expect(promptSpy).not.toHaveBeenCalled(); + }); + it("omits the range row when both notes are unnamed", () => { setNavigatorLanguage("en-US"); const song = createDemoRehearsalSong(); diff --git a/apps/desktop/src/locales/en/common.json b/apps/desktop/src/locales/en/common.json index e03e5f2bf..c7d4ab595 100644 --- a/apps/desktop/src/locales/en/common.json +++ b/apps/desktop/src/locales/en/common.json @@ -59,7 +59,7 @@ "chordEditAriaLabel": "Edit chord for {roleName} in {sectionLabel}, current {chord}", "chordEditPrompt": "Enter new chord:", "chordEditTitle": "Click to edit chord", - "chordEditDisabledExplanation": "Chord editing is not available while analysis is running or without an active project.", + "chordEditDisabledExplanation": "Chord editing is not available in this view.", "harmonySourceUserBadge": "User", "roleSwitcherTitle": "Role-specific View", "allRoles": "All Roles", diff --git a/apps/desktop/src/locales/ko/common.json b/apps/desktop/src/locales/ko/common.json index f4f25f69d..b50bbef06 100644 --- a/apps/desktop/src/locales/ko/common.json +++ b/apps/desktop/src/locales/ko/common.json @@ -59,7 +59,7 @@ "chordEditAriaLabel": "{roleName}의 {sectionLabel} 코드 수정, 현재 {chord}", "chordEditPrompt": "새 코드 입력:", "chordEditTitle": "코드 수정", - "chordEditDisabledExplanation": "분석이 진행 중이거나 활성화된 프로젝트가 없을 때는 코드를 수정할 수 없습니다.", + "chordEditDisabledExplanation": "이 화면에서는 코드를 수정할 수 없습니다.", "harmonySourceUserBadge": "사용자", "roleSwitcherTitle": "악기/보컬 역할", "allRoles": "전체 보기", From e93e77917e4d05097396b0602a815b626fd14d1f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 04:28:53 +0900 Subject: [PATCH 12/15] fix(a11y): expose unavailable chord reason to assistive tech --- .../desktop/src/features/workspace/SectionRoadmap.test.tsx | 6 +++++- apps/desktop/src/features/workspace/SectionRoadmap.tsx | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index 3b8351651..342fd7ac2 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,7 +36,7 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); - it("keeps unavailable chord edits focusable, semantically disabled, and inert", () => { + it("keeps unavailable chord edits focusable, described, semantically disabled, and inert", () => { setNavigatorLanguage("ko-KR"); const song = createDemoRehearsalSong(); const promptSpy = vi.spyOn(window, "prompt"); @@ -48,6 +48,10 @@ describe("SectionRoadmap", () => { expect(button.hasAttribute("disabled")).toBe(false); expect(button.tabIndex).toBe(0); + const descriptionId = button.getAttribute("aria-describedby"); + expect(descriptionId).toBeTruthy(); + expect(document.getElementById(descriptionId!)?.textContent).toBe("이 화면에서는 코드를 수정할 수 없습니다."); + fireEvent.click(button); expect(promptSpy).not.toHaveBeenCalled(); }); diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index 35c3b0b86..a42d7044d 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -128,6 +128,7 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma .filter(role => !activeRole || role.id === activeRole) .map(role => { const validatedRange = playableRange(role.range.lowestNote, role.range.highestNote); + const chordEditDisabledDescriptionId = `chord-edit-disabled-${section.id}-${role.id}`.replace(/[^A-Za-z0-9_-]/g, "-"); return (
+ {!onSongUpdate && ( + + {t("chordEditDisabledExplanation")} + + )} {role.harmony.source === "user" && ( {t("harmonySourceUserBadge")} From f3220da107b00c6a43785639b60d797d0fe2c45c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 14 Sep 2026 04:30:16 +0900 Subject: [PATCH 13/15] fix(a11y): make unavailable chord reason touch-visible --- .../workspace/SectionRoadmap.test.tsx | 8 +++--- .../src/features/workspace/SectionRoadmap.tsx | 26 ++++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index 342fd7ac2..fe4583873 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,21 +36,19 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); - it("keeps unavailable chord edits focusable, described, semantically disabled, and inert", () => { + it("keeps unavailable chord edits focusable, visibly described, semantically disabled, and inert", () => { setNavigatorLanguage("ko-KR"); const song = createDemoRehearsalSong(); const promptSpy = vi.spyOn(window, "prompt"); render(); + const description = screen.getByText("이 화면에서는 코드를 수정할 수 없습니다."); const button = screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" }); expect(button.getAttribute("aria-disabled")).toBe("true"); expect(button.hasAttribute("disabled")).toBe(false); expect(button.tabIndex).toBe(0); - - const descriptionId = button.getAttribute("aria-describedby"); - expect(descriptionId).toBeTruthy(); - expect(document.getElementById(descriptionId!)?.textContent).toBe("이 화면에서는 코드를 수정할 수 없습니다."); + expect(button.getAttribute("aria-describedby")).toBe(description.id); fireEvent.click(button); expect(promptSpy).not.toHaveBeenCalled(); diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index a42d7044d..35daff920 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -18,6 +18,7 @@ interface SectionRoadmapProps { /** Documented. */ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadmapProps) { const sectionRoadmapTitleId = useId(); + const chordEditDisabledDescriptionId = useId(); const locale = useMemo(() => detectPreferredLocale(), []); const t = useMemo(() => createTranslator(locale), [locale]); @@ -91,12 +92,19 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma return (
-
-

-

- {t("sectionRoadmapScrollHint")} +
+
+

+

+ {!onSongUpdate && ( +

+ {t("chordEditDisabledExplanation")} +

+ )} +
+ {t("sectionRoadmapScrollHint")}
!activeRole || role.id === activeRole) .map(role => { const validatedRange = playableRange(role.range.lowestNote, role.range.highestNote); - const chordEditDisabledDescriptionId = `chord-edit-disabled-${section.id}-${role.id}`.replace(/[^A-Za-z0-9_-]/g, "-"); return (
- {!onSongUpdate && ( - - {t("chordEditDisabledExplanation")} - - )} {role.harmony.source === "user" && ( {t("harmonySourceUserBadge")} From 89c313858243d2f10d1459c2004b0f8cd34e4c94 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 13 Sep 2026 20:48:58 +0000 Subject: [PATCH 14/15] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EC=84=B1=20=ED=88=B4=ED=8C=81=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=90=EC=B2=B4=20=EB=B0=8F=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EC=A7=80=EC=9B=90=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workspace/SectionRoadmap.test.tsx | 18 ---------------- .../src/features/workspace/SectionRoadmap.tsx | 21 ++++++------------- apps/desktop/src/locales/en/common.json | 2 +- apps/desktop/src/locales/ko/common.json | 2 +- 4 files changed, 8 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index fe4583873..b1888ee4d 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,24 +36,6 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); - it("keeps unavailable chord edits focusable, visibly described, semantically disabled, and inert", () => { - setNavigatorLanguage("ko-KR"); - const song = createDemoRehearsalSong(); - const promptSpy = vi.spyOn(window, "prompt"); - - render(); - - const description = screen.getByText("이 화면에서는 코드를 수정할 수 없습니다."); - const button = screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" }); - expect(button.getAttribute("aria-disabled")).toBe("true"); - expect(button.hasAttribute("disabled")).toBe(false); - expect(button.tabIndex).toBe(0); - expect(button.getAttribute("aria-describedby")).toBe(description.id); - - fireEvent.click(button); - expect(promptSpy).not.toHaveBeenCalled(); - }); - it("omits the range row when both notes are unnamed", () => { setNavigatorLanguage("en-US"); const song = createDemoRehearsalSong(); diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index 35daff920..35c3b0b86 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -18,7 +18,6 @@ interface SectionRoadmapProps { /** Documented. */ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadmapProps) { const sectionRoadmapTitleId = useId(); - const chordEditDisabledDescriptionId = useId(); const locale = useMemo(() => detectPreferredLocale(), []); const t = useMemo(() => createTranslator(locale), [locale]); @@ -92,19 +91,12 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma return (
-
-
-

-

- {!onSongUpdate && ( -

- {t("chordEditDisabledExplanation")} -

- )} -
- {t("sectionRoadmapScrollHint")} +
+

+

+ {t("sectionRoadmapScrollHint")}
Date: Mon, 14 Sep 2026 06:12:54 +0900 Subject: [PATCH 15/15] fix(a11y): restore touch-visible chord edit reason --- .../workspace/SectionRoadmap.test.tsx | 18 ++++++++++++++++ .../src/features/workspace/SectionRoadmap.tsx | 21 +++++++++++++------ apps/desktop/src/locales/en/common.json | 2 +- apps/desktop/src/locales/ko/common.json | 2 +- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx index b1888ee4d..fe4583873 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.test.tsx @@ -36,6 +36,24 @@ describe("SectionRoadmap", () => { expect(screen.getAllByText("verse 들어가기 전에 이 음역을 악기로 확인해 보세요.").length).toBeGreaterThan(0); }); + it("keeps unavailable chord edits focusable, visibly described, semantically disabled, and inert", () => { + setNavigatorLanguage("ko-KR"); + const song = createDemoRehearsalSong(); + const promptSpy = vi.spyOn(window, "prompt"); + + render(); + + const description = screen.getByText("이 화면에서는 코드를 수정할 수 없습니다."); + const button = screen.getByRole("button", { name: "Bass Guitar의 verse 코드 수정, 현재 C#m7" }); + expect(button.getAttribute("aria-disabled")).toBe("true"); + expect(button.hasAttribute("disabled")).toBe(false); + expect(button.tabIndex).toBe(0); + expect(button.getAttribute("aria-describedby")).toBe(description.id); + + fireEvent.click(button); + expect(promptSpy).not.toHaveBeenCalled(); + }); + it("omits the range row when both notes are unnamed", () => { setNavigatorLanguage("en-US"); const song = createDemoRehearsalSong(); diff --git a/apps/desktop/src/features/workspace/SectionRoadmap.tsx b/apps/desktop/src/features/workspace/SectionRoadmap.tsx index 35c3b0b86..35daff920 100644 --- a/apps/desktop/src/features/workspace/SectionRoadmap.tsx +++ b/apps/desktop/src/features/workspace/SectionRoadmap.tsx @@ -18,6 +18,7 @@ interface SectionRoadmapProps { /** Documented. */ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadmapProps) { const sectionRoadmapTitleId = useId(); + const chordEditDisabledDescriptionId = useId(); const locale = useMemo(() => detectPreferredLocale(), []); const t = useMemo(() => createTranslator(locale), [locale]); @@ -91,12 +92,19 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma return (
-
-

-

- {t("sectionRoadmapScrollHint")} +
+
+

+

+ {!onSongUpdate && ( +

+ {t("chordEditDisabledExplanation")} +

+ )} +
+ {t("sectionRoadmapScrollHint")}