SDD-038 · Kho nội dung trong D1
Một phần của SDD-038 Cổng IELTS. Phần đầu là đặc tả đang áp dụng, mỗi mục giữ số § cũ; diễn biến và lý do các vòng quyết định nằm ở mục Lịch sử quyết định cuối trang.
14. Kho từ nói về CỤM, không còn là một cuốn từ điển (SRC-863, 2026-09-19)
/ielts/lexicon trước đây là họ từ của lĩnh vực đang chọn, ba bậc, mỗi thẻ mở ra collocation và ví dụ (SRC-784). Một từ điển trả lời "từ này còn dạng nào" — câu hỏi chỉ nảy ra khi learner đang viết. Câu được hỏi ở đây khác hẳn: "tôi đã học được bao nhiêu rồi", và một danh sách bốn mươi họ từ không trả lời được nó.
Ba con số một cụm (workers/api/src/modules/ieltsSkills/lexicon.ts):
| Nghĩa | Vì sao tách ra | |
|---|---|---|
total | cụm dạy bao nhiêu từ | mẫu số, không phụ thuộc learner |
met | đã gặp | |
secured | đã gặp LẠI ở một bài khác | gộp vào met là khen learner vì lướt nhiều bài — đúng cách quên nhanh nhất |
Thanh tiến độ vì thế có hai lớp, không phải một phần trăm.
"So với bảy ngày trước" tính bằng cách chạy lại phép đếm trên quá khứ, không lưu ảnh chụp hằng ngày. Cách này luôn cùng mẫu số với con số hôm nay, kể cả khi cụm được bổ sung từ về sau — còn một ảnh chụp cũ thì đóng băng một mẫu số đã đổi, và hai con số cạnh nhau sẽ tính trên hai kho từ khác nhau mà không ai nhìn thấy. Dòng "+6 this week" chỉ hiện khi dương: không có "+0", không có mũi tên đi xuống (cùng luật với §8).
Trang một cụm (/ielts/lexicon/{cluster}) là MỘT danh sách phẳng: từ, nghĩa, và một chấm nói đã gặp chưa (● đã chắc · ○ mới gặp một lần · trống là chưa). Bỏ ba khối bậc có câu dẫn riêng, bỏ thẻ mở ra đóng vào, bỏ hộp collocation. Thứ tự KHÔNG xếp theo trạng thái: xếp theo trạng thái nghĩa là mỗi lần learner học thêm một từ thì cả danh sách nhảy chỗ.
Endpoint GET /v1/learners/{id}/ielts-vocabulary trả thêm done_refs (những bài của cụm đã làm) để app tự đánh dấu từng từ từ bảng nghĩa có sẵn trong bundle — thay vì gửi về hàng nghìn từ mỗi lần mở trang.
Nợ để lại, nói rõ ở đây: dữ liệu họ từ (data/lexicon/*.json, ba bậc + collocation + ví dụ) nay KHÔNG còn màn hình nào đọc tới. Chưa xoá vì nó là nội dung đã soạn và còn dùng được cho một trang "tra từ" về sau; chủ dự án quyết gộp lại hay bỏ.
17. Nội dung luyện rời file JSON, về D1 (SRC-868, 2026-09-19)
Đánh đổi phải nói thẳng: app nay phụ thuộc mạng ở những trang trước đây mở là có ngay. Mọi trang nội dung vì thế phải có trạng thái chờ và trạng thái hỏng thật sự dùng được — một trang trắng khi API chậm là bước lùi so với bản JSON.
Schema (migration 0255): một bảng ielts_content_items cho CẢ BA loại bài (passage, clip, warmup) vì chúng chỉ khác nhau ở phần thân; ielts_content_clusters cho cụm; ielts_content_questions tách riêng vì sub_skill của từng câu là thứ thật sự được truy vấn (thẻ Mastery ở §13 đếm theo đúng cột ấy). Mảng luôn-đọc-trọn-gói (paragraphs, options, glossary) nằm trong cột JSON: tách ra bảng con là thêm ba phép JOIN cho mọi lượt mở bài, đổi lấy một khả năng truy vấn không ai cần.
Số đã nạp: 16 cụm · 71 bài (52 đọc, 16 nghe, 3 khởi động) · 1729 câu hỏi · 200 họ từ của 8 lĩnh vực.
Chặng 2 — đường đọc công khai (modules/ieltsContent): bốn endpoint, đều public có chủ ý (tầng luyện vốn mở cho người chưa đăng nhập, SRC-792, và dữ liệu trả về không có dòng nào của learner). Một lượt gọi trả CẢ CỤM, vì learner đi từ cụm vào bài trong vài giây — đó là cách giành lại phần lớn thứ mất khi rời bundle. Ngược lại, danh sách cụm cố ý KHÔNG mang theo bài.
Chặng 3 — app đọc qua API (data/store.ts): ba lớp để lượt chờ chỉ xảy ra một lần — nhớ trong bộ nhớ tiến trình, gộp các lượt gọi trùng, và cả cụm về trong một lượt. Mọi trang nội dung nay có ba nhánh: đang tải, hỏng (kèm nút thử lại), có dữ liệu. Ba nhánh ấy KHÔNG gộp được với nhánh "gõ sai địa chỉ": gộp lại là báo với learner rằng bài không tồn tại trong khi chỉ có mạng vừa chập.
Phần đọc thẳng file JSON dồn vào data/bundled.ts và chỉ còn TEST với cổng kiểm dùng — chúng kiểm chính các file soạn thảo, nên chúng phải đọc file chứ không đọc API: một phép kiểm đi hỏi API là kiểm bản đã nạp, tức là bắt lỗi sau khi lỗi đã lên production.
E2E cũng đọc từ content/ qua một lớp chặn trong e2e/fixtures.ts. Để test đi thẳng ra api.nemo12.com thì vừa phụ thuộc mạng của máy chạy CI, vừa kiểm dữ liệu ĐANG CHẠY trên production thay vì dữ liệu của chính commit đang kiểm.
Chặng 4 — worker cũng rời JSON (ieltsContent/read.ts). Trước đây ieltsPractice/content.ts nêu tên TỪNG file JSON, vì esbuild của wrangler gói theo đồ thị import tĩnh và worker không có import.meta.glob: thêm một bài mới phải sửa hai chỗ, quên chỗ thứ hai thì endpoint đọc thành tiếng im lặng không biết bài ấy tồn tại. Nay cả hai file nội dung của worker biến mất cùng cái bẫy ấy.
Phép đếm vốn từ vẫn là hàm THUẦN: worker đọc một bảng tra cho cả kỹ năng trong một lượt rồi tra đồng bộ, thay vì hỏi D1 cho từng bài (bảy chục round-trip cho một lần mở trang).
itemsFor trong catalog.ts nay chỉ còn Nói và Viết. Đó không phải thiếu nhất quán: "bài" của hai kỹ năng ấy là một ĐỀ trong PRODUCTION_LADDER, nằm trong mã vì nó là KHUÔN của tính năng chứ không phải nội dung người soạn sửa hằng tuần — cùng lẽ với năm bậc ở levels.ts.
Đổi luôn một cổng kiểm. Cổng nội dung cũ từng bắt "bài này đã được nêu trong file worker chưa"; phép ấy hết nghĩa. Nó đổi thành phép so file seed với content/, rồi tới §19 thì cả hai bên của phép so ấy đều biến mất và nó thành scripts/audit-ielts-content.mjs.
18. Màn soạn nội dung, và chiều ngược từ D1 về repo (SRC-870, 2026-09-19)
admin.nemo12.com → tab Nội dung IELTS: cây cụm/bài bên trái, bài bên phải; sửa tên bài, đoạn văn, bảng nghĩa, từng câu hỏi, và bật tắt nháp/phát hành. Lưu khi RỜI Ô và chỉ gửi ô vừa đổi — cùng luật với MicroSkills và Courses: gửi lại cả bài là hai người sửa hai ô khác nhau cùng lúc thì người lưu sau xoá mất ô của người kia.
Luật nội dung chuyển về server (ieltsContent/validate.ts): từ trong bảng nghĩa phải xuất hiện nguyên văn trong bài, đáp án phải trỏ vào một lựa chọn có thật, không em dash. Khi nội dung còn trong repo, ba luật ấy sống ở cổng CI vì mọi lần sửa đều đi qua một commit. Từ nay có đường sửa KHÔNG qua commit nào, nên chúng phải chạy trước khi ghi. Chúng kiểm trên trạng thái SAU KHI GHÉP: rút ngắn mảng lựa chọn có thể làm đáp án cũ trỏ ra ngoài, và lúc ấy phần vừa gửi thì hợp lệ còn câu hỏi thì hỏng.
Hai thứ phải sửa theo, và cả hai là bẫy thật:
- File seed không còn xoá trước khi nạp. Bản đầu có bốn câu
DELETE, đúng khi repo là bản chính. Từ khi có màn admin, chính bốn câu ấy là thứ nguy hiểm nhất trong file: một lượt chạy lại xoá sạch mọi bài vừa soạn, và workflow seed không có nút hoàn tác. Gỡ một bài khỏi learner nay là đổi trạng thái sangdraft. - Có chiều ngược: một script kéo bản đang chạy từ đường công khai về
content/, rồi bộ sinh seed dựng lại file seed từ đó. Sau một lượt như vậy,git diffchính là bản ghi những gì đã sửa từ màn admin. §19 xoá cả hai script này cùng các file chúng ghi vào, nên đường review ấy hiện KHÔNG còn — đó là cái mất đã ghi rõ ở cuối §19.
Vì diff của lần kéo ấy là bản ghi duy nhất, thứ tự trường trong shape.ts khớp đúng thứ tự trong file soạn thảo — lệch thứ tự thì mọi file đều "đổi" và bản ghi vô dụng.
Một lỗi của SRC-868 lộ ra nhờ chính chiều ngược này: bộ sinh seed bỏ quên bảng nghĩa của bài KHỞI ĐỘNG, mà trang Warmup đọc thẳng item.glossary. Đã vá cả ba tầng (bộ sinh, shape.ts, và một lớp đỡ ở app để một trường thiếu không làm trắng cả trang), và nạp lại dữ liệu.
Số phận các file JSON: xem §19 — chúng đã bị xoá.
19. Xoá hẳn file JSON: D1 là bản duy nhất (SRC-875, 2026-09-19)
content/ielts-reading, content/ielts-listening, content/ielts-warmup và apps/learn/src/ielts/data/lexicon/*.json đã xoá. Bộ sinh file seed, chính file seed, và script export cũng đi theo: cả ba chỉ tồn tại để nối repo với D1, mà nay repo không còn giữ nội dung nào.
Ba thứ dựa vào các file ấy, và chỗ đứng mới của chúng:
| Thứ cũ | Nay |
|---|---|
clusterVocab.ts — kho từ 50 của cụm, nêu tên tám cụm bằng tay | đọc vocabulary_json của cụm ĐỌC cùng slug, ngay trong loadGlossaries |
data/bundled.ts + e2e/fixtures.ts đọc content/ | một file apps/learn/e2e/content.fixture.json đã commit, sinh bằng scripts/gen-ielts-test-fixture.mjs |
check-ielts-content.mjs trong npm run check:code | scripts/audit-ielts-content.mjs, chạy tay: npm run audit:ielts-content |
Vì sao phép rà không còn chắn CI. Thứ đáng rà nay nằm sau một lượt gọi mạng, và một cổng phụ thuộc mạng mà đứng chắn deploy thì đến ngày mạng chập là mọi phiên đứng — cùng lý do check:ui-latest cố ý không chắn CI (DS-001 §0). Phần gác thường trực đã chuyển vào chính lúc ghi (ieltsContent/validate.ts), nơi nó chặn TRƯỚC khi nội dung hỏng kịp tới learner; phép rà là lưới thứ hai, chạy sau một đợt soạn.
Fixture của test không phải nguồn nội dung. Nó là một bản chụp để test có thứ mà dựng màn hình, giữ mọi cụm nhưng mỗi cụm chỉ ba bài đầu. Test vẫn không gọi ra mạng: đi thẳng api.nemo12.com thì vừa phụ thuộc mạng của máy chạy CI, vừa kiểm dữ liệu đang chạy trên production thay vì dữ liệu của chính commit đang kiểm.
Cái mất, nói thẳng. Repo không còn bản sao nội dung nào, nên:
- Một lần sửa từ màn admin KHÔNG để lại diff nào để review. Chiều ngược (
export-ielts-content.mjs) đã xoá cùng các file nó ghi vào. - Khôi phục khi mất dữ liệu nay trông vào Time Travel của chính D1 (Cloudflare giữ 30 ngày), không còn một file seed trong repo để nạp lại.
Hai điều này là hệ quả trực tiếp của chỉ đạo, không phải sơ suất. Muốn lấy lại đường review thì cách rẻ nhất là một lượt chạy định kỳ ghi bản chụp vào một kho riêng — chưa dựng, và chưa ai yêu cầu.
26. Ba ky nang con lai du 20 de (SRC-888, 2026-09-20)
26a. Reading: 12 bai moi, khong dong den code
Mot de Doc la BA bai cung mot cum, nen so de bang tong floor(so bai / 3) cua tung cum. Bon cum day (10 bai) cho 3 de moi cum; bon cum mong (3 bai) chi cho 1 de. Dot nay them 3 bai cho moi cum mong: 8 cum, 64 bai, va dung 20 de. Khong co dong code nao doi - cong thuc ghep de von da dung.
Moi bai giu nguyen rang buoc cu: 5 doan chung 400 tu, 30 cau hoi chia dung 10 nhom moi nhom 3 cau, 20 muc tu va muc nao cung phai XUAT HIEN NGUYEN VAN trong bai (trang to tu bang cach do chuoi).
26b. Writing va Speaking: de la NOI DUNG, nen phai co cho chua
Hai ky nang nay khong ghep de tu bai nao ca. Mot de Writing LA hai de bai, mot de Speaking LA ba chum cau hoi. Truoc dot nay chung nam trong PRODUCTION_LADDER - mot mang trong ma nguon worker, dung cho ma muc 25b vua chot la khong duoc de noi dung nua: sua mot de bai phai sua code, deploy lai worker, va man admin khong voi toi.
Migration 0257 dung hai bang: ielts_exam_papers va ielts_exam_parts. Hai bang chu khong phai mot cot JSON, vi PHAN moi la thu learner mo ra lam, va man admin sua mot phan thi khong nen ghi lai ca de.
Task 1 dung dang TABLE. Task 1 Academic von mo ta BIEU DO, ma he chua co cho chua hinh. Table la mot dang Task 1 co that trong ky thi, nen day khong phai do thay the tam - so lieu viet bang chu trong body_md, learner mo ta y het khi mo ta mot bieu do.
26c. Mot phan de co URL rieng, va van dung o lam bai cu
Truoc: ca hai phan cua de Writing deu tro ve bac 3, nen bam Task 1 hay Task 2 cung ra dung mot trang. Nay moi phan mot dia chi: /ielts/writing/tests/paper-2/1.
Ten route la paperPart chu KHONG phai examPart. examPart da co nghia khac tu SRC-815 - no la mot DANG de trong phan thi (/writing/parts/task-2), khong phai phan thu n cua mot de cu the. Hai thu nghe giong nhau toi muc dung chung mot ten la dat bay cho nguoi sua sau.
PaperPartPage dung lai dung WritingBox va SpeakingBox da co. Hai o ay mang theo nhung thu chi lo ra sau khi co nguoi dung that: ban nhap giu trong localStorage theo ma de, tran do dai bay ra TRUOC khi lam bai, duong nop va duong nhan xet. Dung mot o thu ba la dung lai ca bon thu ay, va cai dung lai se thieu dung nhung cho nguoi ta chi nho ra khi da mat bai mot lan.
26d. Hai kho de, mot duong nop
Server cham theo prompt_id. Thang luyen tra trong ma nguon; de thi tra o D1 qua examPartPrompt(). Duong nop thu kho trong ma truoc vi no khong ton luot doc D1 nao.
De thi cham o BAC 3 (gat nhat, co diem band): mot de dung dang phong thi ma cham o bac 1 thi nhan xet de dai hon thuc te, va learner mang cai de dai ay vao ngay thi. Nhung SAN DO DAI khong lay theo bac ma lay tu chinh phan de: Task 1 can 150 tu, Task 2 can 250, hai phan cung bac.
Bang so lieu va cue card DI THEO de toi model cham. Thieu chung, model doc "Describe the table" ma khong co bang nao, roi che learner bia so lieu.
49. Khu luyện đề: thẻ một dòng, gom theo lĩnh vực, có dấu đã làm (SRC-926, 20.09.2026)
49a. Thông tin không phân biệt được thẻ này với thẻ kia thì không giúp chọn
Mọi đề Đọc đều 60 phút và ba phần, và câu "Three passages from one field..." lặp nguyên văn trên mọi thẻ. Ba dòng ấy bắt tên đề - thứ duy nhất learner chọn theo - cạnh tranh chỗ đứng với hai dòng nói cùng một điều. Số phút và số phần không mất: chúng ở trang đề, nơi learner sắp bắt đầu và lúc ấy mới cần biết mình cam kết bao lâu.
49b. Gom theo lĩnh vực thay cho bộ lọc độ khó
Thang Level 1-5 (SRC-850) là thang của bài tập năng lực: nó nói ngôn ngữ và độ dài đoạn. Khu luyện đề thì mọi đề đều ở độ khó phòng thi - đó là định nghĩa của nó, nên một bộ lọc năm bậc ở đây sẽ có bốn bậc rỗng và learner bấm vào Level 2 thấy trống sẽ kết luận sai rằng nội dung còn thiếu.
Thay vào đó, thẻ gom theo lĩnh vực của cụm, tên nhóm in một lần, và tên thẻ rút còn "Paper 1". Nhóm giữ thứ tự xuất hiện chứ không xếp theo bảng chữ cái, nên các vỏ đề chưa mở tự rơi xuống cuối thành một nhóm riêng thay vì rải ô xám vào mọi nhóm.
49c. Đã làm / đang dở, đọc từ sổ công sức
GET /v1/learners/{id}/ielts-paper-parts?skill= trả về ref thô của những bài learner đã mở (<cụm>:<số bài>), chỉ tính lượt có đo thời gian - useTimeOnTask bỏ qua mọi lượt dưới 20 giây, nên bấm nhầm rồi quay ra không làm một đề sáng lên.
Máy chủ không bóc ref thành "đề số mấy": phép ghép ba passage hoặc bốn clip thành một đề sống ở data/examTests.ts và chỉ ở đó. Bóc hộ ở phía máy chủ là chép luật ghép đề sang chỗ thứ hai, rồi hai bên lệch nhau vào ngày đổi luật.
Ba trạng thái dùng đúng ngôn ngữ hình của SRC-919: đang dở (viền coral đặc, kèm "2/3 parts") nổi hơn đã xong (coral nhạt, mang dấu tích - xem §52, nơi cả hai khu dùng chung một bộ dấu). Viết và Nói chưa có địa chỉ riêng cho từng phần nên không mang nhãn nào - một nhãn ở đó sẽ nói về thứ khác với thứ learner nghĩ.
50. Tiêu chuẩn "đủ đề để luyện", và đợt một làm cho đạt (SRC-930, 20.09.2026)
50.1 Kho đề đo được lúc nhận việc
Đếm trên D1 production, không đếm trên repo - nội dung sống ở D1 từ SRC-875 và màn admin vẫn nạp thêm, nên mọi con số suy ra từ file seed trong repo đều thấp hơn sự thật:
| Kỹ năng | Bài trong kho | Đề ghép được | Câu một đề | Dạng câu hỏi |
|---|---|---|---|---|
| Reading | 64 passage / 8 cụm | 20 | 90 | 1 (multiple choice) |
| Listening | 80 clip / 8 cụm | 20 | 40 (32 choice + 8 gap) | 2 |
| Writing | 20 đề soạn riêng | 20 | Task 1 + Task 2 | 2 |
| Speaking | 20 đề soạn riêng | 20 | Part 1-2-3 | 3 |
Tức là lời hứa "hai mươi đề mỗi kỹ năng" của SRC-862 đã đạt. Thứ còn thiếu không phải số lượng.
50.2 Tiêu chuẩn: năm điều, điều nào cũng đếm được bằng máy
- Khối lượng - 20 đề mỗi kỹ năng, trong đó 4 đề niêm phong làm đề đo. Một learner luyện mười tuần thi một đề mỗi tuần; hết đề trước ngày thi thì tuần cuối không còn gì để đo.
- Hình dạng đúng đề thật - Reading 40 câu / 60 phút, Listening 40 câu / 30 phút, Writing 150 + 250 từ / 60 phút, Speaking Part 1-2-3 / 14 phút.
- Phủ dạng câu hỏi - mỗi dạng có thật của kỳ thi xuất hiện ở ít nhất 3 đề, mỗi đề có ít nhất 5 dạng.
- Phủ ngữ cảnh theo luật của từng kỹ năng - Listening bốn part xen kẽ hội thoại và độc thoại; Reading ba passage cùng lĩnh vực, tăng dần độ khó.
- Làm xong là có điểm - một đồng hồ cho cả đề, một phiếu điểm, một band.
Vì sao tiêu chuẩn KHÔNG phải một con số: một kho hai mươi đề mà đề nào cũng trắc nghiệm thì learner luyện hết vẫn chưa từng gặp dạng câu chiếm nhiều điểm nhất của đề thật. Đếm đề là đếm thứ dễ đếm nhất chứ không phải thứ quyết định.
50.3 Đợt một: điều 1, 2, 4, 5
Điều 3 là soạn nội dung mới (hàng trăm câu, nhiều đợt) nên tách ra làm sau; bốn điều còn lại là việc của mã nguồn và làm trọn trong đợt này.
Một trang riêng để NGỒI LÀM cả đề (ExamSit.tsx, /ielts/{kỹ năng}/tests/{đề}/sit). Đứng riêng khỏi trang đề vì ba luật của nó ngược hẳn với luyện tập, và cả ba phải đúng cùng lúc thì con số cuối cùng mới có nghĩa: không chấm câu nào cho tới lúc nộp; một đồng hồ cho cả đề chứ không phải mỗi phần một cái; đúng số câu của đề thật. Trang đề vẫn giữ các ô mở từng phần ra chỗ luyện - hai cách mở cùng một đề, và sự khác nhau nói thẳng trên nút.
Reading về đúng 40 câu (examSit.pickQuestions). Ba passage 90 câu rút thành 14/13/13. Phép chọn đi vòng tròn qua mười nhóm năng lực chứ không cắt đầu dãy: câu trong bài xếp theo nhóm, nên slice(0, 14) cho ra một đề chỉ hỏi bốn nhóm đầu bảng - và learner yếu Inference vẫn ra band cao. Phép chọn TẤT ĐỊNH, vì hai lần thi thử phải so được với nhau.
Listening xen kẽ hội thoại và độc thoại (examSit.listeningPapers). Bốn part lấy từ bốn lĩnh vực khác nhau như cũ, nhưng nay đúng kiểu: hội thoại, độc thoại, hội thoại, độc thoại. Kho hết clip đúng kiểu thì DỪNG, không ghép một đề bốn độc thoại cho đủ số - một đề sai hình dạng nằm trong danh sách tệ hơn một ô khoá, vì ô khoá thì learner biết mà chờ. Kiểu của từng clip đi kèm danh sách cụm (clip_kinds, kèm seq chứ không phải một dãy theo thứ tự: một bài nháp giữa cụm sẽ làm đề trỏ lệch một nấc).
Phiếu điểm quy band (data/band.ts). Bảng quy đổi chép nguyên, không nội suy: band không tuyến tính theo số câu đúng, và mọi công thức đều sai ở đâu đó. Đề rút gọn quy về thang 40 trước khi tra bảng. Viết lấy (Task 1 + 2 x Task 2) / 3 đúng luật chấm thật; Nói lấy trung bình ba part. Mọi chỗ hiện band đều nói kèm đây là ƯỚC LƯỢNG - bảng thật xê dịch vài câu giữa các kỳ thi.
Bốn đề cuối niêm phong (examSit.isSealed). Tính theo vị trí cuối dãy chứ không đánh dấu tay từng đề: kho đề còn dài ra theo từng đợt soạn, và một danh sách id viết tay sẽ trỏ vào giữa dãy ngay đợt sau. Kho dưới bốn đề thì không niêm phong đề nào - niêm phong hết kho là khoá learner ra ngoài chính thứ họ vào đây để làm.
Viết và Nói đi đường khác, và nói thẳng vì sao. Hai kỹ năng ấy không có đáp án để đối chiếu: mỗi task nộp lên và model chấm. Nên trang ngồi làm đề giữ ĐỒNG HỒ CỦA CẢ ĐỀ (thứ đang thiếu), còn việc chấm vẫn ở trang của từng phần, và phiếu điểm cộng lại từ band server đã ghi - GET /v1/learners/{id}/ielts-paper-bands?skill=. Đường ấy trả lượt mới nhất của mỗi phần chứ không phải lượt cao nhất: ghép những lần làm tốt nhất của từng phần ở những ngày khác nhau ra một điểm tổng là dựng một con số không mô tả buổi thi nào, mà nó lại trông y như một band thật.
50.4 Cái chưa có, nói thẳng
- Điều 3 chưa đạt: Reading vẫn 100% trắc nghiệm. Matching Headings, True/False/Not Given, Summary completion và các dạng còn lại là nội dung phải soạn, và đó là đợt tiếp theo.
- Kho clip Nghe thiếu một hội thoại: 39 hội thoại / 41 độc thoại, mà 20 đề cần 40/40. Đề cuối vì vậy rơi về ô khoá cho tới khi có thêm một clip hội thoại.
- Hết giờ thì nộp, không khoá màn hình: đúng như phòng thi, và cũng để một tab bị treo không xoá trắng công một tiếng của learner.
82. Trang lý thuyết: tên tiếng Anh dễ, và đưa ra từng mẩu (SRC-980, 22.09.2026)
Tên trang: "Cách làm" → "How to do this". Các nhãn mục cũng vậy: "Step by step", "One worked example", "Traps to watch for".
Vì sao đưa ra từng mẩu. Trước bản này cả bốn bước, ví dụ và các bẫy đổ ra cùng lúc - khoảng ba trăm chữ trong một màn. Với một learner vừa làm sai một bài thì đó là một bức tường, và bức tường thì người ta cuộn qua chứ không đọc.
Hai điều giữ lại có chủ ý: mẩu đã mở thì ở lại chứ không bị thay, vì bước 3 chỉ có nghĩa khi còn nhìn thấy bước 2 ngay trên nó - thứ được kiểm soát là tốc độ đưa ra, không phải trí nhớ của learner. Và nút Next đếm số ("Next step · 2 of 4"): một nút Next trơ trọi không nói được còn bao nhiêu, và không biết còn bao nhiêu là lý do người ta bỏ giữa chừng.
Riêng ví dụ có giải thì cắt đúng một chỗ: đề trước, chờ, rồi mới tới lời giải. Đọc lời giải cùng lúc với đề thì learner không bao giờ tự nghĩ, và phần này mất đúng chỗ đáng giá nhất của nó.
109. Nút Report trong mỗi bài luyện (SRC-1029, 24.09.2026)
- Màn hình (
ielts/ReportIssue.tsx): lá cờ nhỏ "Report" dưới cùng bài, bấm mở hộp thoại tick được nhiều lý do (audio chỉ hiện ở Listening), thêm ghi chú tuỳ chọn, gửi xong cảm ơn và learner ở nguyên bài. Báo cáo gắn với ĐÚNG câu đang hiện. - Server:
POST /v1/learners/{id}/ielts-reportsghi vàocontent_reportsvàcontent_review_queue(target_typeielts_exercise, target_id<skill>.<code>.l<level>.<seq>#<item>), cùng hai sổ mà báo cáo câu hỏi của learn vẫn dùng, nên Coral thấy ngay. Không cần migration.
110. Kho câu lớn hơn lượt làm, bốc ngẫu nhiên, né câu bị báo (SRC-1031, 24.09.2026)
- Hai con số tách ra (migration 0290):
ielts_micro_exercises.show_countlà số câu MỘT lượt, điền sẵn bằng số câu đang phát hành. Kho lớn lên (thêm câu), lượt làm giữ độ dài cũ. Thẻ bài nói số câu một lượt (items), kèmbank_itemslà cỡ kho. - Cờ báo lỗi: bài trả mỗi câu kèm
flagged= có reviewielts_exercisecòn mở (§109). Review đã xử xong thì câu quay lại vòng bốc. - Bốc (
drawItems, trên máy learner): đơn vị là CẶP ở bậc 2 và 4; ưu tiên câu đã làm trong lượt dở, rồi câu sạch ngẫu nhiên, rồi mới tới câu bị báo khi kho không đủ câu sạch. "Try again" bốc lượt mới. - Nội dung: phần máy xong trước; ba câu mới cho mỗi bài (khoảng 1.900 câu cho 632 bài) là việc soạn nội dung riêng, nạp bằng migration nội dung, không đổi
show_count.
113. Nạp kho câu: +3 câu mỗi bài (SRC-1038, 25.09.2026)
Phần nội dung của §110. Migration 0294 nạp 1.969 câu cho 632 bài (Writing 854, Reading 635, Speaking 300, Listening 180): bài thường +3, bài cặp +2 cặp. show_count giữ nguyên nên lượt làm không dài ra; bộ khởi động nay kho 6, mỗi lượt 3 (test warmupShape đo show_count).
Soạn bằng workflow 56 agent (28 soạn, 28 rà độc lập) cộng bộ kiểm máy. Giới hạn đã biết: ở các bài True/False/Not Given, bộ kiểm độ dài chặn "Not Given" làm đáp án (nhãn ấy dài nhất), nên câu MỚI chỉ dùng True/False làm đáp án; câu cũ vẫn có Not Given. Cần một ngoại lệ cho nhãn cố định trong luật SRC-990 rồi soạn bù.
131. "Có vẻ thiếu dữ liệu" ở trang How to do this, và audit nội dung (SRC-1092, 27.09.2026)
- Chấm điểm:
scripts/audit-content.mjs(skill/audit-content) chấm 1.030 đơn vị ở 6 khu: lý thuyết sub-skill (51), bài luyện trong D1 (632, cả bốn kỹ năng), bài luyện nguồn JSON (237), Grammar (36), Speak together (11), popover thuật ngữ (63). Kết quả: 100/100 mọi đơn vị. Dữ liệu KHÔNG thiếu. - Thủ phạm là giao diện: trang hiện TỪNG MỤC MỘT (
OneByOne): một bước rồi nút "Next step · 1 of 4", đề ví dụ rồi nút "Show the answer". Kiểm trên Chrome của chủ dự án (chỉ đọc): nút có và hiện rõ, nhưng nằm dưới mép màn hình thì trang trông như cụt. - Sửa:
MicroTheory.tsxhiện ĐỦ các bước, lời giải ví dụ và các bẫy ngay khi qua cổng; bỏOneByOne. Ba cổng đọc hiểu (SDD-039) giữ nguyên. Đảo một phần SRC-980 ("đề trước, chờ, rồi lời giải"): cái giá của việc trông như thiếu dữ liệu lớn hơn cái lợi của một nhịp chờ. - Ca e2e mới trong
ieltsMicroSkill.spec.ts.
Lịch sử quyết định
Từ mục 14. Kho từ nói về CỤM, không còn là một cuốn từ điển (SRC-863, 2026-09-19)
Chỉ đạo chủ dự án: "cần xem được danh sách các cụm, và độ mastery với từng cụm. Đại ý là learner thấy được là 'à, tôi đã học được từng đây, tôi đã tiến bộ hơn so với 7 ngày qua như thế nào'." Và với trang chi tiết: "cần lược bớt rất nhiều element thừa thãi, làm rối loạn cả trang."
Từ mục 17. Nội dung luyện rời file JSON, về D1 (SRC-868, 2026-09-19)
Chỉ đạo chủ dự án: "Chuyển dữ liệu từ JSON vào trong D1 Cloudflare đi, ko dùng JSON nữa." Và khi được hỏi app nên đọc từ đâu, chủ dự án chọn gọi API lúc chạy, cho cả bốn kho: bài đọc, bài nghe, kho họ từ, bài khởi động.
Đây là một lần đảo quyết định cũ, ghi rõ để người sau khỏi đoán. SRC-783 (2026-09-16) chốt ngược lại — "Data lưu ra JSON files" — vì lúc ấy tầng luyện là chỗ thử nghiệm, nội dung thay mỗi ngày, và sửa một file rồi deploy nhanh hơn chạy một migration. Nay nội dung đã ổn định, còn cái giá của JSON thì hiện rõ: sửa một chữ trong một bài là build lại và deploy lại cả app, và không có màn admin nào sửa được nội dung mà không qua một commit.
Nạp (lúc ấy): một bộ sinh đọc các file JSON rồi xuất một file seed, chạy qua seed-data.yml. Cả bộ sinh lẫn file seed đã XOÁ ở §19 cùng các file JSON — nhắc lại ở đây chỉ để đọc được lịch sử, đừng đi tìm chúng trong repo.
Số phận các file JSON: chúng nay là BẢN SAO để review, không còn là bản chính. Bản chính là D1.
Từ mục 18. Màn soạn nội dung, và chiều ngược từ D1 về repo (SRC-870, 2026-09-19)
Chỉ đạo chủ dự án: "Làm màn admin soạn nội dung trước đi" — trước khi bỏ các file JSON.
Từ mục 19. Xoá hẳn file JSON: D1 là bản duy nhất (SRC-875, 2026-09-19)
Chỉ đạo chủ dự án: "xoá file JSON trong content/ đi", sau khi màn admin đã chạy.
Từ mục 26. Ba ky nang con lai du 20 de (SRC-888, 2026-09-20)
Chi dao chu du an: "gio soan Reading, Writing, Speaking cho du 20 de luon".
Từ mục 49. Khu luyện đề: thẻ một dòng, gom theo lĩnh vực, có dấu đã làm (SRC-926, 20.09.2026)
Chỉ đạo chủ dự án 2026-09-20, trên ảnh chụp /ielts/reading/tests: "Bỏ hết mấy thông tin thừa như là '3 parts' hay câu phụ đề, và '60 mins'."
Từ mục 50. Tiêu chuẩn "đủ đề để luyện", và đợt một làm cho đạt (SRC-930, 20.09.2026)
Chủ dự án 2026-09-20, kèm ảnh trang Reading papers: "Như thế nào thì được coi là đủ đề để luyện? Cho tôi một tiêu chuẩn phù hợp. Rồi làm cho đủ tiêu chuẩn đó, cho cả 4 kỹ năng."
Từ mục 82. Trang lý thuyết: tên tiếng Anh dễ, và đưa ra từng mẩu (SRC-980, 22.09.2026)
Chỉ đạo: "Không hiểu từ 'cách làm' nghĩa là sao... cần dùng từ tiếng Anh mà siêu dễ hiểu, cho học sinh 10 tuổi vẫn hiểu được. Nội dung... cần trình bày theo kiểu đưa ra từ từ, mỗi lúc chỉ nên là 1 câu... Next, next thì mới tới câu tiếp. Hãy dẫn dắt từng bước như đang dạy trẻ con lớp 5."
Từ mục 109. Nút Report trong mỗi bài luyện (SRC-1029, 24.09.2026)
Chủ dự án: "Trong mỗi bài luyện tập, dưới cùng cần có chỗ để Learner có thể Report. Cần làm hơi giống như Duolingo tính năng Report."
Từ mục 110. Kho câu lớn hơn lượt làm, bốc ngẫu nhiên, né câu bị báo (SRC-1031, 24.09.2026)
Chủ dự án: "Trong mỗi bài tập cho các sub-skills, cần bổ sung thêm 3 câu nữa, sau đó hệ thống sẽ lấy ngẫu nhiên ra... Thi thoảng learners sẽ đánh dấu câu này hay câu kia có vấn đề, thì hệ thống sẽ không ưu tiên lấy những câu đã bị đánh dấu."
Từ mục 131. "Có vẻ thiếu dữ liệu" ở trang How to do this, và audit nội dung (SRC-1092, 27.09.2026)
Chủ dự án gửi ảnh "One worked example" chỉ có đề, không lời giải, và "Step by step" chỉ một bước: "có vẻ bị thiếu dữ liệu", yêu cầu tạo skill audit khu nội dung, chấm điểm, rồi sửa.