Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@
## 2026-07-02 - Inline clear buttons preserve focus
**Learning:** Inline clear buttons often unmount immediately after clearing state, which can drop keyboard focus to the document body.
**Action:** Move focus back to the owning input before clearing state, and cover the behavior with a DOM focus test.
## 2024-11-20 - Ensure screen reader text isn't duplicated in tooltips
**Learning:** When creating a custom wrapper for disabled buttons to show tooltips, using `title` on the wrapper provides an accessible name. Adding an additional visually hidden `sr-only` child duplicates this text for screen readers.
**Action:** If a wrapper has a `title` attribute, do not include an additional `sr-only` text child.
Comment on lines 37 to +42
Comment on lines +41 to +42
3 changes: 2 additions & 1 deletion apps/desktop/src/features/score/ScoreView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ describe("ScoreView", () => {

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: "Open score: opener.pdf" })).toHaveAttribute("aria-disabled", "true");
expect(screen.getByRole("button", { name: "Remove: opener.pdf" })).toBeDisabled();
expect(screen.getAllByTitle("scoreNavDisabledHint")).toHaveLength(2);
Comment on lines +99 to +101

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

ν…ŒμŠ€νŠΈλ₯Ό μ‹€μ œ 힌트 λ¬Έμžμ—΄κ³Ό μ ‘κ·Όμ„± 계약에 κ³ μ •ν•˜μ„Έμš”.

createTranslator mock에 scoreNavDisabledHintκ°€ μ—†μ–΄ fallback으둜 ν‚€ 이름인 "scoreNavDisabledHint"κ°€ λ°˜ν™˜λ©λ‹ˆλ‹€. λ”°λΌμ„œ λ²ˆμ—­μ΄ λˆ„λ½λ˜κ±°λ‚˜ wrapperκ°€ 슀크린 리더에 μ œλŒ€λ‘œ λ…ΈμΆœλ˜μ§€ μ•Šμ•„λ„ title 두 개만 μ‘΄μž¬ν•˜λ©΄ ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•©λ‹ˆλ‹€.

mock에 μ‹€μ œ 힌트 λ¬Έμžμ—΄μ„ μΆ”κ°€ν•˜κ³ , title 개수 λŒ€μ‹  λͺ…μ‹œμ μΈ aria-describedby/role/name 및 ν‚€λ³΄λ“œ 포컀슀 λ™μž‘μ„ κ²€μ¦ν•˜μ„Έμš”.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/features/score/ScoreView.test.tsx` around lines 99 - 101,
Update the ScoreView test’s createTranslator mock to provide the real
scoreNavDisabledHint text, then replace the title-count assertion with explicit
checks that each disabled navigation control exposes the hint through the
expected aria-describedby relationship and accessible role/name. Also verify the
disabled controls retain the required keyboard-focus behavior.


fireEvent.click(screen.getByRole("button", { name: "Open score: opener.pdf" }));
expect(mockInvoke).not.toHaveBeenCalled();
Expand Down
39 changes: 27 additions & 12 deletions apps/desktop/src/features/score/ScoreView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,24 +184,39 @@ export function ScoreView({ song, projectId, onSongUpdate }: ScoreViewProps) {
<button
type="button"
onClick={projectId ? () => void openAttachment(projectId, attachment) : undefined}
disabled={!projectId}
aria-disabled={!projectId ? "true" : undefined}
title={!projectId ? t("scoreNavDisabledHint") : undefined}
aria-current={selected?.id === attachment.id ? "true" : undefined}
aria-label={`${t("scoreOpen")}: ${attachment.fileName}`}
className="flex min-h-10 min-w-0 flex-1 items-center gap-2 text-left text-sm font-semibold text-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 disabled:cursor-not-allowed disabled:opacity-60"
className="flex min-h-10 min-w-0 flex-1 items-center gap-2 text-left text-sm font-semibold text-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300 aria-disabled:cursor-not-allowed aria-disabled:opacity-60"
>
<FileMusic className="size-4 shrink-0 text-cyan-300" aria-hidden="true" />
<span className="truncate">{attachment.fileName}</span>
</button>
<Button
variant="outline"
size="icon"
onClick={projectId ? () => void handleRemove(projectId, attachment) : undefined}
disabled={!projectId}
aria-label={`${t("scoreRemove")}: ${attachment.fileName}`}
className="size-10 border-rose-300/25 text-rose-200 hover:bg-rose-400/10"
>
<Trash2 className="size-4" aria-hidden="true" />
</Button>
{!projectId ? (
<span tabIndex={0} title={t("scoreNavDisabledHint")} className="inline-flex">
<Button
variant="outline"
size="icon"
disabled={true}
aria-label={`${t("scoreRemove")}: ${attachment.fileName}`}
className="size-10 border-rose-300/25 text-rose-200 hover:bg-rose-400/10"
>
<Trash2 className="size-4" aria-hidden="true" />
</Button>
</span>
) : (
<Button
variant="outline"
size="icon"
onClick={() => void handleRemove(projectId, attachment)}
title={`${t("scoreRemove")}: ${attachment.fileName}`}
aria-label={`${t("scoreRemove")}: ${attachment.fileName}`}
className="size-10 border-rose-300/25 text-rose-200 hover:bg-rose-400/10"
>
<Trash2 className="size-4" aria-hidden="true" />
</Button>
)}
Comment on lines +196 to +219
</li>
))}
</ul>
Expand Down
Loading