# 05k 第 2 關複查(第七次):只驗最後兩輪的修正(2026-10-05) **一句話:最後兩輪的修正都有做到,正常使用下沒有高或中的問題。新找到 3 個低的問題:第一,複習中到「看全部題目」,在上次點過的字上連點兩下,這句會被悄悄改成「還是有沒聽出來的」;第二,「今天只複習」框裡的連續聽,播放時框裡看不出在播;第三,手冊只有第一頁看得到「回到練習」。** > 複查者:清白監工(沒參與製作,也沒做第 1~6 次審查)。只寫紀錄,沒有改 app/ 底下任何檔案。 > 範圍:04-implementation.md 最後兩節(「第 2 關第 6 次」「第 3 關第 5 輪」的修正)。對照 05j-review-r6.md、05d-gate3-r5.md;合約 02-contract.locked.md+02-contract-amend-1.md;已拍板差異 05b-diff.md。 > 只看正常使用:一個人、下班後學約 1.5 小時、有時電腦換手機。手機誤觸算正常使用。 > > 跑過的測試(在 `C:\D槽\英文學習資料`): > - `node --test app/tests/plan.test.js`:52/52 > - `node --test app/tests/sync.test.js`:8/8 > - `python app/tests/server_test.py`:11/11 > - e2e.js:75/75(輸出存在 `audit7/e2e-run1.txt`) > - 這四個數字和 04 寫的一樣。 > > 我自己的腳本都放在 scratchpad 的 `audit7\`: > - `a1_click_in_review.js`:直接載入真正的 plan.js。照 ui.js 送事件的方式(複習卡送 remark、全部題目送 mark、按鈕送 review),跑 20 種點法。每一種都比對「一筆一筆套用」和「整份重放」,兩邊結果都一樣。 > - `a2_plan_reviews.js`:測複習排序的四種情況。 > - `a3_phone_ui.js`、`a4_guide_and_cont.js`、`a5_guide_nav.js`、`a6_phone_learned.js`:用 puppeteer 開真的 Chrome,手機 390×800、觸控(`pointer:coarse` 有生效),本機模式(file://)。輸出存在 `a3-out.txt`~`a6-out.txt`,截圖在 `audit7\shots\`。 --- ## 1. 逐項驗證表 判定只有三種:**修好**/**只修一半**/**修壞了別的東西**。這輪沒有「修壞了別的東西」。 ### plan.js:複習中點字(`clickInReview`、`session.reviewAuto`、apply 的 mark 和 review 分支) | 項目 | 判定 | 證據 | |---|---|---| | r6 低 1:複習中到「看全部題目」點一個新字又取消,應該不留痕跡 | 修好 | a1 S1:結果回到「沒作答」,排程仍是 `{stage:0,due:2}`,缺口清單沒有這個字。plan.test「r6 low 1」也通過 | | 在複習卡點、到全部題目取消(或反過來) | 修好 | a1 S7、S8:結果回到「沒作答」,這次點的字清空 | | r6 低 4:複習卡誤點又取消,應該回到「沒作答」 | 修好 | a1 S2;e2e r6-low4 通過 | | 先按「這次都聽出來了」,再誤點、取消,要回到「都聽出來」,排程也要對 | 修好 | a1 S3:`ok`、`{stage:1,due:6}`。S14 改在全部題目點,結果一樣 | | 點兩個字、只取消一個:仍是「還是有沒聽出來的」;兩個都取消,才回到沒作答 | 修好 | a1 S13a、S13b | | 使用者自己按的「還是有沒聽出來的」不能被退掉 | 修好 | a1 S4(先按、再點、再取消)、S5(先點、再按、再取消)、S6(先按都聽出來、點字、再按沒聽出來、取消):三種都維持 `miss`。原因:按鈕的 review 事件會刪掉「自動」記號(plan.js:340) | | 第 2 次「都聽出來」→ 學會;之後誤點又取消,「學會」要恢復 | 修好 | a1 S12:`mastered=6` → 點字後消失 → 取消後回到 `6`。a6 在手機上看畫面:標籤「學會了」也跟著消失再出現,重新整理後仍在 | | 缺口清單次數:新字點了又取消 | 修好 | a1 S1、S2、S7、S8:次數不變 | | 缺口清單次數:別句也點過的同一個字(例如 the) | 修好 | a1 S15a~c:點下去變 2 次、兩句;取消回到 1 次、只剩 6-2。05j n2b 的情況已不再發生 | | 缺口清單次數:在複習卡點「上次就點過的字」=又沒聽到一次 | 修好 | a1 S10:點下去變 +1,取消回到原數 | | 缺口清單次數:在「看全部題目」,對上次就點過的字連點兩下 | **只修一半** | 見新發現「低 1」。a1 S9、S11;a3 真瀏覽器:結果被改成 `miss`;到複習卡把它點掉後,缺口少算 1 次 | ### plan.js:複習排序(`planReviews`) | 項目 | 判定 | 證據 | |---|---|---| | 到期 10 句以內,照到期先後排(合約第 13 條) | 修好 | plan.js:48-52 只有 `due.length > REVIEW_CAP` 才挑難的。a2:7 句時,最難的那句到期最晚,就排最後;剛好 10 句時,也照到期先後排 | | 超過 10 句:挑最難的 10 句,仍照到期先後排 | 修好 | a2 15 句的例子:挑到的每一句,都不比留下的容易;顯示時照到期先後排。一樣難時,先到期的被挑中(排序是穩定的)。11 句時,0 個點字的那句被留下 | | 05d r5「需要真人確認」第 4 點(第 6 次的順序不是照難易) | 修好 | 現在照到期先後排是對的。只有超過 10 句才照難易挑 | ### ui.js | 項目 | 判定 | 證據 | |---|---|---| | M1:第 2 次連續都聽出來,卡片顯示「學會了」 | 修好 | ui.js:140-143;e2e r5-M1 通過;a6 手機上,標籤是綠字「· 學會了 ✓(之後不再複習)」,不會擠爛版面(標題列 38px 高) | | M1:結束畫面寫「這次有 N 句學會了」 | 修好 | ui.js:218、455;e2e r5-M1;a6 手機上看到「這次有 1 句學會了,之後不用再複習 ✓」 | | M2:「接著學新的」捲到「② 新學」標題 | 修好 | ui.js:512;e2e r5-M2;a3 手機上,標題在畫面頂端,5 個新字表在 72px,展開著(截圖 p02) | | L2:「我懂了」捲到「第 N 次學習」 | 修好 | ui.js:514-517;e2e r5-L2;a3 手機上,標題在畫面頂端 | | L6:「今天只複習」框裡有連續聽的按鈕 | 修好(有一個小缺口) | ui.js:278;a3 框裡有「▶ 先連續聽複習的 3 句」,按了真的會播。但播放時,框裡沒有任何變化,見「低 2」 | | L1/面板文字 | 修好 | ui.js:372;a3 面板顯示「之後還會再出現 3 句(裡面有還沒聽出來的字)」 | ### app.css、guide.html | 項目 | 判定 | 證據 | |---|---|---| | L7 觸控尺寸 | 修好 | app.css:199-204。a3 在觸控模式量到:底部條按鈕 44 高、☰ 44×44;語速鍵 48×40;面板連結 42 高;「重看方法」「學習方法手冊」41 高。05d 點名的五個都過了。沒點名、仍偏小的:面板「收起」49×28、頂端「看全部題目」91×33(只記錄,不算問題) | | 手冊手機版的文字 | 修好 | guide.html:98「電腦在左邊;手機點左上角 ☰」 | | 手冊的「回到練習」 | **只修一半** | 只有開頭那一頁(guide.html:87)看得到。第 1~8 節頁面上都沒有,a4 每節都是 0 個。第 8 節讀完,「下一節」是灰的,要回練習仍得打開 ☰(截圖 g02)。見「低 3」 | | ☰ 蓋住內文 | 大致修好 | 手機上內文上方留了 60px(guide.html:63),☰ 白底加陰影(guide.html:64)。a5 從目錄點進一節時,標題在 60px,☰ 底在 46px,不會蓋到。往下捲時,固定的 ☰ 仍會蓋住左上角的字(g02 蓋到「2.」)。這是固定按鈕本來就會這樣,可以接受 | | 第 7 節標成已過期 | 修好(有一點落差) | 標題寫「(已過期)」,一開頭就有提醒框說「直接看第 8 節」(guide.html:267-268)。但目錄還是寫「7 過渡期怎麼練 10/5–10/6」(guide.html:81)。第 8 節的「上一節」按鈕也一樣,沒寫已過期(a4) | ### 合約對照 - 第 13 條:這輪改完後一樣了(05j 列的「只差順序」已消除)。 - 第 15~17、30 條:照 D6 和本輪設計,正常點法都對。只有「低 1」那條少見的路,缺口次數會少 1。 - 第 22、43 條:面板與手冊入口都在。手冊頁內「回到練習」的連結有,只是第 1~8 節要先開 ☰(見低 3)。 - D1~D6 都照 05b 做。**這輪沒有發現違反合約的地方。** --- ## 2. 新發現 正常使用:0 高、0 中、3 低。 ### 低 1. 複習中到「看全部題目」,在上次點過的字上連點兩下:這句被悄悄改成「還是有沒聽出來的」;去複習卡改回來,缺口少算 1 次 腳本實證:a1 S9、S11;a3 真瀏覽器。 - **怎麼發生:** 1. 今天的複習句(例如 6-3)有上次點過的字。 2. 在「看全部題目」裡,這個字是紅的。 3. 手機上連點兩下:第一下取消,第二下點回來。 - **實際結果:** - 第一下取消,走的是「拿掉舊標記」那條路(plan.js:332 的條件不成立,跳到 336 的 `markWord`)。 - 第二下點回來,走的卻是「這次又沒聽到」那條路(plan.js:334 `clickInReview`)。 - 所以這句被自動改成 `miss`。a3 實測:原本是 `ok`,變成 `miss`。 - 全部題目的畫面上,那個字看起來和原本一樣紅,看不出有變。 - 複習卡上,那個字變成實心紅,「還是有沒聽出來的」亮起,「這次都聽出來了」不能按。複習的進度仍寫「✓ 已完成」(截圖 p05)。 - **後果:** - 如果沒回複習卡,就按完成:這句下一次會再複習。不會丟進度,只是多複習一次。 - 如果回複習卡把那個字點掉:結果恢復成 `ok`。但取消時,因為「上次就點過」,標記保留(plan.js:208),次數卻又扣 1。 - a3 的例子:6-1、6-3 都還標著 the,缺口清單卻寫 the ×1,應該是 ×2。 - 在全部題目連點三下,那個字會一直是紅的,「取消」像沒作用(a1 S9d)。 - **為什麼是低:** 要同時符合三件事:正在複習、跑去看全部題目、在今天複習句「上次點過的字」上連點。會錯的只有缺口次數,和多一次複習。 - **建議改法:** plan.js:332 的條件加一種情況。全部題目點回一個字時,如果那個字是「上次就點過、這次剛被拿掉」(在 `reviewSnap[q].marks` 裡、不在 `reviewMarks[q]` 裡),就走 `markWord(true)`,把舊標記放回去,不要當成「又沒聽到」。 - **要補的測試:** a1 S9 的順序跑完後,結果仍是原本的答案,the 的次數不變。 ### 低 2. 「今天只複習」框裡按連續聽:播放時框裡看不出在播,也停不了 腳本實證:a3、a4;讀程式 ui.js:247、270、278。 - 按「▶ 先連續聽複習的 3 句」後,聲音會播。但框裡的按鈕和文字都沒變(截圖 p03)。 - 「正在播第 1 / 3 句」和「■ 停止」只出現在下面的③區。手機上那裡在 7,293px 的位置,看不到。 - 播完後,框裡也沒有「已聽完」的標示。只有底部條的「連續聽 □」會變成「✓」。 - 播放中按「接著學新的」,再按新題的 🔊: - 連續聽會被打斷,沒算完成。這是對的,因為沒聽完。 - 但③區會一直卡在「■ 停止 正在播第 1 / 2 句」,其實已經沒在播(a4,截圖 p06)。要按一次「停止」才會恢復。 - ③區卡住,是本來就有的行為。這個新按鈕讓人更容易走到這條路。 - **影響:** 不會丟進度。只是累的晚上按下去,看不出到底播完了沒有。 - **建議改法:** `startCont` 播放中,也重畫 `#rvdone`。把框裡那顆按鈕換成「■ 停止(第 k / n 句)」;播完換成「✓ 已聽完」。 ### 低 3. 手冊只有開頭那一頁看得到「回到練習」;目錄和翻頁按鈕沒寫第 7 節已過期 腳本實證:a4、a5;截圖 g02。 - 手機上讀完第 8 節(最常用的那節),頁面上沒有回練習的連結。「下一節」是灰的。要點 ☰,再點「← 回到練習」。 - 05d r5 L5 抱怨的「回練習藏在 ☰ 裡」,在第 1~8 節還是一樣。 - 目錄還寫「7 過渡期怎麼練 10/5–10/6」。第 8 節的「上一節」按鈕也一樣,因為它的文字是從目錄抓的(guide.html:332-335)。 - **建議改法:** 每節的翻頁列(`.pager`)加一個「← 回到練習」;目錄第 7 項加「(已過期)」。 --- ## 3. 壞味道(只看這兩輪改過的部分) | 條號 | 有沒有 | 位置 | 白話 | 建議 | |---|---|---|---|---| | 1 重複程式碼 | 有 | ui.js:140 和 ui.js:455 | 「這次學會了」=`mastered[q] === current`,寫了兩次。05j 已提到「學會」藏在 `stage >= INTERVALS.length`(plan.js:234),現在又多兩處 | plan.js 給一個 `learnedThisSession(s, q)`,兩處都呼叫它 | | 1 重複程式碼 | 有 | guide.html:81 對照 guide.html:267 | 節名寫了兩份:目錄一份、標題一份。這次只改了標題,兩份就不一樣了(低 3) | 翻頁按鈕和目錄改成讀同一個來源,或兩處一起改 | | 2 過長函式 | 輕微 | plan.js:291-360 `apply` | 本來就長。這輪 mark 分支又多了一層條件,還有一個中途 `break`(332-335) | mark 分支抽成 `onMark(s, book, e, now, ses)` | | 3 過大類別 | 沒看到(改動部分) | — | ui.js 整支 593 行,是原本就這樣,這輪沒有明顯變大 | — | | 4 過長參數列 | 輕微 | plan.js:215 `clickInReview(s, book, q, idx, on)` | 五個參數,和 `remark`、`markWord` 一樣 | 跟第 5 條一起處理 | | 5 資料泥團 | 輕微 | plan.js:192、202、215 | `(s, book, q, idx, on)` 這一組一直一起傳 | 包成一個「點字」物件 `{q, i, on}` | | 6 依戀情結 | 輕微 | plan.js:215-226 | `clickInReview` 一直伸手去改 session 的四個欄位:reviewMarks、reviewResult、reviewAuto、reviewSnap | 可以接受:plan.js 本來就管 session。先不動 | | 7 霰彈式修改 | 有 | `reviewAuto`:plan.js:80 設預設、219 設、221-222 用、340 清;app.css 底部條按鈕的規則散在 :153、:174、:200、:207,觸控尺寸也分成 :175、:199 兩段 | `reviewAuto` 什麼時候有效,要看四處才知道。CSS 照「第幾輪審查」分段,改一顆底部按鈕要找四段 | `reviewAuto` 的設和清都放進 `clickInReview`/`reviewAnswer`。CSS 照元件合併(等功能定稿後當成獨立整理) | | 8 發散式變化 | 有 | ui.js:278 對照 ui.js:240-251 | 「連續聽」的入口現在有兩個:框裡的按鈕、③區。播放狀態只更新③區,兩邊不一致(低 2) | 播放狀態由一個函式決定,兩個入口都呼叫它 | | 9 基本型別偏執 | 有 | plan.js:80、219、224 | 「沒作答」用字串 `"none"` 表示,和真正的答案 `"ok"`/`"miss"` 放在同一欄 | 存 `null`,或存 `{ was: undefined }` | | 10 過度耦合的訊息鏈 | 有(舊的,沒動) | plan.js:324、plan.js:347 | `((s.plans[ses] \|\| {}).reviewSnap \|\| {})[e.q]` 仍出現兩次。04 已列為重構候選 | 抽成 `snapAt(s, ses, q)` | | 11 冗贅類別/淺模組 | 沒看到 | — | — | — | | 12 注釋當除臭劑 | 有 | plan.js:331 | 「(except removing an older mark)」是在解釋一個不好讀的四段條件。低 1 就藏在這個例外裡 | 把條件拆成有名字的小函式,例如 `isNewMissInReview(...)`,註解就不需要了 | 建議:照主理人規矩,壞味道的整理是獨立任務,不要跟修低 1~3 混在一起。 --- ## 4. 極端情況(只列一行,不標嚴重度) - 複習卡誤點、又退回「沒作答」時,`reviewPrev` 不會還原(plan.js:224 只還原 review)。依讀程式推理,正常使用碰不到差別,因為同一次裡它不會被用到。 --- 本報告的每一項判斷都來自實際查證:逐行讀 plan.js、ui.js、app.css、guide.html;跑 plan 52/52、sync 8/8、server 11/11、e2e 75/75;跑 audit7 的 a1~a6 腳本(a1、a2 載入真正的 plan.js;a3~a6 用真的 Chrome 在手機觸控模式操作)。標「讀程式」「推理」的只有極端情況那一行,以及壞味道的判斷。