diff --git a/src/lib/free-tools/round-timer.ts b/src/lib/free-tools/round-timer.ts index b0e43c517..657f6624b 100644 --- a/src/lib/free-tools/round-timer.ts +++ b/src/lib/free-tools/round-timer.ts @@ -68,9 +68,14 @@ export function settlePrep(state: RoundState, now: number): RoundState { return a === state.prep.a && b === state.prep.b ? state : { ...state, prep: { a, b } }; } -/** Moves to another segment with a fresh, paused speech clock. Prep banks carry over. */ +/** + * Moves to another segment with a fresh, paused speech clock. Prep banks carry + * over. Asking for the segment already shown, including past either end of the + * round, changes nothing, so a stray arrow key never wipes a running speech. + */ export function goToSegment(state: RoundState, format: RoundFormat, index: number, now: number): RoundState { const segmentIndex = Math.min(Math.max(0, index), format.segments.length - 1); + if (segmentIndex === state.segmentIndex) return state; return { segmentIndex, speech: PAUSED, prep: pausePrep(state.prep, now) }; } diff --git a/src/pages/tools/ToolsHubPage.tsx b/src/pages/tools/ToolsHubPage.tsx index b568f6927..d94263532 100644 --- a/src/pages/tools/ToolsHubPage.tsx +++ b/src/pages/tools/ToolsHubPage.tsx @@ -10,7 +10,7 @@ export default function ToolsHubPage() { Tools for debate practice that work in any browser and need no account. Your rounds and drafts are saved in your browser, not on a server. Use them in a club session, on a projector in class, or embedded on your own site.

} + intro={

Tools for debate practice that work in any browser and need no account. The timer saves your round in this browser, not on a server. Use them in a club session, on a projector in class, or embedded on your own site.

} shortAnswer={{ question: t('shortAnswer.hub.question'), answer: t('shortAnswer.hub.answer', { total: PRACTICE_MOTIONS.length }) }} structuredData={hubStructuredData(FREE_TOOLS.map(({ route, name }) => ({ route, name })))} tool={( diff --git a/src/test/free-tools/round-timer.test.ts b/src/test/free-tools/round-timer.test.ts index eb620b983..3d6f25ae1 100644 --- a/src/test/free-tools/round-timer.test.ts +++ b/src/test/free-tools/round-timer.test.ts @@ -110,6 +110,16 @@ describe('round timer', () => { expect(prepRemainingMs(state, 'a', 10 * MIN)).toBe(210 * S); }); + it('keeps the running speech when navigation is already at the first or last speech', () => { + // The arrow keys can ask for a speech past either end; that must not wipe the clock. + const running = toggleSpeech(initialRoundState(FORMATS.pf), 0); + expect(goToSegment(running, FORMATS.pf, -1, 30 * S)).toBe(running); + const last = toggleSpeech(goToSegment(running, FORMATS.pf, 10, 0), 0); + expect(last.segmentIndex).toBe(10); + expect(goToSegment(last, FORMATS.pf, 11, 30 * S)).toBe(last); + expect(elapsedMs(goToSegment(last, FORMATS.pf, 11, 30 * S).speech, 30 * S)).toBe(30 * S); + }); + it('restores a stored round only when it fits the format', () => { const running = toggleSpeech(goToSegment(initialRoundState(FORMATS.pf), FORMATS.pf, 4, 0), 1234); const stored = JSON.parse(JSON.stringify(running));