@@ -62,6 +62,8 @@ interface StudySessionState {
6262 clearDeckSession : ( deckId : string ) => void ;
6363 /** v0.9.0: 获取当前会话及历史 goldenErrors */
6464 getGoldenErrors : ( ) => GoldenError [ ] ;
65+ /** CL-H5: 评分处理中标志——防双击/连点导致同一卡片被调度两次 */
66+ isRating : boolean ;
6567}
6668
6769// ---------------------------------------------------------------------------
@@ -113,6 +115,7 @@ export const useStudySessionStore = create<StudySessionState>((set, get) => {
113115 lastStabilityAfter : null ,
114116 lastRating : null ,
115117 showStrengthPulse : false ,
118+ isRating : false ,
116119
117120 // -----------------------------------------------------------------------
118121 // startSession:加载到期卡片 + 补充新卡(带每日限额)
@@ -213,17 +216,26 @@ export const useStudySessionStore = create<StudySessionState>((set, get) => {
213216 // rateCard:评分并推进到下一张
214217 // -----------------------------------------------------------------------
215218 rateCard : async ( rating , confidence ) => {
216- const { sessionCards, currentIndex, cardStartTime, isFlipped, goldenErrors } = get ( ) ;
219+ const { sessionCards, currentIndex, cardStartTime, isFlipped, goldenErrors, isRating } = get ( ) ;
217220
218221 // 必须已翻面才能评分
219222 if ( ! isFlipped ) return ;
220223
224+ // CL-H5: 防重入——rateCard 中有 await 落库窗口(isFlipped 尚未重置),
225+ // 快速双击/触屏误触会让同一卡片被调度两次(双条复习记录 + 重复调度)
226+ if ( isRating ) return ;
227+ set ( { isRating : true } ) ;
228+
221229 const card = sessionCards [ currentIndex ] ;
222- if ( ! card || card . id === undefined ) return ;
230+ if ( ! card || card . id === undefined ) {
231+ set ( { isRating : false } ) ;
232+ return ;
233+ }
223234
224- // 宪法第一条:复习行为=秩序波纹。任何评分都是一次对混沌的推退
225- // (包括 Again——唤醒本身即正向,零负向语义),经世界事件总线驱动深海场景
226- useWorldEvents . getState ( ) . emitOrderRipple ( 'flashcards' ) ;
235+ try {
236+ // 宪法第一条:复习行为=秩序波纹。任何评分都是一次对混沌的推退
237+ // (包括 Again——唤醒本身即正向,零负向语义),经世界事件总线驱动深海场景
238+ useWorldEvents . getState ( ) . emitOrderRipple ( 'flashcards' ) ;
227239
228240 // v0.9.0: goldenError 判定 — 高自信答错(Again)
229241 const isWrong = rating === Rating . Again ;
@@ -340,6 +352,7 @@ export const useStudySessionStore = create<StudySessionState>((set, get) => {
340352 isActive : false ,
341353 isFlipped : false ,
342354 cardStartTime : null ,
355+ isRating : false ,
343356 // v0.29: 记忆强度追踪
344357 lastStabilityBefore : card . stability ?? 0 ,
345358 lastStabilityAfter : result . stability ?? 0 ,
@@ -356,13 +369,18 @@ export const useStudySessionStore = create<StudySessionState>((set, get) => {
356369 goldenErrors : newGoldenErrors ,
357370 isFlipped : false ,
358371 cardStartTime : new Date ( ) ,
372+ isRating : false ,
359373 // v0.29: 记忆强度追踪
360374 lastStabilityBefore : card . stability ?? 0 ,
361375 lastStabilityAfter : result . stability ?? 0 ,
362376 lastRating : rating ,
363377 showStrengthPulse : true ,
364378 } ) ;
365379 }
380+ } finally {
381+ // 异常路径(落库失败)也必须释放锁,避免评分永久不可用
382+ set ( { isRating : false } ) ;
383+ }
366384 } ,
367385
368386 // -----------------------------------------------------------------------
0 commit comments