diff --git a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue index 754b626..5dd57f7 100755 --- a/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue +++ b/frontend/src/dialogues/socialnetwork/VocabPracticeDialog.vue @@ -270,10 +270,15 @@ export default { return this.pool.filter((item) => this.isItemMarkedHard(item)); }, hardRemainingCount() { - return this.hardPoolItems.filter((item) => { - const key = this.getHardKey(item); - return (Number(this.hardMasteryByKey[key]) || 0) < HARD_REQUIRED_CONSECUTIVE_CORRECT; - }).length; + // A displayed card can be an expanded answer variant of one stored hard + // item (e.g. "A / B"). Count and track the stored hard item once, not + // every generated variant separately. + const hardKeys = new Set(this.hardPoolItems + .map((item) => this.getHardMasteryKey(item)) + .filter(Boolean)); + return Array.from(hardKeys) + .filter((key) => (Number(this.hardMasteryByKey[key]) || 0) < HARD_REQUIRED_CONSECUTIVE_CORRECT) + .length; } }, methods: { @@ -346,6 +351,11 @@ export default { isItemMarkedHard(item) { return Boolean(this.findMatchingHardKey(item)); }, + getHardMasteryKey(item) { + // Prefer the key of the persisted SRS hard item. This keeps all + // acceptable variants of the same vocabulary pair on one mastery count. + return this.findMatchingHardKey(item) || this.getHardKey(item); + }, removeHardEntriesForItem(item) { const itemLearning = this.normalize(item?.learning || ''); const itemReference = this.normalize(item?.reference || ''); @@ -593,6 +603,13 @@ export default { this.hardVocabMap[key] = { learning: item.learning, reference: item.reference, itemKey: item.itemKey || item.id || null }; }); } + // This dialog is explicitly opened for the hard-vocabulary collection. + // Its correct answers must therefore count towards releasing those items + // from the very first question, rather than only after a full warm-up + // round through the same collection. + if (this.closeOnHardCompletion && this.hardPoolItems.length > 0) { + this.hardPhaseActive = true; + } if (this.maybeStartDailyHardPractice()) return; this.reloadPool(); }, @@ -1049,7 +1066,7 @@ export default { } if (this.hardPhaseActive) { const hardItems = this.hardPoolItems.filter((item) => { - const key = this.getHardKey(item); + const key = this.getHardMasteryKey(item); return (Number(this.hardMasteryByKey[key]) || 0) < HARD_REQUIRED_CONSECUTIVE_CORRECT; }); if (hardItems.length === 0) { @@ -1058,7 +1075,7 @@ export default { } const rankedHard = hardItems .map((item) => { - const key = this.getHardKey(item); + const key = this.getHardMasteryKey(item); const mastery = Number(this.hardMasteryByKey[key]) || 0; const st = this.perId[item.id] || { c: 0, w: 0, streak: 0, lastAsked: 0 }; return { @@ -1204,7 +1221,7 @@ export default { st.streak = st.streak >= 0 ? st.streak + 1 : 1; } if (this.hardPhaseActive && this.current) { - const key = this.getHardKey(this.current); + const key = this.getHardMasteryKey(this.current); this.hardMasteryByKey[key] = Math.max(0, Number(this.hardMasteryByKey[key]) || 0) + 1; const mapKey = this.findMatchingHardKey(this.current); if ((Number(this.hardMasteryByKey[key]) || 0) >= HARD_REQUIRED_CONSECUTIVE_CORRECT && mapKey) { @@ -1222,7 +1239,7 @@ export default { st.w += 1; st.streak = st.streak <= 0 ? st.streak - 1 : -1; if (this.hardPhaseActive && this.current) { - const key = this.getHardKey(this.current); + const key = this.getHardMasteryKey(this.current); this.hardMasteryByKey[key] = 0; } }