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.
|
// The original daily batch remains stable when the dialog is reopened.
|
||||||
// Keep in sync with VocabCourseView. v3 discards legacy empty sessions that
|
// Keep in sync with VocabCourseView. v3 discards legacy empty sessions that
|
||||||
// could block cards due later on the same calendar day.
|
// could block cards due later on the same calendar day.
|
||||||
// Keep this in sync with VocabCourseView. Version 4 discards stale empty
|
// Keep this in sync with VocabCourseView. Version 5 discards sessions that
|
||||||
// sessions that could mask a new daily SRS batch.
|
// counted cards removed by the client-side duplicate filter as still open.
|
||||||
const SRS_SESSION_STORAGE_VERSION = 4;
|
const SRS_SESSION_STORAGE_VERSION = 5;
|
||||||
const HARD_REQUIRED_CONSECUTIVE_CORRECT = 5;
|
const HARD_REQUIRED_CONSECUTIVE_CORRECT = 5;
|
||||||
const MAX_DAILY_DUE = 50;
|
const MAX_DAILY_DUE = 50;
|
||||||
const MAX_DAILY_HARD_VOCABS = 7;
|
const MAX_DAILY_HARD_VOCABS = 7;
|
||||||
@@ -517,7 +517,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.srsSession) {
|
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 = {
|
this.srsSession = {
|
||||||
version: SRS_SESSION_STORAGE_VERSION,
|
version: SRS_SESSION_STORAGE_VERSION,
|
||||||
dateKey: this.getLocalDateKey(),
|
dateKey: this.getLocalDateKey(),
|
||||||
@@ -551,6 +555,12 @@ export default {
|
|||||||
const initialDueIds = Array.isArray(this.srsSession.initialDueIds)
|
const initialDueIds = Array.isArray(this.srsSession.initialDueIds)
|
||||||
? this.srsSession.initialDueIds.slice(0, MAX_DAILY_DUE)
|
? 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 doneSet = new Set(Array.isArray(this.srsSession.doneIds) ? this.srsSession.doneIds : []);
|
||||||
const finalReviewIds = Array.isArray(this.srsSession.finalReviewIds) ? this.srsSession.finalReviewIds : [];
|
const finalReviewIds = Array.isArray(this.srsSession.finalReviewIds) ? this.srsSession.finalReviewIds : [];
|
||||||
const finalReviewSet = new Set(finalReviewIds);
|
const finalReviewSet = new Set(finalReviewIds);
|
||||||
@@ -813,12 +823,17 @@ export default {
|
|||||||
const learning = String(normalizedCandidate?.learning || '').trim();
|
const learning = String(normalizedCandidate?.learning || '').trim();
|
||||||
const reference = String(normalizedCandidate?.reference || '').trim();
|
const reference = String(normalizedCandidate?.reference || '').trim();
|
||||||
if (!this.isTrainablePair(learning, reference)) return null;
|
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;
|
if (seen.has(key)) return null;
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
return {
|
return {
|
||||||
...normalizedCandidate,
|
...normalizedCandidate,
|
||||||
id: candidate?.id || candidate?.itemKey || candidate?.key || `${key}|${index}|${altIndex}`,
|
id: srsItemId || candidate?.key || `${pairKey}|${index}|${altIndex}`,
|
||||||
learning,
|
learning,
|
||||||
reference
|
reference
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user