Skip to content

SDD-048 - Program Grammar ​

Nguồn: SRC-1104. Yêu cầu và mười quyết định: PRD-008.

1. Route ​

learn.nemo12.com/grammar/** là một mạch riêng trong app learn, cùng kiểu với sân IELTS: App.tsx nhận /grammar thành view grammarProgram rồi giao cả thanh địa chỉ cho grammar/GrammarApp.tsx (lazy load). Mạch tự giữ router của nó bằng history.pushState + popstate:

Địa chỉTrang
/grammarDashboard ba ô + danh sách 5 level
/grammar/<level>Giới thiệu level (tiếng Việt) + 6 mảng, mỗi mảng một thẻ danh sách bài
/grammar/<level>/<lesson>Năm khung Video, Rule, Examples, Common mistakes, Practice ("Coming soon"); bài có chủ đề IELTS tương ứng thì có link "Luyện ngay bài này" sang /ielts/grammar/<topic>

Id bài chỉ cần duy nhất trong một level (test gác). Slug tiếng Anh theo luật SRC-604.

2. Dữ liệu ​

Vòng này không có bảng D1 nào: danh mục là packages/grammar-catalog/catalog.json (level → units → lessons, trường ielts_topic nối sang 36 chủ đề cũ). Test grammar.test.ts gác ba điều: đúng 5 level, mỗi level 4-6 mảng; id bài không trùng trong level; tập ielts_topic bằng đúng tập chủ đề trong ielts/grammar/content, để một chủ đề mới thêm vào /ielts/grammar mà quên xếp vào program sẽ làm test đỏ.

Khi làm nội dung (vòng sau): nội dung bài theo khuôn JSON của Grammar hiện tại; tiến độ và phút học ghi vào learning_events (sổ giờ học duy nhất), không mở sổ thứ hai.

3. Giao diện và quyền ​

Dùng lại nguyên khối của sân IELTS: AuthProvider + SignInGate (bắt đăng nhập), NavBar/PageBody của ielts/Chrome.tsx, CardHint, HelpButton, AvatarMenu không ảnh. Không có component giao diện mới nào ngoài bố cục trang, đúng luật bộ ba Motion + shadcn + Tailwind. Chip trên thanh chỉ có phút học hôm nay (đọc ielts-effort, sổ chung cả bốn kỹ năng). Chân trang ký "NEMO GRAMMAR".

Membership (quyết định 7) chưa chặn gì ở vòng này vì chưa có nội dung để chặn; khi có nội dung thì dùng cùng cổng gói của IELTS.

4. Nội dung bài (SRC-1105) ​

lessonContent(level, lesson) trong GrammarApp.tsx chọn nguồn: bài có ielts_topic lấy chủ đề trong ielts/grammar/content (mã tiến độ là chính id chủ đề, nên tiến độ chung với /ielts/grammar); bài còn lại lấy grammar/content/<level>/<lesson>.json, mã tiến độ g-<level>-<lesson> (khớp regex [a-z0-9-]{1,60} của POST /ielts-grammar, không cần đổi API). Trang bài dùng lại TaskCard, Fold, useGrammarDone của ielts/Grammar.tsx. Test gác: mọi bài Starter, Elementary (SRC-1107) , Intermediate (SRC-1111) , Upper-intermediate (SRC-1112) và Advanced (SRC-1114) có nội dung đủ khuôn và đáp án nằm trong lựa chọn.

Chủ dự án 28.09.2026: Grammar, IELTS, SAT và Speak together mỗi khu có một nhóm Zalo riêng; link gửi sau. Bảng link ở apps/learn/src/zaloGroups.tsx (ZALO_GROUPS), nút "Zalo" nằm trên thanh trên của cả bốn khu. Khu được chọn theo đường dẫn (zaloGroupFor): /ielts/speaking/pair là nhóm Speaking dù nằm dưới /ielts. Link còn trống thì nút không hiện, để không có nút dẫn tới đâu cả; có link thì chỉ cần điền vào bảng.

Kỳ miễn phí 14 ngày riêng (SRC-1123, 28.09.2026) ​

Chủ dự án: "NEMO GRAMMAR, free riêng 14 ngày". Bảng learner_grammar_membership (migration 0311), route GET /v1/learners/{id}/grammar-membership, chip "Miễn phí: còn N ngày" trên thanh trên /grammar. Kỳ này độc lập với kỳ IELTS: mở khu này không tạo hay tiêu kỳ bên kia. Chi tiết ở SDD-038 §136. Hiện kỳ chỉ để hiển thị, chưa khoá nội dung khi hết hạn, giống IELTS.

6. Set bài tập và cổng chất lượng (SRC-1174, 01.10.2026) ​

Tiêu chuẩn ở PRD-008 §14. Khuôn dữ liệu, giữ tương thích với file cũ:

Trường trong file lessonNghĩa
tasksSet Quick, 5 câu level 1-5 như hiện nay
sets.practiceSet Practice, 10 câu, cùng khuôn Task
sets.masterySet Mastery, 20 câu, cùng khuôn Task

Unit review nằm ở grammar/content/<level>/_units/<unit>.json, gồm { "unit": "<id>", "tasks": [...] }; mỗi task thêm trường lesson để biết nó thuộc lesson nào. Tiến độ ghi theo mã g-<level>-<lesson> như cũ, set Practice và Mastery là level 6-15 và 16-35.

API POST /v1/learners/{id}/ielts-grammar nay nhận level 1-35 (trước là 1-5) và nhận mã gu-<level>-<unit> (danh sách sinh bởi scripts/gen-grammar-topics.mjs). Phút học: câu Quick vẫn cộng 120 giây; câu của Practice, Mastery và Unit review cộng 30 giây (GRAMMAR_SET_TASK_SECONDS). Lý do: một câu của set dài là một câu, không phải một bậc của cả bài; giữ 120 giây thì 35 câu thành 70 phút học cho một bài, con số không ai tin. ✍️ Quyết định của phiên, chờ chủ dự án duyệt.

Giao diện: bài có sets thì thay nút "Start practice" bằng danh sách ba set (tên, số câu, đã đúng bao nhiêu); bấm một set thì chạy từng câu, làm tiếp từ câu đầu chưa đúng. Trang level thêm dòng "Unit review" ở cuối thẻ mỗi unit khi có file; trang ôn ở /grammar/<level>/units/<unit>. Trang /ielts/grammar và cột n/5 ở trang level chỉ đếm 5 câu Quick (quickDone), vì bài dùng chung ghi cả level 6-35 dưới cùng mã.

Hai công cụ:

  • npm run grammar:coverage --workspace @nemo12/learn (apps/learn/scripts/grammar-coverage.ts) in số lesson và unit đạt chuẩn theo từng level. Chỉ báo cáo, không chặn.
  • grammar.test.ts gác bốn điều máy đo được của PRD §14c cho mọi set mới (Practice, Mastery, Unit review). Năm câu Quick cũ được miễn cho tới khi lesson ấy được nâng, để cổng không đỏ ngay từ hôm nay; lesson đã có sets thì Quick của nó cũng phải đạt.

7. Giao diện tiếng Anh, căn giữa (SRC-1173, 01.10.2026) ​

Nhãn và nút của GrammarApp.tsx viết tiếng Anh. TaskCard (dùng chung với /ielts/grammar) nhận cờ english; không có cờ thì giữ nhãn tiếng Việt cho sân IELTS. helpFor trả hướng dẫn tiếng Anh riêng cho /grammar/** (trước đây rơi vào hướng dẫn mặc định của IELTS). Chip kỳ miễn phí ở §"Kỳ miễn phí" nay ghi "Free trial: N days left". Bố cục: mỗi trang bọc trong mx-auto max-w-3xl (trang level max-w-4xl); ô luyện bọc w-full để không co lại trong hàng căn giữa. e2e grammarProgram.spec.ts đo vị trí và độ rộng.

8. Buổi Live thứ Hai (SRC-1307, 09.10.2026) ​

Yêu cầu: PRD-008 §15. Cùng khuôn lịch Speak together (SRC-1088, SDD-052):

  • Lịch tính từ mốc, không lưu. workers/api/src/modules/grammarLive/schedule.ts: buổi số 0 là thứ Hai 12.10.2026 20:00 giờ VN (13:00Z), mỗi tuần một buổi 90 phút, mã buổi YYYY-MM-DD-2000. sessionById chỉ nhận mã đúng lịch, nên mã bịa không vào được bảng đăng ký.
  • Hai bảng (migration 0340): grammar_live_signups (session_id, learner_id) nhớ ai bấm tham gia; grammar_live_recordings (session_id, title, url) giữ link bản ghi, nạp bằng workflow seed-data sau mỗi buổi (chưa có màn admin).
  • API workers/api/src/modules/grammarLive/routes.ts: GET /v1/learners/{id}/grammar-live trả 6 buổi sắp tới và 12 buổi đã xong; POST / DELETE .../grammar-live/{sessionId} tham gia và huỷ. zoom_url chỉ có khi learner đã tham gia và buổi chưa xong; chưa đặt GRAMMAR_ZOOM_URL thì zoom_pending: true. Không có hạn chót tham gia, vì bấm tham gia là đường duy nhất tới link. Bản ghi hiện cho mọi learner.
  • Giao diện apps/learn/src/grammar/GrammarApp.tsx: LiveCard trên trang chủ, LivePage ở /grammar/live, HowItWorks với nút "Ask a mentor on Zalo" (ẩn khi link nhóm Zalo grammar trong zaloGroups.tsx còn trống).
  • Trang công khai apps/web/src/site/grammarProgram.ts: GRAMMAR_LIVE, GRAMMAR_WEEK. Test trong grammarLive/routes.test.ts gác ngày, giờ, thứ của trang công khai khớp schedule.ts.

9. Phản hồi luyện tập (SRC-1308, 09.10.2026) ​

Yêu cầu: PRD-008 §16. TaskCard (nay ở apps/learn/src/grammar/practice.tsx, SRC-1337) giữ ba trạng thái none | wrong | right và số lần sai; MAX_TRIES = 3. Câu đúng mới gọi markDone và recordGrammarTask như trước, nên phút học và tiến độ không đổi nghĩa. Câu lộ đáp án sau 3 lần sai thì không ghi gì. label="" ẩn nhãn trong thẻ khi trang đã có thanh tiến độ: SetRunner vẽ Progress của shadcn cùng chữ "n / N". e2e grammarProgram.spec.ts đi đủ ba nhánh.

10. Gỡ /ielts/grammar (SRC-1337, 11.10.2026) ​

Yêu cầu: PRD-008 §18. Trường ielts_topic bỏ khỏi danh mục; mọi bài có file riêng và mã tiến độ g-<level>-<bài>. Phần dùng chung (kiểu dữ liệu, kho tiến độ, TaskCard, Fold) chuyển từ ielts/Grammar.tsx (đã xoá) sang apps/learn/src/grammar/practice.tsx. Router IELTS giữ một route grammarMoved để chuyển /ielts/grammar* sang /grammar. scripts/gen-grammar-topics.mjs chỉ còn đọc thư mục của Program Grammar, nên API không còn nhận mã chủ đề IELTS cũ. Endpoint /v1/learners/{id}/ielts-grammar giữ tên (đổi tên là việc riêng).