# 05 第 2 關合約驗收:聽力練習模式(2026-10-04) > 驗收者:獨立審查(沒參與製作)。方法:逐條讀程式碼,並實際跑 `plan.test.js`(23/23 過)與 `server_test.py`(5/5 過)。沒有跑 e2e.js,只讀了它留下的 `evidence/e2e-results.json`(全部 ok)。 **總結:43 條中,一樣 36 條、不一樣 7 條(第 2、4、15、23、27、35、37 條)、沒做到 0 條。** ## 逐條對照 | 條號 | 判定 | 證據(file:line) | 白話說明 | |---|---|---|---| | 1 | 一樣 | plan.js:19;ui.js:292-322;index.html:17 | 第一次打開 `methodSeen` 是 false,說明卡出現在標題下方、題目上方。 | | 2 | 不一樣 | ui.js:448、ui.js:449、ui.js:294 | 按「我懂了」會收起並存檔。但按過「重看方法」後,`methodSeen` 被改回 false。之後只要有任何存檔(例如每 30 秒存一次已練時間),下次打開說明卡就會**自己展開**。 | | 3 | 一樣 | ui.js:295、ui.js:346、ui.js:449 | 收起後,上方一直有「📖 重看方法」,面板裡也有。點了就展開。 | | 4 | 不一樣 | ui.js:388、ui.js:394、ui.js:496 | 三區的順序對(ui.js:217-225)。但「看全部題目」這個選擇會被存起來。上次停在全部題目的話,下次打開看到的是**全部 10 組**,不是這一次的三區。 | | 5 | 一樣 | ui.js:387-396、ui.js:443 | 兩個畫面可以切換,按鈕文字會跟著換成「回到這次的內容」。 | | 6 | 一樣 | plan.js:66-68;ui.js:385、ui.js:420-434 | 播放、作答、點字、連續聽都會呼叫 `touch`,記下「開始」和開始時間。 | | 7 | 一樣 | plan.js:51-53;store.js:112-119 | 這一次的內容只排一次,存在進度裡。重開網頁會沿用,作答和點字也都在。 | | 8 | 一樣 | plan.js:150-153;ui.js:398-407 | 按「這次完成」→ 次數加 1、清掉這一次 → 馬上排好下一次的三區。 | | 9 | 一樣 | plan.js:26、plan.js:36-49 | 所有排程只看「第幾次」,日期只用來記開始時間,不參與排程。 | | 10 | 一樣 | plan.js:10、plan.js:29-33、plan.js:44-49;ui.js:222 | 從 6-1 開始,6→10 組之後接 1→5 組(照停車場 P2 推薦做)。做到一半的組,只排還沒學完的題。這組的 5 個新字也會顯示。 | | 11 | 一樣 | plan.js:9、plan.js:48、plan.js:143-145;ui.js:250 | 一次最多 10 題(同一組)。全部作答後標「✓ 已完成」。 | | 12 | 一樣 | plan.js:46、plan.js:145;ui.js:223;plan.test.js:151 | 都學完時新學清單是空的,顯示「第 1 冊的新題都學完了 🎉…」,並算作完成。有單元測試。 | | 13 | 一樣 | plan.js:36-42 | 只挑到期的,先依到期次數、再依題目順序排,最多 10 句。超過的下一次還是到期。 | | 14 | 一樣 | ui.js:218;plan.js:144 | 沒有到期句時顯示「這次沒有要複習的 ✓」,並算作完成。 | | 15 | 不一樣 | plan.js:77-82;ui.js:426;ui.js:108 | 「這一次」新學區的題目:作答+點過字 → 下一次到期,這部分正確。但在「看全部題目」裡,或已經學過的舊題,點字**不會**排進複習。偏偏那裡的題目也顯示「它會排進之後的複習」這句提示,所以畫面講的和實際做的不一樣。 | | 16 | 一樣 | plan.js:7、plan.js:120-128 | 第一次複習是隔 1 次。之後每按一次「都聽出來了」,間隔依序變成 3 次、7 次;隔 7 次那輪再按就算學會,從複習移除。 | | 17 | 一樣 | plan.js:124;ui.js:420 | 按「還是有沒聽出來的」:下一次再到期,而且可以再點字。補充:再點字是在上次點的字上改,點舊字會變成取消。 | | 18 | 一樣 | ui.js:138;app.css:85 | 複習時,上次點的字用淡紅色標出。 | | 19 | 一樣 | plan.js:131-136;ui.js:189-202、ui.js:239 | 依序連播這次學過的句子。播放中只顯示「正在播第 k / n 句」,不顯示句子。句子之間隔 0.7 秒。播完才算完成,按停止不算。 | | 20 | 一樣 | plan.js:135、plan.js:146;plan.test.js(clause 19, 20 那一條) | 這次沒有新學題時,改播複習句;兩者都沒有時,直接算作完成。小提醒:有新學題、但一題都沒做時,連續聽不能播,要先做新學。 | | 21 | 一樣 | ui.js:253-255;plan.js:147 | 三區都完成時出現大的「這次完成 ✓」。沒完成時是小字的「這次完成(提早結束)」。沒做完的下一次會再排進來。 | | 22 | 一樣 | ui.js:329-333、ui.js:351 | 面板顯示 複習 x/y、新學 x/題數、連續聽 ✓ 或 □、已練幾分。「新學」的分母是這組剩下的題數,不一定是 10。 | | 23 | 不一樣 | ui.js:471-478 | 只用「頁面是不是可見」來判斷。切分頁、縮小視窗會暫停。但切到別的程式時,只要瀏覽器還露在螢幕上(並排視窗、第二個螢幕),時間**繼續算**。 | | 24 | 一樣 | ui.js:245-251;app.css:36-38;ui.js:236 | 每區標題右邊顯示「✓ 已完成」或「x / y」,整區變綠色。空的區塊也會標完成。 | | 25 | 一樣 | ui.js:104-109 | 句子和選項照原樣顯示,沒有遮住。空格在作答前顯示 ______,跟舊頁一樣。 | | 26 | 一樣 | ui.js:13、ui.js:106 | 提示文字跟預告頁 6-3 逐字相同,放在播放鍵那一行的正下方。複習句用的是 6-4 的文字(ui.js:14)。 | | 27 | 不一樣 | ui.js:311-318 對照 02-preview.md §6-1 的表 | 表格有 7 列,時機也都對。但有 7 處文字被改短或改寫,例如「開始做文法題」變成「開始做題」、「修耳朵:把聲音和字配起來」變成「修耳朵」、「意思你早就知道」變成「意思早就知道了」。合約要求這張表的文字照預告頁。 | | 28 | 一樣 | ui.js:94、ui.js:420;plan.js:101-117;app.css:84 | 每個字都可以點,點了變紅底,再點一下取消。空格裡的答案要作答後才點得到。 | | 29 | 一樣 | ui.js:12-21、ui.js:113、ui.js:217-225 | 三區的說明句、點字提示、翻譯提示、複習提示、「老實按就好」都跟預告頁逐字相同。 | | 30 | 一樣 | ui.js:355-361、ui.js:435 | 清單列出字和次數,依次數排序。每個字後面有「▶ 題號」,按了播放那一句。 | | 31 | 一樣 | store.js:112-119;ui.js:378-384、ui.js:436、ui.js:448 | 每個動作都會存檔。server 模式下停 0.8 秒後送到 server,送出前先在這台裝置留一份副本。 | | 32 | 一樣 | plan.js:155-163;store.js:46-51;DEPLOY.md:78-79 | 本機模式第一次啟動時,會讀舊頁的 `tq1v2`,原封不動存一份到 `legacy`,再合併作答紀錄。舊頁的資料沒有被改動。注意:只在第一次啟動時讀;之後在舊頁新做的題目不會再自動帶過來。 | | 33 | 一樣 | store.js:168-189;ui.js:454-468 | 匯出會下載 json 檔。匯入時先問「確定要取代嗎?」,按確定才換掉。也吃舊頁格式的檔。 | | 34 | 一樣 | store.js:58-77 | 打開時先向 server 讀進度。這一次的內容和作答都在進度裡,換裝置可以接著做。(關頁時的風險見下方「其他風險」。) | | 35 | 不一樣 | store.js:78-92、store.js:152-163;ui.js:487 | 做到一半斷線:會顯示紅色的「連不上 server」,資料暫存在這台裝置,每 15 秒重試、連上自動補存,這部分正確。但如果**打開網頁的那一刻**就連不上(而且這台沒有暫存資料),或 server 回 500/502 這類錯誤,會悄悄進入「本機模式」:沒有「連不上 server」的提示,可能顯示空的或舊的進度,之後做的事也**不會**自動補存到 server。 | | 36 | 一樣 | ui.js:262-289、ui.js:431-441;對照 toeic-drill-01.html:293-372 | 全部題目裡的作答、統計、篩選、清除、翻譯、詳解、句子拆解、單字表、字族、整句/單字/拆解播放、語速都在。選項的打亂方式和舊頁相同,所以答案位置不變。唯一的差別:單字表預設是收起的,舊頁預設展開,不影響功能。 | | 37 | 不一樣 | store.js:135-146;plan.js:166-168、plan.js:182 | 「提示+重新載入+合併」就是 D1,已申報,這裡不重提。另外發現一個合併的漏洞:兩台裝置**改到同一個東西**時,一律用正在存檔的這台(比較舊的那頁)的版本,不看哪邊比較新。最容易遇到的是同一句兩邊點了不同的字:每句點的字是一整組一起存的,所以另一台點的字會**整組消失**,耳朵缺口清單的次數也會跟著對不上。 | | 38 | 一樣 | store.js:89-92、store.js:178-183;DEPLOY.md:78-79 | 沒有 server 時,進度存在這台瀏覽器(`tq-app-v3`)。可以先匯出,再匯入到 server 版。 | | 39 | 一樣 | app.css:142-155;ui.js:351;evidence/phone-01、phone-02 | 寬度小於 1240px 時,面板收成底部的一條(進度摘要+「☰ 進度與設定」),點了從下方展開完整面板。截圖裡 390px 寬沒有跑版,選項變成單欄。 | | 40 | 一樣 | toeic-drill-01.html 修改時間 2026-10-03 02:16(本案 10-04 才開始);extract_book.py:12-31 | 用 extract_book.py 的同一套讀法重新讀舊頁,結果跟 book.js 的 W、Q、TITLE **完全相同**(10 組、100 題)。新程式只負責顯示,沒有改內容。沒辦法比對修改歷史,只能從修改時間和內容一致來判斷舊頁沒被動過。 | | 41 | 一樣 | DEPLOY.md:100-104 | 這條是給以後出題的規則。這次沒有新增題目(見第 40 條)。規則已經寫進部署說明的「之後加新題目(第 2 冊)」一節,八項都列了,以後接手的人看得到。 | | 42 | 一樣 | server.py:26、server.py:140-155;guide.html:5、guide.html:60-64 | guide.html 在 server 的允許清單裡,打開不用密碼。有設定手機寬度,窄螢幕時目錄會收起。 | | 43 | 一樣 | ui.js:295、ui.js:321、ui.js:345;guide.html:72 | 入口有三處:面板裡(手機展開的面板是同一個)、說明卡內、說明收起後的那一行。手冊目錄最上方有「← 回到練習」。 | ## 建議修正 1. **第 2 條(說明卡會自己再展開)**:把「已經按過我懂了」和「現在要不要展開」分成兩個狀態。「重看方法」只改畫面上的展開狀態,不要改存起來的 `methodSeen`(ui.js:449)。 2. **第 4 條(打開時停在全部題目)**:開機時一律顯示這一次的三區(ui.js:496 改成固定 `setView("session")`),或不要把 `view` 存進進度。 3. **第 15 條(提示說會排進複習,實際上沒有)**:二選一,我推薦 A。 - A:在「看全部題目」的卡片拿掉「它會排進之後的複習」這句提示(ui.js:108 只在新學區顯示)。 - B:讓所有點過字、而且作答過的題目都排進複習。這會改到合約範圍,要先問主理人。 4. **第 23 條(切到別的程式還在計時)**:計時條件多加一個「這個視窗正在使用中」(`document.hasFocus()`,或監聽 focus/blur),跟現在的「頁面可見」一起判斷。 5. **第 27 條(表格文字被改短)**:把 ui.js:312-318 的 7 個儲存格改回預告頁 §6-1 的原文。 6. **第 35 條(打開時連不上 server,會悄悄變本機模式)**:用網址判斷模式——只要是從 http(s) 打開的,就當成 server 模式。第一次讀進度失敗(連不上,或收到 401 以外的錯誤)時: - 顯示「連不上 server」; - 先拿這台的暫存或空白進度繼續做; - 定時重試; - 連上後用三方合併補存(base 用空白)。 只有用 `file://` 打開時才進本機模式。 7. **第 37 條(合併時同一個東西兩邊都改,會蓋掉較新的)**: - 點的字:改成**每個字**分開合併(取聯集,再扣掉某一邊明確取消的字),不要整組二選一。 - 其他欄位(作答、複習排程、語速):兩邊都改時用時間比較新的那邊。做法是每筆加修改時間,或至少用 `updatedAt` 判斷。 - 加一條單元測試:「兩台裝置在同一句點了不同的字,合併後兩個字都在」。 ## 其他風險(不影響判定,供參考) - **關頁時最後幾個動作可能沒送到 server**(store.js:27-33、ui.js:479):關頁時送出的請求沒有加 `keepalive: true`,可能被瀏覽器中斷。資料不會不見,因為這台有暫存副本。但換另一台裝置時,會看不到最後幾個動作,要等這台再打開才補上。建議加上 `keepalive`。 - **同一個瀏覽器開兩個分頁,又剛好斷線**:兩個分頁共用同一份暫存副本(`tq-app-pending`),後存的會蓋掉先存的。斷線時,其中一個分頁做的事可能不見。 - **密碼失效時**(store.js:147-148):不會再重試。重新整理後如果選「先不用」,會進本機模式,之後做的事不會上 server(暫存副本還在)。 - **匯入時剛好撞到另一台裝置存檔**:匯入的內容會跟 server 那份合併,不是完全取代。 - **預告頁 §6-5 面板裡的「同步設定」連結沒做**:面板只顯示同步狀態文字(ui.js:335-350)。沒有對應的合約條文,所以不算進判定。 - **衝突後面板的文字不準**(ui.js:337):寫的是「已改用 server 上比較新的進度」,但實際做法是合併,建議跟上方提示的文字統一。 本報告的每一項判斷都來自實際查證(讀程式碼、跑兩組自動測試、用 extract_book.py 重新讀舊頁比對 book.js、看 e2e 的結果與截圖),不是推測。