Sổ nguồn SRC-0150 tới SRC-0174
Một mảnh của sổ tiếp nhận. Chỉ CHÈN dòng bằng node scripts/src-new.mjs, không ghi đè cả file.
| ID | Ngày nhận | Nguồn | Canonical hóa vào | Trạng thái |
|---|---|---|---|---|
| SRC-150 | 2026-08-16 | Chỉ đạo: đưa tên tiếng Anh vào bên cạnh các concept tiếng Việt ("Ý chính", "Dẫn chứng"…) trong mọi Learning Experience, vì từ tiếng Việt dễ bị nhầm lẫn | Từ điển khái niệm glossary.ts (hơn 190 mục, 8 nhóm môn) + component <Term>: tiếng Việt là chữ chính, tiếng Anh nhỏ và nhạt hơn đi kèm. Khác LangSwitch — LangSwitch đổi cả giao diện sang một ngôn ngữ, Term hiện CẢ HAI cùng lúc ở mọi chế độ, vì tiếng Anh ở đây đóng vai mã định danh: "ý chính / ý chủ đạo / nội dung chính" là ba cách gọi cho một thứ, còn "Main idea" thì chỉ có một. Gắn ở engine của Explore chứ không rải vào từng config (hơn 500 nhãn trong configs.tsx, và mọi bài mới sau này được luôn): chip của archetype highlight/picker, tiêu đề khối blocks, loại từ của word-web; cộng tiêu đề Learning Experience (dùng skill_nodes.title_en có sẵn trong DB). Luật đưa từ vào từ điển: chỉ nhận tên khái niệm, không nhận câu/bước làm bài/mẹo thi; không có trong bảng thì im lặng — chú sai một khái niệm còn tệ hơn không chú, vì learner sẽ mang từ sai đó đi tra sách tiếng Anh | ✅ |
| SRC-151 | 2026-08-16 | Feedback: các phương án của câu "Vì sao con chọn đáp án C" vẫn hiện gần như cùng một thứ tự dù đã khai là có xáo | Lỗi thật trong hàm xáo, không phải quên xáo: shuffleJustifications băm khoá rồi lấy bit cao của MỘT bước LCG — hai id chỉ khác ký tự cuối (câu 1, 2, 3… trong cùng một bài) cho hạt giống lệch nhau ~1, mà một bước LCG chỉ đẩy đi 1664525/2³² ≈ 0,04% của dải, nên số rút ra gần như y hệt. Đo trên 12 câu của một bài: chỉ 8 thứ tự khác nhau và "Cảm giác, đoán" đứng cuối ở 9/12 câu — learner quen tay chọn theo vị trí thì lớp đo thứ 3 (REQ-EXAM-07) mất giá trị. Sửa bằng finalizer kiểu murmur3 (khuếch tán từng bit) cho cả bước băm khoá lẫn từng vòng rút. Sau khi sửa: đủ 24/24 hoán vị, mỗi lựa chọn đứng đầu/đứng cuối ~25%. Test khoá cả ba luật: cố định trong một câu · các câu liền nhau phải khác nhau · không vị trí nào bị một lựa chọn chiếm quá 40% | ✅ |
| SRC-152 | 2026-08-16 | Feedback trong một đợt: (a) hình 2 con cá cần to gấp 2-3 lần và quay mặt sang phải (đang nhìn ra rìa màn hình); (b) câu chúc mừng chuỗi khó hiểu, cần đổi thành "Wow, con đúng 3 câu liên tiếp, có vẻ đúng hướng đó!", sang câu tiếp thì phải biến mất, và nút đóng dùng icon thay chữ "Ẩn"; (c) màn kết thúc quá nhiều chữ; (d) trả lời đúng liên tục mà vẫn thấy "Cần xây lại từ gốc" — nghe hoảng loạn; (e) bỏ dòng "Câu này được xếp lại xuống cuối" và dòng "Đáp án đúng: …" | (a) Con vật dẫn 48px → 96/112px, thêm .n12-flip-x (nét vẽ mòng biển và cá ngựa trong N12Art quay mặt sang trái; cua đối xứng nên không lật); banner chuỗi bỏ emoji 🐠 (phạm SRC-068) đổi sang chính con vật dẫn. (b) cheerFor(n) nói đúng số câu — bản cũ luôn ghi "Ba câu" kể cả khi learner đã đúng 6, 9, 12 câu liên tiếp; advance() xoá banner; nút đóng thành icon ✕ có aria-label. (c) Màn kết thúc bỏ câu văn diễn giải lại con số và câu "đã ghi vào hồ sơ học tập" (chuyện của hệ thống), để con số 3/4 tự nói ở cỡ chữ lớn — 4 dòng còn 2. (d) Lỗi trình bày, không phải lỗi mô hình: state là mức vững của CẢ BÀI tại thời điểm trả lời câu đó, dán vào từng câu là nói sai chuyện, mà câu đầu bài luôn mang giá trị cũ nhất/tệ nhất → gỡ khỏi từng câu, còn một dòng ở đầu phần xem lại lấy trạng thái lần trả lời cuối, kèm câu giải thích khi làm đúng cả bài mà mức vững chưa lên. (e) Bỏ hai dòng; đáp án đúng không hiện nữa vì câu sai sẽ gặp lại ở cuối hàng đợi — hiện sẵn thì lần gặp lại chỉ còn là chép lại. Phần giải thích misconception vẫn giữ (nói vì sao sai, không nói đáp án) | ✅ |
| SRC-153 | 2026-08-16 | Câu hỏi chủ dự án: mỗi Learning Experience nên có đúng 10 câu không? → chốt làm phần luật trước, phần soạn câu để sau | Nên — nhịp cố định đáng giá với trẻ hơn tối ưu từng bài: biết trước "10 câu là xong" thì đứa trẻ mới quyết định bắt đầu. practiceSize bỏ phép cắt 60% (kho 10 câu chỉ ra 4 câu luyện) → lấy đúng 10 nếu kho cho phép. Hai điều không được phá: không lấn phần kho để dành đo (lấn thì bài đo quay lại đo trí nhớ phiên vừa xong — lỗi SRC-141) và không lặp câu cho đủ số (lặp là dạy đáp án). Kho chưa đủ thì phiên ngắn hơn 10 và pool_warning nói thẳng con số thật, hiện ngay đầu phiên. Kèm lấy mẫu câu "vì sao": 10 câu × 3 lớp đo = 30 lượt bấm, trẻ ngán rồi bấm bừa → chỉ hỏi ở 3 câu đầu · câu tự nhận chưa chắc (tự tin ≤2) · câu gặp lại sau khi từng sai; cắt ~40% thao tác, giữ nguyên tín hiệu hiệu chuẩn. Điều kiện kho: 16 câu/bài (16 − 6 để dành = 10) → toàn hệ cần 4.384 câu, đang có 2.314, còn thiếu ~2.070. SDD-010 §12b; selection.test 17 → 22 test; smoke test +1 luồng | ✅ |
| SRC-154 | 2026-08-15 | Chỉ đạo: Constraint Model và Learning Plan Model cũng mô tả kỹ như Retention — khép lại đủ 7 model lõi có trang riêng | reference/constraint-model.md + reference/learning-plan-model.md; sidebar + index xếp theo đúng thứ tự chạy của WF-04 | ✅ |
| SRC-155 | 2026-08-16 | Chỉ đạo: soạn thêm câu cho đủ 16 câu mỗi bài (điều kiện của luật 10 câu, SRC-153) | Pipeline seed-items-topup.mjs + scripts/items-topup-*.json. Khác seed-items-empty.mjs ở chỗ biết kho hiện có, nên chặn được ba lỗi bản kia không thấy: nạp xong không đúng 16 câu · trùng đề với câu đã có (so sau chuẩn hoá — kho phình lên mà learner vẫn gặp câu cũ) · bài không đủ 6 câu discriminator/boundary cho phần để dành đo. Bộ kiểm bắt 5 lỗi thật ngay lượt đầu. Đợt 1: GenAI 84 → 288 câu, 18/18 bài đủ 16. Toàn hệ còn thiếu 1.866 câu | 🔄 |
| SRC-156 | 2026-08-16 | Feedback nhiều đợt: (a) bỏ dòng "Bài này hiện có N câu luyện (chuẩn là 10)"; (b) câu "Wow, con đúng 3 câu liên tiếp" phải nằm dưới, hiện cùng kết quả và biến mất khi sang câu tiếp; (c) đề đọc hiểu tách thành 3 dòng; (d) hai mục cùng tên Algebra; (e) hiện tới 2 nút cùng lúc; (f) đường cong đánh dấu góc vẽ sai | (a) Bỏ ở cả UI lẫn poolWarning — kho mỏng là việc của người soạn câu. (b) Banner chuyển vào trong khối kết quả: đặt ở đầu màn thì nó sống độc lập với câu hỏi nên trôi sang câu sau. (c) splitPrompt() tách dẫn · đoạn trích · câu hỏi, chỉ tách khi đủ ba phần (5 test — cắt nhầm còn tệ hơn để nguyên). (d) Strand SH "Số học & Đại số"/"Number & Algebra" trùng ý với DS; nội dung SH toàn số học nên đổi thành "Số học"/"Number" (sửa cả seed-data.json lẫn D1). (e) Khối "mức tự tin" thiếu điều kiện !reveal nên khi bỏ qua lớp "vì sao" (SRC-153) nó nằm lại cùng khối kết quả — lỗi do chính SRC-153 tạo ra. (f) arcPath để cứng sweep-flag = 0; với cung đi theo chiều giảm góc thì cặp cờ không khớp hình học và SVG lặng lẽ chọn tâm khác, vẽ ra đường cong phình ngược. Test cũ chỉ kiểm hai đầu mút nên lọt → test mới dựng lại tâm cung từ chuỗi path đúng cách SVG dựng và đòi nó trùng đỉnh góc | ✅ |
| SRC-157 | 2026-08-16 | Chỉ đạo: rà soát lại, đừng để hiện 2 button đồng thời; và bỏ dòng "Có N câu sai đã được xếp lại xuống cuối"; ưu tiên mỗi Toán, việc còn lại lưu tạm cho đỡ quên | Rà cả ba bước nhập: khối "chọn đáp án" cũng thiếu !reveal — trước đây luật đúng nhờ may mắn của luồng (mọi đường tới kết quả đều đi qua bước tự tin hoặc bước vì sao) chứ không nhờ điều kiện, nên chỉ cần thêm một đường đi mới là vỡ, và đúng là SRC-153 đã làm vỡ. Nay cả ba bước đều có !reveal, kèm 2 smoke test khoá bất biến "mỗi lúc chỉ đúng một nút hành động chính" — kiểm ở từng bước và kiểm riêng đường bỏ qua lớp "vì sao" (đường đã sinh ra lỗi). Bỏ dòng đếm câu xếp lại — số câu sai và số lượt làm thêm đã có ở màn kết thúc, nhắc giữa chừng chỉ là đếm lỗi trước mặt learner. Lập backlog.md ghi việc hoãn: 9 môn còn thiếu 1.866 câu (thứ tự sau Toán: AP · IELTS · SAT), câu hỏi có nên vượt mốc 16 lên 24-32, và chỉnh bộ trường khai báo lúc mới vào (bỏ năm sinh · thêm tháng thi · thêm quỹ thời gian/tuần — planning.ts đang rơi về mặc định 45 phút/ngày) | ✅ |
| SRC-158 | 2026-08-16 | Chỉ đạo: đổi font toàn bộ hệ thống sang Arial — Design System, learn.nemo12.com, trang public www.nemo12.com và mọi trang khác | Đổi một token duy nhất --font-sans trong packages/design-system/tokens/base.css: mọi app đều @import Design System nên learn · web · marlins · coral · admin · mentors đổi theo, pearl bắc cầu qua --vp-font-family-base. Riêng docs không import Design System (VitePress gói sẵn Inter) → thêm theme mỏng apps/docs/.vitepress/theme/ chỉ để đè biến font, không fork theme. Chuỗi dự phòng Arial, Helvetica, "Liberation Sans", "Nimbus Sans", sans-serif: hai font sau là bản thay thế đúng số đo của Arial trên Linux — thiếu chúng thì máy Linux (gồm máy chạy CI và chụp ảnh kiểm giao diện) rơi về một font sans bất kỳ, chữ xô lệch so với bản đã duyệt. Kiểm: build cả 8 app; đọc getComputedStyle trên trình duyệt thật ra đúng chuỗi Arial ở cả body lẫn heading; chuỗi Arial nằm SAU dòng --font-sans: var(--font-sans) của Tailwind trong bundle nên không bị tham chiếu vòng | ✅ |
| SRC-159 | 2026-08-16 | Chỉ đạo: tạo nốt câu hỏi cho Số học và Đại số (phần Toán được ưu tiên trước) | 330 câu cho 55 bài (Số học 162 · Đại số 168), cả hai nhánh nay đủ 16 câu/bài. Soạn bằng bộ sinh có tham số (gen-items-math-sh.mjs, gen-items-math-ds.mjs): đáp án đúng và cả ba phương án nhiễu đều được tính ra từ cùng một bộ tham số, nên không có rủi ro sai đáp án do nhẩm tay — mà một câu sai đáp án còn tệ hơn không có câu, vì learner làm đúng vẫn bị chấm sai và bằng chứng sai đó chảy thẳng vào Learner Model. Bộ sinh tự chặn khi hai phương án trùng nhau (bắt 2 lỗi thật), và seed-items-topup.mjs chặn thêm 5 lỗi nữa (4 đề trùng với câu đã có trong kho, 1 đề quá ngắn). Cả 6 câu mỗi bài đều là discriminator/boundary: đo cho thấy cả 83 bài Toán trước đó có đúng 0 câu phân loại, nên phần kho để dành cho bài đo (SDD-010 §12) toàn câu luyện thường — 6 câu mới chính là phần để dành đó, nên chúng nhắm vào lỗi hiểu cụ thể hoặc ranh giới khái niệm, không phải bài tập lặp. File đã nạp chuyển sang scripts/seeded/. Còn lại của Toán: Hình học 168 câu | 🔄 |
| SRC-160 | 2026-08-16 | Chỉ đạo giao diện, năm tin liên tiếp: (a) "nhiều người nhận xét UI không tạo cảm giác Premium, nhìn cứ lổn nhổn, nhà quê" — nhờ tư vấn thế nào là premium rồi dựng thử 1-2 giao diện; (b) giao AI tự quyết toàn bộ, kể cả đổi font, kèm cảnh báo "có vài lần tôi dùng font khác thì nó hay bị vỡ font"; (c) "ký tự tiếng Việt có dấu, font to tầm 20 trở lên, thường bị lỗi hoặc nhìn xấu" và "Arial mà font nhỏ dưới 15 thì vô cùng đẹp"; (d) xem bảng đối chiếu Arial/Inter rồi chốt giữ Arial; (e) "nhiều màu sắc quá, tôi thấy kinh tởm lắm, ít màu thôi, thậm chí hầu hết không có màu gì cũng ok, chỉ có rất ít màu để làm button (để làm CTA) thôi" | DS-001 v0.4 §2·0 + §3b + §5b luật 1, REQ-UX-11/12. Màu — gần như đơn sắc: thang ocean/aqua kéo về xám (giữ tên biến, chỉ đổi giá trị nên không phải sửa hàng trăm chỗ dùng text-ocean-900), school bỏ đè --accent (còn --school-mark 3px), hero bỏ gradient/tia sáng/bong bóng thành mặt phẳng gần đen, icon bỏ gloss, nút accent phẳng, --accent-soft/ink hoá trung tính; --n12-sun-400 phải sáng lên #E0B45C để giữ AA trên nền mới (5,19:1) nên chỉ được dùng trên nền tối hoặc làm chấm nhỏ. Bố cục: thêm --measure-text/app/wide + .n12-page*; sửa luật tối giản §5b điều 1 từ "full-width" thành "nền tràn viền, chữ có cột" — nguyên nhân số một của cảm giác "lổn nhổn" là tiêu đề ba chữ nằm cách mũi tên 1400px. Hình khối: radius 1.25rem→0.875rem, bóng về gần mặt phẳng. Component mới: .n12-seg (segmented control thay cụm viên thuốc lọc), .n12-chev (SVG thay ký tự ▸▾→ — Arial không có U+2192 nên nó rơi sang font khác, đúng kiểu "vỡ font" chủ dự án gặp). Chữ: giữ Arial + 4 luật chống bết dấu (§3b) + bỏ -webkit-font-smoothing: antialiased. Quét toàn bộ chuỗi 3 app: 280 ký tự ngoài ASCII, 141 nằm ngoài vùng phủ Inter — trong đó 95 là emoji (luôn lấy từ font hệ điều hành, không liên quan), phần còn lại là toán học và mũi tên. Áp vào 3 site (chỉ đạo bổ sung "sửa toàn bộ learn · marlins · nemo12.com đều theo chung một Design System"): 14 file đổi shell w-full px-4 sm:px-8 sang .n12-page*; 30 file dọn cỡ chữ theo §3b (65 chỗ font-extrabold→font-bold, 25 chỗ bỏ tracking-tight, 37 chỗ hạ cỡ về trần 32px); 4 cụm lọc đổi sang .n12-seg (Phòng Lab, Orca, Roadmap, Goals của marlins) và bỏ icon/emoji trong từng ô; 7 chỗ ký tự tam giác ▸▾▲▼ đổi sang <Chev/> SVG mới thêm vào N12Art. Kiểm: contrast-check 145/145 PASS, typecheck sạch, lint sạch, 318 test xanh, build web·learn·marlins | ✅ |
| SRC-161 | 2026-08-16 | Chỉ đạo: cho 20 link tới các Learning Experience có hình vẽ tương tác; kèm câu hỏi các Experience dạng rich content / 3D gọi tên là gì | URL trước đây chỉ tới được Phòng Lab của môn (/{school}/lab/{môn}), muốn chỉ ai đó một bài cụ thể thì phải mô tả bằng lời. Thêm deep link /{school}/lab/{môn}/{bài} — chỗ nhận node đã có sẵn (prop focusNodeId, dùng cho nút "Bắt đầu" ở cockpit), phần còn thiếu duy nhất là đọc node từ URL. Smoke test khoá luật này vì router rất dễ vỡ khi sửa. Trả lời câu hỏi tên gọi: ba thứ khác nhau — (1) Lab = Learning Experience tương tác cao, nội dung nằm ở DB (labs + lab_content), có trường engine: picker 135 · parametric 59 · stepper 14 · inquiry 8 · sim 6 · scene3d 3; (2) Khám phá (Explore the idea) = bước mở đầu của Learning Experience, chạy bằng config trong bundle, 65 bài có kéo-trượt thật; (3) hình vẽ trong câu hỏi (item_figures, 10 hình/bài cho 12 bài Hình học). Chưa có gì dùng three.js — toàn bộ là SVG sinh bằng thuật toán (zero-asset, SDD-010), và 3 lab scene3d hiện mới ở mức inquiry_only | ✅ |
| SRC-162 | 2026-08-16 | Feedback: cụm "Vì sao con chọn đáp án C" cần hai lựa chọn mỗi dòng (đang một dòng một lựa chọn) | Learning.tsx: lưới lựa chọn thành sm:grid-cols-2. Bốn lựa chọn xếp một cột đẩy nút gửi xuống quá sâu, learner phải cuộn mới thấy. Màn hẹp vẫn giữ một cột vì hai cột thì nhãn dài bị vỡ dòng. Popover dấu "?" vẫn nằm gọn trong thẻ, đã kiểm bằng ảnh chụp thật. Smoke test kiểm bằng toạ độ thật (hai lựa chọn đầu cùng dòng và khác cột, lựa chọn thứ ba xuống dòng) chứ không kiểm tên class — class đúng mà lưới vỡ thì test vẫn phải đỏ | ✅ |
| SRC-163 | 2026-08-16 | Feedback: khi learner trả lời chưa đúng thì phản hồi quá khô, quá chán, không còn tí cảm xúc nào để làm câu tiếp | Đảo lại luật của SRC-149 (câu sai thì không có con vật). Nỗi lo cũ — nhân vật đứng cười lúc learner vừa sai thì thành trêu — vẫn đúng, nhưng cách chữa không phải bỏ nhân vật mà là bỏ phần ăn mừng: giữ con vật ở tư thế bình thản (.n12-mascot-calm: hiện lên nhẹ, không nhún, không bong bóng). pickNudge() chọn câu nói theo tình huống thật — bỏ qua · gặp lại vẫn sai · rất chắc mà trượt · tự nhận chưa chắc · sai 2 câu liền · còn lại xoay vòng — và mỗi câu làm đúng ba việc: bình thường hoá cái bẫy ("nhiều bạn cũng dính đúng chỗ này"), nói cái vừa thu được ("giờ con biết chỗ nào cần nắm lại"), giữ nhịp đi tiếp. Lời giải thích misconception chuyển vào trong bóng thoại vì đó là thứ đáng đọc nhất ở màn này. Luật không được phá: không khen một câu sai — trẻ nhìn ra ngay và sẽ thôi tin mọi lời khen khác. Smoke test đổi theo luật mới (có con vật bình thản, không bong bóng, bóng thoại phải có chữ) | ✅ |
| SRC-164 | 2026-08-16 | Feedback: hình vẽ trong Learning Experience nhiều màu quá; và cụm "Con thấy phần này thế nào?" cần hai lựa chọn mỗi dòng | Bảng DIAGRAM từ 22 giá trị / 6 họ màu rút về 8 giá trị / 2 họ (xem DS-001 §4e): MAIN cho đối tượng đang học, MARK cho đúng một điểm nhấn, còn lại trung tính. Key cũ giữ nguyên tên làm bí danh nên 193 chỗ dùng không phải sửa. Có test canh số giá trị (≤10) và số họ màu (≤2) — luật về sau: muốn thêm màu thì phải bỏ một màu. Sửa kèm hai lỗi phát hiện lúc làm: (a) thanh trượt lấy màu xanh mặc định của hệ điều hành — một màu ngoài bảng, mỗi máy một sắc → đặt accent-color; (b) figures/engine.tsx viết var(--n12-ocean-500, #1F7AB8) mà thang --n12-ocean-* là thang trung tính (xám), nên biến vẫn tồn tại và hex dự phòng không bao giờ được dùng — nét chính của mọi hình hình học lâu nay ra màu xám chứ không phải xanh như ý định. Cụm tự đánh giá cũng thành sm:grid-cols-2 như SRC-162. Fixture e2e thêm node có phần Khám phá để test chạm được tới hình tương tác | ✅ |
| SRC-165 | 2026-08-16 | Feedback: mấy viên ngọc (bong bóng mạng) ở góc trên phải không đổi màu | BUBBLE_ART đã bị đổi sang toàn màu xám (body #C3C9CE, spent #E8EBED) nên bong bóng CÒN và bong bóng ĐÃ MẤT gần như trùng màu — learner mất một mạng mà nhìn không ra, và đồng hồ đếm mạng thành vô nghĩa. Khôi phục màu nước cho bong bóng còn, giữ xám cho bong bóng đã mất. Nguồn gốc đáng ghi lại: thay đổi đó không phải của phiên này — nó là việc đang dở của một phiên Claude chạy song song trên cùng thư mục, bị cuốn vào commit SRC-162 do tôi dùng git add apps/learn quét cả thư mục. Đây là lần thứ hai (lần đầu ở SRC-153 với hai file docs). Luật từ nay: add đúng file của mình, không add cả thư mục. Thêm test canh: bong bóng còn phải có sắc màu thật (chroma > 60), bong bóng mất phải xám (chroma < 30), hai trạng thái phải cách nhau đủ xa để nhìn là thấy | ✅ |
| SRC-166 | 2026-08-16 | Chỉ đạo (chọn "mức mạnh" trong hai phương án AI đưa ra): "toàn bộ mascot và icon thành đơn sắc" | DS-001 §2·0 vòng 3. 106 giá trị trong ART chuyển sang thang mực bằng cách tính độ sáng cảm nhận của từng hex rồi thay bằng xám đúng độ sáng đó, lệch nhẹ về xanh lạnh — KHÔNG dùng filter: grayscale vì filter cho ra xám bùn và làm bẹt nét, còn cách này giữ nguyên khối sáng-tối nên mascot vẫn đọc ra hình. Tách khối LOGO ra khỏi ART để logo cá hề cam sống sót (SRC-049/069 đã chốt riêng logo là cam, và logo là dấu nhận diện chứ không phải mascot minh hoạ); NemoLogo.tsx đổi sang đọc LOGO. Giữ màu --school-mark vì khi mascot đã đơn sắc thì vạch 2px là thứ duy nhất còn phân biệt sáu school. explore/palette.ts không đụng tới — màu ở đó mang nghĩa dạy học, đã rút riêng ở SRC-164. Đồng bộ 4 file art sang web·learn·marlins·coral. Kiểm: typecheck sạch, build 4 app, chụp lại trang school để soát bằng mắt. Sau đợt này mỗi màn còn đúng ba nguồn màu trang trí: logo (1 lần), nút hành động chính (≤1 lần), vạch school 2px. Vá ngay sau đó: lượt xám hoá đã đè mất bản sửa SRC-165 của phiên song song (bong bóng "mạng" dùng SẮC MÀU để phân biệt còn/mất — đó là thông tin chứ không phải trang trí) và làm đỏ apps/learn/src/palette.test.ts. Đã khôi phục BUBBLE_ART cùng gradient .n12-bubbles, ghi rõ đây là ngoại lệ của luật đơn sắc. Rút ra luật chung: màu mang nghĩa thì không xám hoá, màu trang trí thì có. Lỗi kiểm chứng của lượt đầu: lọc output npm test bằng tail -1 nên chỉ đọc kết quả workspace cuối, che mất phần learn đang đỏ; số "318 test xanh" trong commit 56cc364 là sai, con số đúng của cả repo là 372 (learn 54 + api 318) | ✅ |
| SRC-167 | 2026-08-16 | Chỉ đạo: cần một mục theo từng môn để learner vào xem bất kỳ lab nào ngay lập tức, không cần làm quiz hay tìm hiểu gì cả; ưu tiên lab Toán | Thư viện Lab: API GET /v1/subjects/{môn}/labs + trang /{school}/thu-vien-lab/{môn}, nhóm theo unit, bấm là mở thẳng. Phát hiện khi làm: /labs?subject_id=… lọc node_id IS NOT NULL, mà 99/225 lab chưa gắn vào bài nào → gần một nửa số lab đã migrate không xuất hiện ở bất kỳ đâu trong app; thư viện là đường đầu tiên chạm tới chúng. Toán có 71 lab (13 chạy trọn vẹn, 58 mới có predict → observe → check) — thư viện nói thẳng lab nào chưa dựng xong phần mô phỏng. Chỉ tải metadata ~200B/lab. 2 smoke test. SDD-010 §10b. (Số cũ SRC-166 bị một phiên song song dùng trùng và ghi đè hàng trong sổ — đánh lại số ở đây.) | ✅ |
| SRC-168 | 2026-08-16 | Câu hỏi chủ dự án: nên để progress bar hay bỏ? — "không có thì cứ thấp thỏm, không biết bao giờ mới xong"; muốn thanh chia thành các khối cách nhau để đếm được còn bao nhiêu cục nữa | Nên giữ, và phải là dạng khối (.n12-steps, mỗi ô một câu, cách nhau 4px). Hai lý do chọn khối thay vì thanh liền: (1) thanh liền trả lời "được bao nhiêu phần trăm" nhưng câu learner thật sự hỏi là "còn mấy câu nữa" — khối đếm được bằng mắt; (2) số câu một phiên có thể tăng vì câu sai được xếp lại xuống cuối, với thanh liền thì tiến độ 80% bỗng tụt còn 73% — nhìn như bị phạt, còn với khối thì chỉ là thêm một ô ở cuối, phần đã làm không mất đi. Ô thêm mang màu khác nên việc số ô tăng tự giải thích, không cần thêm chữ. Ba trạng thái phân biệt bằng độ đậm chứ không bằng màu, để hợp với hướng đơn sắc đang được áp cho hệ token. Có role="progressbar" + nhãn "Câu X trên Y" cho trình đọc màn hình. Smoke test khoá: số ô = số câu, và sai một câu thì có đúng một ô được thêm | ✅ |
| SRC-169 | 2026-08-16 | Feedback: mở lab ra thì "chả tương tác được, chả có gì hay ho", có lab còn rỗng hẳn; rồi chỉ đạo xử lý nốt lab các môn khác, đặc biệt Lý và Hoá | Đo trước khi sửa: 57/58 lab Toán dạng inquiry_only có node ĐÃ CÓ hình tương tác trong Explore Engine — thứ learner cần nằm sẵn trong hệ, chỉ là LabPlayer không lấy ra dùng mà hiện một dòng chữ "đang được dựng lại". Nối vào: lab lấy hình của chính bài nó gắn. Với 25 lab Lý · Hoá · Sinh không gắn bài nào (node_id rỗng), thêm bảng ánh xạ tay labViz.ts (23 lab) — viết tay từng dòng, không đoán theo tên, vì một hình sai chủ đề còn tệ hơn không có hình: learner sẽ tin hình đó minh hoạ cho điều đang đọc. Cố ý để trống 2 lab (hoá hữu cơ, tiến hoá) vì hệ chưa có hình đúng chủ đề — UI nói thật thay vì ghép tạm. API thư viện loại lab rỗng (không quan sát, không mảnh, không câu kiểm tra): mở ra thấy trang trắng còn tệ hơn không thấy lab đó. Test: mọi hình được ánh xạ phải CÓ THẬT, hai lab để trống phải giữ nguyên chủ ý; smoke test kiểm lab chưa có mô phỏng riêng vẫn hiện thanh trượt | ✅ |
| SRC-170 | 2026-08-16 | Feedback: click vào một lab thì cần có URL riêng | /{school}/thu-vien-lab/{môn}/{lab}: mở lab thì URL đổi theo, dán URL vào trình duyệt là vào thẳng lab đó, thoát lab thì URL trở lại danh sách. Dùng lại đúng đoạn thứ tư của URL đã có từ SRC-161 (deep link tới Learning Experience) nên không thêm khái niệm route mới. Smoke test khoá cả ba chiều: mở → URL đổi · vào thẳng bằng URL · thoát → URL sạch | ✅ |
| SRC-171 | 2026-08-16 | Câu hỏi chủ dự án: "không gắn node" là không có Knowledge Node tương ứng đúng không? Có nên tạo node đơn giản không? → chốt làm bước 1, rồi làm bảng rà cho bước 2 | Rà ra điều then chốt: node đã có sẵn, chỉ là lab không được nối vào — Vật lí có 17 node, Hoá 30, Sinh 16, đúng những chủ đề của lab mồ côi. Tư vấn: đừng tạo node mới. Knowledge Node không phải hộp đựng nội dung mà là đơn vị ĐO (mastery · readiness · retention · learning plan · đường đi Unit · luật mở khoá đều khoá theo node); tạo node trùng chủ đề sẽ chẻ đôi mastery trên cùng một năng lực, cả hai con số đều sai. Bước 1 đã làm: nối 29 lab Lý·Hoá·Sinh vào node có sẵn → lab vào đúng bài trong đường đi Unit, hình tự tìm được, câu kiểm tra thành bằng chứng gắn vào node có mastery thật; bảng ánh xạ tạm labViz.ts co từ 23 dòng còn 1 (ngoại lệ lệch môn). Lab chưa gắn node: 99 → 70. Bước 2: bảng rà — 35 lab ghép được vào node có sẵn (chờ duyệt theo lô), 35 lab gom thành 6 mảng năng lực kèm câu hỏi cần trả lời cho từng mảng. Nguyên tắc chốt: node sinh ra từ chương trình, không sinh ra từ nội dung | ✅ |
| SRC-172 | 2026-08-16 | Chỉ đạo: lab dạng picker cần mỗi bước một section riêng trên một dòng, hai cột (lời giải · giải thích), có nút icon nhỏ để hiện bước tiếp / xoá bước gần nhất; thử ở lab "Trục căn ở mẫu" trước, ổn thì áp cho lab khác | Dựng lại phần Khám phá của engine picker (135 lab dùng chung engine này, nên sửa một chỗ là đổi hết). Bản chip cũ có một lỗi thiết kế thật: mỗi lần bấm là thay chỗ nội dung cũ, nên learner không bao giờ thấy được hai bước cạnh nhau — mà chính việc so bước trước với bước sau mới là chỗ hiểu ra. Nay các bước xếp chồng và hiện dần: bắt đầu ở 1 bước, hai nút icon + / − để hiện tiếp hoặc bỏ bước gần nhất, kèm bộ đếm "2/3 bước". Mỗi bước hai cột: trái là lời giải (nền thẻ, giữ nguyên đánh dấu ~…~), phải là giải thích. Learner tự điều khiển nhịp — muốn tự nghĩ trước thì dừng, muốn xem tiếp thì bấm. Smoke test khoá: mở ra chỉ có bước 1 · bấm + thì bước 2 hiện mà bước 1 vẫn còn · bấm − thì bước 2 biến mất | ✅ |
| SRC-173 | 2026-08-16 | Feedback: bố cục lab lủng củng, dài dòng, quá nhiều text thừa — cần thoáng, nhiều khoảng trống, giảm chữ, thay bằng icon | Tìm ra chỗ dài dòng nằm trong dữ liệu chứ không phải trong CSS: chuỗi stage của lab legacy nhét CẢ lời giải lẫn lời giải thích, ngăn bằng "←" ("1/√2 = √2/2 ← nhân tử & mẫu cho √2 để mẫu hết căn"), mà cột phải đã nói đúng điều đó → hai cột lặp nguyên câu. splitStage() cắt phần sau mũi tên, chỉ giữ khi bước đó không có lời giải thích riêng (thà lặp còn hơn mất). Bố cục: bỏ khung thẻ lồng nhau, các bước cách nhau bằng khoảng trắng + một đường kẻ mảnh; chữ "Bước 1" → huy hiệu số tròn; cột giải thích thành chữ nhạt có vạch dọc thay vì hộp nền; chữ "2/3 bước" → dãy chấm đếm được bằng mắt; nút icon to lên 40px. Smoke test khoá luật cắt lặp và luật hiện dần | ✅ |
| SRC-174 | 2026-08-16 | Chỉ đạo: xem lab English/Vietnamese, chỉnh nốt cho đồng bộ | Ba môn dùng chung một engine nên bố cục đã đồng bộ sẵn sau SRC-173; phần thật sự cần chỉnh là chữ lặp giữa hai cột. Đo trên toàn bộ 400 bước có đủ hai cột: 168 bước có phần giải thích nhét sau dấu "←" (đã cắt từ SRC-173), 19 bước có cột phải lặp ≥70% từ của cột trái, 55 bước lặp một phần, 317 bước bổ sung nhau thật. Vì vậy isEcho() chỉ giấu cột phải ở nhóm lặp rõ (≥70%) — hạ ngưỡng xuống 50% sẽ ăn mất 55 bước mà phần lớn trong đó cột phải nêu quy tắc khái quát, thứ đáng giá nhất của cột giải thích (ví dụ trái là "She go → She goes", phải là "hoà hợp chủ–vị, thì đúng, số ít/nhiều" — không được cắt). Ngưỡng là quyết định có số liệu, ghi rõ trong test để lần sau ai đổi cũng thấy cơ sở. 5 unit test + 1 smoke test trên lab English thật | ✅ |