diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index 9d274ab..9229572 100755 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -180,7 +180,7 @@ export default { // SRS session (Langzeitgedächtnis) progress & persistence // Stored per day and course so the user can close/reopen without starting over. - srsSession: null, // { version, dateKey, courseId, initialTotalDue, initialDueIds, doneIds, correctCount, wrongCount } + srsSession: null, // { version, dateKey, courseId, initialTotalDue, initialDueIds, doneIds, retryPendingIds, finalReviewIds, correctCount, wrongCount } srsQueueIds: [], // remaining due ids for this session, in due order srsServerTotalDue: null, @@ -545,6 +545,8 @@ export default { initialTotalDue: initialTotal, initialDueIds: dueIds.slice(0, initialTotal), doneIds: [], + retryPendingIds: [], + finalReviewIds: [], correctCount: 0, wrongCount: 0, }; @@ -566,7 +568,13 @@ export default { } const doneSet = new Set(Array.isArray(this.srsSession.doneIds) ? this.srsSession.doneIds : []); - this.srsQueueIds = dueIds.filter((id) => !doneSet.has(id)); + const finalReviewIds = Array.isArray(this.srsSession.finalReviewIds) ? this.srsSession.finalReviewIds : []; + const finalReviewSet = new Set(finalReviewIds); + const availableIds = new Set(dueIds); + this.srsQueueIds = [ + ...dueIds.filter((id) => !doneSet.has(id) && !finalReviewSet.has(id)), + ...finalReviewIds.filter((id) => !doneSet.has(id) && availableIds.has(id)) + ]; // 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); @@ -574,7 +582,7 @@ export default { 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: limited.length, initialDueIds: limited, 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: [], retryPendingIds: [], finalReviewIds: [], correctCount: 0, wrongCount: 0 }; } } this.saveSrsSession(); @@ -1259,12 +1267,20 @@ export default { if (!this.srsMode || !this.answered || this.locked) { return; } - this.locked = true; - await this.reportSrsReview(this.lastCorrect, rating); - const id = this.current?.id; const treatAsAgain = rating === 'again' || !this.lastCorrect; const moveToHardPractice = treatAsAgain && this.isItemMarkedHard(this.current); + const retryPendingIds = Array.isArray(this.srsSession?.retryPendingIds) + ? this.srsSession.retryPendingIds + : []; + const needsFinalRepeat = this.lastCorrect && retryPendingIds.includes(id); + + this.locked = true; + // The immediate correction after a wrong answer is a rehearsal, not the + // SRS result. Only the later repetition at the end is recorded as right. + if (!needsFinalRepeat) { + await this.reportSrsReview(this.lastCorrect, rating); + } if (id && this.srsSession) { if (treatAsAgain && !moveToHardPractice) { @@ -1273,6 +1289,24 @@ export default { if (Array.isArray(this.srsSession.doneIds)) { this.srsSession.doneIds = this.srsSession.doneIds.filter((x) => x !== id); } + if (!retryPendingIds.includes(id)) { + this.srsSession.retryPendingIds = [...retryPendingIds, id]; + } + } else if (needsFinalRepeat) { + // The correction was right, but the item must be recalled once more + // after the rest of today's cards before it counts as complete. + this.srsSession.retryPendingIds = retryPendingIds.filter((x) => x !== id); + const finalReviewIds = Array.isArray(this.srsSession.finalReviewIds) + ? this.srsSession.finalReviewIds + : []; + if (!finalReviewIds.includes(id)) { + this.srsSession.finalReviewIds = [...finalReviewIds, id]; + } + const remaining = Array.isArray(this.srsQueueIds) + ? this.srsQueueIds.filter((x) => x !== id) + : []; + remaining.push(id); + this.srsQueueIds = remaining; } else { // Hard cards leave the daily review after a wrong answer. They are // practiced separately through the hard-vocab drill. @@ -1281,6 +1315,10 @@ export default { done.push(id); this.srsSession.doneIds = done; } + this.srsSession.retryPendingIds = retryPendingIds.filter((x) => x !== id); + this.srsSession.finalReviewIds = (Array.isArray(this.srsSession.finalReviewIds) + ? this.srsSession.finalReviewIds + : []).filter((x) => x !== id); if (Array.isArray(this.srsQueueIds) && this.srsQueueIds[0] === id) { this.srsQueueIds = this.srsQueueIds.slice(1); } else if (Array.isArray(this.srsQueueIds)) {