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("sectionRoadmapTitle")}
-
-
{t("sectionRoadmapScrollHint")}
+
+
+
+
+ {t("sectionRoadmapTitle")}
+
+ {!onSongUpdate && (
+
+ {t("chordEditDisabledExplanation")}
+
+ )}
+
+
{t("sectionRoadmapScrollHint")}