Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
8b20d83
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ๊ฐœ์„ ์„ ์œ„ํ•œ ํˆดํŒ ํ‘œ์‹œ
seonghobae Sep 13, 2026
ac67328
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ๊ฐœ์„ ์„ ์œ„ํ•œ ํˆดํŒ ํ‘œ์‹œ
seonghobae Sep 13, 2026
715f21f
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ๊ฐœ์„ ์„ ์œ„ํ•œ ํˆดํŒ ํ‘œ์‹œ
seonghobae Sep 13, 2026
32f63be
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ๊ฐœ์„ ์„ ์œ„ํ•œ ํˆดํŒ ํ‘œ์‹œ
seonghobae Sep 13, 2026
45ca66c
fix(ui): explain unavailable chord editing
seonghobae Sep 13, 2026
a84df83
fix(ui): localize unavailable chord editing
seonghobae Sep 13, 2026
b792dfc
fix(ui): explain disabled chord edit control
seonghobae Sep 13, 2026
d854d60
test(ui): cover unavailable chord editing
seonghobae Sep 13, 2026
f6822f7
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ํˆดํŒ ์ปดํฌ๋„ŒํŠธ ๊ต์ฒด ๋ฐ ๋ชจ๋ฐ”์ผ ์ง€์› ๊ฐ•ํ™”
seonghobae Sep 13, 2026
cb7eaeb
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ํˆดํŒ ์ปดํฌ๋„ŒํŠธ ๊ต์ฒด ๋ฐ ๋ชจ๋ฐ”์ผ ์ง€์› ๊ฐ•ํ™”
seonghobae Sep 13, 2026
6f23507
fix(ui): keep unavailable chord edit semantics factual and tested
seonghobae Sep 13, 2026
e93e779
fix(a11y): expose unavailable chord reason to assistive tech
seonghobae Sep 13, 2026
f3220da
fix(a11y): make unavailable chord reason touch-visible
seonghobae Sep 13, 2026
89c3138
๐ŸŽจ Palette: ์ ‘๊ทผ์„ฑ ํˆดํŒ ์ปดํฌ๋„ŒํŠธ ๊ต์ฒด ๋ฐ ๋ชจ๋ฐ”์ผ ์ง€์› ๊ฐ•ํ™”
seonghobae Sep 13, 2026
22ae12a
fix(a11y): restore touch-visible chord edit reason
seonghobae Sep 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion apps/desktop/src/features/workspace/SectionRoadmap.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<SectionRoadmap song={song} activeRole="bass-guitar" />);

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();
Expand Down Expand Up @@ -90,7 +108,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);
});

Expand Down
65 changes: 41 additions & 24 deletions apps/desktop/src/features/workspace/SectionRoadmap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -17,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]);

Expand Down Expand Up @@ -90,12 +92,19 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma

return (
<div className="mt-6 space-y-4">
<div className="flex items-center justify-between">
<h2 id={sectionRoadmapTitleId} className="flex items-center text-xl font-black tracking-tight text-white">
<Music2 className="mr-2 size-5 text-cyan-300" aria-hidden="true" />
{t("sectionRoadmapTitle")}
</h2>
<span className="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">{t("sectionRoadmapScrollHint")}</span>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0">
<h2 id={sectionRoadmapTitleId} className="flex items-center text-xl font-black tracking-tight text-white">
<Music2 className="mr-2 size-5 text-cyan-300" aria-hidden="true" />
{t("sectionRoadmapTitle")}
</h2>
{!onSongUpdate && (
<p id={chordEditDisabledDescriptionId} className="mt-1 text-xs font-medium text-slate-400">
{t("chordEditDisabledExplanation")}
</p>
)}
</div>
<span className="shrink-0 text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">{t("sectionRoadmapScrollHint")}</span>
</div>

<div
Expand Down Expand Up @@ -152,24 +161,32 @@ export function SectionRoadmap({ song, activeRole, onSongUpdate }: SectionRoadma
<div className="space-y-3">
<div className="flex items-center gap-2">
<span className="text-[0.7rem] font-bold uppercase tracking-wider text-slate-400">{t("sectionChordLabel")}</span>
<button
type="button"
aria-label={editChordLabel(role, section.label)}
className={`-ml-2 rounded px-2 py-0.5 text-lg font-black tracking-tight transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 ${
onSongUpdate
? "cursor-pointer hover:bg-white/10"
: "cursor-default"
} ${
role.harmony.source === "user"
? "bg-indigo-300/15 text-indigo-200"
: "text-cyan-100"
}`}
onClick={() => handleChordEdit(section.id, role)}
title={onSongUpdate ? t("chordEditTitle") : undefined}
disabled={!onSongUpdate}
>
{role.harmony.chord}
</button>
<Tooltip>
<TooltipTrigger
type="button"
aria-label={editChordLabel(role, section.label)}
aria-describedby={!onSongUpdate ? chordEditDisabledDescriptionId : undefined}
className={`-ml-2 rounded px-2 py-0.5 text-lg font-black tracking-tight transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 aria-disabled:opacity-50 ${
onSongUpdate
? "cursor-pointer hover:bg-white/10"
: "cursor-default"
} ${
role.harmony.source === "user"
? "bg-indigo-300/15 text-indigo-200"
: "text-cyan-100"
}`}
onClick={() => {
if (!onSongUpdate) return;
handleChordEdit(section.id, role);
}}
aria-disabled={!onSongUpdate ? "true" : undefined}
>
{role.harmony.chord}
</TooltipTrigger>
<TooltipContent sideOffset={4}>
{onSongUpdate ? t("chordEditTitle") : t("chordEditDisabledExplanation")}
</TooltipContent>
</Tooltip>
{role.harmony.source === "user" && (
<Badge variant="secondary" className="h-4 bg-indigo-300/20 px-1 text-[0.6rem] text-indigo-100 hover:bg-indigo-300/20">
{t("harmonySourceUserBadge")}
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +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 in this view.",
"harmonySourceUserBadge": "User",
"roleSwitcherTitle": "Role-specific View",
"allRoles": "All Roles",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/locales/ko/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"chordEditAriaLabel": "{roleName}์˜ {sectionLabel} ์ฝ”๋“œ ์ˆ˜์ •, ํ˜„์žฌ {chord}",
"chordEditPrompt": "์ƒˆ ์ฝ”๋“œ ์ž…๋ ฅ:",
"chordEditTitle": "์ฝ”๋“œ ์ˆ˜์ •",
"chordEditDisabledExplanation": "์ด ํ™”๋ฉด์—์„œ๋Š” ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.",
"harmonySourceUserBadge": "์‚ฌ์šฉ์ž",
"roleSwitcherTitle": "์•…๊ธฐ/๋ณด์ปฌ ์—ญํ• ",
"allRoles": "์ „์ฒด ๋ณด๊ธฐ",
Expand Down