# 04 實作紀錄 ## 票 A(進行中) - app/tools/extract_book.py:從 toeic-drill-01.html 抽出 W、Q、TITLE,寫成 app/js/book.js(window.BOOK)。 - 遇到的問題:①JSON 結尾多了換行導致 rstrip(";") 沒作用 → 先 strip;②數字鍵的取代規則會誤改字串裡的數字 → 只在行首、「{」、「null,」後面取代。 - 驗證(免測收據的替代驗證):與 make-toeic-audio.py 解析舊頁的結果逐題比對,100 題題幹、答案、句子拆解全部相同;40 個單字相同。 - 桶 2 決定:題目內容與程式分開(book.js),理由:之後第 2 冊只要加資料檔,不用改程式(長期目標)。 ## 交付物 - 學習方法手冊:docs/reports/2026-10-04-listening-method.html(主理人要求先交,讓這兩天能照著練)。Chrome 實測 9 節切換正常、電腦與手機寬度版面正常、無錯誤。 ## 合約增補 1(42、43):學習方法手冊放上 server - app/guide.html:手冊正本(從 docs/reports 複製),側欄最上方加「← 回到練習」連到 index.html;第 7 節標成「過渡期 10/5–10/6」。 - 主畫面的入口(左側面板、手機展開面板、方法說明卡)在票 H 做,因為主畫面還沒建。 ## 票 B 排程邏輯(js/plan.js) - 測試先紅後綠:tests/plan.test.js。第一次「紅」是指令寫錯(node --test 不能給資料夾),不算;改指令並暫時移開 plan.js 後確認真紅,再綠(17/17)。 - 之後陸續加測試:連續聽在還沒學任何題時不能算完成(e2e 前的冒煙測試抓到)、三方合併 5 條。目前 23/23。 ## 票 I server(server.py) - 測試先紅(找不到 server 模組)後綠:tests/server_test.py 5/5。 - 修正一個真 bug:sqlite3 的 `with` 只負責 commit 不會關連線,Windows 上資料庫檔會一直被鎖 → 改用 contextlib.closing。 - 桶 2:只有進度 API 需要密碼;靜態檔用白名單(index.html、guide.html、app.css、js/、audio/),config.json、server.py、data/ 一律 404;history 保留最近 200 版供救援。 ## 票 C、D~H、J 畫面與存取(index.html、app.css、js/store.js、js/ui.js) - 版面:面板放左側的寬度門檻由 1080 改成 1240(面板內容變多,1080~1239 會壓到題目);窄螢幕用底部一條+展開面板。 - 「這題用新方法練完了」按鈕:給已經用舊方式答過、但這次要用新方法練的題目。 - 「看全部題目」裡的「清除全部紀錄」只清作答與標記,不清新方法的學習進度(confirm 文字已註明)。 ## 第 1 關(機器關):tests/e2e.js 逐條走合約 - 第一次 38/43。查出: 1. **真 bug:關頁面時送出的存檔,server 收到但頁面沒收到回覆 → 下次打開誤報衝突、還多下載一個檔。** 修法:打開時若 server 內容與未送出的副本完全相同,視為已存好。 2. **真 bug(設計缺陷):斷線時做的進度,在另一台裝置剛好也存檔(例如只更新已練分鐘)後,恢復連線會被整份丟掉。** 修法:改成三方合併(Plan.merge),兩邊做過的事都保留;見 05b-diff.md 差異 D1。 3. 其餘失敗是測試時機(提示早就出現,測試沒等到真正結果就往下)→ 改成等真正的狀態。 - 修完 44/44。 ## 第 2 關(05-review.md)+第 4 關(05c-smells.md)第一輪的修正 | 來源 | 問題 | 修法 | 驗證 | |---|---|---|---| | 合約 2 | 「重看方法」把已讀紀錄改回未讀 | 改用暫時的 methodOpen,不動 prefs.methodSeen | e2e:重看後重新整理,卡片不再自動展開 | | 合約 4 | 上次停在全部題目,下次打開不是這一次 | 不再存 view;打開一律 session | e2e | | 合約 15 | 在全部題目點字不排複習,但畫面說會 | syncFirstReview 不再限定「這次學的題」 | plan 測試+e2e | | 合約 23 | 視窗只要看得見就計時 | 加 document.hasFocus() | 程式檢查(無頭瀏覽器無法模擬焦點) | | 合約 27 | 方法表 7 處文字與預告頁不同 | 改回預告頁原文 | 逐字對照 02-preview.md §6 | | 合約 35+第 4 關 | 打開時連不上或 server 出錯 → 偷偷變本機模式 | 新增「離線開始」:用暫存或上次同步的副本繼續,連上後合併補存;只有沒有 API(404)或使用者選擇不輸入密碼才是本機模式 | e2e:開頁時 API 回 502 | | 合約 37 | 兩台點了同一句不同的字,另一台的會消失 | 點字改成集合合併;兩台都學過同一題取較早的次數 | plan 測試 | | 第 4 關 高 | 救援版本 1~1.5 小時就被洗光 | history:最近 200 次+每天最後一份保留一年;內容沒變(不計 updatedAt)就不送;計時存檔 30 秒改 60 秒 | server 測試 test_7 | | 第 4 關 | 補存失敗仍顯示已同步 | 有 pending 時狀態交給 resolvePending/pushNow 決定 | 程式檢查 | | 第 4 關 | server 資料庫重建或還原成舊版時會掉資料 | 409 回來的版本比自己記得的舊、或資料是空的 → 用這台的進度補回去 | 程式檢查 | | 第 4 關 | 切字規則兩份 | Plan.sentenceParts/tokenSpans 為唯一來源,畫面改用它 | 100 句逐句比對:文字一致、標籤成對、字數一致 | | 第 4 關 | 欄位清單三處 | Plan.MAP_FIELDS 一處 | plan 測試 | | 第 4 關 | iPhone Safari 需要分段讀取 | server 支援 Range(206/416) | server 測試 test_6 | - 測試現況:plan 27/27、server 7/7、e2e 49/49(連跑 3 次全過)。 - **無法重現的一次失敗**:e2e「恢復連線後補存」曾失敗一次(server 沒收到新的存檔),之後連跑 4 次都通過,無法重現,依規矩不憑推測修改。實際使用時另有每 15 秒自動重試,進度不會掉。記在這裡,若實驗室電腦上再出現再追。 - 測試資料修正一筆:「每個欄位都會被合併」的測試把同一題同時放進 review 和 mastered(不可能的狀態),改成每個欄位用不同題號;程式不變。 ## 重大技術調整:進度改成「事件流水帳」(第 2 關第二輪後,規則三:修兩次未過 → 換方法) - 起因:05e-review-r2.md 顯示「整份進度三方合併」在 server 還原、開頁補送、同瀏覽器兩分頁等路徑仍會掉資料;已修兩輪,每修一條路又冒出一條。依規則三,判斷是方法錯了,不是手殘。 - 桶 2 決定(理由:長期目標=資料永不遺失、多裝置必然一致): - 每個動作記成一筆事件 {id, dev, n, t, type, ...};進度=把所有事件依 (t, dev, n) 排序後重放(Plan.replay)。 - 兩台/兩分頁/離線:事件以 id 取聯集,重放結果在每台都一樣。 - server 只追加事件、以 id 去重(INSERT OR IGNORE);永不覆蓋,所以本身就是完整歷史,任何時間點都能還原。 - server 被還原或重建:這台發現 server 的流水號比自己記得的小 → 把自己知道的事件全部補送(server 去重)。 - 匯入進度:記一筆「reset」事件(內容是檔案算出的進度),符合合約 33「換成檔案內容」。 - API 由 /api/progress 改為 GET /api/events?after=N、POST /api/events。舊 API 尚未部署過,直接移除。 ## 事件流水帳上線後的結果 - plan 28/28、server 5 個 API 測試+2 個靜態檔測試全過、e2e 56/56。 - 追查一次 e2e 失敗(A 拉不到 B 的 6-2):單獨重現正常;原因是 B 開頁時狀態已是「已同步」,測試在 B 的事件送出前就往下走。順帶修一個真問題:一做動作就要立刻顯示「儲存中…」(record() 設定狀態),否則畫面會短暫顯示已同步但其實還沒送。 ## 第 3 關第 1 輪(05d-gate3-r1.md,學習者角色)的修正 - 平均約 3.3~3.4;4 格低於 3。注意:試用途中我正在改程式,試用者有一段遇到舊 server 配新網頁(/api/events 404 → 看似進度消失),已提醒部署時兩者一起更新,並新增「http 網址卻沒有 API 時的紅色提示」。 | 編號 | 問題 | 修法 | |---|---|---| | H1 | 複習時再點字會取消舊標記、扣缺口次數 | 新事件 remark:複習中點字=「這次又沒聽到」,上次的字保持、這次的點擊另計;再點只取消這次 | | H2 | 沒聽就答也得到「✓ 用新方法練過」 | 新事件 play;答題前有播放才顯示「✓ 用新方法練過」,否則灰字「已作答(這題沒先聽)」;仍算學過(不加鎖) | | M1 | 做完不收、找不到下一題 | 答完/複習按完後出現「下一題 ↓」;重新打開時自動捲到還沒做的地方;面板與手機底部有「↓ 接著做」 | | M2 | 完成鈕只在最底 | 面板與手機底部也有(做完是深色「這次完成 ✓」,沒做完是小字「提早結束」) | | M3 | 手機點擊範圍太小 | 觸控裝置放大字的點擊範圍、行距、播放鍵與拆解按鈕 | | M4 | 句子拆解直接露出中文 | 每塊的中文意思先收起,按「看中文意思」才出現;文法說明與課綱照常 | | M5 | 不知道聽了幾遍 | 播放鍵顯示「已聽 N 遍」 | | M6 | 合併後跳回頂端、提示不消失 | 載入其他裝置進度時保持捲動位置;藍色提示 6 秒後自動消失 | ## 第 2 關第 3 次(05f-review-r3.md,攻擊流水帳)的修正 | 發現 | 修法 | 驗證 | |---|---|---| | 高:事件編號 4 個亂數字會撞號、撞到就被當重複丟掉 | 事件 id 改 128 位元亂數(crypto.getRandomValues,純 http 的 Tailscale 網址也能用;randomUUID 不行) | 程式檢查 | | 中:mark/remark 是「切換」,兩台會互相抵銷 | 事件帶 on: true/false(設成有/沒有),重複套用不改變結果;舊的無 on 事件仍按切換處理(相容) | plan 測試 | | 中:這一次的複習清單會被晚到事件重算 | 每一次的第一個動作前自動記一筆 plan 事件,固定複習與新學清單與快照;跟一次學習有關的事件都帶 ses(第幾次),晚到的複習按鈕照它那一次的快照算 | plan 測試(晚到 mark、晚到 review、兩台各按一次完成只算一次) | | 中:本機模式存滿後寫成空清單 | 本機模式存不進去時絕不縮減,改顯示紅色提示請匯出;server 模式才只留未送出的 | 程式檢查 | | 中:流水帳只增不減、每次開頁全下載 | 快取完整時開頁只拿新事件;計時改 5 分鐘一筆;仍需長期方案(見下方已知限制) | 程式檢查 | | 合約 15:已學會、或排到 3/7 次後的句子再點新字不會提前複習 | 點新字一律排到下一次;取消那個字會恢復原本的安排(reviewPrev) | plan 測試 | ## 第 3 關第 2 輪(05d-gate3-r2.md):平均 3.9/3.6/4.0/3.5/3.4,無低於 3、無高 | 編號 | 問題 | 修法 | |---|---|---| | M1 | 完成後面板仍可再按完成,多出空的一次、縮短複習間隔 | 還沒做任何動作的一次不顯示完成鈕;排程也不承認空的一次(finish 只在 started 時生效) | | M2 | 複習點了沒聽到的字還能按「都聽出來了」 | 複習中點字=自動算「還是有沒聽出來的」;有這次點的字時「都聽出來了」不能按;加一行說明 | | M3 | 已聽 N 遍是累計 | 改成這一次聽了幾遍(session.plays) | | M4 | 斷線提示捲動後看不到 | 紅色提示固定在上方;手機底部顯示「⚠ 未同步」 | | M5 | 他台進度傳來整頁重畫 | 這一次的清單沒變時只就地更新,打開的拆解與捲動位置都保留 | - 測試修正兩筆(皆為測試情境不符合新規則或抓錯位置,程式不變):①「晚到的複習」測試補一個 touch(空的一次不能完成)②r2-M1 檢查改看面板與底部條(完成後顯示的是「辛苦了」畫面,沒有完成區塊)。 - 現況:plan 36/36、server 全過、e2e 60/60。 ## 已知限制(長期) - 流水帳會一直變長。(2026-10-05 更正:原本寫「一年約 2~4 MB、2 年後做快照」是估錯的;第 2 關 r5 實測約 10 個月就超過 localStorage 的 5 MB。)現況:已改存 IndexedDB,空間不再是問題;實測 3 萬筆時每點一下 0.1ms、開頁 0.2 秒。剩下的只有「新裝置第一次打開要下載整份」。哪天這一步明顯變慢,再另開案子做「快照」(把很舊的事件壓成一份進度)。 ## 第 2 關第 4 次(05g-review-r4.md)+第 4 關重掃(05h-smells-r2.md)+第 3 關第 3 輪(05d-gate3-r3.md)的修正 - 新增 tests/sim.js(第 4 次監工寫的模擬器,載入真正的 plan.js+store.js,模擬多台裝置、離線、空間不足、server 還原)與 tests/sync.test.js(7 個會自己判斷對錯的同步情境)。 | 來源 | 問題 | 修法 | |---|---|---| | r4 高 | 本機模式做的事之後永遠不上傳 | 「還沒送出」改由每筆事件自己記:_a=確認它的 server 資料庫的 epoch;沒有目前 epoch 確認的一律送出(本機模式、離線、其他分頁的都會送) | | r4 中 | server 還原後互缺資料 | server 加 epoch(meta 表);`python server.py --restore FILE` 還原時換新 epoch;裝置發現 epoch 變了就整份比對、把 server 沒有的全部重送。修正中發現:舊副本的確認標記會被合併回來 → 確認標記改存 epoch,舊 epoch 自動不算 | | r4 中 | 裁過的快取離線開過一次就被當完整 | 「完整」標記只在一次從頭拿到、而且沒被裁的時候才設 | | r4 中 | 全學完又沒到期複習時卡死 | 沒有新題也沒有到期複習時,把最近一批的複習提前(保留原本間隔順序) | | r4 中 | 兩台各開始同一次,後開始那台的複習答案被丟 | plan 事件改為「合併清單」;review 事件自帶 snap,清單裡沒有也能算 | | r4 中 | 晚到的「取消這次點字」刪掉上次的標記 | 晚到時只取消「這次才點的」字 | | r4 中 | 本機模式兩分頁互蓋 | 所有模式都監聽其他分頁;存檔前先與儲存區的版本合併 | | r4 中 | 重試迴圈一直整份下載 | 同時只允許一個拉取;重試只拿新事件 | | r4 合約 15 | 複習按過「都聽出來了」後在別處點新字不改 | 在今天的複習句點新字=改成「還是有沒聽出來的」 | | r4 合約 33 | 匯入後畫面沒更新 | 匯入強制整頁重畫 | | 重掃 高 | 匯入舊格式整份進度會讓所有裝置打不開 | reset 一律丟掉檔案裡的 session、補齊欄位;重放時單筆事件出錯只跳過並計數(不再讓整份進度壞掉) | | 重掃 中 | 一次學習的事件類型寫了兩份 | 只留 Plan.SESSION_TYPES | | 重掃 中 | 分頁共用「未送出清單」的競態 | 移除共用清單(見 r4 高) | | 重掃 中 | 按鈕規則三份對不上 | nextStep() 一處決定;面板、底部條、完成區都用它 | | 重掃 中 | 點擊處理過長 | 拆成 onWord/onDataButton/ACTIONS 表 | | r3 M1 | 完成後面板像歸零、按鈕沒反應 | 完成後面板顯示「第 N 次已完成 ✓」與「開始下一次」;點了會自動收起面板 | | r3 M2 | 只想複習的晚上沒有出口 | 複習做完、還沒開始新題時出現「接著學新的/今天只複習,這次完成」;提早結束改成正常大小的按鈕;沒學新題時連續聽改播這次複習過的句子 | | r3 M3 | 斷線時關頁沒提醒 | 有未送出的進度且連不上時,關頁會跳出瀏覽器的確認;提示文字說明「要等連上後才會出現在別台」 | | r3 H1、M4 | 複習越堆越多;偷懶的題不回來 | 動到主理人定的規則 → 停車場 P3、P4。先只做顯示:面板「已學會 N 句・待複習 M 句」 | - 測試修正(測試期待錯,程式不變):①「全學完」只提前最近一批 ②「重試不整份下載」原本用空 server,改成先放資料。 - 現況:plan 43、sync 7、server 8、e2e 62,全過。(2026-10-05 更正:這裡原本寫 server 9,實際當時是 8 個;9 個是下一輪加了 test_10 之後。) ## 第 2 關第 5 次(05i-review-r5.md)+授權代決 D6+第 3 關第 4 輪(05d-gate3-r4.md)的修正(2026-10-05) | 來源 | 問題 | 修法 | 改了哪裡 | |---|---|---|---| | D6 代決 | 複習要做幾次才算學會 | 間隔 `[1,3,7]` → `[1,4]`:連續 2 次「都聽出來」就學會。理由:主理人說學過的字之後會在別題自然出現 2~3 次,舊題只需要練真正難的 | js/plan.js `INTERVALS` | | D6 代決 | 到期超過 10 句時先做哪些 | 點過的字最多的先排(穩定排序,同樣難時照原本 due/題號) | js/plan.js `planReviews` | | r5 條 11、13 | 兩台合併清單時可能超過 10+10 | 合併時也守上限 | js/plan.js `pinPlan` | | r5 條 15 | 在今天的複習句別處點新字,卡片上看不到 | 點的字同時記進 `reviewMarks`;有這次點的字時「都聽出來了」不成立 | js/plan.js `apply` mark/review | | r5 條 37 | 晚到的複習答案在沒看過那次 plan 的裝置上被丟 | review 事件自帶 snap,找不到那次的快照時用它 | js/plan.js `apply` review | | r5 還原安全 | `--restore` 指到錯的檔會把資料庫換成壞的 | 先檢查備份真的是這個程式的資料庫;換之前先把現在的複製一份 `progress.db.before-restore-時間` | server.py `restore_backup`;server_test.py test_10 | | r5 效能(最大的一項) | 流水帳全部存成 localStorage 的一段文字:約 10 個月會超過 5MB 上限,而且每點一下要把整段重寫(100~220ms) | 改存 IndexedDB、每個動作只寫一筆;開不了時退回 localStorage。另一分頁用 BroadcastChannel 通知 | js/store.js 快取段 | | 實作中發現 | IndexedDB 寫入是「排隊」的,點完立刻關頁會掉資料(實測:連點 20 下後立刻重新整理,掉 8 筆) | 寫入前在 localStorage 記「還沒寫完」便條,寫完撕掉;開頁時補寫。改後實測三次一筆不少 | js/store.js `pendingNote` | | 實作中發現 | 開頁 0.3 秒後的「自動跳到上次那題」會把剛切到「看全部題目」的人拉回來 | 跳之前確認還在這次的畫面 | js/ui.js `boot` | | 第 3 關 r4 M1 | 底部條的主要按鈕白底白字 | `.bar button.primary` 深色底白字 | app.css | | 第 3 關 r4 M2 | 「下一題」按鈕在還沒作答時就出現(CSS 蓋掉 hidden) | `[hidden]{display:none!important}` | app.css | | 第 3 關 r4 M3 | 只複習的晚上,最後一題複習按「下一題」直接跳進新題 | 複習做完、還沒開始新題時,「下一題」先帶到「今天只複習,這次完成」框;框裡「接著學新的」才去新題 | js/ui.js `nextTarget`、`reviewOnlyStop`、ACTIONS `to-new` | | 第 3 關 r4 M4 | 面板「已學會/待複習」看不懂 | 改成「之後還會再出現 N 句(有沒聽出來的字)/已經不用再複習 N 句(連續 2 次都聽出來)」 | js/ui.js `renderPanel` | 桶 2 自己決定的(一行理由): - 用 IndexedDB 而不是「定期壓縮舊事件」:壓縮要改資料格式、要處理多台合併,風險大;IndexedDB 不改格式、空間足夠用好幾年。 - 便條用 localStorage:它是唯一「當場寫完」的瀏覽器儲存;便條只放還沒寫完的幾筆,很小。 - 分頁通知用 BroadcastChannel:IndexedDB 沒有像 storage 那樣的變更通知;沒有 BroadcastChannel 的瀏覽器仍會在 30 秒的 server 檢查時拿到。 實測(30,000 筆事件,本機 Chrome,scratchpad/bench_idb.js):每點一下 0.1ms(中位數),重新整理 0.2 秒,重開後一筆不少。改版前第 2 關監工量到的是每點一下 100~220ms。 測試修正(說明原因,程式不變): - e2e 第 16 項:期待值 `due 5` → `due 6`(D6 改了間隔,第 2 次學習的 2+4=6)。 - e2e 第 35 項:原本直接讀 localStorage 的事件;改成問 `Store.hasUnsent()`(事件已不在 localStorage)。 - e2e r4-M3:捲動停在 -0.47 像素被判為不在畫面內;容許 5 像素。 - e2e r5-idb:我放的舊事件時間設太早(t=5),排在前面「匯入測試」的重設之前被清掉;改用現在時間。 - e2e r5-tabs:四次失敗一次。背景分頁沒有畫面更新,用預設的「每次重畫才檢查」等不到;改每 0.1 秒檢查。改後連跑 9 次全過。 新增測試:plan.test.js 4 個(r5 條 11/13、15、37,D6 最難先排);server_test.py test_10;e2e r4-M1、r4-M2、r4-M3×2、r4-M4、r5-idb、r5-tabs、r5-moved-up、r5-close。 現況:plan 47、sync 7、server 9、e2e 71,全過(e2e 最後連跑 5 次都 71/71)。 ### 同一輪追加:第 2 關第 5 次「建議修正」裡剩下的便宜項目(2026-10-05) | 05i 編號 | 問題 | 修法 | 改了哪裡 | 新測試 | |---|---|---|---|---| | 攻擊 5/建議 4 | server 兩個查詢中間有人寫入,那一筆永遠拿不到 | 先查最大流水號,再只拿到那一號為止 | server.py `read_events` | server_test test_12 | | 攻擊 6/建議 5 | 直接把資料庫檔案蓋回去(沒用 --restore)不會被發現 | server 的流水號比這台記得的還小 → 當成被還原:所有確認標記撕掉、覆寫本機副本(不和舊副本合併,否則標記會被合併回來)、整份重拿、補送 | js/store.js `takeServer` | sync.test 第 4 個 | | 攻擊 8/建議 8 | 很舊的快取,晚到的複習答案把「已學會」改回「要複習」 | 晚到的複習答案只在「那句的排程從那時到現在都沒變」才算 | js/plan.js `apply` review | plan.test「a very old device's late review…」 | | 攻擊 10/建議 10 | 未來第 2 冊的題號被舊頁面讀到:改一半、或畫面畫不出來 | 題號不在這本書裡的事件整筆不理 | js/plan.js `apply` 開頭 `inBook` | plan.test「events about questions this book does not have…」 | | 攻擊 11/建議 11 | Content-Length 不是數字、超長數字的 after/Range 會讓連線直接斷 | 都先檢查是不是 18 位以內的數字;不是就回 400 或當成沒有 Range | server.py `do_GET`、`do_POST`、`serve_static` | server_test test_11 | 刻意不做(寫給下一位,不要重複討論): - 攻擊 9/建議 9(匯入後,另一台 ses 比較大的事件排得太晚):匯入很少用、後果只是晚幾次複習,所以不做。(2026-10-05 更正:原本另外寫「時鐘不準時會把另一台下一次的清單併進這一次」,那是推理、沒有實證;第 2 關 r6 監工把建議的改法套在副本上,全部測試都過,包括 5 秒時間差的 fuzz,所以那個理由不成立,刪除。) - 建議 11 的「前端收到 400 時把批次砍半」:一批最多 4000 筆,實測最大 0.53 MB,離 2 MB 上限很遠。 - 建議 6(合併清單整段拿掉):已改成合併時守 10+10 上限,效果相同、改動較小。 - 05i 第 15 條附帶的 r4 情況 C(已學會的句子點新字被拉回、再取消,有舊標記時不恢復已學會):行為是「多複習一次」,比「漏掉」安全,維持。 測試修正(原因): - plan.test「one broken event is skipped」:原本用「不存在的題號」當壞事件;現在那種事件是刻意整筆不理,不算壞。改用清單欄位是數字的 plan 事件(真的會在重放時出錯)。 - plan.test「a review answer arriving after another device ended…」:假快照少了 `due`;程式產生的快照一定有 due(snapOf 會複製整個排程),補上。 現況:plan 49、sync 8、server 11、e2e 71,全過。 ## 第 2 關第 6 次(05j-review-r6.md)的修正(2026-10-05) 主理人指示(10/5):試用與檢查只用正常情境。監工結論:合約 43 條 41 條一樣、2 條小差異;正常使用下 0 高 0 中 5 低。 | 05j | 問題 | 處理 | 改了哪裡 | 新測試 | |---|---|---|---|---| | 低 1 | 複習中到「看全部題目」點字又取消:複習卡卡住、缺口清單少算 | 今天複習句的點字/取消,不論在哪個畫面都走同一個函式 `clickInReview`(順便消掉壞味道 1 的重複) | js/plan.js `apply` mark、`clickInReview` | plan.test「during a review, a word clicked and unclicked in the all view…」 | | 低 2(合約 13) | 到期 10 句以內也照難易排 | 只有超過 10 句時才照難易挑;挑出來的仍照到期先後排 | js/plan.js `planReviews` | plan.test「10 or fewer due: earliest due first…」 | | 低 4 | 複習卡誤點一個字又取消,仍算「還是有沒聽出來的」 | 點字自動算「沒聽出來」時,記下原本的答案(`session.reviewAuto`);這次點的字全部取消時退回原本的答案(沒作答就回到沒作答)。使用者自己按的「還是有沒聽出來的」不會被退掉 | js/plan.js `clickInReview`、`ensureSession`、`apply` review | plan.test「a mis-tap on a review card…」、e2e r6-low4 | | 低 3 | 學新題期間複習會先堆起來(模擬最多 44 句到期) | 不改:這是 D6「超過上限先排最難」的必然結果,每次仍是 10+10 做得完;寫進工作報告讓主理人知道 | — | — | | 低 5 | 已知限制的舊數字;攻擊 9 的理由沒實證 | 兩處都更正(見上面「已知限制」與上一節「刻意不做」) | 本檔 | — | 重構候選(只記錄、不動手;主理人規矩:重構是獨立任務)——05j 壞味道表: - store.js 兩種快取的 `put` 名字一樣、做的事不一樣(localStorage 版偷偷整份合併),takeServer 因此要特地 `keepOnly`(第 8、12 條)。 - `cache !== lsCache` 散在三處(第 7 條);takeServer 做五件事(第 2 條)。 - plan.js `((s.plans[ses] || {}).reviewSnap || {})[q]` 重複兩次(第 1、10 條);「學會」藏在 `stage >= INTERVALS.length`(第 9 條)。 現況:plan 52、sync 8、server 11、e2e 72,全過。 ## 第 3 關第 5 輪(05d-gate3-r5.md)的修正(2026-10-05) 試用結果:沒有低於 3 分的格子;平均 4.3/4.1/4.1/3.7/3.6(上輪 4.0/3.6/3.6/3.3/3.6);上輪 M1–M4 都修好;連續 2 次學會(D6)實際驗證過。 | 編號 | 問題 | 處理 | 改了哪裡 | |---|---|---|---| | M1(中) | 一句話學會的那一刻畫面沒表示 | 卡片題號旁顯示「· 學會了 ✓(之後不再複習)」(綠色);完成畫面加「這次有 N 句學會了,之後不用再複習 ✓」 | js/ui.js `updateCard`、`finishSession`、`renderSession`;app.css | | M2(中) | 複習做完按「接著學新的」跳過這組的 5 個新字 | 改捲到「② 新學」標題(單字表就在下面);`nextTarget`/`goNext` 不再需要參數 | js/ui.js ACTIONS `to-new` | | L1 | 「(有沒聽出來的字)」會讀成「有沒有」 | 改「(裡面有還沒聽出來的字)」 | js/ui.js `renderPanel` | | L2 | 按「我懂了」停在單字表中間 | 捲到「第 N 次學習」 | js/ui.js ACTIONS `method-ok` | | L5 | 手冊手機版:「左邊點一節」、回練習藏在 ☰、☰ 蓋內文、第 7 節教舊網頁 | 文字改「電腦在左邊;手機點左上角 ☰」;標題上方放「← 回到練習」;☰ 加白底陰影、內文上留白;第 7 節標「已過期」並說明直接看第 8 節 | guide.html | | L6 | 「今天只複習」框沒有連續聽 | 框裡加「▶ 先連續聽複習的 N 句」 | js/ui.js `updateSession` | | L7 | 手機按鈕偏小 | 觸控裝置上:底部條按鈕 44px、語速鍵 40px、面板與頂端連結加大點擊範圍 | app.css | | L3 | 複習卡帶著上次的選項和對錯 | 不改:要動版面較大,列入工作報告給主理人決定 | — | | L4 | 沒先聽的題只有灰字 | 不改:P4 已由主理人 10/5 裁決(偷懶且沒點字的不回來);試用者拿到的是舊資訊 | — | 字眼(桶 1)說明:M1、L1、L6 的新文字照試用者建議先做(主理人 10/5 授權依「長期目標/會不會一直用」自行判斷),列在工作報告的「可以改的字眼」讓主理人改。 測試修正:e2e r4-M3 的第二項「接著學新的 → 第一題新題」改成 r5-M2「→ ② 新學標題」(需求本身被這輪的 M2 改了)。 新增測試:e2e r5-M1×2(在乾淨的本機模式頁面快轉 5 次學習)、r5-M2、r5-L2。 現況:plan 52、sync 8、server 11、e2e 75,全過。 ## 第 2 關第 7 次(05k-review-r7.md,新監工只驗最後兩輪)的修正(2026-10-05) 監工結論:最後兩輪的修正都做到;正常使用 0 高 0 中 3 低;沒有違反合約。四套測試它重跑也全過。 | 05k | 問題 | 處理 | 改了哪裡 | 新測試 | |---|---|---|---|---| | 低 2 | 「今天只複習」框按連續聽,框裡看不出在播、停不了;播放中按別的 🔊 後③區卡在「正在播」 | 框裡那顆按鈕跟著狀態變:「■ 停止(正在播第 k / n 句)」→「✓ 已聽完 · 再聽一次」;連續聽以外的任何聲音會先正常停掉連續聽 | js/ui.js `renderRvdone`(從 updateSession 抽出)、`renderCont`、`speak` 加 fromCont | e2e r7-low2×2(停掉的部分讀程式驗證:播放太短,測試點不到中途) | | 低 3 | 手冊只有開頭看得到「回到練習」;目錄沒寫第 7 節已過期 | 每一節底部的翻頁列都加「← 回到練習」;目錄第 7 節改「(已過期)」(翻頁鈕的字跟著目錄) | guide.html | — | | 低 1 | 複習中到「看全部題目」,在上次點過的字上連點兩下:這句悄悄變「還是有沒聽出來的」 | 不修:要連點兩下舊字才會發生,後果是多複習一次;記下來給之後 | — | — | 現況:plan 52、sync 8、server 11、e2e 77,全過(e2e 連跑 2 次)。