---
url: https://docs.nemo12.com/reference/course-design-standard/ielts-and-concepts.md
description: >-
  Mạch 12 course IELTS, key concepts và bộ ví dụ, sáu khoá ELA, key takeaways và
  cụm bài đọc của tầng luyện đọc IELTS.
---

# Chuẩn Course · Course IELTS, key concepts, ELA và cụm bài đọc

Một phần của [Chuẩn thiết kế Course · Unit · Lesson (SRC-609)](./index.md). Mạch 12 course IELTS, key concepts và bộ ví dụ, sáu khoá ELA, key takeaways và cụm bài đọc của tầng luyện đọc IELTS.

## CD-13 · Mạch 12 course IELTS kiểu mới (SRC-658)

Chỉ đạo chủ dự án 2026-08-31. Một progression 12 course, **IELTS không chi phối cấu trúc Unit**;
IELTS chỉ xuất hiện như bối cảnh xác thực để learner biểu hiện năng lực đã dựng được.

| Tầng | Course |
| --- | --- |
| Nền | 01 Language Foundations · 02 Reading the World · 03 Listening to Meaning |
| Giao tiếp | 04 Speaking with Purpose · 05 Writing with Purpose · 06 Ideas and Arguments |
| Học thuật | 07 Critical Reading · 08 Academic Language · 09 Research and Synthesis |
| Tích hợp | 10 Communication and Interaction |
| Biểu hiện | 11 IELTS Performance |
| Thuần thục | 12 IELTS Mastery |

Nguyên tắc chủ dự án đặt ra và đáng giữ cho cả 12 course: **một task IELTS không phải một Learning
Experience.** "Làm 10 câu True/False/Not Given" là luyện tập hoặc đo, không phải học. Learning
Experience là: learner đọc một claim, tìm bằng chứng, phân biệt stated / implied / unsupported, bảo
vệ quyết định của mình — rồi mới gặp dạng câu hỏi IELTS. Nhờ vậy IELTS Score tách khỏi Learner
Capability, và khi điểm thấp hơn năng lực thì hệ hỏi được câu đúng: nghẽn ở task interpretation,
quản lý thời gian, hay ở chính năng lực?

Nội dung nguồn: `scripts/course-content/ielts-core/*.json` — chữ của chủ dự án, chép nguyên văn.
Sinh migration bằng `node scripts/ielts-core-to-sql.mjs`. `subject_id` là `ielts_core`, tách khỏi
bốn môn `ielts_reading/listening/writing/speaking` đang có: 12 course này là MỘT mạch liên tục,
xếp vào bốn môn kia là cắt rời chính cái progression làm nên giá trị của nó.

**Song ngữ** (migration 0181): mỗi mẩu chữ learner đọc ở tầng Course có hai cột `_vi` và `_en`. Spec
viết bằng tiếng Anh nên lúc nạp ghi tạm chính chuỗi tiếng Anh vào cả hai cột; dấu hiệu "chưa dịch"
là `_vi = _en`, không phải một cột cờ — một bản dịch thật không thể trùng nguyên văn bản gốc, nên
dấu hiệu này không nói dối được. Chặng `translate` trong `workers/api` lấp dần bản Việt bằng model.

Một Unit có nhiều Essential Question và một Lesson có nhiều Guiding Question:
`curriculum_unit_questions` và `curriculum_lesson_questions` giữ đủ bộ, còn cột đơn cũ luôn chứa
câu thứ nhất để mọi thứ đang đọc nó vẫn chạy.

## CD-14 · Key Concepts, Skills và bộ ví dụ (SRC-659)

Chỉ đạo chủ dự án 2026-08-31.

**Mỗi Unit có hai danh sách TÁCH RIÊNG** (`curriculum_unit_concepts`, migration 0183):

| | Là gì | Dạng chữ |
| --- | --- | --- |
| Key Concept | thứ learner phải **hiểu** | danh từ hoặc cụm danh từ |
| Skill | thứ learner phải **làm được** | bắt đầu bằng động từ, quan sát được |

Không gộp một danh sách: một unit đủ concept mà không có skill nào thì nó là bài giảng chứ không
phải unit, và gộp lại thì thiếu vắng ấy vô hình. Mỗi mục kèm một câu nói rõ nó nghĩa là gì **trong
đúng unit này** — nhãn trần ("Suy luận") đọc xong ai cũng gật, và chính vì thế nó không kiểm được:
hai người hiểu hai kiểu vẫn cùng gật. Cổng `rejectConceptShape` chặn hai lỗi máy đọc được: một nhãn
nằm ở cả hai danh sách, và skill viết thành danh từ.

**Mỗi Lesson có 3 ví dụ đúng và 3 ví dụ dễ nhầm** (`curriculum_lesson_examples`). Cặp này phải đi
cùng nhau và đó là toàn bộ ý nghĩa của nó: một khái niệm chỉ thật sự rõ khi learner thấy được
**đường biên** của nó. Ba ví dụ đúng dạy cái bên trong; ba ví dụ suýt-đúng mới dạy cái bên ngoài, và
biên giới nằm ở giữa. Vì thế `near_miss` phải **trông như đúng** và chỉ gãy ở một chỗ nhỏ — một ví
dụ sai lộ liễu chỉ làm đủ số, không dạy được gì. Cột `why` của near_miss là phần đáng giá nhất:
không nói ra chỗ gãy thì learner chỉ học thuộc rằng "cái này sai".

Cả hai do xưởng sinh (`curriculum.author-unit-concepts` v1, `curriculum.author-examples` v1) qua
chặng `concepts` và `examples` của `runCurriculumAuthoring` — cách tốt nhất, tuy từ SRC-672 không còn bắt buộc.

**Câu chuyện SCQA có cổng chất lượng, và cổng ấy nhìn lại cả cái đã sinh.** `rejectStory` chặn ba
kiểu hỏng: tình huống mở bằng một LOẠI người ("bạn…", "học sinh…") thay vì một người cụ thể; chỗ
vướng nói chung chung ("không chắc chắn", "gặp khó khăn") — "chưa biết" không phải một chỗ vướng, nó
là chỗ trống; và lối thoát chỉ báo rằng sắp học ("chúng ta cần tìm hiểu…") thay vì kể người ấy đã
LÀM GÌ. Luật giống hệt rubric của `workers/foundry`: hai cỗ máy sinh nội dung phải cùng một thước,
không thì cái yếu hơn quyết định chất lượng thật.

Cột `curriculum_lesson_stories.weak` (migration 0184) đánh dấu dòng cần viết lại, và chặng `story`
nhận cả bài chưa có chuyện lẫn bài có chuyện trượt cổng. Không có cột này thì cổng chỉ chặn cái sắp
sinh, còn thứ đã nằm trên production ở lại mãi — đúng cái bẫy đã xảy ra với Big Idea. Lần đánh dấu
đầu tiên bắt **249 trong 366 câu chuyện**.

**Bốn competency bổ sung cho khung ELA — ĐÃ BỎ (SRC-699, chỉ đạo chủ dự án 2026-09-10).** ELA.8
Listen for Meaning · ELA.9 Use Academic Language · ELA.10 Adapt to Audience · ELA.11 Improve My Own
Performance từng được migration 0183 (SRC-659) thêm vào framework `nemo`, vì bảy competency gốc của
Building 21 không có chỗ treo cho bốn năng lực mà mạch 12 khoá dựng nên. Chủ dự án xem lại khung và
quyết bỏ cả bốn, ở cả `nemo` lẫn khung English (migration 0211).

**Chỗ treo dựng lại bằng một lĩnh vực RIÊNG (SRC-700, migration 0212).** Bốn năng lực mới nằm trong
lĩnh vực `EAP` — English for Academic and Exam Purposes — của khung `english`, KHÔNG nối thêm số vào
ELA: bảy competency ELA là bản gốc của Building 21, và nối số vào đó chính là chỗ lần trước phải gỡ
ra. Chỉ thêm ở khung `english`, không thêm ở `nemo`.

| Competency | Khoá dựng nó |
| --- | --- |
| `EAP.1` Listen for Meaning | 03 Listening to Meaning |
| `EAP.2` Speak and Interact with Purpose | 04 Speaking with Purpose · 10 Communication and Interaction |
| `EAP.3` Use Academic Language | 08 Academic Language |
| `EAP.4` Perform and Improve Under Exam Conditions | 11 IELTS Performance · 12 IELTS Mastery |

Tên và câu tuyên bố lấy từ chính unit của sáu khoá, không nghĩ ra từ đầu, nên mỗi năng lực có thật
một mạch bài dạy nó. Quan hệ khoá ↔ năng lực ghi bằng `curriculum_course_sources` (`kind='b21'`,
`ref_code='EAP.n'`) — đây đúng thứ ELA.8-11 ngày trước KHÔNG có, nên "chỗ treo" khi ấy chỉ tồn tại
trong tài liệu.

**Còn để ngỏ có chủ ý:** bốn competency này CHƯA có skill và bậc. Đó là việc soạn nội dung có cổng
riêng, không phải thứ sinh kèm trong một migration; bày ra một cái thang bậc nghĩ vội còn tệ hơn
chưa có thang, vì Learner Model sẽ chấm theo nó.

## CD-14b · Sáu khoá ELA, môn `ela` (SRC-701)

Chỉ đạo chủ dự án 2026-09-10: "Soạn ra 6 khoá khác, không phải là IELTS Core, mà là để học tiếng
Anh, dựa theo các competency framework trong English Language Arts."

**Môn riêng `ela` trong Squid, không nhét vào `english`.** Thang `core` của môn `english` đã kín 15
slot (level 1-5 × seq 1-3), nhưng lý do chính không phải là chỗ: E1-E15 dựng **nền ngôn ngữ** (âm,
từ, câu, ngữ pháp), còn sáu khoá này dựng **năng lực dùng** cái nền ấy. Hai mạch trả lời hai câu
khác nhau nên đứng thành hai môn.

| Khoá | Competency ELA nó dựng |
| --- | --- |
| A01 Đọc kỹ, đọc đúng | ELA.1 Read Critically |
| A02 Nói ra điều mình nghĩ | ELA.2 Express Ideas |
| A03 Kể lại một câu chuyện | ELA.5 Write Narrative Texts |
| A04 Giải thích cho người khác hiểu | ELA.4 Write Informative Texts |
| A05 Lập luận và tranh luận | ELA.3 Compose Evidence-based Arguments · ELA.6 Engage in Discussion |
| A06 Tìm hiểu và tổng hợp | ELA.7 Conduct Research |

Bảy competency của khung ELA phủ hết trong sáu khoá; A05 mang hai vì lập luận và thảo luận là một
năng lực nhìn từ hai phía. Quan hệ khoá ↔ competency ghi bằng `curriculum_course_sources`
(`kind='b21'`), đọc được bằng một câu SQL — đúng bài học của ELA.8-11 (SRC-699/700).

**Mỗi khoá 4 unit × 3 lesson**, nạp bằng `scripts/ela-to-sql.mjs` từ
`scripts/course-content/ela/*.json` (migration 0213). Spec có **đủ hai bản vi và en**, nên quy ước
"`_vi === _en` nghĩa là chưa dịch" của 12 khoá IELTS Core không áp dụng ở đây.

**Còn để ngỏ có chủ ý:** lesson mới có tên và Guiding Question. Học liệu, câu kiểm, điều đọng lại
để **xưởng nội dung** soạn tiếp — môn `ela` đã được thêm vào `SUBJECTS` của
`workers/api/src/modules/curriculum/authoring.ts`, đúng đường 12 khoá IELTS Core đã đi. Gõ tay hàng
trăm câu hỏi ngay trong một migration là cách chắc chắn để có một kho câu không ai soi được.

## CD-15 · Key Takeaways, và chuỗi phụ thuộc của các chặng (SRC-660)

Chỉ đạo chủ dự án 2026-08-31: "Bổ sung Key Takeaways vào cuối các Lesson, và Units."

**Takeaway không phải tóm tắt.** Tóm tắt kể lại bài đã nói gì; takeaway nói điều learner MANG ĐI,
dùng được ở chỗ khác kể cả khi đã quên bài này tên là gì. Đó cũng là chỗ model rơi vào mặc định, vì
kể lại thì chỉ cần đọc, còn nói điều mang đi được thì phải hiểu bài dùng vào việc gì — nên cổng
`rejectTakeaways` chặn ba dấu hiệu: mở đầu bằng lối kể lại, chép nguyên văn câu hỏi cốt lõi, và hai
takeaway trùng nhau.

Hai bảng riêng (`curriculum_lesson_takeaways`, `curriculum_unit_takeaways`, migration 0185) vì hai
hạng khác nhau: của một bài là điều đọng lại sau một buổi; của một unit là điều còn lại sau khi quên
hết các bài. Một bảng chung có cột phân loại thì mọi truy vấn phải nhớ lọc, quên một lần là trộn hai
hạng vào nhau.

Vị trí: **cuối** trang Unit và trang Lesson trên `ielts.nemo12.com`, và **đầu chặng 4** trên trang
học — chặng 4 chính là cuối bài, và điều đáng mang đi nên tới trước chỗ tự viết ra mình nhận thấy gì.

### Hai chiều dịch, hai prompt

Nội dung ở tầng Course đi theo **hai chiều dịch ngược nhau**, và gộp chúng làm một là hỏng:

| Chiều | Lo phần nào | Prompt | Dấu hiệu "chưa dịch" |
| --- | --- | --- | --- |
| Anh → Việt | chủ dự án viết (tên khoá, Big Idea, câu hỏi) | `b21.translate-vi` | `_vi = _en` |
| Việt → Anh | xưởng sinh (key concept, skill, takeaway, ví dụ) | `curriculum.translate-en` | `_en IS NULL` |

Mỗi chiều có cái bẫy riêng. Chiều sang Việt: chữ Hán chèn vào, Viết Hoa Từng Từ theo lối tiếng Anh.
Chiều sang Anh: dịch cả thuật ngữ tiếng Anh vốn đang nằm trong câu tiếng Việt, và nở một nhãn ngắn
thành cả câu giải thích.

Chặng `translate-en` đánh địa chỉ dòng bằng **`rowid`**, không bằng khoá chính: mấy bảng này có khoá
ghép (`curriculum_unit_concepts` là `unit_id, kind, seq`), nên `UPDATE ... WHERE unit_id = ?` sẽ ghi
cùng một bản dịch đè lên mọi dòng của unit ấy — bảng vẫn đầy, chữ vẫn có, chỉ là sai.

### Prompt phải cho model THẤY khuôn JSON, không tả bằng lời

Bốn prompt của xưởng cùng hỏng một kiểu trong ngày 2026-08-31: prompt tả hình dạng bằng lời ("mỗi
phần tử đủ ba ô", "mỗi mục kèm một câu") và model trả về sai hình dạng — mảng chuỗi thay vì mảng
object, hoặc đúng ngược nhau giữa hai trường. Cả bốn đều trượt schema, bị `runPrompt` trả về lỗi, và
không sinh được gì suốt nhiều nhịp cron.

Cách chữa rẻ và chắc: **dán một khuôn JSON thật vào prompt**, có dữ liệu mẫu đúng miền. Cùng bài học
với prompt dịch `b21.translate-vi` (SRC-642): model bắt chước ví dụ mạnh hơn nghe lời dặn. Đây là
luật cho mọi prompt mới của xưởng.

### Chuỗi phụ thuộc giữa các chặng, và một lỗi im lặng

Bốn chặng (`checks`, `pitfalls`, `examples`, `takeaways`) lấy **Lesson Outcome** làm đầu vào và
`continue` khi bài không có outcome nào. Mạch 12 khoá nạp từ spec chủ dự án, mà spec đặt outcome ở
tầng Unit — nên 201 lesson không có outcome, và bốn chặng ấy **lặng lẽ bỏ qua toàn bộ 201 bài mỗi
nhịp cron, không để lại một dòng lý do nào**. Nhìn từ ngoài: nội dung mãi không xuất hiện, sổ lý do
trống trơn, mọi cổng đều xanh.

Chặng `lesson-outcomes` sinh ra để gỡ đúng chỗ ấy, và nó đứng **trước** bốn chặng kia trong danh
sách. Bài học rộng hơn: **một chặng bỏ qua việc phải ghi lý do**, không thì trạng thái "không có gì
để làm" và "không làm được" trông y hệt nhau.

## CD-17 · Cụm bài đọc của tầng luyện đọc IELTS (SRC-783)

Chỉ đạo chủ dự án 2026-09-16: `ielts.nemo12.com` có một tầng **bài tập để thử nghiệm và để trao đổi
với team**, bắt đầu bằng luyện đọc. Hình dạng do chủ dự án đặt:

| Tầng | Ràng buộc |
| --- | --- |
| Unit | nhiều **cụm bài đọc** |
| Cụm | khoảng **50 từ vựng** quanh một lĩnh vực + **10 bài đọc** |
| Bài đọc | **30 câu hỏi** thuộc **10 nhóm**, mỗi nhóm **3 câu khác nhau** |

Mười nhóm: Main Idea · Detail · Evidence · Inference · Vocabulary in Context · Reference · Author's
Purpose · Tone · Organisation · Summary.

**Vì sao gom thành cụm chứ không rải chủ đề mỗi bài một nơi.** Chủ dự án nói thẳng chỗ này: passage
thứ hai KHÔNG đổi sang lĩnh vực mới mà viết lại cùng một trường từ vựng bằng một cách viết khác.
Nhờ vậy learner gặp lại **cùng một vốn từ** và **cùng mười thao tác nhận thức** nhiều lần, thay vì
mỗi bài phải dựng lại bối cảnh từ đầu. Đây cũng là lý do câu hỏi của một bài nằm CẠNH bài đó, không
tách sang trang khác: cái đang luyện là thao tác quay lại văn bản.

**Lưu ra JSON files, không vào D1** (chỉ đạo cùng ngày). Chỗ này là nơi thử nghiệm, nội dung còn
thay mỗi ngày; sửa file rồi deploy nhanh hơn và để lại vết review rõ hơn một lần seed. Mỗi cụm là
một thư mục trong `apps/learn/src/ielts/data/reading/`: `cluster.json` giữ phần chung và 50 từ vựng, mỗi
bài đọc một file `p<seq>.json`. **Một bài một file** vì mỗi cụm sẽ lên tới 10 bài: thêm bài là thêm
file, hai phiên soạn hai bài khác nhau chạm hai file khác nhau (luật nhiều-phiên-song-song).

Đáp án lưu bằng **chỉ số** trong mảng `options`, không bằng chữ "B" — đổi thứ tự lựa chọn là việc
thường xuyên, còn một đáp án ghi "B" thì im lặng sai sau mỗi lần đổi. Mỗi câu kèm một lời giải
tiếng Việt nói **cách tìm ra đáp án**, không chỉ nói đáp án là gì.

Khuôn dữ liệu bằng máy: `apps/learn/src/ielts/data/schema.ts`. Trang: `apps/learn/src/ielts/Reading.tsx`
(`/reading`, `/reading/<cụm>`, `/reading/<cụm>/passages/<n>`), chấm theo TỪNG NHÓM kỹ năng để kết
quả đọc ra là "yếu ở suy luận" chứ không phải "sai câu 17". Chỗ trao đổi với team nằm ngay dưới mỗi
bài (`apps/learn/src/ielts/TeamNotes.tsx`); **chưa có backend** nên ghi chú ở localStorage kèm nút chép ra
JSON để dán vào repo — muốn trao đổi thời gian thực thì đường đúng là một route trong `workers/api`.

Đợt đầu (2026-09-16): 3 cụm × 3 bài = 270 câu. Bảy bài còn lại của mỗi cụm lấp dần.

### CD-17b · Lĩnh vực của learner, và kho từ của lĩnh vực (SRC-784)

Chỉ đạo 2026-09-16: "Mỗi learner được chọn ra 1 domain chính (lĩnh vực) mà họ yêu thích, lưu vào
local storage, và luôn hiển thị ở đâu đó. Các bài reading cần được lọc ra để liên quan với domain
đó, và các bài listening cũng vậy." Và: "Khi learner đã chọn 1 domain, cần có chỗ xem word families
và lexical chính, sắp xếp theo độ phổ biến hoặc độ khó để học dần."

**Một danh mục lĩnh vực cho MỌI kỹ năng** — `apps/learn/src/ielts/data/domains.ts`, tám lĩnh vực. Reading
và listening đọc cùng bộ mã; nếu mỗi kỹ năng tự đặt danh mục riêng thì "lĩnh vực của tôi" mang hai
nghĩa khác nhau ở hai trang. Mã tiếng Anh (đi vào JSON và localStorage nên là định danh kỹ thuật),
nhãn tiếng Việt. **Mã chỉ được THÊM, không sửa**: sửa mã là làm hỏng lựa chọn đã lưu trên máy người
học.

Danh mục cố ý ngắn. Tám lĩnh vực đủ phủ các trường chủ đề IELTS hay gặp mà mỗi lĩnh vực vẫn có
nhiều cụm; chia hai mươi ngăn thì lọc theo lĩnh vực biến thành lọc mất gần hết nội dung.

Lựa chọn hiện ở **header của mọi trang**, không nằm trong một trang cài đặt: nó quyết định learner
nhìn thấy nội dung nào nên phải ở trong tầm mắt và đổi được ngay tại chỗ đang đứng. Lọc **không
xoá** cụm ngoài lĩnh vực, chỉ lùi chúng xuống sau một nút — lọc là để đỡ phải chọn, không phải để
cấm.

**Kho từ** (`apps/learn/src/ielts/data/lexicon/<domain>.json`, trang `/lexicon`): họ từ chứ không phải
danh sách từ rời, vì learner thường đã biết `pollute` mà vẫn kẹt khi cần `pollutant` hay `heavily
polluted` — một họ mở ra bốn chỗ dùng trong cùng một bài viết. Xếp **ba bậc theo độ phổ biến giảm
dần**, không xếp theo bảng chữ cái: bảng chữ cái là thứ tự để TRA, còn học dần cần một thứ tự mà
học xong bậc 1 đã dùng được ngay. Mỗi họ có collocation và một câu ví dụ lấy từ chính bài đọc của
lĩnh vực ấy.

### CD-17c · Đánh dấu vàng và hai mươi từ có sẵn nghĩa (SRC-785)

Chỉ đạo 2026-09-17: "Cho phép user được high-light vài từ trong đoạn thành màu vàng ở nền, để đánh
dấu. Trong mỗi bài đọc, có khoảng 20 từ là hệ thống đã chuẩn bị sẵn giải nghĩa."

Luật phân chia cử chỉ phải nói được bằng một câu: **bấm vào từ thường thì bật/tắt nền vàng; bấm vào
từ có gạch chân thì hiện nghĩa đã soạn** (trong bảng nghĩa có luôn nút đánh dấu). Không dùng thao
tác bôi đen cho việc thứ hai: trên điện thoại bôi đen bằng ngón tay là vật lộn với chính trình
duyệt.

Dấu vàng lưu theo **chữ của từ**, không theo vị trí trong đoạn — cùng một từ xuất hiện ba lần thì cả
ba chỗ cùng vàng (đúng điều learner muốn khi đánh dấu một từ mới: thấy nó quay lại ở đâu), và sửa
một dấu phẩy trong bài không làm lệch toàn bộ dấu của người học.

Bảng nghĩa hiện **ngay dưới bài**, không phải popup nổi đè lên chữ: learner đang đọc dở một câu, che
câu ấy đi để giải nghĩa một từ trong chính nó là tự phá việc mình vừa giúp.

Hai mươi mục nghĩa của mỗi bài nằm trong trường `glossary` của file bài đọc, và **phải xuất hiện
nguyên văn trong `paragraphs`** — trang dò chuỗi để tô, nên một mục sai chính tả sẽ lặng lẽ không
bao giờ hiện ra. `scripts/audit-ielts-content.mjs` (trong `npm run check:code`) bắt đúng chỗ đó,
cùng với 30 câu / 10 nhóm / 3 câu mỗi nhóm, đáp án nằm trong `options`, và `domain` có trong danh
mục.

### CD-17d · Hai cột, và đúng một câu hỏi mỗi lúc (SRC-786)

Chỉ đạo 2026-09-17: "Trình bày lại để bên trái là bài đọc, bên phải là câu hỏi. Mỗi thời điểm chỉ
có đúng 1 câu hỏi được hiển thị thôi."

Ba mươi câu xếp dọc dưới bài buộc learner cuộn qua cuộn lại, và mỗi lần cuộn là một lần mất chỗ
đang đọc. Bài đứng yên bên trái, câu hỏi thay nhau bên phải thì thao tác "quay lại đoạn 3 để kiểm
chứng" chỉ còn là liếc sang — mà đó đúng là thao tác cả mười nhóm câu hỏi đang luyện.

Một câu mỗi lúc còn cắt được cái nhìn trộm: thấy sẵn phương án của câu sau là thấy sẵn một phần câu
trả lời, rõ nhất ở nhóm evidence và summary nơi nhiều câu soi vào cùng một đoạn.

**Thanh ba mươi ô** trên đầu cột phải là chỗ duy nhất thấy được toàn cảnh (trắng chưa làm, xanh đã
chọn, xanh lá đúng, đỏ sai) và bấm thẳng tới câu bất kỳ. Một câu mỗi lúc mà không có thanh này thì
learner mất hẳn cảm giác mình đang ở đâu.

Chấm **từng câu** chứ không chấm từng nhóm như bản đầu: khi trên màn chỉ có một câu thì nút "chấm
cả nhóm" chấm những câu người học không nhìn thấy.

Trên màn hẹp không có trái phải: bài nằm trên, câu hỏi nằm dưới, kèm một lối tắt xuống thẳng câu
hỏi, và vệt breadcrumb rút còn nhánh cuối để tiêu đề bài không bị đẩy khỏi màn.

### CD-17e · Đọc bài thành tiếng qua AI Gateway (SRC-787)

Chỉ đạo 2026-09-17: "Hệ thống gọi vào API, rồi từ đó gọi vào AI Gateway của Cloudflare, để sinh ra
audio files, để đọc chính cái bài reading lên. Cho phép đọc toàn bộ, hoặc là đọc từng đoạn."

Đường đi: trình duyệt → `GET /v1/public/ielts/reading/{cluster}/{seq}/audio?paragraph=N&voice=…` →
`env.AI.run` với `gateway` (AS-10.1.1, cùng đường mọi lượt gọi model khác) → PCM ghép thành WAV →
R2 `CONTENT`. Model và hàm đọc dùng lại của bài chép chính tả (`modules/dictation/tts.ts`), không
dựng đường thứ hai.

**Endpoint nhận MÃ BÀI, không nhận chữ.** Nhận chữ tự do từ trình duyệt là mở một dịch vụ đọc thành
tiếng miễn phí cho cả internet trên hoá đơn của mình. Nhận mã bài thì tập nội dung sinh ra được là
hữu hạn và đã biết trước.

**Khoá R2 mang băm của chính đoạn chữ**, cùng mã bài và giọng. Sửa một chữ trong bài là khoá đổi và
file mới được sinh — không có cảnh audio cũ đọc bản chữ đã sửa, mà cũng không phải nhớ xoá cache.

**Một đoạn một file, kể cả khi đọc cả bài**: "đọc toàn bộ" ở phía trình duyệt là phát lần lượt các
file đoạn. Lượt chờ đầu tiên chỉ là một đoạn (vài giây thay vì gần một phút, và không chạm hạn của
một request Cloudflare); file đoạn dùng lại cho cả hai chế độ; và khi đang đọc cả bài vẫn biết đang
ở đoạn nào để tô sáng đúng đoạn ấy.

**Nội dung bài đọc dời về `content/ielts-reading/`** vì từ nay nó có hai người dùng: app ielts đọc
bằng `import.meta.glob`, worker API đọc bằng import tĩnh (esbuild không có glob) khai trong
`modules/ieltsReading/content.ts`. Để nội dung trong `src/` của một app rồi cho worker với tay sang
là dựng một phụ thuộc ngược giữa hai workspace. Thư mục `content/` ở gốc repo đã là chỗ của loại dữ
liệu ấy (`content/dictation/`).

### CD-17f · Tầng luyện nghe, lọc theo cùng lĩnh vực (SRC-789)

Chỉ đạo 2026-09-16 đòi listening cũng lọc theo lĩnh vực; 2026-09-17 chủ dự án chọn hướng **dựng
tầng nghe ngay trong ielts.nemo12.com** thay vì gắn domain cho kho "Nghe & chép lại" bên learn. Lý
do: kho ấy toàn chủ đề phim hoạt hình, gắn domain vào đó thì gần như cả kho rơi vào một lĩnh vực và
bộ lọc không có việc gì để làm.

Ngữ liệu ở `content/ielts-listening/`, **cùng danh mục domain với bài đọc** (CD-17b) nên một lựa
chọn của learner lọc được cả hai kỹ năng. Mỗi cụm nghe cố ý trùng lĩnh vực và trùng vốn từ với cụm
bài đọc: nghe lại chính những từ vừa đọc là bước chuyển từ nhận mặt chữ sang nhận ra âm, và nó rẻ
hơn nhiều so với học một vốn từ thứ hai.

Mỗi bài nghe 10 câu: 2 gist · 3 detail · 2 note completion (điền từ) · 2 inference · 1 attitude.
Điền từ **chấm bằng so khớp chuỗi đã chuẩn hoá** với danh sách cách viết được chấp nhận (`accept`),
không chấm bằng model: một bài luyện phải cho cùng một kết quả ở mọi lần chạy.

**Bản chữ bị giấu cho tới khi đã chấm hết câu hỏi.** Nghe mà nhìn được chữ thì đang luyện đọc. Mở
sớm một câu là mở cho cả bài, vì mọi câu đều nằm trong cùng một đoạn tiếng.

**Một file cho cả bài** (khác bài đọc, nơi mỗi đoạn một file): người ta nghe một mạch rồi mới trả
lời, cắt nhỏ ra là bỏ mất phần khó nhất của kỹ năng này. Đổi lại lượt sinh đầu tiên lâu hơn, nên
sau mỗi lần thêm bài mới thì gọi trước một lượt cho mỗi bài để learner luôn gặp bản đã có ở R2.

Bài **hội thoại** khai `turns` — người nói của từng câu — và API đọc hai giọng khác phái trong cùng
chất giọng. Khai tường minh chứ không suy ra bằng chỉ số chẵn lẻ: sẽ có ngày hai câu liền nhau
thuộc cùng một người, và lúc ấy quy tắc chẵn lẻ đổi giọng giữa chừng mà không ai nhận ra cho tới
lúc nghe.

### CD-17g · Bài khởi động: 3 tới 5 câu, đúng 3 câu hỏi (SRC-794)

Chỉ đạo 2026-09-17: "Tạo ra các bài reading dạng nhỏ, chỉ tầm 3-5 câu. Và đi kèm với nó cũng là 3
câu hỏi thôi. Để learners có thể dễ dàng khởi động, mà không thấy bị quá áp lực."

**Đây là một LOẠI NỘI DUNG RIÊNG, không phải bài đọc rút ngắn.** Khuôn của bài đọc (30 câu, 10
nhóm, 20 từ có sẵn nghĩa) chính là thứ tạo ra cái áp lực mà dạng này sinh ra để tránh; để chung một
kiểu dữ liệu là mời người soạn sau "bổ sung cho đủ". Nội dung ở `content/ielts-warmup/`, mỗi bài
một file phẳng.

**Không thuộc cụm nào**, chỉ gắn `domain`. Learner mở bài khởi động khi chưa muốn cam kết vào một
mạch mười bài; bắt họ chọn cụm trước là dựng lại đúng rào cản ấy. Bộ lọc lĩnh vực (CD-17b) vẫn áp
dụng.

Ràng buộc, do `scripts/audit-ielts-content.mjs` thi hành: 3 tới 5 câu, đúng 3 câu hỏi, **ba nhóm kỹ
năng khác nhau** (ba câu cùng hỏi chi tiết thì learner luyện một thao tác mà tưởng đã khởi động
xong), đúng một đáp án mỗi câu (dạng chọn nhiều làm người mới chùn tay và thuộc về bài dài), và mọi
từ trong bảng nghĩa phải xuất hiện nguyên văn. Cổng chặn chiều PHÌNH RA, không chặn chiều ngắn lại.

Trang `/warmup` mở thẳng một bài, không có danh sách: một danh sách ba mươi bài là một danh sách ba
mươi việc chưa làm. Ba câu hỏi hiện cùng lúc và chấm một lần (ở bài dài, mỗi lúc một câu là để cắt
cái nhìn trộm; ở đây không có gì để trộm). **Không lưu điểm, không đếm chuỗi ngày** — đúng hai thứ
ấy biến một việc nhẹ thành một nghĩa vụ. Chấm xong: lời giải, một nút sang bài khác, và một lối đi
sang bài dài cho ai đã sẵn sàng.
