From 108774ea619633afffdf08cd858bdcae7ee9a547 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Thu, 3 Sep 2026 16:13:03 +0000 Subject: [PATCH 1/2] fix: render practice rating hints as visible button subtext (#6000) --- client/src/components/songbook/PracticeLogger.jsx | 7 ++++--- .../components/songbook/PracticeLogger.test.jsx | 14 +++++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/components/songbook/PracticeLogger.jsx b/client/src/components/songbook/PracticeLogger.jsx index 3688bced8e..39b382693f 100644 --- a/client/src/components/songbook/PracticeLogger.jsx +++ b/client/src/components/songbook/PracticeLogger.jsx @@ -59,7 +59,7 @@ export default function PracticeLogger({ song, onLogged, className = '' }) { -
+
{SONG_PRACTICE_RATINGS.map((rating) => ( ))}
diff --git a/client/src/components/songbook/PracticeLogger.test.jsx b/client/src/components/songbook/PracticeLogger.test.jsx index 02fe38b0fe..8d2c42525c 100644 --- a/client/src/components/songbook/PracticeLogger.test.jsx +++ b/client/src/components/songbook/PracticeLogger.test.jsx @@ -47,13 +47,21 @@ describe('PracticeLogger', () => { expect(screen.getByText(/1 session ·/)).toBeTruthy(); }); + it('renders each rating hint visibly for touch users, not hover-only', () => { + render(); + expect(screen.getByText('Fell apart — regress a stage and practice again today')).toBeTruthy(); + expect(screen.getByText('Got through it — hold the stage, review sooner')).toBeTruthy(); + expect(screen.getByText('Played it with hesitation — advance a stage')).toBeTruthy(); + expect(screen.getByText('Played it clean — advance a stage, review later')).toBeTruthy(); + }); + it('posts the grade and hands the updated record back — no client-side scheduling', async () => { const updated = { ...song(), stage: 'learned', practice: { nextReview: future(), sessions: 1 } }; practiceSong.mockResolvedValue(updated); const onLogged = vi.fn(); render(); - fireEvent.click(screen.getByRole('button', { name: 'Solid' })); + fireEvent.click(screen.getByRole('button', { name: /Solid/ })); await waitFor(() => expect(onLogged).toHaveBeenCalledWith(updated)); // The grade is the ONLY thing sent; the server owns stage + schedule. @@ -64,7 +72,7 @@ describe('PracticeLogger', () => { it('sends the low grade for a failed run', async () => { practiceSong.mockResolvedValue({ ...song(), stage: 'learning', practice: { nextReview: new Date().toISOString() } }); render(); - fireEvent.click(screen.getByRole('button', { name: 'Struggled' })); + fireEvent.click(screen.getByRole('button', { name: /Struggled/ })); await waitFor(() => expect(practiceSong).toHaveBeenCalledWith('song-1', 0, { silent: true })); }); @@ -73,7 +81,7 @@ describe('PracticeLogger', () => { const onLogged = vi.fn(); render(); - fireEvent.click(screen.getByRole('button', { name: 'Clean' })); + fireEvent.click(screen.getByRole('button', { name: /Clean/ })); await waitFor(() => expect(toast.error).toHaveBeenCalled()); expect(onLogged).not.toHaveBeenCalled(); From 1f2f3721a3bd8b74c536681b46ca9320aa260505 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Thu, 3 Sep 2026 16:14:43 +0000 Subject: [PATCH 2/2] fix: drop redundant title tooltip now that rating hints are visible (#6000) --- client/src/components/songbook/PracticeLogger.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/songbook/PracticeLogger.jsx b/client/src/components/songbook/PracticeLogger.jsx index 39b382693f..38b143b07c 100644 --- a/client/src/components/songbook/PracticeLogger.jsx +++ b/client/src/components/songbook/PracticeLogger.jsx @@ -66,7 +66,6 @@ export default function PracticeLogger({ song, onLogged, className = '' }) { type="button" onClick={() => logPractice(rating.quality)} disabled={logging} - title={rating.hint} className="min-h-[48px] px-2.5 py-2 text-left sm:text-center rounded-lg border border-port-border text-gray-300 hover:text-white hover:border-port-accent/50 hover:bg-port-border/50 disabled:opacity-50 flex flex-col justify-center" > {rating.label}