SDD-043 · Tick từng bước và vòng duyệt nội dung 08.10.2026
Một phần của SDD-043. learner tick từng bước của lesson, lưu theo learner, quyền xem; vòng duyệt nội dung 08.10.2026.
15. Tick từng bước: "Mình đã làm xong bước này" (SRC-1283)
Chủ dự án 07.10.2026: lesson gồm các Activity, Activity gồm các Bước (AI Teen 1 chuyển sang năm Activity mỗi lesson ở SRC-1281; AI Teen 2-3 giữ ba journey Bắt đầu · Tìm hiểu · Đúc kết, cũng là Activity). Learner tick từng bước là đã làm xong; hệ ghi ở server theo learner (đổi máy vẫn còn); card Activity (§14) và NavBar phản ánh. Đã dựng 07.10.2026 (REQ-LRN-48); những gì dựng và các quyết định lúc dựng ở §15.8.
15.1 Ba loại bước, một luật xong
Bước của lesson không cùng một loại. Có bước máy đã biết là xong hay chưa (qua cổng đọc, trả lời đúng câu kiểm), có bước chỉ learner biết (đọc câu hỏi mở đầu, viết "Mang theo", làm thử trên máy). Tick là tín hiệu cho loại thứ hai; nó không được là đường vòng qua loại thứ nhất.
Loại (kind) | Bước | Xong khi | Ô tick trong card |
|---|---|---|---|
gated | phần đọc có cổng (part-k) | cổng của phần ấy QUA (§10), đọc từ dòng :gates | không bấm được; tự tick khi qua; trước đó là ổ khoá hoặc ô trống kèm lời "Qua kiểm tra nhanh để xong phần này" |
checked | câu kiểm (qN), bước tổng kết câu kiểm (result của SRC-1281) | câu ấy đúng (§8), hay mọi câu của Activity đã trả lời | không bấm được; tự tick |
self | mọi bước còn lại: question, read (bài không có cổng), output, next, bước ví dụ hay thực hành của năm Activity | learner tick | bấm được; bấm lại là bỏ tick |
- ✍️ Tick KHÔNG thay cổng, cũng không thay câu kiểm. Server từ chối tick cho bước
gatedvàchecked(422STEP_NOT_TICKABLE), nên một client sửa tay cũng không đi vòng được. Một bước cổng "xong" chỉ có một nghĩa: đúng 3/3; cho tick thêm một nghĩa thứ hai là để hai sự thật cùng nói về một bước và sớm muộn nói ngược nhau. - ✍️ Tick không đổi luật qua lesson. Lesson vẫn "qua" theo §8 (mọi trắc nghiệm đúng), unit vẫn sang Practice theo §9.2. Tick là tự khai công sức, không phải bằng chứng học được (bài học SRC-849: sổ bằng chứng không phải sổ công sức). Activity xong khi MỌI bước của nó xong theo bảng trên; lesson hiện "k/N bước" trong card và trong Mục lục, tách khỏi nhãn "đã qua".
- Bước
selfở cuối vùng chính có thêm một nút "Xong bước này" (cùng việc với ô tick, rồi cuộn tới bước kế), vì trên điện thoại card thường thu gọn. Nút ấy là chữ hành động nên tiếng Việt, không em dash.
15.2 Khoá lưu: slot, Activity, Bước
<course>:u<unit>:s<slot>:<activity>:<step> ví dụ ai-teen-1:u1:s7:understand:question- Slot, không phải vị trí (§12.4): đổi thứ tự lesson không làm tick gắn sang lesson khác; lesson gộp hay soạn lại lấy slot mới nên không thừa hưởng tick cũ.
- Id Activity, không phải số:
understand,check,reviewvà các id của năm Activity mới (SRC-1281 chọn). Đổi tên hiển thị không đổi id (bài học SRC-1279). - Id Bước là định danh NỘI DUNG: bước
selfphải mang id khai trong dữ liệu (question,output,next, hay slug của bước ví dụ nhưcompare-prompts), dạng^[a-z][a-z0-9-]{1,31}$, KHÔNG được là id theo vị trí.part-kvàqNlà id theo vị trí nhưng chỉ dùng cho bướcgated/checked, vốn không lưu tick (sự thật của chúng nằm ở cổng và kết quả bài tập, đã đượcslots.test.tsgiữ). Soạn lại một bước đến mức đổi nghĩa thì đổi id; id cũ nghỉ, dòng cũ ở D1 bị bỏ qua khi đọc. - Test thuần giữ luật: mọi bước
selfcó id khai, không trùng trong một Activity, không có dạngpart-\d+/q\d+.
15.3 Lưu ở đâu
Bảng mới ai_teen_step_ticks (số migration cấp bằng npm run migration:new lúc dựng, skill d1-migrate):
| Cột | Kiểu | Luật |
|---|---|---|
learner_id | TEXT | FK learners, ON DELETE CASCADE |
course_id | TEXT | id có trong courseIds.ts |
unit_no | INTEGER | CHECK 1-20 |
lesson_slot | INTEGER | CHECK 1-20 (cùng trần slot của §12.4) |
activity_id | TEXT | CHECK độ dài 2-32 |
step_id | TEXT | CHECK độ dài 2-32 |
ticked_at | TEXT | giờ server, giữ nguyên khi tick lại |
ticked_by_user_id | TEXT | luôn là tài khoản của chính learner (§15.4) |
Khoá chính là cả sáu cột đầu; một dòng = một bước đang được tick, bỏ tick là xoá dòng (không giữ lịch sử, cùng luật §8). Chỉ mục (learner_id, course_id).
✍️ Bảng riêng, không nhét vào ai_teen_journeys như cổng đọc (§10) và chỉ báo (§13.4). Hai lần trước là trạng thái nhỏ, ghi thưa, một máy; tick thì nhiều (mỗi lesson 10-20 bước), ghi dày và hay từ hai máy cùng lúc. Một blob JSON mỗi unit nghĩa là đọc, sửa, ghi cả blob: hai lần tick gần nhau từ hai máy thì lần sau đè mất lần trước. Một dòng mỗi tick là UPSERT/DELETE nguyên tử, và đếm "k/N bước" cho mentor là một câu COUNT thay vì giải JSON.
15.4 API
| Route | Ai | Làm gì |
|---|---|---|
GET /v1/ai-teen/courses/{courseId}/steps?learner_id= | learner, phụ huynh, mentor (canAccessLearner, QG-008) | mọi tick self của learner trong course: {ticks: [{unit, slot, activity, step, ticked_at}]} |
PUT /v1/ai-teen/courses/{courseId}/steps | chỉ chính learner, đã ghi danh course | body {learner_id, unit, slot, activity, step, done}; done: true UPSERT (giữ ticked_at cũ nếu đã có), false xoá; trả {tick} hay {tick: null} |
- Chỉ learner ghi. Ngoài
canAccessLearner, PUT đòi tài khoản đang đăng nhập là tài khoản CỦA learner ấy (403NOT_LEARNERcho phụ huynh, mentor, staff): tick là lời learner tự nói về mình, bố mẹ tick hộ thì con số mất nghĩa. Chưa ghi danh: 409, như §8. - Server kiểm bước có thật và là
self. Bản kê bước tick được của từng course sinh vào phía API bằngnode scripts/aiteen/gen-api-catalog.mjs(cạnhtaskSkills.ts),program.test.tsđối chiếu với nội dung; khi nội dung vào D1 (SDD-056) server đọc thẳng bản phát hành. Bước lạ: 400UNKNOWN_STEP; bướcgated/checked: 422STEP_NOT_TICKABLE. - Giới hạn. zod
.strict():learner_id1-64 ký tự,unitvàslot1-20,activity/steptheo regex 15.2,doneboolean; thân yêu cầu dưới 1 KB. Rate limit bằngenforceRateLimitvới luật mớiaiTeenStepTick(120 lần / 60 giây / tài khoản, khai trongRATE_LIMITS), 429 kèmRetry-After. Số dòng mỗi learner bị chặn tự nhiên bởi bản kê (không ghi được bước không có). - Idempotent: tick hai lần cùng một bước không đổi gì; bỏ tick bước chưa tick trả
{tick: null}. - Test hành vi (skill
api-test,createTestDb()): tick và đọc lại, tick lại giữticked_at, bỏ tick xoá, phụ huynh và mentor đọc được nhưng ghi bị 403, người lạ 401 (§15.8), chưa ghi danh 409, bước cổng và câu kiểm 422, bước lạ 400, slot ngoài 1-20 400, quá rate 429.
15.5 Client
- Luật thuần
stepState(step, { gates, results, ticks })trảdone | open | lockedtheo bảng 15.1, ở cạnhjourneys.ts; card, NavBar, Mục lục và bộ đếm "k/N bước" đều gọi nó (một chỗ khai luật xong). - Tick là lạc quan: card đổi ngay, PUT gửi ngay (không độ trễ, một lần bấm là một ý), lỗi thì trả ô về trạng thái cũ và báo một dòng.
- Chưa ghi danh: tick giữ ở
localStorage(nemo12.aiTeen.steps.<learner>.<course>, bọc try/catch), card nói "Ghi danh course để lưu tiến độ trên mọi máy"; ghi danh xong client gửi các tick ấy một lượt (hợp hai bên, không xoá tick nào ở server). - Dịch slot ở đúng ranh giới kho như §12.4 (
slots.ts): giao diện làm việc theo vị trí, kho theo slot.
15.6 Mentor, phụ huynh và Evidence Registry
- ✍️ Không ghi vào Evidence Registry. Tick không chứng minh hiểu hay làm được gì; đưa nó vào sổ bằng chứng là đúng lỗi SRC-849 (đếm công sức như năng lực). Bằng chứng của lesson vẫn là trắc nghiệm và thực hành (§11). Không nguồn
EVIDENCE_SOURCESmới, Learner Model không đổi. - Mentor (Dolphin, tab AI Teen của §12 trang lát cắt): mỗi lesson thêm dòng "k/N bước đã tự đánh dấu xong · lần cuối DD.MM.YYYY", tính lúc đọc từ bảng (
modelPage.ts). Đặt cạnh "đã qua" để mentor thấy chỗ đáng hỏi: tick đủ mà chưa qua câu kiểm, hay qua câu kiểm mà bỏ trống bước thực hành. - Phụ huynh: đọc được qua cùng route GET (quan hệ gia đình); Marlins chưa có màn AI Teen nên chưa hiện gì. Khi có, chỉ hiện số đếm theo lesson và ngày, không hiện giờ từng tick.
15.7 Riêng tư
- Dữ liệu chỉ là id + giờ, không có chữ tự do của learner; không gửi bên thứ ba, không vào analytics.
- Không route công khai nào trả tick; mentor đọc qua
canAccessLearner, nên lượt đọc của vai nội bộ vàoaudit_lognhư mọi dữ liệu learner. - Xoá learner xoá tick (CASCADE). Rút course không xoá tick: ghi danh lại thì tiến độ còn nguyên, như kết quả bài tập.
15.8 Đã dựng (07.10.2026, SRC-1283)
| Phần | Ở đâu |
|---|---|
| Bảng | migrations/0339_ai_teen_step_ticks.sql, đúng §15.3 |
| Bản kê bước | workers/api/src/modules/aiTeen/stepCatalog.ts, sinh bằng node scripts/aiteen/gen-api-catalog.mjs từ CHÍNH lessonJourneys + stepKind của learn (plugin esbuild thay import.meta.glob), nên không có bản luật thứ hai; steps.test.ts bên learn đối chiếu lại |
| API | GET / PUT /v1/ai-teen/courses/{courseId}/steps ở routes.ts, dịch vụ steps.ts; mã lỗi mới NOT_LEARNER (403), UNKNOWN_STEP (400), STEP_NOT_TICKABLE (422) trong shared/errors.ts và errors.md; luật aiTeenStepTick trong RATE_LIMITS |
| Luật xong | stepKind + khoá ở apps/learn/src/aiTeen/steps.ts; luật xong vẫn là MỘT hàm, lessonStepState (journeys.ts), nay nhận thêm ticks; lessonStepCount cho "k/N bước" |
| Client | useStepTicks.ts (lạc quan, trả về khi lỗi, bản trình duyệt khi chưa ghi danh); ô tick trong LessonOutline (card và sheet), nút "Xong bước này" cuối bước self; "k/N bước" đầu card và trong Mục lục (CourseTree.tsx) |
| Dolphin | modelPage.ts trả steps (k/N theo lesson), apps/mentors/src/AiTeenReport.tsx hiện trong thẻ unit |
Quyết định lúc dựng:
- ✍️ Người lạ nhận 401, không phải 403. Ca test ở §15.4 ghi "người lạ 403", nhưng luật chung của errors.md là 401 cho mọi ai không có quyền với dữ liệu trẻ em, vì 403 lộ rằng learner ấy có thật.
NOT_LEARNER403 chỉ trả cho người ĐÃ đọc được learner (phụ huynh, mentor, staff), nên không lộ thêm gì. - ✍️ Bước
selfxong CHỈ khi được tick. Trước đâyquestiontự "xong" khi qua phần đọc 1,output/nextkhi lesson đã qua; bảng §15.1 nóiselfxong khi learner tick, nên các luật suy đoán ấy bỏ hẳn, để không có hai sự thật cho một bước. Hệ quả: learner cũ mở lại lesson đã học sẽ thấy các bướcselfchưa tick (đúng nghĩa: em chưa từng tự khai). - ✍️
q0(lesson không có câu kiểm) là bướcchecked: luôn xong, không tick tay. - ✍️ N của Dolphin là số bước
self("k/N bước đã tự đánh dấu xong"): server chỉ biết tick và bản kê, còn "k/N bước" của learner trong card đếm MỌI bước theolessonStepState. Hai con số đo hai thứ khác nhau và chữ nói rõ cái nào là cái nào. - ✍️ Bản trình duyệt có dấu
pending. Bản sao trình duyệt còn làm gương cho server (tải lại khi mất mạng vẫn thấy tick), nên chỉ tick làm lúc CHƯA ghi danh mới được gửi lên khi ghi danh; không có dấu ấy thì một tick đã bỏ ở máy khác sẽ sống lại từ bản sao của máy này. - Bước có cổng / câu kiểm hiện ô viền đứt (không bấm được,
titlenói cách xong), tick đặc khi xong; ô tick của bướcselflàrole="checkbox"đứng CẠNH nút bước (control lồng trong button là HTML sai và không tới được bằng bàn phím), vùng chạm 40px.
Lịch sử quyết định
16. Vòng duyệt nội dung 08.10.2026 (SRC-1297)
Chủ dự án 08.10.2026 uỷ quyền duyệt toàn bộ nội dung đã soạn ("Bạn quyết toàn bộ và làm hết đi"). Vòng này chỉ sửa CHỮ: không đổi số lesson, slot, phần, câu, phương án hay khoá cổng.
- Cổng đọc và câu kiểm (18 unit,
gates/*,lessons/*): soát theo CD-8c và skill course-design §5b-§5g. Đáp án dài nhất (tính theo chữ, dài hơn cả hai nhiễu): sửa 250 câu cổng và 246 câu kiểm, nay không còn ca nào. Mâu thuẫn: 6 (AI Teen 1 U1: 2; AI Teen 3: 4 về số liệu và luồng), cộng 1 câu đáp không khớp dạng câu hỏi. Nhiễu nửa đúng: 1 (AI Teen 3 U4). Tham chiếu cũ "bài trước"/"lesson đầu": 4. Ký tự cấm: không còn ca nào; một dấu ngoặc thừa ở AI Teen 1 U1 đã gỡ. - Phát biểu kỹ thuật được làm cho chính xác và có rào: phím tắt DevTools (tuỳ trình duyệt và hệ điều hành, Safari phải bật menu Develop); vị trí trong ảnh (chỉ khi camera có quyền vị trí, nhiều mạng xã hội gỡ khi tải lên, gửi dạng file hay đặt trên web riêng thì thường còn); nslookup (IP mẫu thuộc dải tài liệu/riêng, ghi "máy em sẽ khác"); chữ hoa chữ thường trong tên file (nhiều máy chủ web phân biệt); cảnh báo chứng chỉ (chữ tuỳ trình duyệt, https chỉ là kết nối được mã hoá, không chứng minh trang đáng tin); CNAME ở tên miền gốc; giới hạn tuổi tài khoản dịch vụ; Teachable Machine (huấn luyện trong trình duyệt, dữ liệu chỉ rời máy khi lưu hoặc tải mô hình lên).
- Nhãn khung tiếng Anh: U2 "Question / Size / Check / Spark" đổi thành Question / Size / Check / Hook (Hook là từ quen cho "điều kéo người đọc vào"); U4 giữ Fixed parts / Slots, đổi "Slot notes" thành Slot hints (gợi ý cho chỗ trống), áp đồng bộ ở bài đọc, cổng, câu kiểm và outcome.
- Outcome (CD-6): bỏ mọi dấu "drafted / chờ chủ dự án duyệt" ở course, unit, lesson. Sửa 9 Lesson Outcome: gọn lại câu ba vế, bỏ khẳng định tuyệt đối ("chỉ ... mới", "ngay lập tức", "mất tác dụng"), chữa outcome Feedback loop mâu thuẫn với lesson kế, đồng bộ nhãn Slot hints.
- Chỉ báo skill (§13.5):
sourceđổi từ"nemo-draft"thành"nemo"; chỉ báo Level 1 của "Prompt AI effectively" đổi thành bốn phần mục tiêu, bối cảnh, yêu cầu, giới hạn cho khớp Unit 1. - Seed nội dung sinh lại bằng
scripts/aiteen/export-content.mjs; nạp qua seed-data (lệnh ở đầu từng filescripts/aiteen/seed-content-ai-teen-*.sql).