[{"data":1,"prerenderedAt":1615},["ShallowReactive",2],{"content-/quiz-streak-filter-design":3,"all-pages-for-dir":1613,"og-image-/quiz-streak-filter-design":1614},{"id":4,"title":5,"body":6,"category":1595,"description":1596,"extension":1597,"meta":1598,"navigation":132,"ogImage":1599,"path":1600,"project_name":1601,"published":1602,"publishedAt":1603,"seo":1604,"stem":1605,"tags":1606,"todo":1611,"unpublished":1602,"updatedAt":1599,"__hash__":1612},"pages/2026-05-15/quiz-streak-filter-design.mdx","クイズの復習フィルタを「累計誤答」から「直近連続誤答」に変えた話",{"type":7,"value":8,"toc":1582},"minimark",[9,13,17,20,24,40,48,310,317,573,576,580,583,851,854,858,872,875,886,892,895,899,902,1212,1215,1218,1246,1249,1359,1364,1370,1373,1379,1420,1426,1429,1432,1522,1525,1528,1547,1550,1553,1575,1578],[10,11,5],"h1",{"id":12},"クイズの復習フィルタを累計誤答から直近連続誤答に変えた話",[14,15,16],"p",{},"都道府県の位置を当てる地図クイズで、「過去3回の解答履歴を localStorage に残して、間違った県だけ復習できるようにする」機能を入れた。最初は素直に「累計誤答回数」でフィルタしていたが、すぐに学習体験として詰まることに気づいて、「直近で何連続 × か」でフィルタする形に直した。",[14,18,19],{},"仕分けクイズなど他の暗記系コンテンツでも同じ判定パターンを使い回したいので、ビフォー／アフター・なぜ直したのかをまとめておく。",[21,22,23],"h2",{"id":23},"やりたかったこと",[25,26,27,31,34,37],"ul",{},[28,29,30],"li",{},"47県のうち、間違いやすい県だけ繰り返し復習したい",[28,32,33],{},"過去の正誤履歴はローカルに残し、画面で一望できるようにしたい",[28,35,36],{},"1問につき「正解 or その問題内で1度でも誤答したら ×」の1試行として記録",[28,38,39],{},"履歴は直近3回ぶんで十分",[14,41,42,43,47],{},"直近3回の履歴を ",[44,45,46],"code",{},"[newest, mid, oldest]"," の順に localStorage に保存する。",[49,50,55],"pre",{"className":51,"code":52,"language":53,"meta":54,"style":54},"language-typescript shiki shiki-themes vitesse-light vitesse-light","type Result = 'o' | 'x'\ntype HistoryMap = Record\u003Cstring, Result[]> // prefId -> 直近3件、先頭が最新\n\nconst addHistoryResult = (prefId: string, result: Result) => {\n  const cur = history.value[prefId] ?? []\n  const next = [result, ...cur].slice(0, 3)\n  history.value = { ...history.value, [prefId]: next }\n  writeHistory(history.value)\n}\n","typescript","",[44,56,57,96,127,134,177,211,255,288,304],{"__ignoreMap":54},[58,59,62,66,70,74,78,82,85,88,90,93],"span",{"class":60,"line":61},"line",1,[58,63,65],{"class":64},"stQ0i","type",[58,67,69],{"class":68},"sSkh3"," Result",[58,71,73],{"class":72},"shFtX"," =",[58,75,77],{"class":76},"sMJiu"," '",[58,79,81],{"class":80},"sdGka","o",[58,83,84],{"class":76},"'",[58,86,87],{"class":72}," |",[58,89,77],{"class":76},[58,91,92],{"class":80},"x",[58,94,95],{"class":76},"'\n",[58,97,99,101,104,106,109,112,115,118,120,123],{"class":60,"line":98},2,[58,100,65],{"class":64},[58,102,103],{"class":68}," HistoryMap",[58,105,73],{"class":72},[58,107,108],{"class":68}," Record",[58,110,111],{"class":72},"\u003C",[58,113,114],{"class":68},"string",[58,116,117],{"class":72},",",[58,119,69],{"class":68},[58,121,122],{"class":72},"[]>",[58,124,126],{"class":125},"sxvE3"," // prefId -> 直近3件、先頭が最新\n",[58,128,130],{"class":60,"line":129},3,[58,131,133],{"emptyLinePlaceholder":132},true,"\n",[58,135,137,140,144,146,149,153,156,158,160,163,165,168,171,174],{"class":60,"line":136},4,[58,138,139],{"class":64},"const ",[58,141,143],{"class":142},"senZ8","addHistoryResult",[58,145,73],{"class":72},[58,147,148],{"class":72}," (",[58,150,152],{"class":151},"s4oTP","prefId",[58,154,155],{"class":72},": ",[58,157,114],{"class":68},[58,159,117],{"class":72},[58,161,162],{"class":151}," result",[58,164,155],{"class":72},[58,166,167],{"class":68},"Result",[58,169,170],{"class":72},")",[58,172,173],{"class":72}," =>",[58,175,176],{"class":72}," {\n",[58,178,180,183,186,188,191,194,197,200,202,205,208],{"class":60,"line":179},5,[58,181,182],{"class":64},"  const ",[58,184,185],{"class":151},"cur",[58,187,73],{"class":72},[58,189,190],{"class":151}," history",[58,192,193],{"class":72},".",[58,195,196],{"class":151},"value",[58,198,199],{"class":72},"[",[58,201,152],{"class":151},[58,203,204],{"class":72},"]",[58,206,207],{"class":64}," ?? ",[58,209,210],{"class":72},"[]\n",[58,212,214,216,219,221,224,227,229,232,234,237,240,243,247,249,252],{"class":60,"line":213},6,[58,215,182],{"class":64},[58,217,218],{"class":151},"next",[58,220,73],{"class":72},[58,222,223],{"class":72}," [",[58,225,226],{"class":151},"result",[58,228,117],{"class":72},[58,230,231],{"class":72}," ...",[58,233,185],{"class":151},[58,235,236],{"class":72},"].",[58,238,239],{"class":142},"slice",[58,241,242],{"class":72},"(",[58,244,246],{"class":245},"sM54T","0",[58,248,117],{"class":72},[58,250,251],{"class":245}," 3",[58,253,254],{"class":72},")\n",[58,256,258,261,263,265,268,271,273,275,278,280,283,285],{"class":60,"line":257},7,[58,259,260],{"class":151},"  history",[58,262,193],{"class":72},[58,264,196],{"class":151},[58,266,267],{"class":72}," = { ...",[58,269,270],{"class":151},"history",[58,272,193],{"class":72},[58,274,196],{"class":151},[58,276,277],{"class":72},", [",[58,279,152],{"class":151},[58,281,282],{"class":72},"]: ",[58,284,218],{"class":151},[58,286,287],{"class":72}," }\n",[58,289,291,294,296,298,300,302],{"class":60,"line":290},8,[58,292,293],{"class":142},"  writeHistory",[58,295,242],{"class":72},[58,297,270],{"class":151},[58,299,193],{"class":72},[58,301,196],{"class":151},[58,303,254],{"class":72},[58,305,307],{"class":60,"line":306},9,[58,308,309],{"class":72},"}\n",[14,311,312,313,316],{},"表示は「右が新しい」順にしたいので、画面側は ",[44,314,315],{},"reverse"," 相当に並べ替える。",[49,318,320],{"className":51,"code":319,"language":53,"meta":54,"style":54},"const displayMarks = (prefId: string): Array\u003C'o' | 'x' | 'empty'> => {\n  const arr = history.value[prefId] ?? []\n  const padded: Array\u003C'o' | 'x' | 'empty'> = ['empty', 'empty', 'empty']\n  for (let i = 0; i \u003C arr.length && i \u003C 3; i++) {\n    padded[2 - i] = arr[i]!\n  }\n  return padded\n}\n",[44,321,322,377,402,469,526,556,561,569],{"__ignoreMap":54},[58,323,324,326,329,331,333,335,337,339,342,345,347,349,351,353,355,357,359,361,363,365,368,370,373,375],{"class":60,"line":61},[58,325,139],{"class":64},[58,327,328],{"class":142},"displayMarks",[58,330,73],{"class":72},[58,332,148],{"class":72},[58,334,152],{"class":151},[58,336,155],{"class":72},[58,338,114],{"class":68},[58,340,341],{"class":72},"):",[58,343,344],{"class":68}," Array",[58,346,111],{"class":72},[58,348,84],{"class":76},[58,350,81],{"class":80},[58,352,84],{"class":76},[58,354,87],{"class":72},[58,356,77],{"class":76},[58,358,92],{"class":80},[58,360,84],{"class":76},[58,362,87],{"class":72},[58,364,77],{"class":76},[58,366,367],{"class":80},"empty",[58,369,84],{"class":76},[58,371,372],{"class":72},">",[58,374,173],{"class":72},[58,376,176],{"class":72},[58,378,379,381,384,386,388,390,392,394,396,398,400],{"class":60,"line":98},[58,380,182],{"class":64},[58,382,383],{"class":151},"arr",[58,385,73],{"class":72},[58,387,190],{"class":151},[58,389,193],{"class":72},[58,391,196],{"class":151},[58,393,199],{"class":72},[58,395,152],{"class":151},[58,397,204],{"class":72},[58,399,207],{"class":64},[58,401,210],{"class":72},[58,403,404,406,409,411,414,416,418,420,422,425,427,429,431,433,435,437,439,442,444,446,448,450,452,454,456,458,460,462,464,466],{"class":60,"line":129},[58,405,182],{"class":64},[58,407,408],{"class":151},"padded",[58,410,155],{"class":72},[58,412,413],{"class":68},"Array",[58,415,111],{"class":72},[58,417,84],{"class":76},[58,419,81],{"class":80},[58,421,84],{"class":76},[58,423,424],{"class":72}," | ",[58,426,84],{"class":76},[58,428,92],{"class":80},[58,430,84],{"class":76},[58,432,424],{"class":72},[58,434,84],{"class":76},[58,436,367],{"class":80},[58,438,84],{"class":76},[58,440,441],{"class":72},"> =",[58,443,223],{"class":72},[58,445,84],{"class":76},[58,447,367],{"class":80},[58,449,84],{"class":76},[58,451,117],{"class":72},[58,453,77],{"class":76},[58,455,367],{"class":80},[58,457,84],{"class":76},[58,459,117],{"class":72},[58,461,77],{"class":76},[58,463,367],{"class":80},[58,465,84],{"class":76},[58,467,468],{"class":72},"]\n",[58,470,471,475,477,480,483,485,488,491,494,497,500,502,506,509,511,513,515,517,519,522,524],{"class":60,"line":136},[58,472,474],{"class":473},"sHkkW","  for",[58,476,148],{"class":72},[58,478,479],{"class":64},"let ",[58,481,482],{"class":151},"i",[58,484,73],{"class":72},[58,486,487],{"class":245}," 0",[58,489,490],{"class":72},";",[58,492,493],{"class":151}," i",[58,495,496],{"class":72}," \u003C",[58,498,499],{"class":151}," arr",[58,501,193],{"class":72},[58,503,505],{"class":504},"sz8Xr","length",[58,507,508],{"class":64}," && ",[58,510,482],{"class":151},[58,512,496],{"class":72},[58,514,251],{"class":245},[58,516,490],{"class":72},[58,518,493],{"class":151},[58,520,521],{"class":64},"++",[58,523,170],{"class":72},[58,525,176],{"class":72},[58,527,528,531,533,536,539,541,543,545,547,549,551,553],{"class":60,"line":179},[58,529,530],{"class":151},"    padded",[58,532,199],{"class":72},[58,534,535],{"class":245},"2",[58,537,538],{"class":64}," - ",[58,540,482],{"class":151},[58,542,204],{"class":72},[58,544,73],{"class":72},[58,546,499],{"class":151},[58,548,199],{"class":72},[58,550,482],{"class":151},[58,552,204],{"class":72},[58,554,555],{"class":64},"!\n",[58,557,558],{"class":60,"line":213},[58,559,560],{"class":72},"  }\n",[58,562,563,566],{"class":60,"line":257},[58,564,565],{"class":473},"  return",[58,567,568],{"class":151}," padded\n",[58,570,571],{"class":60,"line":290},[58,572,309],{"class":72},[14,574,575],{},"ここまでは特に迷うところがない。問題はフィルタの定義の方だった。",[21,577,579],{"id":578},"ビフォー累計誤答回数でフィルタ","ビフォー：累計誤答回数でフィルタ",[14,581,582],{},"最初に作ったときは、こう書いていた。",[49,584,586],{"className":51,"code":585,"language":53,"meta":54,"style":54},"// 過去3回の中で × が何回あったか\nconst countWrongInHistory = (h: HistoryMap, prefId: string): number =>\n  (h[prefId] ?? []).filter((r) => r === 'x').length\n\nconst selectByFilter = (h: HistoryMap, f: RangeFilter): Prefecture[] => {\n  if (f === 'all') return [...allPrefectures]\n  const min = f === 'wrong1' ? 1 : f === 'wrong2' ? 2 : 3\n  return allPrefectures.filter((p) => countWrongInHistory(h, p.prefId) >= min)\n}\n",[44,587,588,593,629,678,682,721,753,802,847],{"__ignoreMap":54},[58,589,590],{"class":60,"line":61},[58,591,592],{"class":125},"// 過去3回の中で × が何回あったか\n",[58,594,595,597,600,602,604,607,609,612,614,617,619,621,623,626],{"class":60,"line":98},[58,596,139],{"class":64},[58,598,599],{"class":142},"countWrongInHistory",[58,601,73],{"class":72},[58,603,148],{"class":72},[58,605,606],{"class":151},"h",[58,608,155],{"class":72},[58,610,611],{"class":68},"HistoryMap",[58,613,117],{"class":72},[58,615,616],{"class":151}," prefId",[58,618,155],{"class":72},[58,620,114],{"class":68},[58,622,341],{"class":72},[58,624,625],{"class":68}," number",[58,627,628],{"class":72}," =>\n",[58,630,631,634,636,638,640,642,644,647,650,653,656,658,660,663,666,668,670,672,675],{"class":60,"line":129},[58,632,633],{"class":72},"  (",[58,635,606],{"class":151},[58,637,199],{"class":72},[58,639,152],{"class":151},[58,641,204],{"class":72},[58,643,207],{"class":64},[58,645,646],{"class":72},"[]).",[58,648,649],{"class":142},"filter",[58,651,652],{"class":72},"((",[58,654,655],{"class":151},"r",[58,657,170],{"class":72},[58,659,173],{"class":72},[58,661,662],{"class":151}," r",[58,664,665],{"class":64}," === ",[58,667,84],{"class":76},[58,669,92],{"class":80},[58,671,84],{"class":76},[58,673,674],{"class":72},").",[58,676,677],{"class":504},"length\n",[58,679,680],{"class":60,"line":136},[58,681,133],{"emptyLinePlaceholder":132},[58,683,684,686,689,691,693,695,697,699,701,704,706,709,711,714,717,719],{"class":60,"line":179},[58,685,139],{"class":64},[58,687,688],{"class":142},"selectByFilter",[58,690,73],{"class":72},[58,692,148],{"class":72},[58,694,606],{"class":151},[58,696,155],{"class":72},[58,698,611],{"class":68},[58,700,117],{"class":72},[58,702,703],{"class":151}," f",[58,705,155],{"class":72},[58,707,708],{"class":68},"RangeFilter",[58,710,341],{"class":72},[58,712,713],{"class":68}," Prefecture",[58,715,716],{"class":72},"[]",[58,718,173],{"class":72},[58,720,176],{"class":72},[58,722,723,726,728,731,733,735,738,740,742,745,748,751],{"class":60,"line":213},[58,724,725],{"class":473},"  if",[58,727,148],{"class":72},[58,729,730],{"class":151},"f",[58,732,665],{"class":64},[58,734,84],{"class":76},[58,736,737],{"class":80},"all",[58,739,84],{"class":76},[58,741,170],{"class":72},[58,743,744],{"class":473}," return",[58,746,747],{"class":72}," [...",[58,749,750],{"class":151},"allPrefectures",[58,752,468],{"class":72},[58,754,755,757,760,762,764,766,768,771,773,776,779,782,784,786,788,791,793,795,797,799],{"class":60,"line":257},[58,756,182],{"class":64},[58,758,759],{"class":151},"min",[58,761,73],{"class":72},[58,763,703],{"class":151},[58,765,665],{"class":64},[58,767,84],{"class":76},[58,769,770],{"class":80},"wrong1",[58,772,84],{"class":76},[58,774,775],{"class":64}," ? ",[58,777,778],{"class":245},"1",[58,780,781],{"class":64}," : ",[58,783,730],{"class":151},[58,785,665],{"class":64},[58,787,84],{"class":76},[58,789,790],{"class":80},"wrong2",[58,792,84],{"class":76},[58,794,775],{"class":64},[58,796,535],{"class":245},[58,798,781],{"class":64},[58,800,801],{"class":245},"3\n",[58,803,804,806,809,811,813,815,817,819,821,824,826,828,830,833,835,837,839,842,845],{"class":60,"line":290},[58,805,565],{"class":473},[58,807,808],{"class":151}," allPrefectures",[58,810,193],{"class":72},[58,812,649],{"class":142},[58,814,652],{"class":72},[58,816,14],{"class":151},[58,818,170],{"class":72},[58,820,173],{"class":72},[58,822,823],{"class":142}," countWrongInHistory",[58,825,242],{"class":72},[58,827,606],{"class":151},[58,829,117],{"class":72},[58,831,832],{"class":151}," p",[58,834,193],{"class":72},[58,836,152],{"class":151},[58,838,170],{"class":72},[58,840,841],{"class":72}," >=",[58,843,844],{"class":151}," min",[58,846,254],{"class":72},[58,848,849],{"class":60,"line":306},[58,850,309],{"class":72},[14,852,853],{},"UI ボタンも「≧1誤 / ≧2誤 / 3誤」で、シンプルに「過去3回のうち何回以上間違えたか」で絞る形。",[855,856,857],"h3",{"id":857},"何が困ったか",[14,859,860,863,864,867,868,871],{},[44,861,862],{},"≧1誤"," を選ぶと、「1回間違えて、その後2回連続で正解した」県もずっと出題対象に残り続ける。3回目までは履歴が ",[44,865,866],{},"[o, o, x]"," のように残るので、",[44,869,870],{},"× が 1回以上ある"," を満たしてしまう。",[14,873,874],{},"学習者の感覚としては、",[25,876,877,880,883],{},[28,878,879],{},"1回間違えた",[28,881,882],{},"次に正解した",[28,884,885],{},"もう一度正解した",[14,887,888,889,891],{},"の時点で「もうこの県は覚えた」と判定したい。なのに ",[44,890,862],{}," のままだと一向に対象から外れない。結果、出題リストが減らず、復習モードに入ったメリットが薄い。",[14,893,894],{},"「卒業条件」が暗黙にも明示にも定義されていない、というのが本質的な問題だった。",[21,896,898],{"id":897},"アフター直近の連続誤答数でフィルタ","アフター：直近の連続誤答数でフィルタ",[14,900,901],{},"「直近から連続して × が続いている数」をカウントし、その連続数でフィルタするように直した。",[49,903,905],{"className":51,"code":904,"language":53,"meta":54,"style":54},"// 直近から連続している × の数（一度でも ○ が入れば 0 にリセット）\n// 履歴配列は [newest, mid, oldest] の順で保存している\nconst countRecentWrongStreak = (h: HistoryMap, prefId: string): number => {\n  const arr = h[prefId] ?? []\n  let streak = 0\n  for (const r of arr) {\n    if (r === 'x') streak++\n    else break\n  }\n  return streak\n}\n\nconst selectByFilter = (h: HistoryMap, f: RangeFilter): Prefecture[] => {\n  if (f === 'all') return [...allPrefectures]\n  const min = f === 'streak1' ? 1 : f === 'streak2' ? 2 : 3\n  return allPrefectures.filter((p) => countRecentWrongStreak(h, p.prefId) >= min)\n}\n",[44,906,907,912,917,950,971,984,1003,1028,1036,1040,1048,1053,1058,1093,1120,1165,1207],{"__ignoreMap":54},[58,908,909],{"class":60,"line":61},[58,910,911],{"class":125},"// 直近から連続している × の数（一度でも ○ が入れば 0 にリセット）\n",[58,913,914],{"class":60,"line":98},[58,915,916],{"class":125},"// 履歴配列は [newest, mid, oldest] の順で保存している\n",[58,918,919,921,924,926,928,930,932,934,936,938,940,942,944,946,948],{"class":60,"line":129},[58,920,139],{"class":64},[58,922,923],{"class":142},"countRecentWrongStreak",[58,925,73],{"class":72},[58,927,148],{"class":72},[58,929,606],{"class":151},[58,931,155],{"class":72},[58,933,611],{"class":68},[58,935,117],{"class":72},[58,937,616],{"class":151},[58,939,155],{"class":72},[58,941,114],{"class":68},[58,943,341],{"class":72},[58,945,625],{"class":68},[58,947,173],{"class":72},[58,949,176],{"class":72},[58,951,952,954,956,958,961,963,965,967,969],{"class":60,"line":136},[58,953,182],{"class":64},[58,955,383],{"class":151},[58,957,73],{"class":72},[58,959,960],{"class":151}," h",[58,962,199],{"class":72},[58,964,152],{"class":151},[58,966,204],{"class":72},[58,968,207],{"class":64},[58,970,210],{"class":72},[58,972,973,976,979,981],{"class":60,"line":179},[58,974,975],{"class":64},"  let ",[58,977,978],{"class":151},"streak",[58,980,73],{"class":72},[58,982,983],{"class":245}," 0\n",[58,985,986,988,990,992,994,997,999,1001],{"class":60,"line":213},[58,987,474],{"class":473},[58,989,148],{"class":72},[58,991,139],{"class":64},[58,993,655],{"class":151},[58,995,996],{"class":64}," of ",[58,998,383],{"class":151},[58,1000,170],{"class":72},[58,1002,176],{"class":72},[58,1004,1005,1008,1010,1012,1014,1016,1018,1020,1022,1025],{"class":60,"line":257},[58,1006,1007],{"class":473},"    if",[58,1009,148],{"class":72},[58,1011,655],{"class":151},[58,1013,665],{"class":64},[58,1015,84],{"class":76},[58,1017,92],{"class":80},[58,1019,84],{"class":76},[58,1021,170],{"class":72},[58,1023,1024],{"class":151}," streak",[58,1026,1027],{"class":64},"++\n",[58,1029,1030,1033],{"class":60,"line":290},[58,1031,1032],{"class":473},"    else",[58,1034,1035],{"class":473}," break\n",[58,1037,1038],{"class":60,"line":306},[58,1039,560],{"class":72},[58,1041,1043,1045],{"class":60,"line":1042},10,[58,1044,565],{"class":473},[58,1046,1047],{"class":151}," streak\n",[58,1049,1051],{"class":60,"line":1050},11,[58,1052,309],{"class":72},[58,1054,1056],{"class":60,"line":1055},12,[58,1057,133],{"emptyLinePlaceholder":132},[58,1059,1061,1063,1065,1067,1069,1071,1073,1075,1077,1079,1081,1083,1085,1087,1089,1091],{"class":60,"line":1060},13,[58,1062,139],{"class":64},[58,1064,688],{"class":142},[58,1066,73],{"class":72},[58,1068,148],{"class":72},[58,1070,606],{"class":151},[58,1072,155],{"class":72},[58,1074,611],{"class":68},[58,1076,117],{"class":72},[58,1078,703],{"class":151},[58,1080,155],{"class":72},[58,1082,708],{"class":68},[58,1084,341],{"class":72},[58,1086,713],{"class":68},[58,1088,716],{"class":72},[58,1090,173],{"class":72},[58,1092,176],{"class":72},[58,1094,1096,1098,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118],{"class":60,"line":1095},14,[58,1097,725],{"class":473},[58,1099,148],{"class":72},[58,1101,730],{"class":151},[58,1103,665],{"class":64},[58,1105,84],{"class":76},[58,1107,737],{"class":80},[58,1109,84],{"class":76},[58,1111,170],{"class":72},[58,1113,744],{"class":473},[58,1115,747],{"class":72},[58,1117,750],{"class":151},[58,1119,468],{"class":72},[58,1121,1123,1125,1127,1129,1131,1133,1135,1138,1140,1142,1144,1146,1148,1150,1152,1155,1157,1159,1161,1163],{"class":60,"line":1122},15,[58,1124,182],{"class":64},[58,1126,759],{"class":151},[58,1128,73],{"class":72},[58,1130,703],{"class":151},[58,1132,665],{"class":64},[58,1134,84],{"class":76},[58,1136,1137],{"class":80},"streak1",[58,1139,84],{"class":76},[58,1141,775],{"class":64},[58,1143,778],{"class":245},[58,1145,781],{"class":64},[58,1147,730],{"class":151},[58,1149,665],{"class":64},[58,1151,84],{"class":76},[58,1153,1154],{"class":80},"streak2",[58,1156,84],{"class":76},[58,1158,775],{"class":64},[58,1160,535],{"class":245},[58,1162,781],{"class":64},[58,1164,801],{"class":245},[58,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1187,1189,1191,1193,1195,1197,1199,1201,1203,1205],{"class":60,"line":1167},16,[58,1169,565],{"class":473},[58,1171,808],{"class":151},[58,1173,193],{"class":72},[58,1175,649],{"class":142},[58,1177,652],{"class":72},[58,1179,14],{"class":151},[58,1181,170],{"class":72},[58,1183,173],{"class":72},[58,1185,1186],{"class":142}," countRecentWrongStreak",[58,1188,242],{"class":72},[58,1190,606],{"class":151},[58,1192,117],{"class":72},[58,1194,832],{"class":151},[58,1196,193],{"class":72},[58,1198,152],{"class":151},[58,1200,170],{"class":72},[58,1202,841],{"class":72},[58,1204,844],{"class":151},[58,1206,254],{"class":72},[58,1208,1210],{"class":60,"line":1209},17,[58,1211,309],{"class":72},[14,1213,1214],{},"UI ボタンは「直近× / 2連× / 3連×」に変えた。",[855,1216,1217],{"id":1217},"この設計の良いところ",[25,1219,1220,1230,1240],{},[28,1221,1222,1226,1227,1229],{},[1223,1224,1225],"strong",{},"「1回正解できれば卒業」が暗黙に定義される","：直近1回でも ",[44,1228,81],{}," を出したら streak がリセットされてフィルタ対象から外れる",[28,1231,1232,1235,1236,1239],{},[1223,1233,1234],{},"失敗が積み上がる感覚","：連続して間違えるほど streak が伸び、表示の ",[44,1237,1238],{},"× × ×"," がそのまま視覚的なヤバさになる",[28,1241,1242,1245],{},[1223,1243,1244],{},"「過去にやらかしたが今はできる」と「今もできない」を分けられる","：累計だと両者が混じるが、直近連続だけ見れば「現状」の弱点が出る",[14,1247,1248],{},"例：",[1250,1251,1252,1274],"table",{},[1253,1254,1255],"thead",{},[1256,1257,1258,1262,1265,1268,1271],"tr",{},[1259,1260,1261],"th",{},"履歴（新→古）",[1259,1263,1264],{},"累計×",[1259,1266,1267],{},"直近連続×",[1259,1269,1270],{},"旧フィルタ ≧1誤",[1259,1272,1273],{},"新フィルタ 直近×",[1275,1276,1277,1294,1311,1327,1343],"tbody",{},[1256,1278,1279,1285,1287,1289,1292],{},[1280,1281,1282],"td",{},[44,1283,1284],{},"o o o",[1280,1286,246],{},[1280,1288,246],{},[1280,1290,1291],{},"対象外",[1280,1293,1291],{},[1256,1295,1296,1301,1303,1305,1308],{},[1280,1297,1298],{},[44,1299,1300],{},"o x x",[1280,1302,535],{},[1280,1304,246],{},[1280,1306,1307],{},"対象 ❌",[1280,1309,1310],{},"対象外 ✅",[1256,1312,1313,1318,1320,1322,1324],{},[1280,1314,1315],{},[44,1316,1317],{},"x o x",[1280,1319,535],{},[1280,1321,778],{},[1280,1323,1307],{},[1280,1325,1326],{},"対象",[1256,1328,1329,1334,1336,1338,1340],{},[1280,1330,1331],{},[44,1332,1333],{},"x x o",[1280,1335,535],{},[1280,1337,535],{},[1280,1339,1326],{},[1280,1341,1342],{},"対象（2連でも対象）",[1256,1344,1345,1350,1353,1355,1357],{},[1280,1346,1347],{},[44,1348,1349],{},"x x x",[1280,1351,1352],{},"3",[1280,1354,1352],{},[1280,1356,1326],{},[1280,1358,1326],{},[14,1360,1361,1363],{},[44,1362,1300],{}," が旧フィルタでは「対象」になっていたのが、新フィルタでは「対象外」になる。これがちゃんと「卒業」できる形。",[14,1365,1366,1367,1369],{},"逆に ",[44,1368,1317],{}," は「最近やらかしたが、その前は1回できていた」状態で、まだ油断できない。新フィルタでは直近1連×として残る。意図通り。",[21,1371,1372],{"id":1372},"どこをいじったか",[14,1374,1375,1378],{},[44,1376,1377],{},"apps/web/app/pages/prefecture-quiz/find-prefecture.vue"," の以下を差し替え：",[25,1380,1381,1393,1400,1409],{},[28,1382,1383,1385,1386,1389,1390],{},[44,1384,708],{}," の型: ",[44,1387,1388],{},"'wrong1' | 'wrong2' | 'wrong3'"," → ",[44,1391,1392],{},"'streak1' | 'streak2' | 'streak3'",[28,1394,1395,1396,1389,1398],{},"判定関数: ",[44,1397,599],{},[44,1399,923],{},[28,1401,1402,1403,1389,1406],{},"UI ラベル: ",[44,1404,1405],{},"'≧1誤' / '≧2誤' / '3誤'",[44,1407,1408],{},"'直近×' / '2連×' / '3連×'",[28,1410,1411,1412,1415,1416,1419],{},"localStorage キー (",[44,1413,1414],{},"prefectureQuiz:find-prefecture:filter",") の値は古い値が読まれたら ",[44,1417,1418],{},"'all'"," にフォールバック",[14,1421,1422,1423,1425],{},"履歴の表示そのもの（右が新しい ",[44,1424,1300],{}," 風の3マス）は変えていない。フィルタの解釈だけを差し替えた。",[21,1427,1428],{"id":1428},"他のクイズに使い回すときのチェックリスト",[14,1430,1431],{},"同じ「直近連続誤答」パターンを別のクイズに移植するとき、最低限これだけ揃えれば動く。",[25,1433,1436,1452,1458,1467,1481,1487,1493,1502,1508,1514],{"className":1434},[1435],"contains-task-list",[28,1437,1440,1444,1445,1447,1448,1451],{"className":1438},[1439],"task-list-item",[1441,1442],"input",{"disabled":132,"type":1443},"checkbox"," 履歴を ",[44,1446,46],{}," の順で保存する（先頭に ",[44,1449,1450],{},"unshift"," 相当、長さ3でカット）",[28,1453,1455,1457],{"className":1454},[1439],[1441,1456],{"disabled":132,"type":1443}," 1問につき1試行ぶんを記録する（その問題内で何回誤答しても × は1つ）",[28,1459,1461,1463,1464,1466],{"className":1460},[1439],[1441,1462],{"disabled":132,"type":1443}," 「直近で連続している × の数」を数える関数を1つ用意する（先頭から ",[44,1465,92],{}," が続く長さを返すだけ）",[28,1468,1470,1472,1473,1476,1477,1480],{"className":1469},[1439],[1441,1471],{"disabled":132,"type":1443}," フィルタは ",[44,1474,1475],{},"streak >= 1 / 2 / 3"," の3段階で十分。",[44,1478,1479],{},">="," にしておくと「2連× を選ぶと 3連× も含まれる」自然な挙動になる",[28,1482,1484,1486],{"className":1483},[1439],[1441,1485],{"disabled":132,"type":1443}," フィルタを変えた瞬間に出題範囲を切り替えて再シャッフル（途中まで解いた状態を引きずらない）",[28,1488,1490,1492],{"className":1489},[1439],[1441,1491],{"disabled":132,"type":1443}," フィルタ後に0件になる選択肢はボタンを非アクティブにする（または選んだ瞬間に注意を出す）",[28,1494,1496,1498,1499,1501],{"className":1495},[1439],[1441,1497],{"disabled":132,"type":1443}," 「履歴リセット」ボタンを置く。リセット後はフィルタも ",[44,1500,737],{}," に戻す",[28,1503,1505,1507],{"className":1504},[1439],[1441,1506],{"disabled":132,"type":1443}," 履歴の各マークは「右が新しい」で揃える（人間が直感的に時系列を読みやすい）",[28,1509,1511,1513],{"className":1510},[1439],[1441,1512],{"disabled":132,"type":1443}," 表示パネルでは「過去3回に1回でも × があった県」を薄く色付け（一覧で弱点が目立つ）。これは累計誤答の判定なのでフィルタとは別の用途で残しておく",[28,1515,1517,1519,1520,1419],{"className":1516},[1439],[1441,1518],{"disabled":132,"type":1443}," localStorage の値が壊れていても落ちないようバリデーション。古いフィルタ値が残っていたら ",[44,1521,737],{},[21,1523,1524],{"id":1524},"仕分けクイズに移すときに気をつけたい点",[14,1526,1527],{},"仕分けクイズだと「問題=1つの仕訳」「正解=借方科目と貸方科目の組み合わせ」になる。1問の中で複数の判断（借方科目・貸方科目・金額）を行うので、「その問題で1度でも誤答したら ×」のルールをそのまま使うか、判定の粒度を上げるかの分岐がある。",[25,1529,1530,1538],{},[28,1531,1532,1533,1535,1536],{},"粗くする：仕訳1件まるごと正解で ",[44,1534,81],{},"、どこかでも違ったら ",[44,1537,92],{},[28,1539,1540,1541,1543,1544,1546],{},"細かくする：借方/貸方/金額の各ステップで ",[44,1542,81],{},"/",[44,1545,92],{}," を別々に持ち、ステップ単位で復習対象を絞る",[14,1548,1549],{},"最初は粗くで十分。学習者が「この仕訳パターンは何回やってもまだ間違える」を視覚化できれば、その時点で復習フィルタは仕事をしている。細かい粒度はあとから足せる。",[21,1551,1552],{"id":1552},"まとめ",[25,1554,1555,1558,1561,1568],{},[28,1556,1557],{},"「過去N回で何回間違えたか」で復習対象を決めると、卒業条件が定義できなくて学習が前に進まない",[28,1559,1560],{},"「直近で連続何回間違えているか」に切り替えると、1回正解した時点でフィルタから外れて自然に卒業する",[28,1562,1563,1564,1567],{},"履歴の保存形式（",[44,1565,1566],{},"[newest, ..., oldest]"," の固定長3）はそのままで、判定関数だけ差し替えれば移行できる",[28,1569,1570,1571,1574],{},"表示の ",[44,1572,1573],{},"○ × －"," は触らずに済む。学習者から見える情報は同じで、復習対象の解釈だけが変わる",[14,1576,1577],{},"仕分け系・暗記系のクイズコンテンツを増やすときは、最初からこのパターンで作る。",[1579,1580,1581],"style",{},"html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sSkh3, html code.shiki .sSkh3{--shiki-default:#2E8F82;--shiki-dark:#2E8F82}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}",{"title":54,"searchDepth":98,"depth":98,"links":1583},[1584,1585,1588,1591,1592,1593,1594],{"id":23,"depth":98,"text":23},{"id":578,"depth":98,"text":579,"children":1586},[1587],{"id":857,"depth":129,"text":857},{"id":897,"depth":98,"text":898,"children":1589},[1590],{"id":1217,"depth":129,"text":1217},{"id":1372,"depth":98,"text":1372},{"id":1428,"depth":98,"text":1428},{"id":1524,"depth":98,"text":1524},{"id":1552,"depth":98,"text":1552},"dev","都道府県位置クイズで採用していた『過去3回で1回以上間違えた』フィルタを『直近で連続して間違えた』フィルタに置き換えた。仕分けクイズなど他の暗記系コンテンツでも転用したい復習パターンのメモ。","mdx",{},null,"/quiz-streak-filter-design","mdx-playground",false,"2026-05-15T00:00:00.000Z",{"title":5,"description":1596},"2026-05-15/quiz-streak-filter-design",[1607,1608,1609,1610],"quiz","ux","vue","localStorage","memo","TDbVScr5b8aYdmU7BikCQRrCK6LQK_RcjCUX10Jn9HA",[],"https://log.eurekapu.com/og/blog/quiz-streak-filter-design.png?v=2026-05-15T00%3A00%3A00.000Z&title=%E3%82%AF%E3%82%A4%E3%82%BA%E3%81%AE%E5%BE%A9%E7%BF%92%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%82%92%E3%80%8C%E7%B4%AF%E8%A8%88%E8%AA%A4%E7%AD%94%E3%80%8D%E3%81%8B%E3%82%89%E3%80%8C%E7%9B%B4%E8%BF%91%E9%80%A3%E7%B6%9A%E8%AA%A4%E7%AD%94%E3%80%8D%E3%81%AB%E5%A4%89%E3%81%88%E3%81%9F%E8%A9%B1&author=Kei%20Komatsu&sig=4b77df980f0bbd48",1782528889434]