From bfec885a1f7810baec52cf84be0e1db37a4e1502 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 4 Jun 2026 18:52:20 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20verbessere=20initialTotalDue=20und=20be?= =?UTF-8?q?grenze=20t=C3=A4gliche=20=C3=9Cbungsanzahl=20auf=20MAX=5FDAILY?= =?UTF-8?q?=5FDUE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/services/vocabService.js | 12 ++++---- .../socialnetwork/VocabPracticeDialog.vue | 29 +++++++------------ 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index 17afc83..cf79f25 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -193,14 +193,16 @@ export default class VocabService { } // Determine safe default nextDueAt for newly created items: - // - default: next calendar day at 08:00 - // - ensure at least 24 hours after creation + // - prefer a calendar time (08:00) at least one day after creation + // - ensure at least 48 hours after creation so freshly practiced items + // don't immediately appear as due the next day const createdAt = new Date(now); const nextCalendar08 = new Date(createdAt); nextCalendar08.setHours(8, 0, 0, 0); - nextCalendar08.setDate(nextCalendar08.getDate() + 1); - const min24h = new Date(createdAt.getTime() + 24 * 60 * 60 * 1000); - const safeNextDue = nextCalendar08.getTime() < min24h.getTime() ? min24h : nextCalendar08; + // consider the day after tomorrow as earliest calendar slot + nextCalendar08.setDate(nextCalendar08.getDate() + 2); + const min48h = new Date(createdAt.getTime() + 48 * 60 * 60 * 1000); + const safeNextDue = nextCalendar08.getTime() < min48h.getTime() ? min48h : nextCalendar08; const created = await VocabSrsItem.create({ userId, diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index 3625a01..ac4a3bb 100644 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -161,6 +161,7 @@ const PRACTICE_MIN_EXPOSURES = 3; const SRS_SESSION_STORAGE_VERSION = 2; const HARD_REQUIRED_CONSECUTIVE_CORRECT = 5; const SRS_AGAIN_REINSERT_OFFSET = 3; +const MAX_DAILY_DUE = 50; export default { name: 'VocabPracticeDialog', @@ -514,13 +515,14 @@ export default { } if (!this.srsSession) { + const initialTotal = Math.min(MAX_DAILY_DUE, Number.isFinite(Number(this.srsServerTotalDue)) ? Number(this.srsServerTotalDue) : dueIds.length); this.srsSession = { version: SRS_SESSION_STORAGE_VERSION, dateKey: this.getLocalDateKey(), courseId: this.openParams.courseId, - // Prefer server-reported total due count when available (shows full-course due count) - initialTotalDue: Number.isFinite(Number(this.srsServerTotalDue)) ? Number(this.srsServerTotalDue) : dueIds.length, - initialDueIds: dueIds, + // For the user's session we cap the number practiced per day to MAX_DAILY_DUE + initialTotalDue: initialTotal, + initialDueIds: dueIds.slice(0, initialTotal), doneIds: [], correctCount: 0, wrongCount: 0, @@ -536,26 +538,22 @@ export default { // If the stored session has an invalid initialTotalDue (e.g. 0), repair it from server/pool data. if (this.srsSession && (!Number.isFinite(Number(this.srsSession.initialTotalDue)) || Number(this.srsSession.initialTotalDue) <= 0)) { const repairedTotal = Number.isFinite(Number(this.srsServerTotalDue)) && Number(this.srsServerTotalDue) > 0 ? Number(this.srsServerTotalDue) : dueIds.length; - try { console.debug('[VocabPracticeDialog] repair srsSession.initialTotalDue', { before: this.srsSession.initialTotalDue, repairedTotal }); } catch (_) {} - this.srsSession.initialTotalDue = repairedTotal; - this.srsSession.initialDueIds = Array.isArray(this.srsSession.initialDueIds) && this.srsSession.initialDueIds.length ? this.srsSession.initialDueIds : dueIds; + const capped = Math.min(MAX_DAILY_DUE, repairedTotal); + this.srsSession.initialTotalDue = capped; + this.srsSession.initialDueIds = Array.isArray(this.srsSession.initialDueIds) && this.srsSession.initialDueIds.length ? this.srsSession.initialDueIds.slice(0, capped) : dueIds.slice(0, capped); try { this.saveSrsSession(); } catch (_) {} } const doneSet = new Set(Array.isArray(this.srsSession.doneIds) ? this.srsSession.doneIds : []); this.srsQueueIds = dueIds.filter((id) => !doneSet.has(id)); - try { - console.debug('[VocabPracticeDialog] initSrsSessionFromPool', { courseId: this.openParams?.courseId, dueIdsLen: dueIds.length, srsQueueLen: this.srsQueueIds.length, stored: !!this.srsSession }); - } catch (_) {} - // Fallback: if SRS mode but queue is empty (e.g. mismatch between stored session and current pool), fall back to pool order if (this.srsMode && Array.isArray(this.srsQueueIds) && this.srsQueueIds.length === 0) { const fallbackIds = (this.pool || []).map((it) => it.id).filter(Boolean); if (fallbackIds.length > 0) { - try { console.debug('[VocabPracticeDialog] initSrsSessionFromPool: srsQueueIds empty, falling back to pool ids', { fallbackLen: fallbackIds.length }); } catch (_) {} - this.srsQueueIds = fallbackIds; + const limited = fallbackIds.slice(0, MAX_DAILY_DUE); + this.srsQueueIds = limited; // keep initialTotalDue stable if possible - if (!this.srsSession) this.srsSession = { version: SRS_SESSION_STORAGE_VERSION, dateKey: this.getLocalDateKey(), courseId: this.openParams.courseId, initialTotalDue: fallbackIds.length, initialDueIds: fallbackIds, doneIds: [], correctCount: 0, wrongCount: 0 }; + if (!this.srsSession) this.srsSession = { version: SRS_SESSION_STORAGE_VERSION, dateKey: this.getLocalDateKey(), courseId: this.openParams.courseId, initialTotalDue: limited.length, initialDueIds: limited, doneIds: [], correctCount: 0, wrongCount: 0 }; } } this.saveSrsSession(); @@ -595,10 +593,6 @@ export default { document.addEventListener('keydown', this.handleKeyDown); }); this.loadHardVocabMap(); - // Expose instance for temporary debugging in browser console - try { - window.__vocabPracticeDialog = this; - } catch (_) {} this.reloadPool(); }, close() { @@ -747,7 +741,6 @@ export default { }); const result = mapped.filter(Boolean); - try { console.debug('[VocabPracticeDialog] normalizePool', { inputLen, outLen: result.length, sample: result.slice(0,5) }); } catch (_) {} return result; } catch (e) { console.warn('[VocabPracticeDialog] normalizePool failed', e);