Skip to content

SDD-038 · Learning Plan ​

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.

39. Learning plan: một lời hẹn, không phải một biểu mẫu (SRC-916, 2026-09-20) ​

a. Thứ bị gỡ, và vì sao gỡ là đúng ​

Trang có hai khối, cả hai đều chạy đúng: bốn ô chọn band hiện tại, rồi một nút gọi model chia các mốc trung gian. Nhưng chúng trả lời sai câu hỏi mà learner mang tới đây. Nó mở "Learning plan" để XEM kế hoạch của mình, và gặp một biểu mẫu bắt nó tự khai điểm rồi tự bấm nút để máy đoán hộ.

Chỗ hỏng không nằm ở kỹ thuật. Một kế hoạch máy chia từ bốn con số learner tự đoán trông y hệt một kế hoạch thật: cùng bảng, cùng ngày tháng, cùng con số band. Nó mời một đứa trẻ tin vào thứ chưa có ai nhìn. Kế hoạch thật ra đời ở buổi gặp 1-1, nơi thầy cô đã đọc bài viết và nghe đoạn nói của chính đứa trẻ ấy.

b. Không mất đường nào ​

Khai điểm hiện tại vẫn còn ở ba chỗ (IeltsSettings, IeltsLearningSetup, IeltsGoalCard) — và ba chỗ ấy mới đúng là nơi của nó: chúng là ngăn khai báo, không phải trang xem kế hoạch. Gỡ ở đây không lấy đi khả năng nào, nó chỉ thôi hỏi một câu ở sai chỗ.

Dòng "Learning plan" ở lại trong IELTS_TASKS: nó là bước cuối của năm chặng, và bỏ hẳn thì learner mất luôn chỗ để biết bước ấy tồn tại và đang chờ điều gì. Trang nay nói vì sao phải chờ chứ không chỉ nói "hãy chờ", và có một đường sang đặt buổi gặp để nó không thành ngõ cụt.

c. Tiếng Anh, và mấy chuỗi sót lại ​

learn.nemo12.com/ielts/** nói tiếng Anh (SRC-834/835), và trang này đã đổi từ SRC-865. Ba chuỗi tiếng Việt vừa gỡ cùng biểu mẫu ("Con đang ở đâu?", "Lưu", "Chia mốc") là sót lại từ trước lần đổi ấy, không phải một ngoại lệ có chủ ý.

d. Gác bằng e2e, vì typecheck không thấy một biểu mẫu mọc lại ​

Thứ phải vắng mặt ở đây là một biểu mẫu. Nó mọc lại thì không có gì đỏ. Nên phép kiểm gác cả hai vế: câu thông báo có mặt, và main select cùng nút Lưu đếm bằng 0.

60. Plan Board: bốn kỹ năng × các tháng, và số giờ phải bỏ ra (SRC-952, 22.09.2026) ​

60a. Nó khác /ielts/setup/plan ở chỗ nào ​

Trang mốc (plan) nói learner phải TỚI ĐÂU ở vài thời điểm. Plan Board nói phải BỎ RA BAO NHIÊU để đi từ mốc nọ sang mốc kia. Một learner đọc mốc thì biết đích; đọc bảng này mới biết tuần sau mình phải ngồi mấy giờ. Hai câu hỏi khác nhau nên hai trang khác nhau, và gộp lại thì bảng mốc (vốn ngắn, đọc một liếc) sẽ bị một bảng mười hai cột nuốt mất.

60b. Ba con số là luật ​

Giá trịỞ đâu
Mốcngày 1 hoặc 15 hàng tháng, cách nhau 30-31 ngàyplanMilestones
Mặc địnhmỗi tháng 2 kỹ năng, mỗi kỹ năng +0.5defaultPlan
Quy đổi0.1 điểm ≈ 3 giờHOURS_PER_TENTH

Hiện chúng là hằng số trong bundle. Khi nối back-end chúng phải thành cấu hình đọc từ server: con số 3 giờ là một giả định về việc học, và một giả định sửa được bằng tay ở production thì không được ghim trong mã trình duyệt.

60c. Learner chỉnh từng ô, bước 0.1 ​

Hai nút − và + thay cho kiểu bấm-để-xoay-vòng: xoay vòng thì muốn 0.3 phải bấm ba lần và đi qua hai giá trị sai, còn muốn lùi thì phải đi hết vòng. Nút + khoá khi ô chạm trần — khoảng còn thiếu tới mục tiêu TẠI thời điểm ấy — vì cộng quá đích là bày ra một kế hoạch tiêu giờ vào phần điểm không tồn tại.

Cộng dồn bằng phần mười nguyên (Math.round(x * 10)), không cộng thẳng số thực: 0.1 + 0.2 trong dấu phẩy động ra 0.30000000000000004.

60d. Chưa lưu, và trang nói thẳng ​

Thiếu mục tiêu hoặc thiếu điểm hiện tại thì không dựng bảng, chỉ nói thiếu gì: một bảng vẽ từ điểm bịa ra trông y hệt một bảng thật.

60e. Đường vào ​

/ielts/plan-board, đứng ngang setup · settings · profile — không phải một IeltsTaskKey, vì nó không có điểm kết thúc. Ở hub /ielts nó là ô ĐẦU trong hàng ba lối đi (trước Live class và Leaderboard) và mang màu accent: hai cái kia là chỗ xem cho vui, còn đây là thứ duy nhất trong hàng đổi được việc learner làm hôm nay.

Chỗ dễ sai: nhánh legacy /ielts/<task> trong pathToRoute nhận mọi khoá lạ rồi đổ sang mạch luyện, nên plan-board phải đứng TRƯỚC nó — cùng bẫy đã sập ở SRC-950 với skills. Có test chặn trong ieltsRoutes.test.ts.

60f. Tooltip cột tháng nói tiếng Anh ​

Bấm vào đầu cột hiện "In November 2026 · milestone 01.11.2026". Bản prototype viết câu này bằng tiếng Việt; chủ dự án chốt tiếng Anh ngày 22.09.2026, đúng luật sân luyện /ielts/** (SRC-834/835) — một câu tiếng Việt ở đây sẽ là ngoại lệ duy nhất trong cả màn hình.

66. Learning Plan: đọc trước, sửa sau, và một cái thước giờ (SRC-958, 22.09.2026) ​

  • Tên trang là Learning Plan. "Plan Board" là tên của một thứ đồ nghề; learner không đi tìm một cái bảng, họ đi tìm kế hoạch học của mình.
  • Mặc định đọc. Tám mươi cái nút tròn (bốn hàng × mười cột × hai nút) đập vào mắt trước cả con số. Chúng chỉ hiện khi bấm Edit, cùng luật với BandStepper ở §62a.
  • Bỏ ngày dưới tên tháng ở đầu cột: nó lặp lại đúng thông tin của dòng trên bằng chữ nhỏ hơn. Ngày đầy đủ vẫn đọc được trong tooltip khi bấm vào tháng.
  • Hai thẻ điểm ở trên cùng, chỉ đọc. Sửa chúng là việc của trang Goal; hai chỗ cùng sửa một giá trị là hai chỗ sẽ lệch nhau.
  • Cụm "Study time per month" ở cuối trang, learner sửa được. Nó không phải một con số của kế hoạch mà là cái THƯỚC để đọc kế hoạch, nên nó đứng sau thứ nó đo. Hàng tổng giờ được tô theo nó: vượt mức là coral, nhẹ hơn 60% mức là xanh. Coral chứ không đỏ — REQ-UX-03, và một tháng nặng là việc cần cân lại, không phải một lỗi sai.
  • Hai dòng hướng dẫn cũ đã bỏ: cách chỉnh một ô thì hai cái nút tự nói ra khi learner bấm Edit.

67. Nút xếp hộ: nhịp khởi động bốn kỹ năng (SRC-959, 22.09.2026) ​

67a. Hai luật, và cả hai nói về việc HỌC ​

  1. Thứ tự vào cuộc. Reading và Listening trước vì chúng là kỹ năng TIẾP NHẬN: learner tiến được mà chưa cần tự tạo ra ngôn ngữ. Writing và Speaking là kỹ năng TẠO RA, và chúng ăn vào chính vốn từ với cấu trúc mà hai kỹ năng đầu vừa dựng. Đẩy Speaking vào tháng đầu là bắt learner nói bằng thứ họ chưa đọc đủ để có.
  2. Nhịp tăng dần. Tháng đầu nhẹ (0.2 mỗi kỹ năng), tháng hai vừa (0.4), từ tháng ba là nhịp đầy (0.5). Một kế hoạch đòi nhịp đầy ngay tuần đầu là kế hoạch vỡ ở tuần thứ ba.

RAMP_START_MONTH và RAMP_STEPS trong IeltsPlanBoard.tsx; hàm thuần rampPlan, có test riêng ở planBoard.test.ts (8 ca).

67b. Ba chỗ dễ sai, đã có ca gác ​

  • Vượt đích. Cột cuối của một kỹ năng phải là phần lẻ còn lại, không phải một nhịp đầy tràn qua mục tiêu: vượt đích nghĩa là bảng xếp giờ học cho phần điểm không tồn tại.
  • Kỹ năng đã ở đích không được nhận cột nào khác 0.
  • Cả bốn đã ở đích thì vẫn trả một cột rỗng, để bảng có chỗ đứng thay vì biến mất.

67c. Nút chỉ hiện khi đang sửa ​

Nó GHI ĐÈ cả bảng, nên nó không được đứng trong một màn learner tưởng là chỉ để đọc. Câu dưới nút nói trước nó sắp làm gì — một nút ghi đè không báo trước là một nút người ta chỉ dám bấm một lần rồi thôi.

68. Learning Plan lưu xuống CSDL (SRC-960, 22.09.2026) ​

68a. Ba chỗ bảng chưa đúng, tìm ra khi rà trước lúc lưu ​

  1. Mốc trôi. Các mốc tính từ ngày hôm nay. Lưu "cột thứ n tăng bao nhiêu" thì learner mở lại sau hai tháng sẽ thấy đúng những con số cũ nằm dưới những cái tháng MỚI — cả kế hoạch đổi nghĩa mà không ai đụng vào. Mỗi ô nay mang theo ngày mốc của chính nó.
  2. Bảng thiếu cột. Learner hạ mức tăng của vài tháng là đích lùi ra ngoài cột cuối, và dòng tổng kết nói "not reached yet" mà không có chỗ nào sửa cho tới nơi — một bảng tự mâu thuẫn. Nay thiếu thì tự nối thêm cột rỗng, tới trần 24.
  3. Ngày trong chuỗi ISO. new Date("2026-10-01") được đọc là UTC; ở giờ Việt Nam nó ra 01.10 lúc 07:00 — đúng ngày, nhưng chỉ là may. Cả hai chiều nay đi qua isoDate/spreadSavedPlan, dựng Date từ ba con số theo giờ địa phương.

68b. Hai bảng, khoá theo ngày ​

learner_ielts_plan (một dòng mỗi learner: monthly_hours) và learner_ielts_plan_cells (một dòng mỗi ô: milestone_date · skill · gain). Migration 0270, idempotent.

Không dùng một cột plan_json: nó ghi nhanh hơn, nhưng mọi câu hỏi về sau ("tháng này learner nào đang đặt quá 40 giờ") đều phải kéo cả cục JSON ra rồi tự bóc.

Ghi là THAY CẢ BẢNG trong một batch: learner bấm Save một lần cho cả lưới, và ghi từng ô thì hai lượt lưu chồng nhau sẽ để lại một bảng lai — vài ô của lượt trước, vài ô của lượt sau, không lượt nào đúng. Có ca e2e-style gác đúng điều này, vì bảng lai vẫn trả 200 và vẫn vẽ ra được.

Ô bằng 0 không lưu: nó là mặc định "tháng ấy không nâng kỹ năng này". Lưu chỉ làm bảng phình ra bốn lần. spreadSavedPlan điền lại chỗ trống khi đọc.

68c. Lọc đầu vào ở server, không tin client ​

Mốc phải rơi đúng ngày 1 hoặc 15 và là ngày có thật; gain phải là bội của 0.1 trong khoảng 0-5; khoá trùng là lỗi chứ không lấy dòng cuối; monthly_hours là số nguyên 1-200. Một gain bằng 3.7 ghi xuống sẽ sinh ra kế hoạch mà chính giao diện cũng không vẽ lại được.

Một lượt PUT hỏng không được đụng tới bảng đang lưu — learner gõ nhầm một ô rồi mất cả kế hoạch là kiểu mất mát không ai tha thứ được. Có ca gác riêng.

72. Learning Plan: hai thẻ sửa được, và một ranh giới (SRC-967, 22.09.2026) ​

  • Bỏ dòng "← IELTS": thanh trên đã có logo bấm về được, nên một đường lùi thứ hai chỉ đẩy tiêu đề xuống thêm một dòng.
  • Hai thẻ sửa được tại chỗ. Ở SRC-958 chúng chỉ đọc, với lý do "sửa là việc của trang Goal" — mà trang Goal đã bỏ ở SRC-961, và đây đúng là màn learner đang nhìn khi nhận ra con số sai. Vẫn MỘT nguồn: cả hai lưu thẳng vào ielts-baseline và ielts-goal, không có bản sao nào ở đây.
  • Ba nút Edit, ba phạm vi rõ ràng. Thẻ Current, thẻ Target, và bảng — mỗi thứ một EditGate. Nút của bảng đứng ở góc trên phải của chính bảng; để nó ở đầu trang cạnh hai nút kia thì không ai đoán được nó sửa cái gì.
  • Một đường kẻ và một quãng trống giữa hai thẻ và bảng: hai thẻ nói learner đang ở đâu và đi tới đâu, bảng nói đi bằng cách nào.
  • Cụm dưới cùng gộp thành một dải bốn ô: ba con số kết quả (tổng giờ · số tháng · ngày chạm đích) và một con số learner tự đặt (giờ mỗi tháng). Chúng vốn là cùng một câu chuyện — "kế hoạch này tốn bao nhiêu, và bấy nhiêu có vừa sức con không" — nên tách làm hai khối thì phải đọc hai lần mới ghép lại được. Ô sửa được có viền accent, thay cho dòng chú thích đã bỏ.

Ba aria-label (Current · Target · Monthly plan) là cách duy nhất chỉ đúng một khối trên một trang có ba nút Edit giống hệt nhau — cho trình đọc màn hình, và cho e2e.

73. Learning Plan: chiều cao đứng yên, và ba nguyên tắc (SRC-970, 22.09.2026) ​

73a. Nút cộng trừ GIỮ CHỖ ở chế độ đọc ​

SRC-954 ẩn hẳn hai nút ấy, và đó vẫn đúng: một nút mờ cạnh con số là lời mời bấm mà bấm không làm gì. Nhưng ẩn bằng cách bỏ khỏi luồng thì ô co lại, cả thẻ đổi chiều cao, và hai thẻ cạnh nhau nhảy lên nhảy xuống mỗi lần bấm Edit.

Nay chúng dùng invisible chứ không hidden: không vẽ ra, nhưng vẫn chiếm chỗ. Kèm aria-hidden và tabIndex={-1} để bàn phím và trình đọc màn hình không vấp phải một nút vô hình. Có ca e2e đo chiều cao thẻ trước và sau khi bấm Edit.

73b. Bốn ô band đọc theo CỘT ​

Listening │ Writing
Reading   │ Speaking

Cột trái là hai kỹ năng tiếp nhận, cột phải là hai kỹ năng tạo ra. Đó là cách learner nghĩ khi quyết đẩy cái nào trước, và cũng là cách nút "Let Nemo arrange it" xếp lịch (§67). Xếp theo hàng L-R-W-S thì hai nhóm bị trộn vào nhau, và cái lưới không nói được gì ngoài thứ tự của một quy ước. Đổi ở BAND_SKILLS nên /ielts/settings theo cùng.

73c. Cả hai thẻ đều sửa được ngày ​

Thẻ Current mang ngày lần thi đã qua, Target mang ngày thi sắp tới. Cả hai đều là thứ learner tự khai và tự nhớ sai được. Một hàng ngày duy nhất, h-9 cố định cho cả hai chế độ — xem 73a.

73d. Thẻ "How to build a plan that works" ​

Ba nguyên tắc, đặt ở CUỐI trang:

  1. Bắt đầu bằng một hai kỹ năng, không phải cả bốn. Reading và Listening trước.
  2. Ít nhất mười giờ một tuần, khoảng bốn buổi ba giờ.
  3. Nửa band một tháng là nhịp tốt.

Vì sao cần chúng: bảng phía trên cho learner sửa bất cứ ô nào, kể cả thành một kế hoạch không ai theo nổi. Nút xếp hộ đưa ra bản đúng nhịp nhưng không NÓI RA vì sao nhịp ấy đúng — và một learner không hiểu lý do sẽ sửa nó về đúng cái kế hoạch vừa được sửa đi.

Đặt cuối chứ không đầu: learner mở trang này để xem kế hoạch của mình, không phải để đọc luật.

Chữ tiếng Anh, viết cho một đứa trẻ lớp năm — câu ngắn, từ thường ngày, và chỗ nào cần một khái niệm thì nói bằng hình ảnh ("your brain needs to take in a lot of English before it can make good English"). Đây là ngoại lệ có chủ ý của SRC-953 (chữ hướng dẫn nói tiếng Việt): chính chủ dự án chốt tiếng Anh cho riêng mấy dòng này.

76. Monthly plan: đầu bài lên trên, và mốc là cuối tháng (SRC-974, 22.09.2026) ​

Vì sao hai con số phải đứng trên bảng. Chúng không phải kết quả, chúng là ĐẦU BÀI: số cột và mức giờ learner kham được mỗi tháng. Hàng tổng của bảng được tô màu theo đúng mức giờ ấy (coral khi vượt), nên để con số ở cuối trang là bắt learner đọc một bảng đã tô màu rồi mới xuống tìm xem nó đang tô theo luật gì. Dải dưới chân bảng nay chỉ còn hai con số kết quả: tổng giờ và ngày chạm đích.

Vì sao cộng trừ chứ không chỉ một ô gõ số. Chỉnh mức học là việc dò vài nấc rồi dừng, không phải việc biết trước con số. Ô gõ vẫn còn cho ai muốn nhảy thẳng từ 30 sang 12.

Vì sao mốc là ngày cuối tháng, và vì sao tháng hiện tại phải có mặt. Mốc cũ là ngày 1 hoặc 15 gần nhất SAU hôm nay, nên tháng đang sống không có cột: learner mở trang ngày 22.09 thì cột đầu là tháng 10, và những giờ họ học trong tám ngày còn lại của tháng 9 không có chỗ nào để đứng. Lấy ngày cuối tháng vì một mức tăng band là thứ đạt được SAU khi học hết tháng, không phải vào một ngày giữa tháng. Migration 0273 dựng lại learner_ielts_plan_cells để milestone_date nhận mọi ngày có thật (28, 29, 30, 31), và cổng phía API đổi theo: thứ còn bị chặn là ngày KHÔNG CÓ THẬT như 2026-02-30.

77. Learning Plan: hai thẻ gập lại (SRC-975, 22.09.2026) ​

Learner mở trang này để xem KẾ HOẠCH. Điểm hiện tại, điểm đích và ba nguyên tắc đều là thứ họ đã biết, chỉ thỉnh thoảng mới cần. Để cả ba mở sẵn thì cái bảng - thứ họ đến để xem - bị đẩy xuống dưới màn hình.

Thẻ gập ở đây khi gập vẫn nói ra một dòng NỘI DUNG (6.5 → 8.5 overall · test 17.12.2027), không chỉ một cái tên. Một thẻ gập chỉ còn tên là thứ learner phải mở ra mới biết có đáng mở không, và họ sẽ mở hết mọi thẻ đúng một lần rồi thôi. Trạng thái mở KHÔNG lưu: gập lại là để bảng nổi lên, nên mỗi lần vào trang phải bắt đầu từ trạng thái ấy.

87. Monthly plan: sửa được số tháng, và hai dải số đọc được (SRC-984, 22.09.2026) ​

Số tháng có một cái bẫy riêng. Hiệu ứng "nối thêm cột cho đủ tới đích" (SRC-960) sẽ mọc lại đúng cột learner vừa cắt, ngay trong lượt render kế tiếp - nút trừ trông như một cái nút không làm gì cả. Nên nay có cờ monthsPinned: learner đã tự đặt số tháng thì hiệu ứng thôi can thiệp. Cắt cột thì mức tăng của những tháng bị cắt mất hẳn, không giữ âm thầm ở đâu: một con số vô hình vẫn cộng vào tổng giờ là thứ không ai gỡ ra được. Thêm thì thêm cột rỗng.

Hai dải số. Bản trước xếp chúng bằng flex, nên hai con số dồn về mép trái một dải rộng gần hai nghìn pixel và bên phải là một khoảng trống không nói gì - đọc ra như một khối bị lệch chứ không như hai con số ngang hàng. Nay mỗi dải là một lưới hai cột chia đôi đúng chiều rộng bảng, có vạch dọc ngăn giữa, nhãn trên số dưới. Cùng một Stat cho cả bốn ô nên hai dải trên và dưới bảng đọc như một cặp.

Stepper tách ra dùng chung cho cả số giờ lẫn số tháng: hai ô cạnh nhau mà một ô có nút còn ô kia chỉ gõ được là thứ learner phải thử mới biết.

92. Chia sẻ kế hoạch ra một đường dẫn công khai (SRC-994, 23.09.2026) ​

Cái được chia sẻ là một CÁI KHOÁ, không phải một bản sao. Bảng learner_ielts_plan_shares (migration 0282) chỉ nối một slug với một learner; trang công khai đọc kế hoạch ĐANG SỐNG. Learner sửa bảng thì người cầm link thấy bản mới - đó đúng là điều họ muốn khi gửi cho bố mẹ hay thầy cô. Một bản chụp đông cứng sẽ nói sai ngay lần sửa đầu tiên.

MỘT learner MỘT slug. Bấm Share lần thứ hai trả lại đúng đường dẫn cũ, vì link ấy có thể đã nằm trong tin nhắn gửi cho ai đó. Ngược lại, ai đã thu hồi rồi bật lại thì nhận slug MỚI: làm sống lại link cũ là phản bội chính hành động thu hồi.

Trang công khai mang HỌ TÊN người chia sẻ (chỉ đạo chủ dự án 23.09.2026, SRC-1003, đảo quyết định "không tên" ban đầu của SRC-994). API trả name = learners.display_name, và trang hiện nó dưới tiêu đề. Lý do: người nhận link cần biết đây là kế hoạch của ai, và learner đã tự bấm Share. Vẫn KHÔNG trả id learner, không trả email - test route khoá đúng hai điều ấy.

Slug ngẫu nhiên 12 ký tự, bảng chữ bỏ các ký tự dễ đọc nhầm. Tên hay id learner trong URL là mời người ta thử tên khác. Slug sai và slug đã thu hồi trả cùng một 404: phân biệt hai cảnh ấy là nói cho người lạ biết đường dẫn này từng tồn tại.

Đã trả nợ (SRC-1207, chỉ đạo chủ dự án 03.10.2026): có nút thu hồi, và chỉ CHÍNH learner được chia sẻ. Audit 03.10.2026 tìm ra rằng không chỗ nào ghi revoked_at, nên link lỡ lọt vào nhóm chat bày họ tên và điểm mãi mãi; và vì POST chỉ đòi quyền xem learner, bố mẹ hay mentor cũng bật được chia sẻ thay learner.

  • GET .../ielts-learning-plan/share trả { slug, can_share }: ai xem được kế hoạch thì đọc được trạng thái, can_share chỉ đúng khi phiên là chính learner (selfLearnerId).
  • POST và DELETE cùng chặn mọi phiên không phải chính learner. DELETE ghi revoked_at; bật lại sau đó ra slug mới như luật trên.
  • Màn hình đọc trạng thái khi mở trang: đã chia sẻ thì link hiện ngay kèm nút Stop sharing; bố mẹ và mentor mở trang thì không có nút nào. Chữ theo luật tiếng Anh đơn giản của trang này (SRC-1019).

Route đọc được khai vào PUBLIC của authCoverage.test.ts kèm lý do - cổng ấy là chỗ duy nhất trong repo cho phép một route /v1 không đi qua requireSession, và nó bắt phải viết ra vì sao.

93. Số tháng được lưu, và mặc định là 12 (SRC-996, 23.09.2026) ​

Vì sao số tháng biến mất. Phần lưu (SRC-960) chỉ ghi những ô có mức tăng lớn hơn 0 - đúng cho nội dung, nhưng số cột thì không suy ra được từ chúng: một bảng 24 tháng mà nửa sau để trống trông y hệt một bảng 12 tháng. Learner kéo bảng ra rồi mở lại thấy nó co về, không lỗi nào hiện ra, và họ phải kéo lại mỗi lần vào. Số tháng là một QUYẾT ĐỊNH ("tôi định học trong hai năm"), nên nó phải có chỗ để lưu: cột months trong learner_ielts_plan (migration 0283).

Cột cho phép NULL, và các hàng cũ giữ NULL: một giá trị mặc định nhét vào chúng sẽ CẮT NGẮN kế hoạch của những learner đang có bảng dài hơn, mà một migration thì không được phép sửa nội dung kế hoạch của ai. Khi months là NULL, màn hình dựng số cột như trước.

Khi đọc lại, số cột đã lưu thắng số cột suy ra từ các ô: phần trống ở đuôi bảng chính là kế hoạch của learner, không phải chỗ thừa để cắt.

Mặc định 12 tháng. Trước đây số cột được TÍNH từ khoảng cách tới đích, nên hai learner cùng mục tiêu mà lệch nhau nửa band lại mở ra hai bảng dài ngắn khác hẳn - một con số nhảy nhót mà không ai hiểu vì đâu. Một năm là mốc người ta thật sự nghĩ bằng, và learner vẫn kéo ra 24 tháng được.

PUT không gửi months thì server GIỮ NGUYÊN giá trị đang lưu (COALESCE), nên một client cũ vẫn lưu được kế hoạch mà không xoá mất lựa chọn của learner. Gửi một giá trị sai thì báo lỗi chứ không im lặng bỏ qua - im lặng ở đây là để learner tưởng đã lưu.

106. Learner mới đặt được mục tiêu ngay ở Learning Plan (SRC-1022, 24.09.2026) ​

Chủ dự án: "Với một learner mới, họ vào trang Learning Plan, thì họ ko thể setup điểm hiện tại và điểm mục tiêu được." Hai thẻ sửa điểm chỉ nằm trong nhánh ĐÃ có mục tiêu; nhánh chưa có chỉ bày một câu nhắc không có ô nào để điền. Nay nhánh ấy có cả hai thẻ, thẻ Target mở sẵn chế độ sửa (EditGate startEditing) với 6.5 làm gợi ý; lưu xong trang sang nhánh có bảng kế hoạch.

Lịch sử quyết định ​

Từ mục 39. Learning plan: một lời hẹn, không phải một biểu mẫu (SRC-916, 2026-09-20) ​

Chủ dự án mở /ielts/setup/plan và viết: "Bỏ hết các item trên màn hình này. Trên đây có thông báo đại ý là 'Sau khi trao đổi với Mentors 1-1, lúc đó sẽ có Learning Plan chi tiết'."

Ảnh chụp đầu tiên ra trang cũ: Playwright dùng lại server cổng 4173 còn sống của một phiên khác, phục vụ một bản build khác. Cái test vừa viết cũng xanh trên bản build ấy — một kết quả xanh vô nghĩa. Phải đọc thẳng bundle trên đĩa và dựng server riêng ở cổng khác mới kết luận được.

Đây đúng là cái bẫy CLAUDE.md đã ghi cho check:docs, chỉ khác bề mặt: cổng chạy local nói dối theo hướng dễ dãi khi nhiều phiên dùng chung một máy.

Từ mục 60. Plan Board: bốn kỹ năng × các tháng, và số giờ phải bỏ ra (SRC-952, 22.09.2026) ​

Chỉ đạo chủ dự án 22.09.2026:

"Trang đó ghi 4 skill trên một table, cột 1 là danh sách các skill, mỗi cột sau là một mốc ngày làm tròn, là ngày 1 hoặc 15 hàng tháng. (…) mỗi cột sẽ chứa 2 skills mà tăng 0.5 (…) Learner có thể tự chỉnh số điểm tăng của từng tháng. (…) Mỗi 0.1 điểm của một trong 4 skills, thì cần khoảng 3 giờ để học. Dòng cuối cùng là tổng thời gian mỗi tháng (…) cột cuối là tổng thời gian bỏ ra để tăng mỗi skill." Và sau khi xem prototype: "Tooltip hiển thị tiếng Anh nhé. (…) Cần có link từ màn hình home của ielts để tới trang Plan Board."

Vòng này chỉ có màn hình: sửa thì bảng tính lại ngay, đóng trang là mất. Có một dòng "Draft view — changes are not saved yet" ngay trên bảng. Một trang lặng lẽ không lưu là một trang nói dối, và learner sẽ dựng lịch học theo một bản kế hoạch không tồn tại ở đâu cả.

Từ mục 66. Learning Plan: đọc trước, sửa sau, và một cái thước giờ (SRC-958, 22.09.2026) ​

"Khi mới vào thì cần giảm tối thiểu các element, và mọi thứ đều là read-only (…) Bấm vào Edit thì luôn có 2 button Save và Cancel xuất hiện ngay ở chỗ button Edit (…) Dòng 1 trong table, cần bỏ ngày đi (…) Có một cụm settings ở dưới cùng (…) 'thời gian học mỗi tháng: 30 hours' (…) Hệ thống sẽ so sánh các con số ở dòng dưới cùng trong bảng với con số 'giờ học tiêu chuẩn mỗi tháng', để nó bôi màu (…) bổ sung luôn 2 cards là 'điểm hiện tại' và 'điểm mục tiêu'." Và: "Đổi tên trang từ 'Plan Board' thành 'Learning Plan'."

Bảng vẫn chưa lưu lên server (back-end làm sau, đúng ý chủ dự án), nên nút Save hiện chỉ đóng chế độ sửa. Chỗ nối useRegisterSave đã sẵn cho ngày có API.

Từ mục 67. Nút xếp hộ: nhịp khởi động bốn kỹ năng (SRC-959, 22.09.2026) ​

"Có một button nào đó, click vào thì hệ thống sẽ tự tính toán và sắp xếp giúp. Đại ý là: Trong tháng đầu tiên sẽ khởi động với Reading và Listening, mỗi thứ một chút. Tháng thứ 2 gia tăng thêm. Từ tháng thứ 3 thì thêm Writing. Từ tháng thứ 4 thì thêm Speaking."

Từ mục 68. Learning Plan lưu xuống CSDL (SRC-960, 22.09.2026) ​

Chỉ đạo: "Tìm cách lưu đi. Còn bảng có đúng hay không, thì tôi không rõ, bạn tự kiểm tra giúp."

Từ mục 72. Learning Plan: hai thẻ sửa được, và một ranh giới (SRC-967, 22.09.2026) ​

"Bỏ link 'IELTS' ở ngay trên cùng. Bổ sung 2 button Edit vào Current và Target. Cần có đường kẻ và khoảng cách để ngăn cách 2 card bên trên với cái Learning Plan chi tiết ở bên dưới. Chuyển button Edit hiện tại xuống dưới (…) ngay bên trên Learning Plan table. Bỏ label 'Months above this number…'. Trình bày lại thông tin của cụm dưới cùng."

Từ mục 73. Learning Plan: chiều cao đứng yên, và ba nguyên tắc (SRC-970, 22.09.2026) ​

"Trong 2 cards trên cùng, cho dù click vào Edit hay Read Only, thì độ cao của 2 card đều không được thay đổi. READING cần đổi chỗ cho WRITING. Trong card 1, cần sửa được ngày. Bỏ dòng 'Starts with Reading and Listening…'. Bổ sung một card ở dưới cùng, kiểu như 'nguyên tắc để tạo kế hoạch' (…) cần dùng tiếng Anh hết, nhưng ở dạng cực kỳ đơn giản để trẻ con lớp 5 cũng hiểu."

Từ mục 76. Monthly plan: đầu bài lên trên, và mốc là cuối tháng (SRC-974, 22.09.2026) ​

Chỉ đạo chủ dự án: đưa Study time per month và Months lên trên bảng, số giờ chỉnh bằng nút cộng trừ; bảng phải chứa cả tháng hiện tại; Target reached là ngày cuối tháng.

Từ mục 77. Learning Plan: hai thẻ gập lại (SRC-975, 22.09.2026) ​

Chỉ đạo chủ dự án: thẻ "How to build a plan that works" có header bấm vào mới mở, mặc định gập; hai thẻ Current và Target nằm trong một thẻ bao, cũng gập sẵn, khi gập chỉ hiện vài thông tin quan trọng nhất.

Từ mục 87. Monthly plan: sửa được số tháng, và hai dải số đọc được (SRC-984, 22.09.2026) ​

Chỉ đạo: sửa được Months; trình bày lại hai dải số vì "các thông tin trên đều bị lệch hẳn về bên trái"; và chữ SKILL ở cột một viết hoa.

Từ mục 92. Chia sẻ kế hoạch ra một đường dẫn công khai (SRC-994, 23.09.2026) ​

Chỉ đạo: "Tạo button để Learner có thể 'Share my plan'. Mọi người có thể vào xem từ trang nào đó trên www.nemo12.com/ielts/learning-plans/slug".

revoked_at có mặt ngay từ migration đầu tiên dù màn hình chưa có nút tắt: một đường dẫn công khai không có đường thu hồi là thứ không nên dựng. Nút tắt là việc còn nợ, ghi ở đây.

Từ mục 93. Số tháng được lưu, và mặc định là 12 (SRC-996, 23.09.2026) ​

Chủ dự án: "Chưa thấy hệ thống cho phép lưu lại thông tin. Mặc định là 12 tháng để đặt plan nhé."