---
url: https://docs.nemo12.com/architecture/sdd-038/home-journeys.md
description: >-
  Hub chia hai cụm, dashboard hiện ngay, trang kỹ năng tối giản, Home /ielts
  sinh từ Learner State và Stage, sáu hành trình, JourneyShell, rà mobile
---

# SDD-038 · Home theo trạng thái và JourneyShell

> Một phần của [SDD-038 Cổng IELTS](index.md). 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.

## 90. Hub chia làm hai cụm: đọc ở trên, bấm ở dưới (SRC-988, 22.09.2026)

**Vì sao ranh giới ấy đáng vẽ ra.** Trước bản này hai loại nội dung xen kẽ nhau: khối Learning
Setup (một việc phải làm) đứng trên cùng, rồi ba thẻ số, rồi hai đường dẫn trôi nổi, rồi bốn kỹ
năng. Mở trang ra là phải đọc lướt cả màn mới biết chỗ nào để XEM và chỗ nào để BẤM. Nay một
đường kẻ và một dòng "What to do now" chia đúng hai loại ấy.

**Learning Setup xuống cuối.** Nó lên đầu ở SRC-945 vì khi ấy dashboard của người mới còn rỗng
gần hết, nên khối dẫn đường là thứ duy nhất đáng đọc. Lý do đó hết hiệu lực từ khi dashboard có
"This month" và "Small wins" - hai thẻ có nội dung ngay từ buổi học đầu tiên. Và với learner đã
khai xong, một khối "còn hai việc" đứng trên cùng mỗi ngày là lời nhắc về việc họ đã làm rồi.

**Live class và Leaderboard vào một thẻ có tên "Learn with others".** Cùng khuôn với thẻ bốn kỹ
năng ngay trên nó. Chúng đứng chung vì cùng một loại: đây là nơi learner gặp NGƯỜI KHÁC, không
phải nơi họ luyện bài.

**Thẻ bốn kỹ năng có tên "Practise a skill".** Một thẻ bốn dòng không tiêu đề thì learner phải
đọc hết bốn dòng mới đoán ra nó là cái gì.

**Bỏ thẻ 12 tuần.** Với learner mới nó là mười một cột trống cạnh một cột, và con số "0.3 a week
on average" vừa đúng vừa vô dụng - nó chia công sức của một tuần cho mười hai. Nhịp đều đặn đã có
thẻ "Am I showing up?" nói bằng bảy ô vuông của tuần này, tức là bằng thứ learner còn làm gì được
với nó.

Ca e2e neo đúng cái ranh giới (mọi thứ để đọc nằm trên vạch, mọi thứ để bấm nằm dưới), vì thứ tự
trên trang chủ là thứ trôi nhanh nhất: mỗi lần thêm một thẻ, người thêm đặt nó ở chỗ tiện nhất
trong mã chứ không phải chỗ đúng trên màn.

Bổ sung cùng ngày: cụm hành động xếp **hai cột** từ `lg` trở lên - trái là "Practise a skill",
phải là "Learn with others" chồng lên "Learning Setup". Xếp dọc như bản đầu thì nửa phải của màn
rộng bỏ trống hoàn toàn, và learner phải cuộn qua ba thẻ chồng nhau để thấy hết một cụm mà mỗi thẻ
chỉ có hai tới bốn dòng. Dưới `lg` vẫn một cột và việc luyện vẫn nằm trên: trên điện thoại thứ
quan trọng nhất phải ở trên chứ không ở cạnh. Ca e2e đo toạ độ x, vì một lưới hỏng sẽ lặng lẽ xếp
lại thành một cột.

Lượt chỉnh thứ hai cùng ngày, cho riêng thẻ **Learning Setup**:

· **Rộng hết cột.** Bản trước bó còn một phần ba màn (SRC-971) vì khi ấy nó đứng một mình dưới một
dải trống. Nay nó nằm trong cột phải cạnh "Learn with others", nên một thẻ hẹp hơn thẻ trên nó
đọc ra như một thẻ bị lỗi - và chính hai dòng bên trong bị cắt cụt thành "Lea…", "Learnin…".

· **Luôn mở, bỏ hẳn nút gập** (đảo lại SRC-940). Nút gập sinh ra khi khối này đứng TRÊN cả bốn kỹ
năng và learner phải cuộn qua nó mỗi lần vào trang; từ khi nó xuống cuối cột phải thì cái giá ấy
không còn, mà một khối hai dòng gập lại thì chỉ còn một dòng tiêu đề không nói gì. Khoá
localStorage nhớ trạng thái gập bỏ theo: giữ một khoá đã hết người đọc là hẹn ngày ai đó tin vào
nó.

· **Việc đã xong chỉ còn một chấm tròn nhỏ, đặc, màu accent** thay cho nhãn chữ "Done". Một việc đã
xong không cần chiếm chỗ bằng chữ; thứ cần chữ là việc CHƯA xong, vì ở đó cái nhãn nói ra bước
tiếp theo. Lời "Done" vẫn còn dưới dạng `sr-only`, nên dấu hiệu này không chỉ là màu.

## 92. Dashboard trang chủ hiện ngay, không chờ mười lăm giây (SRC-992, 22.09.2026)

Chủ dự án: ba thẻ trên cùng của `/ielts` *"hiện nay hiển thị khá lâu, thường là sau 10-15 giây mới
hiển thị"*, và phải xuống dưới một giây.

**Ba nguyên nhân, xếp theo mức đóng góp.**

**Một: mỗi lần mở trang chủ, worker kéo TOÀN BỘ kho bài đọc và bài nghe về.**
`loadGlossaries` đọc mọi bài đã phát hành của một kỹ năng, kèm trọn `body_json` của từng bài, và
endpoint `ielts-effort` gọi nó hai lần. Tất cả chừng ấy byte chỉ để tính một dòng: *"You met 26 new
words this week."* Đây là loại hỏng tệ nhất trong các loại hỏng về tốc độ, vì cái giá lớn lên theo
KHO chứ không theo learner: mỗi bài soạn thêm làm trang chủ của mọi learner chậm thêm một chút, và
trên máy người viết mã thì kho chỉ có vài bài nên không ai thấy gì. Nay nó chỉ hỏi những bài
learner ĐÃ LÀM - vài chục thay vì cả kho - và bài đọc không kéo `body_json` nữa vì từ vựng của bài
đọc nằm trong `extra_json`. Hai lượt đọc cũng chạy song song thay vì nối đuôi.

**Hai: năm câu hỏi, năm lượt gửi.** `Promise.all` năm câu SQL nghe như song song, nhưng D1 nằm sau
một lượt gọi mạng: năm gói tin đi, năm gói tin về, và trang chờ gói chậm nhất. `c.env.DB.batch`
gộp cả năm vào một gói. Hai câu `safeIelts` ở lại ngoài batch có chủ ý: chúng đọc bảng của module
khác và ĐƯỢC PHÉP không tồn tại, còn `batch` chạy cả chùm trong một giao dịch nên một bảng thiếu
sẽ kéo đổ cả bốn câu kia theo.

**Ba: khối dashboard không vẽ gì trong lúc chờ.** `EffortDashboard` trả `null` tới khi dữ liệu về,
nên thứ learner nhìn thấy đầu tiên là một khoảng trống - kể cả khi lượt gọi chỉ mất vài trăm mili
giây. Nay nó vẽ ngay bằng số của lần mở trước (`lastSeen.ts`, `localStorage`), rồi thay bằng số
thật khi lượt gọi về. Cái giá phải nói rõ: trong một nhịp vẽ, learner có thể thấy con số của hôm
qua - chẳng hạn chuỗi ngày chưa kịp đứt. Đổi lại họ thấy dashboard thay vì thấy khoảng trống. Vì
vậy bộ nhớ ấy CHỈ dùng cho số liệu nỗ lực: không dùng cho quyền truy cập, số lượt còn lại hay tiền,
nơi sai một nhịp là sai thật.

**Cổng giữ chỗ.** `read.test.ts` gác phép lọc (lọc đúng bài, và bảng từ sau khi lọc y như khi đọc
cả kho). Trong `routes.test.ts` có một ca nghe lén mọi câu SQL mà endpoint gửi xuống D1 và đòi câu
hỏi về `ielts_content_items` phải mang mệnh đề `IN (`. Ca ấy đo CÂU HỎI GỬI ĐI chứ không đo kết
quả, vì một bản quay lại đọc cả kho vẫn trả về đúng từng con số - chỉ chậm dần theo số bài đã soạn,
và không có gì đỏ để ai nhận ra.

**Một lỗi phát hiện kèm theo.** Bản giả D1 trong `runlog.testkit.ts` hiện thực `batch` bằng cách
gọi `run()` cho mọi câu, mà `run()` ném hết dòng đọc được đi - nên mã gộp câu đọc vào `batch` chạy
đúng trên D1 thật nhưng nhận mảng rỗng trong test. Đúng cái bẫy mà đoạn đầu chính file ấy cảnh báo.
Đã sửa để bản giả bám hợp đồng thật: đọc thì trả dòng, ghi thì trả số dòng đã đổi.

## 128. Trang kỹ năng tối giản và thanh trên có số liệu (SRC-1083, 27.09.2026)

* **"Where am I strong?"** bớt một nửa element: bỏ nhãn "Sub-skill breakdown", câu mô tả, câu tiếng
  Việt về task type, ô khoá "Practise by task type", số thứ tự và mũi tên mỗi dòng; còn tiêu đề, dấu
  "?", tên sub-skill, vạch mastery và nhãn bậc. Nút "By task type" của Reading chỉ hiện khi đã mở.
* **Ba ô dashboard** thành ô số liệu nhỏ, không viền, nền mờ: nhãn, dấu "?", con số và một hình cao
  đúng 32px ở cả ba ô; bỏ dòng chú thích dưới đáy. Khác hẳn thẻ hành động phía dưới. Áp cho cả bốn
  trang kỹ năng vì chúng dùng chung `SkillStatusCard`.
* **Video** ở trang Listening và Reading hiện sẵn trình phát, căn giữa, khoảng thoáng trên dưới
  (`IeltsClip shown`).
* **"In the exam · The four parts"** của Listening chuyển xuống dải dưới cùng, cùng dòng với "Exam
  practice" (`ExamPracticeBand questionTitle`).
* **Thanh trên** luôn có band Overall mục tiêu và số ngày còn lại tới ngày thi; ở bốn trang kỹ năng
  thêm "phút hôm nay / chuẩn ngày · phần trăm" (`NavStats` trong `Shell.tsx`, đọc goal và effort một
  lần mỗi phiên trang). Ẩn trên màn hẹp. Ảnh đại diện cạnh tên đã bỏ; tên kèm dấu ▾ là nút mở menu.
* Hai ca e2e kiểm thiết kế cũ (số thứ tự dòng, ảnh đại diện) sửa theo thiết kế mới.

### 126c. Speak together nói tiếng Anh đơn giản toàn bộ (SRC-1084, 27.09.2026)

Chủ dự án: đưa ô mã ra một góc (panel bên phải), bỏ nhãn "chọn 1 trong 3 cách hỏi", bỏ chỉ dẫn tiếng
Việt và thay bằng tiếng Anh cực kỳ đơn giản cho học sinh 10 tuổi; trên các trang này không có tiếng
Việt. Đây là ngoại lệ của SRC-953 cho `/ielts/speaking/pair/**`, cùng hàng với Learner Profile,
Membership, Learning Plan (SRC-1019); đã thêm vào CLAUDE.md.

* Ô mã phòng, link mời, trạng thái bạn cùng cặp, đồng hồ và "Leave room" nằm ở panel phải, dính khi cuộn.
* Bối cảnh chỉ còn bản tiếng Anh; cụm từ bỏ bản dịch; gợi ý lượt 3-4 dùng `hint_en` mới (60 câu,
  tối đa 10 từ). Lỗi từ máy chủ (tiếng Việt) được thay bằng câu tiếng Anh đơn giản phía giao diện.

### 126d. Ảnh minh hoạ cho 12 tình huống (SRC-1085, 27.09.2026)

* `PairScene.tsx`: mỗi tình huống một ảnh SVG vẽ trong code, hai người (một nam tóc ngắn áo xanh, một
  nữ tóc dài váy đỏ) đứng giữa một không gian riêng: bảng "Hello!" ở lớp, phòng khách, bàn ăn, công
  viên, giá quần áo, biển chỉ đường, trời mưa, kệ sách, trường học, bãi biển, điện thoại, lịch.
  Đồ vật đặt ở hai bên để không đè lên người.
* Trang danh sách: mỗi thẻ là ảnh, rồi số thứ tự và tên; bỏ dòng mô tả.
* Trang bài: ảnh hiện ngay dưới tiêu đề; "Useful phrases" thành một thẻ, mỗi cụm một ô.

## 134. Home `/ielts` theo khuôn trang kỹ năng, bớt 50% chữ (SRC-1101, 28.09.2026)

Chủ dự án: NavBar và dashboard ở bốn trang kỹ năng đã ổn, áp sang Home; Home giảm một nửa chữ và element.

* **NavBar:** Home dùng cùng `NavStats` của `ielts/Shell.tsx` (Free days left, Target, days left, phút hôm nay), bỏ logo và ảnh avatar, chỉ còn chữ NEMO IELTS và tên. `NavStats` nhận `learnerId` qua prop vì Home đứng ngoài AuthProvider của mạch trang kỹ năng.
* **Dashboard:** ba thẻ dùng chung `StatCard` của trang kỹ năng (nhãn, một con số, một hình cao 32px, không dòng chú thích). Chi tiết từng kỹ năng của This month, chuỗi ngày và các câu khen còn lại chuyển vào dấu "?": đọc khi cần, không liếc mỗi ngày.
* **Bỏ:** link video 1 phút, vạch kẻ ngang; thẻ Next up gọn một hàng, bỏ nhãn hành trình và câu lý do (lý do nằm trong `title` của tên việc).

## 145. Rà mobile toàn bộ `/ielts/**` và `/speaking/**` (SRC-1135, 29.09.2026)

**Cổng thường trực** `apps/learn/e2e/mobileAudit.spec.ts` (chạy trong bộ e2e thường, khoảng 1,3 phút): 73 URL, một URL
cho mỗi loại route của `src/ielts/router.ts` ở cả gốc `/ielts` và `/speaking`, cộng các trang tài khoản nằm ngoài router
(`/ielts`, `learner-profile`, `membership`, `learning-plan`, `settings`, `referral`). Mỗi URL mở ở 375x812 và 320x640,
cuộn hết trang một lượt (khối `FadeIn` chỉ hiện khi lọt vào tầm nhìn), rồi kiểm:

* không cuộn ngang (`scrollWidth <= innerWidth + 1`);
* không phần tử nào lòi ra ngoài khung, trừ khi nằm trong một hộp tự cuộn hoặc tự cắt (`overflow-x` auto, scroll, hidden);
* nút và link ở thanh trên, link dạng nút: cao ít nhất 40px (vùng bấm mở rộng bằng `after:-inset-*` được tính);
* chữ đọc liền (p, li, td, dd có từ 30 ký tự) ít nhất 14px;
* không lỗi console.

Ảnh chụp cả trang của từng URL nằm ở `test-results/mobile/`. Ba endpoint mà mock chung trả `{}` (kho từ theo cụm, trang
một dạng câu hỏi, trạng thái chẩn đoán 4 kỹ năng) và catalog khoá học được giả đúng hình trong `e2e/mobileMocks.ts`.

**Đã sửa:**

| Trang | Lỗi | Sửa |
| --- | --- | --- |
| `/ielts` | Thẻ Next up rộng hơn khung ở 320px, trang cuộn ngang 3px | Card đặt lại `flex-row`; nhãn và tên việc chiếm cả dòng dưới `sm` |
| `/ielts/learning-plan`, `/ielts/settings` (chế độ sửa band) | Nút −/+ đè ra ngoài mép ô band | Lưới band một cột dưới `sm`, nút 40px |
| `/speaking`, `/ielts/speaking/pair` | Hàng "Next session" bóp ngày giờ thành một cột hẹp | Nhãn xuống dòng riêng dưới `sm` |
| `/speaking/sessions` | Hàng buổi học bóp tên và ngày | Khối tên chiếm cả dòng dưới `sm` |
| `/ielts/leaderboard` | Tên bị cắt còn "Bà...", chip YOU còn "Y..." | Tên được xuống dòng, "+N Nemo" chuyển xuống dưới tên dưới `sm` |
| `/ielts/skills/<kỹ năng>/<năng lực>` | Ô bậc cắt "5 Việt + 5 Anh" | Ô cao hơn và cho hai dòng dưới `sm` |
| `/ielts/reading/<cụm>/passages/<n>` | Lề trái quá rộng, bài đọc chỉ còn khoảng 180px ở 320px | Padding và khoảng cách cột số đoạn nhỏ lại dưới `sm` |
| Thanh trên của mọi trang | Link vệt đường dẫn 16px, nút hướng dẫn 36px, nút tài khoản 20px | Vùng bấm 40px (padding trên link inline nên không đổi bố cục) |
| Nhiều trang | Chip lọc cụm, tab Upcoming/Week/Month, nút Report, Skip, "← IELTS", "Learning Setup →", "Skip the tour", link mở video | Cao 40px |
| Bài nghe, bài đọc, bài tập | Chấm câu hỏi 10px, vạch tiến độ 6px; chấm đầu bị cắt vòng viền | Vùng bấm 20x40px bằng pseudo-element, hàng chấm lùi vào 4px |
| Bài nghe, trang dạng câu hỏi Đọc | Chữ hướng dẫn 12px | 14px |

**Chưa sửa, có lý do:** thanh trên của sân luyện xuống hai dòng ở 320px (vệt đường dẫn một dòng, nút một dòng) là thiết kế
`stackOnMobile` đã chọn, không phải lỗi; thẻ hướng dẫn (Tour) che một phần thẻ bên dưới là hành vi của tour.

## 160. Home /ielts sinh từ Learner State + Stage, chế độ test cho chủ dự án (SRC-1186, 02.10.2026)

Chỉ đạo chủ dự án 02.10.2026: Home của `learn.nemo12.com/ielts` không còn là một bảng nhiều khối mà là
MỘT khối "WHAT TO DO NOW", sinh từ Learner State + Stage. Đường ống: State + Stage + Learner Model +
Evidence -> Recommendation -> Next Best Action -> Home UI.

**Năm State, 22 Stage** (`apps/learn/src/ielts/learnerState.ts`, hằng `STAGES`):

| State | Câu hỏi của learner | Stage |
| --- | --- | --- |
| Discover | Mình bắt đầu từ đâu? | Welcome, Set Goal, Diagnostic, Diagnostic Complete |
| Diagnosed | Mình nên học gì? | Understand Result, Understand Gap, Review Roadmap, Start Learning |
| Learning | Tiếp theo mình làm gì? | Plan, Learn, Practice, Feedback, Review, Reassess |
| Needs Attention | Có gì đang không ổn? | Falling Behind, Stuck, Needs Review, Overloaded |
| Ready | Tiếp theo là gì? | Milestone Achieved, Confirm Readiness, Full Assessment, Set Next Goal |

**Suy ra từ dữ liệu có sẵn, không thêm bảng, không thêm API.** `homeSignals.ts` gọi các đường đọc đã có:
`ielts-goal` (mục tiêu, mốc), `ielts-effort` (có lịch sử chưa, phút theo ngày 30 ngày, điểm hiện tại),
`ielts-learning-plan` (đã lưu kế hoạch chưa), `ielts-diag4` (trạng thái bốn bài chẩn đoán, báo cáo mới nhất)
và `ielts-skills/{skill}` x4 (mastery từng kỹ năng con, tỉ lệ đúng trước/nay). Một đường hỏng thì tín hiệu ấy
thành "không có", không thành trang trắng. Hàm `deriveLearnerState` thuần, có test.

**Thứ tự ưu tiên:** (1) chưa có mục tiêu: Welcome; chưa luyện gì: Set Goal / Diagnostic / Diagnostic Complete;
(2) đạt mục tiêu: Set Next Goal; vừa đạt mốc: Milestone Achieved; (3) Needs Attention, chỉ khi đã luyện:
Falling Behind, Overloaded, Stuck, Needs Review; rồi Ready theo bằng chứng luyện: Full Assessment, Confirm
Readiness; (4) có báo cáo mà chưa luyện: Review Roadmap (chưa lưu kế hoạch) / Start Learning; (5) vòng
Learning: Review, Reassess, Plan, Feedback, Learn, Practice.

**Ngưỡng (hằng có tên trong `learnerState.ts`):**

| Hằng | Giá trị | Nghĩa |
| --- | --- | --- |
| `FALLING_BEHIND_DAYS` | 7 | Falling Behind: 7 ngày trở lên không học |
| `STUCK_ATTEMPTS` x `ITEMS_PER_ATTEMPT` | 3 x 5 | Stuck: 15 câu trở lên trên một kỹ năng con, vẫn bậc đầu, đúng dưới `STUCK_RATE` = 50% |
| `OVERLOADED_OPEN` | 7 | Overloaded: 7 kỹ năng con đã mở mà chưa lên bậc |
| `NEEDS_REVIEW_ITEMS` | 12 | Needs Review: 12 câu sai trở lên trên kỹ năng con đúng dưới `REVIEW_RATE` = 70% |
| `REVIEW_ITEMS` | 5 | Review (trong vòng Learning): 5 câu sai trở lên |
| `LEARN_MIN_ITEMS` | 5 | Learn khi kỹ năng con trọng tâm còn dưới 5 câu, sau đó Practice |
| `REASSESS_DAYS` | 30 | Reassess: báo cáo chẩn đoán cũ từ 30 ngày |
| `DIAGNOSTIC_FRESH_DAYS` | 2 | Diagnostic Complete: báo cáo trong 2 ngày và chưa luyện |
| `READY_ACCURACY` / `FULL_TEST_ACCURACY` | 75% / 85% | Confirm Readiness (từ hai kỹ năng) / Full Assessment (cả bốn), mỗi kỹ năng ít nhất `READY_MIN_ITEMS` = 20 câu |
| `MILESTONE_RECENT_DAYS` | 14 | Milestone Achieved: mốc có ngày trong khoảng 14 ngày và điểm hiện tại đã chạm |

Xấp xỉ có chủ ý: "3 lần luyện" của Stuck đếm bằng số câu (5 câu một lượt) vì mastery không trả lịch sử theo
lượt; "việc đang mở" là kỹ năng con đã làm mà chưa lên bậc. Understand Result và Understand Gap không suy ra
riêng được (không có dấu vết learner đã mở báo cáo), nên learner thật đi Diagnostic Complete -> Review Roadmap
-> Start Learning; hai stage ấy xem được qua chế độ test.

**Chữ learner đọc: tiếng Anh siêu đơn giản** (ngoại lệ của SRC-953, ghi ở CLAUDE.md). Không bao giờ hiện chữ
"State" hay "Stage" cho learner. Mỗi stage đúng MỘT nút accent, trỏ vào route có thật (`/ielts/diagnostic`,
`/ielts/diagnostic/report`, `/ielts/learning-plan`, `/ielts/{skill}`, `/ielts/tests`, `/ielts/settings`,
`/ielts/profile`). Welcome giữ dữ liệu cũ: chọn band rồi Continue ghi vào `ielts-goal` như form Goal (bốn kỹ năng
cùng band, ngày thi mặc định +180 ngày, sửa ở Settings).

**Các khối cũ của Home** (dashboard, Next up, bốn kỹ năng, Find your level, Build your base, Learn with others,
Learning Setup) nằm dưới nút "See more", gập sẵn; không xoá trang nào. Tour của hub chỉ chạy khi đã mở.

**Chế độ test** (`HomeTestPanel.tsx`): chỉ hiện với email đăng nhập `dac2205@gmail.com`. Nút "Test" góc phải
dưới mở bảng có hai ô chọn (State rồi Stage) và ô mô tả tiếng Việt (nghĩa của State, câu hỏi của learner,
mục tiêu UI và ưu tiên UI của Stage). Lựa chọn lưu ở `localStorage` (bọc try/catch), nút "Về trạng thái thật"
xoá nó. Khi đang ép stage, Home không truyền hàm ghi nào, nên không có gì ghi vào dữ liệu học thật. Không có
lượt gọi server nào riêng cho chế độ test nên không có gì để gác ở server. Tài khoản thiếu dữ liệu cho stage
bị ép thì hiện số mẫu (`SAMPLE_FACTS`) kèm nhãn "sample data", chỉ trong chế độ test.

Kiểm: `apps/learn/src/ielts/learnerState.test.tsx` (mọi ngưỡng, và render tĩnh cả 22 stage: đúng một CTA, href
thuộc danh sách route thật, không chữ "STATE:/STAGE:", không dấu tiếng Việt, không em dash).

## 166. Trang chủ /ielts thành sáu hành trình trong ba tầng (SRC-1249, 05.10.2026)

Chỉ đạo chủ dự án 05.10.2026: dựng lại toàn bộ trang chủ sau đăng nhập `learn.nemo12.com/ielts`
quanh **6 hành trình** của learner, mỗi hành trình trả lời một câu hỏi tự nhiên của họ, xếp thành
**3 tầng** thành một luồng dọc có đánh số, cùng khuôn với `www.nemo12.com/ielts/consider`
(SRC-1248): vòng số bên trái, đường nối dọc, thẻ bo tròn có tiêu đề và một dòng trạng thái.

| Tầng | Hành trình | Câu hỏi | Trang |
| --- | --- | --- | --- |
| A. Find my path | 1 Discover My Level | Mình đang ở đâu? | `/ielts/level` |
| | 2 Set My Goal | Mình cần bao nhiêu điểm? | `/ielts/goal` |
| | 3 Build My Plan | Mình phải học thế nào? | `/ielts/learning-plan` (giữ nguyên, tiếng Anh đơn giản SRC-1019) |
| B. Make progress | 4 Learn & Practice | Hôm nay mình học gì? | `/ielts/today` |
| | 5 Check My Progress | Mình có tiến bộ không? | `/ielts/progress` |
| C. Get better | 6 Get Unstuck | Sao mình mãi không lên? | `/ielts/unstuck` |

**Luật của hub.** Mỗi thẻ hiện MỘT dòng trạng thái thật (vd. `Band 6.0 · đo ngày 20.09.2026`,
`Target 7.5 · còn 438 ngày`, `Chưa có kế hoạch`); vòng số tô đặc khi đầu ra của hành trình đã có.
Đúng MỘT thẻ sáng lên và mang nút chính duy nhất của màn: hành trình đầu tiên của tầng A còn thiếu
đầu ra; xong tầng A thì một stage Needs Attention (SRC-1186) đưa sang Get Unstuck; không thì Learn &
Practice nếu hôm nay chưa học, còn lại Check My Progress. Bấm thẻ là sang trang của hành trình.

**Nguồn dữ liệu, không thêm endpoint.** Mọi dòng đọc từ `loadHomeSignals` (SRC-1186): `ielts-goal`,
`ielts-effort` (phút hôm nay, chuẩn phút/ngày, 7 ngày, 12 tuần), `ielts-learning-plan`, trạng thái
chẩn đoán 4 kỹ năng cùng danh sách báo cáo và báo cáo mới nhất, bốn trang kỹ năng (mastery, tỉ lệ
đúng). Luật suy ra nằm trong `apps/learn/src/ielts/journeys/model.ts`, thuần, có test.

**Trạng thái trống trung thực.** Thiếu dữ liệu thì dòng nói thiếu (`Chưa đo trình độ`, `Chưa có
lịch sử luyện tập`, `Chưa có số phút hôm nay`) và bước trống chỉ tới việc sinh ra dữ liệu ấy. Get
Unstuck chỉ kết luận về một kỹ năng nhỏ khi nó có ít nhất 15 câu trả lời (3 lượt x 5 câu, cùng
ngưỡng `STUCK_ATTEMPTS` của SRC-1186); dưới ngưỡng ấy trang nói "Chưa đủ dữ liệu để chẩn đoán" và
mời luyện thêm hoặc đo lại, không dựng một phép dò giả.

**Trang hành trình bọc, không viết lại.** Mỗi trang là một luồng bước đánh số cùng khuôn; phần việc
thật vẫn là màn có sẵn: bài chẩn đoán và báo cáo, `/ielts/gap`, `/ielts/settings`, bộ chọn band
(Welcome), thẻ "What to do now" cùng bảng test của chủ dự án, Next up, các khối luyện cũ (bốn kỹ
năng, chẩn đoán, ngữ pháp, học cùng người khác, Learning Setup) nay mở sẵn ở `/ielts/today`, và các
thẻ công sức ở `/ielts/progress`. Nút "See more" và thẻ "What to do now" trên hub đã bỏ.

**Ngôn ngữ.** Tên tầng, tên hành trình, tên bước, tên kỹ năng: tiếng Anh. Câu hỏi, dòng trạng
thái, lời dặn và nhãn nút hành động: tiếng Việt (SRC-953).

## 167. JourneyShell trên mọi trang /ielts, không còn link thoát ra (SRC-1263, 06.10.2026)

Luật bố cục chủ dự án 06.10.2026 (mẫu chung: [Guided Journey §9](../../design-system/guided-journey/journey-shell.md)):
mọi trang `learn.nemo12.com/ielts/**` có NavBar mà menu là sáu journey của §166, thanh bước ngay dưới
nêu tên từng bước của journey đang đứng, và Status Card góc trên phải thay hàng chip.

**NavBar.** NEMO IELTS (về `/ielts`; là chữ khi đang ở `/ielts`) · Set My Goal · Discover My Level ·
Build My Plan · Learn & Practice · Check My Progress · Get Unstuck · trợ giúp · tài khoản. Sáu tab
chương trình cũ (Roadmap, Curriculum, Methodology, Outcomes, Resources, Tuition, phần lớn trỏ sang
nemo12.com) và lưới chương trình đã rời `/ielts`. Journey của một trang đọc từ đường dẫn
(`journeyOfPath`): `/ielts/diagnostic/**` và `/ielts/gap` thuộc Discover My Level, `/ielts/settings`
thuộc Set My Goal, `/ielts/learning-plan` thuộc Build My Plan, `/ielts/profile` thuộc Check My
Progress, mọi trang luyện (kỹ năng, đề, ngữ pháp, từ vựng, live, speaking pair...) thuộc Learn &
Practice. Hub và ba trang tài khoản (learner-profile, membership, referral) không thuộc journey nào:
chúng hiện journey KẾ TIẾP (`nextJourney`, luật §166).

**Thanh bước.** Tên và luật "xong" của từng bước ở MỘT chỗ (`journeySteps` trong
`apps/learn/src/ielts/journeys/model.ts`); luồng đánh số của trang hành trình đọc cùng định nghĩa ấy.

| Journey | Bước |
| --- | --- |
| Set My Goal | Target band → Test date → Gap → Next step |
| Discover My Level | Start → Diagnostic → Results → Understand my gaps → Next step |
| Build My Plan | Target → Current level → Monthly plan → First session (bước trỏ tới trang tạo ra đầu vào ấy) |
| Learn & Practice | READING → LISTENING → WRITING → SPEAKING (mỗi pill mở trang kỹ năng ấy, SRC-1321) |
| Check My Progress | This week → Accuracy by skill → Study hours, 12 weeks → Level checks → Leaderboard → Stuck? |
| Get Unstuck | Enough data? → Diagnosis → What to do → Recheck |

**Năm journey, menu thu gọn (SRC-1300, chủ dự án 08.10.2026).** NavBar còn năm mục tên ngắn: 1 Goal ·
2 My Level · 3 Plan · 4 Learn · 5 Progress, mặc định gập về mục hiện tại, bấm thì mở ngang. Get Unstuck
rời menu và hub (tầng C bỏ), thành bước cuối "Stuck?" của Check My Progress (This week → Accuracy by
skill → Study hours, 12 weeks → Level checks → Stuck?); `/ielts/unstuck` giữ URL và thuộc Progress
(`journeyOfPath` trả `progress`/`stuck`). Không còn dấu tick trên NavBar và hàng bước. Thiết kế:
[guided-journey §9x](../../design-system/guided-journey/journey-shell.md).

**Learn gọn lại (chủ dự án 09.10.2026).** Bước 4 Learn chỉ còn việc luyện: bốn kỹ năng (Reading,
Listening, Writing, Speaking) và thẻ "Build your base" (Grammar, Full tests). "What to do now" và
"Next up" gộp thành MỘT thẻ "What to do now" chỉ thẳng một kỹ năng để luyện hôm nay, một nút chính
(`pickTodaySkill` trong `journeys/JourneyPages.tsx`: kỹ năng đang kẹt, rồi focus của Learner State,
rồi kỹ năng đầu tiên chưa luyện, cuối cùng là Reading). Thẻ chỉ báo "Band x.x reached" khi đã có
band ĐO ĐƯỢC (báo cáo chẩn đoán): bản cũ nói "You reached 6.0" trong khi Status Card ghi "Not measured
yet", vì mốc đọc band ước tính. Panel Test của chủ dự án vẫn ép được stage để xem thẻ cũ. Các lối vào
dời chỗ, không mất: Speak together và Live class thành hai ô "Practise with people" trên trang
Speaking; Leaderboard thành bước "Leaderboard" của Progress (`/ielts/leaderboard` thuộc Progress
trong `journeyOfPath`); Diagnostic chỉ còn ở bước 2 My Level. Thẻ Next up (`JourneyNextUp`) vẫn ở
trang hồ sơ squid. Test: `e2e/ieltsDashboard.spec.ts` (lối vào ở chỗ mới), `e2e/smoke.spec.ts`,
`e2e/ieltsHomeNow.spec.ts`.

**Menu luôn mở, Learn luôn nổi, hàng 2 của Learn là bốn kỹ năng (SRC-1321, chủ dự án 10.10.2026).**
Đảo phần "menu thu gọn" của SRC-1300: NavBar cấp 1 luôn hiện đủ năm mục, không gập, không bấm để mở
(`featured` + `tags` của `JourneyNavBar`, `collapsible` không còn dùng ở `/ielts`). Mục **4 Learn**
luôn là pill tô `bg-accent-deep` chữ trắng ở MỌI trang, kể cả khi đang ở journey khác, vì đó là đường
chính; đang ở chính Learn thì thêm vòng viền đậm. Bốn mục kia là tag nhỏ (viền mảnh, chữ 14px nhẹ),
vùng bấm vẫn cao 40px nhờ phần đệm quanh tag; tag của trang hiện tại có viền đậm + chữ đậm, khác hẳn
kiểu tô của Learn. Ở 375px menu xuống dòng thay vì cuộn ngang, nên cả năm mục luôn trong tầm nhìn.
Hàng 2 (hàng bước) **giữ nguyên** ở mọi journey; riêng Learn, hàng 2 thành bốn kỹ năng READING,
LISTENING, WRITING, SPEAKING, mỗi pill mở `/ielts/<skill>` (`journeySteps("today")`). Pill sáng khi
learner ở trang kỹ năng ấy hoặc một trang luyện dưới nó (`journeyOfPath` trả `step` = kỹ năng của
đoạn đường thứ hai); ở `/ielts/today`, Grammar, Full tests, Live... không pill nào sáng, vì thẻ kỹ năng
của trang đã nói kỹ năng nên luyện. Status Card ẩn trên trang luyện như cũ (`practice` của
`journeyOfPath`).

Trang `/ielts/today` gọn còn khoảng một nửa: h1 (bỏ dòng phụ), MỘT thẻ kỹ năng với một dòng ngắn
"Khoảng 10 phút." và một nút chính. Bỏ hai bước đánh số "What to do now", "Choose what to practise",
bỏ thẻ "Practise a skill" (bốn kỹ năng đã là hàng 2), nhãn "Start here", nút `?`, và tour
"Four skills, in order" đi cùng thẻ ấy. Cột phải: Status Card ba dòng (Target, Level now, Today), dưới
nó khối "Build your base" (Grammar n/180, Full tests; `LearnBaseLinks` trong `IeltsPathway.tsx`), rồi
Learning Setup khi còn việc (SRC-945). Đo bằng Playwright 1280px, vùng nội dung + cột phải: 67 phần tử
(chữ, icon, nút, link) và 169 từ trước, 26 phần tử và 51 từ sau (-61%, -70%), nút/link 18 xuống 7.
Test: `e2e/ieltsJourneyShell.spec.ts` (năm mục luôn hiện, Learn có lớp tô, tag 40px, hàng kỹ năng sáng
đúng trang), `e2e/ieltsHomeNow.spec.ts` (không bước đánh số, Grammar/Full tests ở cột phải, ngân sách
≤34 phần tử, ≤85 từ), `e2e/smoke.spec.ts`, `e2e/ieltsAuditPass4.spec.ts`.

Bước hiện tại là bước của chính trang (vd. `/ielts/diagnostic/report` là Results), nếu không thì bước
chưa xong đầu tiên. Trên trang hành trình, bấm một bước là cuộn tới thẻ `#step-<key>`; ở trang khác là
mở trang hành trình ở đúng thẻ ấy. Dòng "Journey n of 6" và link "← Your IELTS path" đầu trang hành
trình đã bỏ.

**Status Card** (`ieltsStatusRows`): Membership (Free: N days left / Free trial ended / Member), Target
(Band x.x hoặc Not set yet), Test date (N days left), Level now (band của báo cáo chẩn đoán gần nhất
hoặc Not measured yet), Today (phút/chuẩn · %). Nhãn tiếng Anh (nhãn trạng thái, SRC-834). Dữ liệu từ
một lượt `loadHomeSignals` mỗi lần xem trang, dùng chung với trang hành trình
(`journeys/signalsStore.ts`), cộng `ielts-membership`.

**Không link thoát ra khỏi cụm /ielts.** Đã gỡ, mỗi chỗ một dòng:

| Chỗ | Link cũ | Nay |
| --- | --- | --- |
| Thanh trên (`ProgramMenu`) | Roadmap, Curriculum, Methodology, Outcomes, Resources, Tuition sang `nemo12.com/ielts/...` | menu sáu journey |
| Thanh trên | lưới chương trình `/programs` (cả bản điện thoại) | bỏ |
| Thanh trên | nút Zalo (`ZaloLink`, khi có link nhóm) | bỏ ở `/ielts` |
| Menu tài khoản (cả hai mạch) | "What's new" sang `nemo12.com/ielts/whats-new` | bỏ ở `/ielts`, `/speak` giữ |
| Màn mời đăng nhập | "← Back to Nemo12" sang `nemo12.com` | bỏ |
| Chân trang | `nemo12.com` và bốn icon mạng xã hội | một dòng "© năm NEMO IELTS", không link |
| Learning Plan, Share my plan | link `www.nemo12.com/ielts/learning-plans/<slug>` | chữ để chép |
| Speaking pair, thẻ ngữ pháp | "See the NEMO GRAMMAR lesson" sang `/grammar/...` | bỏ |
| Speaking pair, "Why speaking practice works" | sáu link nguồn nghiên cứu ra ngoài | tên nguồn + tên miền bằng chữ (`/speak` giữ link) |

✍️ Giữ lại, chờ chủ dự án quyết: nút **Join on Zoom** của buổi live (`Live.tsx`) và buổi Speak
(`SpeakSchedule.tsx`, `SpeakRegister.tsx`). Đó là cách duy nhất để vào buổi học, không phải lối sang
nơi khác; bỏ đi là learner không dự được buổi.

**Test.** `apps/learn/e2e/ieltsJourneyShell.spec.ts`: menu sáu journey theo thứ tự, journey của trang
sáng, hub trỏ journey kế; thanh có tên đủ các bước và đúng một bước hiện tại; Status Card 3-5 dòng,
hàng chip đã rời thanh trên; mọi `a[href]` trên hơn 40 trang `/ielts` (và màn mời đăng nhập) nằm dưới
`/ielts`; 375px không cuộn ngang, bước hiện tại trong tầm nhìn, Status Card dưới thanh; bàn phím đi
được menu. Unit: `journeys/model.test.ts`.

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

### Từ mục 90. Hub chia làm hai cụm: đọc ở trên, bấm ở dưới (SRC-988, 22.09.2026)

Chỉ đạo: tách "cụm dashboard" ở trên khỏi "cụm hành động" ở dưới; Learning Setup xuống dưới; gom
Live class và Leaderboard vào một thẻ và cũng đưa xuống dưới; thẻ bốn kỹ năng phải có tên; bỏ thẻ
"Hours a week, last 12 weeks".

### Từ mục 128. Trang kỹ năng tối giản và thanh trên có số liệu (SRC-1083, 27.09.2026)

Chủ dự án gửi năm ảnh trang Listening:

### Từ mục 145. Rà mobile toàn bộ `/ielts/**` và `/speaking/**` (SRC-1135, 29.09.2026)

Chỉ đạo chủ dự án 29.09.2026: rà mọi trang learn.nemo12.com/ielts và /speaking, đảm bảo mobile-friendly.
Learn nằm sau đăng nhập Google nên việc rà chạy bằng Playwright trên bản build thật với API giả.
