Skip to content

Commit fba0ed6

Browse files
committed
Merge remote-tracking branch 'origin/main' into cursor/fix-sentry-and-rate-limits-8245
2 parents 66c653b + 09faf68 commit fba0ed6

6 files changed

Lines changed: 20 additions & 5 deletions

File tree

‎src/lib/free-tools/round-timer.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,14 @@ export function settlePrep(state: RoundState, now: number): RoundState {
6868
return a === state.prep.a && b === state.prep.b ? state : { ...state, prep: { a, b } };
6969
}
7070

71-
/** Moves to another segment with a fresh, paused speech clock. Prep banks carry over. */
71+
/**
72+
* Moves to another segment with a fresh, paused speech clock. Prep banks carry
73+
* over. Asking for the segment already shown, including past either end of the
74+
* round, changes nothing, so a stray arrow key never wipes a running speech.
75+
*/
7276
export function goToSegment(state: RoundState, format: RoundFormat, index: number, now: number): RoundState {
7377
const segmentIndex = Math.min(Math.max(0, index), format.segments.length - 1);
78+
if (segmentIndex === state.segmentIndex) return state;
7479
return { segmentIndex, speech: PAUSED, prep: pausePrep(state.prep, now) };
7580
}
7681

‎src/locales/tools/en.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"shortAnswer": {
1414
"hub": {
1515
"question": "What free debate tools can I use without an account?",
16-
"answer": "Four, all free and all usable without signing up: a round timer that knows the speech order and times of seven formats, a motion generator that draws from {{total}} original practice motions, a printable flow sheet with a column for every speech, and a speech time calculator. They run in your browser and save your rounds and drafts there, not on a server."
16+
"answer": "Four, all free and all usable without signing up: a round timer that knows the speech order and times of seven formats, a motion generator that draws from {{total}} original practice motions, a printable flow sheet with a column for every speech, and a speech time calculator. They run in your browser, and the timer saves your round there, not on a server."
1717
},
1818
"timer": {
1919
"question": "Is there a free debate timer that knows each format's speech times?",

‎src/locales/tools/es.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"shortAnswer": {
1414
"hub": {
1515
"question": "¿Qué herramientas de debate gratis puedo usar sin crear una cuenta?",
16-
"answer": "Cuatro, todas gratis y sin registro: un cronómetro de rondas que conoce el orden y la duración de los discursos de siete formatos, un generador que saca mociones de un banco de {{total}} mociones de práctica originales, una hoja de flujo imprimible con una columna por discurso y una calculadora de duración de discursos. Funcionan en tu navegador y guardan ahí tus rondas y borradores, no en un servidor."
16+
"answer": "Cuatro, todas gratis y sin registro: un cronómetro de rondas que conoce el orden y la duración de los discursos de siete formatos, un generador que saca mociones de un banco de {{total}} mociones de práctica originales, una hoja de flujo imprimible con una columna por discurso y una calculadora de duración de discursos. Funcionan en tu navegador, y el cronómetro guarda ahí tu ronda, no en un servidor."
1717
},
1818
"timer": {
1919
"question": "¿Hay algún cronómetro de debate gratis que conozca los tiempos de cada formato?",

‎src/locales/tools/hu.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"shortAnswer": {
1414
"hub": {
1515
"question": "Milyen ingyenes vitaeszközöket használhatok regisztráció nélkül?",
16-
"answer": "Négyet, mind ingyenes, és egyikhez sem kell fiók: egy fordulóidőmérőt, amely hét formátum beszédsorrendjét és beszédidejét ismeri, egy tételgenerátort, amely {{total}} eredeti, gyakorlásra írt vitatételből húz, egy nyomtatható jegyzetlapot, amelyen minden beszédnek saját oszlopa van, és egy beszédidő-kalkulátort. A böngésződben futnak, és a fordulóidat és piszkozataidat ott tárolják, nem szerveren."
16+
"answer": "Négyet, mind ingyenes, és egyikhez sem kell fiók: egy fordulóidőmérőt, amely hét formátum beszédsorrendjét és beszédidejét ismeri, egy tételgenerátort, amely {{total}} eredeti, gyakorlásra írt vitatételből húz, egy nyomtatható jegyzetlapot, amelyen minden beszédnek saját oszlopa van, és egy beszédidő-kalkulátort. A böngésződben futnak, és az időmérő ott tárolja a fordulódat, nem szerveren."
1717
},
1818
"timer": {
1919
"question": "Van olyan ingyenes vitaidőmérő, amely ismeri az egyes formátumok beszédidejét?",

‎src/pages/tools/ToolsHubPage.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function ToolsHubPage() {
1010
<ToolPageLayout
1111
route="/tools"
1212
title="Free debate tools"
13-
intro={<p>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.</p>}
13+
intro={<p>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.</p>}
1414
shortAnswer={{ question: t('shortAnswer.hub.question'), answer: t('shortAnswer.hub.answer', { total: PRACTICE_MOTIONS.length }) }}
1515
structuredData={hubStructuredData(FREE_TOOLS.map(({ route, name }) => ({ route, name })))}
1616
tool={(

‎src/test/free-tools/round-timer.test.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ describe('round timer', () => {
110110
expect(prepRemainingMs(state, 'a', 10 * MIN)).toBe(210 * S);
111111
});
112112

113+
it('keeps the running speech when navigation is already at the first or last speech', () => {
114+
// The arrow keys can ask for a speech past either end; that must not wipe the clock.
115+
const running = toggleSpeech(initialRoundState(FORMATS.pf), 0);
116+
expect(goToSegment(running, FORMATS.pf, -1, 30 * S)).toBe(running);
117+
const last = toggleSpeech(goToSegment(running, FORMATS.pf, 10, 0), 0);
118+
expect(last.segmentIndex).toBe(10);
119+
expect(goToSegment(last, FORMATS.pf, 11, 30 * S)).toBe(last);
120+
expect(elapsedMs(goToSegment(last, FORMATS.pf, 11, 30 * S).speech, 30 * S)).toBe(30 * S);
121+
});
122+
113123
it('restores a stored round only when it fits the format', () => {
114124
const running = toggleSpeech(goToSegment(initialRoundState(FORMATS.pf), FORMATS.pf, 4, 0), 1234);
115125
const stored = JSON.parse(JSON.stringify(running));

0 commit comments

Comments
 (0)