fix(vocab): keep SRS cards and counters aligned
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
All checks were successful
Deploy to production / deploy (push) Successful in 2m56s
This commit is contained in:
@@ -143,9 +143,9 @@ const PRACTICE_MIN_EXPOSURES = 3;
|
||||
// The original daily batch remains stable when the dialog is reopened.
|
||||
// Keep in sync with VocabCourseView. v3 discards legacy empty sessions that
|
||||
// could block cards due later on the same calendar day.
|
||||
// Keep this in sync with VocabCourseView. Version 4 discards stale empty
|
||||
// sessions that could mask a new daily SRS batch.
|
||||
const SRS_SESSION_STORAGE_VERSION = 4;
|
||||
// Keep this in sync with VocabCourseView. Version 5 discards sessions that
|
||||
// counted cards removed by the client-side duplicate filter as still open.
|
||||
const SRS_SESSION_STORAGE_VERSION = 5;
|
||||
const HARD_REQUIRED_CONSECUTIVE_CORRECT = 5;
|
||||
const MAX_DAILY_DUE = 50;
|
||||
const MAX_DAILY_HARD_VOCABS = 7;
|
||||
@@ -517,7 +517,11 @@ export default {
|
||||
}
|
||||
|
||||
if (!this.srsSession) {
|
||||
const initialTotal = Math.min(MAX_DAILY_DUE, Number.isFinite(Number(this.srsServerTotalDue)) ? Number(this.srsServerTotalDue) : dueIds.length);
|
||||
// `dueIds` is the actual exercise pool. The server's total can be
|
||||
// larger than the requested page (and formerly also included cards
|
||||
// that the client filtered), so it must never define this batch's
|
||||
// counter.
|
||||
const initialTotal = Math.min(MAX_DAILY_DUE, dueIds.length);
|
||||
this.srsSession = {
|
||||
version: SRS_SESSION_STORAGE_VERSION,
|
||||
dateKey: this.getLocalDateKey(),
|
||||
@@ -551,6 +555,12 @@ export default {
|
||||
const initialDueIds = Array.isArray(this.srsSession.initialDueIds)
|
||||
? this.srsSession.initialDueIds.slice(0, MAX_DAILY_DUE)
|
||||
: [];
|
||||
// A resumed session can only count cards that were actually stored in
|
||||
// its batch. This prevents an empty prompt when an older client had
|
||||
// collapsed distinct SRS cards with identical wording.
|
||||
if (this.srsSession.initialTotalDue !== initialDueIds.length) {
|
||||
this.srsSession.initialTotalDue = initialDueIds.length;
|
||||
}
|
||||
const doneSet = new Set(Array.isArray(this.srsSession.doneIds) ? this.srsSession.doneIds : []);
|
||||
const finalReviewIds = Array.isArray(this.srsSession.finalReviewIds) ? this.srsSession.finalReviewIds : [];
|
||||
const finalReviewSet = new Set(finalReviewIds);
|
||||
@@ -813,12 +823,17 @@ export default {
|
||||
const learning = String(normalizedCandidate?.learning || '').trim();
|
||||
const reference = String(normalizedCandidate?.reference || '').trim();
|
||||
if (!this.isTrainablePair(learning, reference)) return null;
|
||||
const key = `${this.normalize(learning)}|${this.normalize(reference)}`;
|
||||
const pairKey = `${this.normalize(learning)}|${this.normalize(reference)}`;
|
||||
// In normal practice, identical pairs are duplicates. In SRS,
|
||||
// however, the same pair may intentionally occur in both
|
||||
// directions and is represented by separate item keys.
|
||||
const srsItemId = candidate?.id || candidate?.itemKey;
|
||||
const key = this.srsMode && srsItemId ? `srs:${srsItemId}` : pairKey;
|
||||
if (seen.has(key)) return null;
|
||||
seen.add(key);
|
||||
return {
|
||||
...normalizedCandidate,
|
||||
id: candidate?.id || candidate?.itemKey || candidate?.key || `${key}|${index}|${altIndex}`,
|
||||
id: srsItemId || candidate?.key || `${pairKey}|${index}|${altIndex}`,
|
||||
learning,
|
||||
reference
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user