---
url: https://docs.nemo12.com/architecture/sdd-037-program-dashboards.md
description: >-
  Bản nháp khung dashboard theo chương trình (SDD-037): thước đo 'lường trước',
  bốn quyết định và ai thuộc một dashboard IELTS.
---

# SDD-037 — Khung dashboard theo chương trình

Chỉ đạo 2026-09-16: *"Có nhiều dashboard, và trong nhiều cái, thì có 1 dashboard riêng để rà soát
và theo dõi các IELTS Learners, và 1 dashboard riêng để theo dõi SAT Learners. Sẽ còn rất nhiều
dashboard khác, do đó cần lường trước."*

Câu cuối là câu quyết định thiết kế. Nếu chỉ cần hai dashboard thì viết hai màn hình là xong và
nhanh hơn. Nhưng "rất nhiều" biến bài toán thành bài toán khác: thứ phải thiết kế là **chi phí của
cái thứ mười một**, không phải hình dạng của cái thứ nhất.

## 1. Thước đo của việc "lường trước"

Một câu hỏi duy nhất: **thêm một dashboard thì phải sửa bao nhiêu chỗ?**

Khung này trả lời: **một chỗ.** Một dòng trong `workers/api/src/modules/dashboards/registry.ts`.
Không route mới, không truy vấn mới, không màn hình mới, không chuỗi dịch mới ngoài tiêu đề.

```ts
{
  id: "sat-learners",
  title: "SAT Learners",
  subjects: ["sat"],
  cards: ["learners_total", "active_recent", "stalled", "never_practised", "no_mentor", "exams_taken"],
  columns: ["learner", "last_activity", "evidence_count", "avg_mastery", "last_exam", "mentor"],
  stalledAfterDays: 21,
}
```

## 2. Bốn quyết định, và cái giá của phương án ngược lại

### 2.1 Đặc tả nằm trong CODE, không trong D1

Giữ đặc tả thành các dòng trong D1 nghe linh hoạt hơn: sửa dashboard không cần deploy. Nhưng đặc tả
này **trỏ tên các chỉ số**. Nếu nó là dữ liệu thì một dòng trỏ sai tên chỉ số chỉ nổ lúc chạy,
trên máy người dùng, và không cổng nào bắt được trước.

Trong code, `MetricId` là union kiểu: trỏ sai là **đỏ lúc typecheck**. Cùng lập luận đã dùng cho
danh sách tag đóng của Dory và cho `prompts.ts` — thứ gì mã nguồn phải hiểu thì để mã nguồn giữ.

### 2.2 API trả về CẢ đặc tả lẫn dữ liệu

`GET /v1/mentor/dashboards/{id}` trả thẻ đã tính xong, cột đã đặt nhãn, hàng đã xếp thứ tự.

Đây là chỗ quyết định việc lường trước có thật hay không. Nếu client phải biết "dashboard IELTS có
những thẻ nào" thì mỗi dashboard mới lại là một lần sửa giao diện, và khung chỉ đỡ được nửa việc.
`Dashboards.tsx` **không có một nhánh `if (id === …)` nào**; đó là bất biến cần giữ, và là thứ đáng
kiểm khi review một dashboard mới.

### 2.3 Chỉ số đọc BẢNG CHUNG, không đọc bảng riêng của chương trình

Mọi bảng tín hiệu learner đã có sẵn cột `subject_id`: `learner_evidence`, `learner_skill_state`,
`exam_attempts`, `learner_retention`. Nên một dashboard theo chương trình dựng được từ bảng chung,
và **thêm SAT không cần thêm bảng nào**.

Điều này đáng ghi ra vì hệ đang đi hướng khác ở một chỗ: IELTS hiện có **bốn bảng riêng** —
`learner_ielts_baselines` · `_declarations` · `_goals` · `_milestones`. Cách đó **đúng** cho thứ chỉ
IELTS mới có (band 0–9, bốn kỹ năng, mục đích thi), và SDD-035 đã lập luận rõ vì sao lời khai phải
tách khỏi bằng chứng.

Nhưng nếu mỗi chương trình mới đều kéo theo bốn bảng thì "rất nhiều dashboard" sẽ thành rất nhiều
lược đồ song song — đúng RISK-012 ở tầng dữ liệu. Khung này **cố ý không đọc** các bảng riêng đó,
nên một chương trình mới có dashboard chạy được ngay; phần đặc thù là phần **bù thêm** sau, không
phải điều kiện để bắt đầu.

> **Việc chưa làm, và nói rõ:** chưa có luật chung nào cho "lời khai đặc thù của một chương trình".
> Khi chương trình thứ ba cần thứ tương tự IELTS baseline, đó là lúc phải quyết một khuôn chung —
> chứ không phải lúc thêm bảng thứ chín. Xem §5.

### 2.4 `subjects` liệt kê tường minh, không khớp tiền tố

`ielts-learners` liệt kê cả năm subject (`ielts`, `ielts_listening`, `ielts_reading`,
`ielts_speaking`, `ielts_writing`) thay vì khớp `ielts*`.

Tiền tố là thứ trông đúng cho tới ngày có một subject tên na ná mà không thuộc nhóm — và lúc đó
dashboard **âm thầm đếm thêm người**. Liệt kê tay thì khi thêm subject phải nhớ thêm vào đây; đổi
lại không bao giờ đếm sai. Test trong `facts.test.ts` chốt rằng `sat-learners` không hút subject
IELTS nào.

## 3. Ai thuộc một dashboard: HỢP của hai đường vào

Phạm vi learner là hợp của:

* đã đăng ký một khoá thuộc subject đó (`course_enrollments` ⋈ `courses`), **và**
* đã có dấu vết học ở subject đó (`learner_evidence`, `exam_attempts`).

Thiếu một trong hai là dashboard có lỗ đúng ở chỗ cần nhìn:

| Chỉ lấy theo | Bỏ sót ai | Vì sao đó là nhóm đáng nhìn nhất |
| --- | --- | --- |
| đăng ký | người đang tự luyện, chưa vào khoá | họ đang quan tâm mà chưa ai nhận |
| dấu vết | người vừa đăng ký, chưa làm gì | họ cần xếp buổi đầu tiên |

## 4. Luật của các con số

**"Chững lại" không gộp với "chưa từng học".** Hai nhóm cần hai việc khác nhau: người chững lại cần
gọi để kéo lại, người chưa bắt đầu cần xếp buổi đầu. Gộp thành một con số thì con số đó không dẫn
tới hành động nào. Trên bảng, người chưa bắt đầu hiện chữ *"chưa bắt đầu"* chứ không hiện một số
ngày rất lớn.

**Mastery trung bình chỉ tính trên người CÓ dữ liệu.** Coi người chưa học là 0 thì trung bình tụt
mỗi lần thêm học sinh mới, và một con số đi xuống khi có thêm học viên là con số nói sai chuyện.

**Mọi số đếm đi kèm mẫu số.** "3" một mình không nói được gì; "3 / 24" thì nói được.

**Không có dữ liệu thì để TRỐNG, không vẽ dấu gạch.** Một dấu gạch chiếm đúng chỗ của thông tin
thật mà không mang thông tin nào (cùng luật đã chốt ở SDD-023 §25.4).

**Điểm lần thi gần nhất ≠ `MAX(score)`.** `MAX(score)` là điểm cao nhất, một con số khác hẳn và rất
dễ đọc thành "đang ở mức này". Truy vấn riêng lấy đúng lần thi mới nhất.

**Hoạt động gần nhất là muộn nhất trong BA nguồn** (làm bài, cập nhật kỹ năng, thi). Lấy một nguồn
thì một người vừa thi xong vẫn hiện ra là "ba tháng không thấy gì".

**Thứ tự bảng là thứ tự của việc cần làm**, không theo tên: chưa từng học → im lâu nhất → còn lại
theo hoạt động gần nhất. Xếp theo tên thì người đáng gọi nhất nằm giữa trang và không ai thấy.

**Hai ngưỡng `stalledAfterDays` (14 cho IELTS, 21 cho SAT) là PHỎNG ĐOÁN**, chưa đối chiếu với dữ
liệu bỏ học. Người ôn IELTS thường có ngày thi gần và học dày nên hai tuần im là đã đáng hỏi; SAT ở
Việt Nam thường chuẩn bị dài hơi hơn. Khi có số thật thì sửa đúng một con số trong `registry.ts`.

## 5. Giới hạn, nói thẳng

1. **Chưa có chỉ số nào đặc thù chương trình.** Dashboard IELTS hiện **không** đọc band mục tiêu
   hay bốn kỹ năng từ `learner_ielts_*`. Đó là lựa chọn của đợt này (§2.3), không phải quên. Muốn
   có thì thêm một `MetricId` mới và một hàm tính — nhưng lúc đó phải quyết luôn: chỉ số đặc thù
   nằm ở đâu để dashboard khác không thấy nó.
2. **Chưa phân trang.** Bảng trả về toàn bộ learner trong phạm vi. Ổn ở quy mô hiện tại; khi một
   chương trình vượt vài trăm người thì phải thêm `LIMIT` + con trỏ, và **thẻ số phải tính trên
   toàn bộ chứ không trên trang đang xem** — đây là bẫy kinh điển của việc thêm phân trang sau.
3. **Chưa có bộ lọc.** Không lọc được "chỉ học viên tôi chăm". Khi cần, chỗ đúng là thêm tham số
   vào endpoint chi tiết, không phải thêm dashboard mới cho mỗi cách lọc.
4. **Chỉ số chưa có cổng máy kiểm sự thật.** Test kiểm luật tính toán, không kiểm rằng con số khớp
   dữ liệu production. Một chỉ số sai vì truy vấn sai vẫn xanh.

## 6. Trace

| SRC | Hiện thực | REQ |
| --- | --- | --- |
| [SRC-743](../intake.md) | `workers/api/src/modules/dashboards/registry.ts` · `facts.ts` · `service.ts` · `routes.ts` · `apps/mentors/src/Dashboards.tsx` | REQ-MEN-11, REQ-MEN-12 |
| [SRC-796](../intake.md) | `workers/api/src/modules/desk/forecast.ts` · `steps.ts` · `service.ts` · `routes.ts` · `apps/mentors/src/Desk.tsx` | REQ-MEN-11, REQ-MEN-12 |
| [SRC-800](../intake.md) | `apps/mentors/src/scorecardText.ts` · `scripts/check-lang-plural.mjs` | REQ-MEN-11 |
| [SRC-802](../intake.md) | `workers/api/src/modules/desk/journeys.ts` · `apps/mentors/src/Desk.tsx` | REQ-MEN-11, REQ-MEN-12 |

## 7. Bàn theo dõi từng người (SRC-796)

Chỉ đạo 2026-09-17: *"cần có chỗ để xem riêng các IELTS Learners… đóng vai người theo dõi users
(người đã login, các hoạt động của từng người, email đã gửi cho họ, họ tương tác như thế nào, các
score-card để đánh giá từng người theo step trong Learners Journey hoặc Parents Journey), hệ thống
tính toán ra xác suất để họ đi bước tiếp theo, hoặc thời điểm họ sẽ tới bước tiếp theo, và các
process để Nemo12 xử lý."*

### 7.1 Vì sao là mục RIÊNG, không phải thêm cột vào Dashboards

Hai mục dùng **chung cohort và chung danh sách subject** (`desk/routes.ts` đọc thẳng
`dashboards/registry.ts`, không khai lại — khai lại là mở đường cho ngày hai màn cùng nói "IELTS
Learners" mà đếm ra hai con số khác nhau). Nhưng chúng trả lời hai câu hỏi khác nhau:

| | Dashboards (SRC-743) | Bàn theo dõi (SRC-796) |
| --- | --- | --- |
| Câu hỏi | cả chương trình đang thế nào | hôm nay tôi phải chạm vào ai, và làm gì |
| Thứ tự | theo việc rà soát (chưa học → im lâu) | theo **độ gấp của việc phải làm** |
| Đơn vị đọc | một dòng trong bảng | một người, mở ra đọc sâu |

Nhét cả hai vào một bảng thì mỗi câu hỏi chỉ được trả lời một nửa — và nửa bị hi sinh luôn là câu
thứ hai, vì nó cần chỗ hơn.

### 7.2 Scorecard bốn trục

`presence` (có quay lại) · `effort` (có làm bài) · `attention` (có đọc thư) · `progress` (đi tới
đâu trong hành trình). Bốn thứ hỏng theo bốn kiểu, chữa bằng bốn cách — gộp thành một điểm tổng
thì điểm đó không dẫn tới hành động nào.

Hai luật đã chốt trong `forecast.ts`:

* **Độ tươi phân rã mũ, không dùng ngưỡng cứng.** Ngưỡng cứng làm một người rơi hạng vào đúng nửa
  đêm ngày thứ 31 mà không có gì thay đổi trong đời họ.
* **Chưa gửi thư nào thì `attention` = 50, không phải 0.** 0 ở đó là lỗi của Nemo12, không phải của
  người nhận; chấm 0 là đổ lỗi cho họ về một việc mình chưa làm.

### 7.3 Xác suất và thời điểm

Một logistic trên bốn trục cộng một khoản phạt vì đứng yên lâu. **Xác suất luôn có khung thời gian:
30 ngày tới** — một xác suất không có khung thì không kiểm chứng được, và thứ không kiểm chứng được
thì không sửa được.

`expected_days` **suy ra TỪ** xác suất (`-30 / ln(1-p)`, phân phối mũ), không tính riêng: một công
thức thì chỉ có một chỗ sai, hai công thức thì có thể mâu thuẫn nhau trên cùng một màn hình.

Trọng số đáng chú ý nhất là `progress` mang **dấu âm**: người đã xong 4/5 bước thì bước thứ năm là
bước **khó nhất** (đặt lịch, cam kết thời gian và tiền bạc), không phải dễ nhất. Đây là chỗ một mô
hình ngây thơ sẽ sai ngược dấu, nên có test riêng khoá quan hệ này.

**Không đưa ra ngày khi `confidence` thấp.** Không có đăng nhập và không có bài làm thì con số chỉ
phản ánh tiên nghiệm trong `WEIGHTS`, không phản ánh người này — viết "285 ngày nữa" cạnh một người
hệ chưa từng thấy dấu vết là dối, dù số học có ra.

### 7.4 Process: việc của Nemo12, chọn theo TÌNH TRẠNG

Một bảng xác suất không kèm việc phải làm chỉ là một bảng để lo lắng. Việc được chọn theo tình
trạng chứ **không theo bước**: hai người cùng kẹt ở bước "Chẩn đoán" — một người không quay lại,
một người vào mỗi ngày — cần hai việc khác nhau. Chọn theo bước thì cả hai nhận cùng một việc, và
một trong hai chắc chắn là việc sai.

Mỗi việc mang `owner` (`system` tự chạy / `mentor` cần người), hạn, và **lý do**. Ba luật:

* Chưa bao giờ đăng nhập → **gọi điện**, không phải gửi thêm thư. Một lá thư nữa không mở được cánh
  cửa chưa ai bước qua.
* 30 ngày Nemo12 không gửi gì → việc đó là việc của **hệ**, không phải của learner.
* Không có gì đáng làm thì **nói thẳng là không có**. Bịa một việc ở đây chỉ là tiếng ồn, và tiếng
  ồn làm hỏng chính danh sách "cần chạm ngay".

### 7.5 Hai nửa của một gia đình đứng chung một màn

Màn chi tiết trả về cả hành trình của bố mẹ (`guardians` ⋈ `sessions` ⋈ `family_journey`). Ở Nemo12
người quyết định thường là bố mẹ; tách hai hành trình ra hai chỗ là mời người đọc quên một nửa.
"Chưa có tài khoản bố mẹ nào được nối" hiện thành một **phát hiện**, không phải một ô trống.

### 7.6 Giới hạn, nói thẳng

1. **Trọng số là PHỎNG ĐOÁN, không học từ dữ liệu.** Nemo12 chưa có đủ người đi hết hành trình để
   khớp một mô hình. Chúng được viết thành hằng số có tên, có lý do, để ngày có dữ liệu thật thì
   biết chỗ mà sửa. Test khoá **quan hệ** (ai cao hơn ai), không khoá con số — khoá con số là khoá
   cứng một thứ cố ý để mềm.
2. **`daysOnCurrentStep` là xấp xỉ.** Hệ không ghi thời điểm mỗi bước xong, nên nó là "bao lâu rồi
   không có gì động đậy". Xấp xỉ này đúng hướng nhưng **hiền hơn sự thật** với người chăm vào mà
   không tiến. Sửa được khi có bảng lịch sử bước.
3. **Năm bước IELTS nay chỉ còn ở bàn mentor** (`desk/steps.ts`). Phía learner đã thay bằng Journey
   Engine ở SRC-1077 (SDD-045): `ielts/journey.ts` bỏ, kiểu `JourneyStepKey` chuyển vào `desk/steps.ts`.
   Bàn mentor có chuyển sang portfolio hành trình hay không là Q-169, chờ chủ dự án.
4. **Tỉ lệ mở thư là cận dưới.** Vào công thức với trọng số nhẹ nhất, và không bao giờ một mình kéo
   ai xuống nhóm nguội. Trên màn hình viết *"chưa ghi nhận lượt mở"*, **không** viết "chưa đọc".
5. **Chưa có cổng máy kiểm dự báo.** Không có vòng đối chiếu "đã dự báo 60% — thực tế có đi không".
   Đó là việc đầu tiên phải làm khi có đủ dữ liệu, và là điều kiện để trọng số thôi là phỏng đoán.

### 7.7 Bốn lỗi chỉ thấy khi mở màn hình thật ra xem (SRC-800)

30 test xanh, typecheck xanh, build xanh — rồi mở production lên xem thì thấy ngay bốn lỗi. Đáng
ghi lại vì cả bốn đều **nằm ngoài tầm với của test đang có**, không phải vì test viết ẩu:

| Lỗi | Vì sao test không thấy |
| --- | --- |
| Lẫn tiếng Anh giữa màn tiếng Việt | test kiểm *giá trị* trả về, không kiểm *thứ tiếng* của nó |
| "Bước 6/5" khi xong cả năm bước | không có learner nào xong hết trong bộ dữ liệu test |
| "1 days ago" | số ít là chuyện của ngôn ngữ, mà backend lại đang ghép sẵn câu |
| Ngày `2026-09-17`, và một dấu thời gian ISO thô | cổng SRC-748 quét **mã nguồn**, còn chuỗi này sinh lúc chạy từ API |

Ba trong bốn cùng một gốc: **server ghép sẵn câu cho người đọc**. Sửa gốc là chuyển toàn bộ câu
chữ của bàn này sang `Phrase` (khoá + tham số) theo đúng khuôn SRC-626/628, để client ghép theo
ngữ pháp thứ tiếng đang bật và xử lý số ít ở nơi duy nhất biết luật số ít của từng tiếng. Bản
tiếng Việt được **viết như tiếng Việt**, không dịch từng chữ: "pieces of work" thành "bài đã làm",
"Their silence is ours first" thành một câu nói thẳng rằng lỗi thuộc về Nemo12.

Lỗi thứ hai được sửa ở **hình dạng dữ liệu**, không ở chỗ vẽ: API trả `current_position: null` khi
đã xong hết, thay vì để client tính `done_count + 1`. Xong cả năm bước là một **trạng thái khác**,
không phải bước thứ sáu — và một con số vượt mẫu số thì đọc như lỗi dữ liệu. Có test khoá lại.

> Bài học đáng giữ: bộ test này tốt ở chỗ nó canh **logic**, và vô hình ở chỗ **chữ trên màn hình**.
> Với một màn hình song ngữ thì không có cách nào thay thế việc mở nó ra và đọc.

**Lỗi thứ năm, chỉ lộ ra ở lần xem thứ hai.** Sau khi sửa xong bốn lỗi trên, một dòng vẫn là tiếng
Anh: *"Last signed in yesterday · 6 sessions in 30 days"*. Nguyên nhân là hai luật đúng cộng lại
thành một cái bẫy — `renderPhrase` chọn khoá `<key>#one` khi tham số đếm bằng 1, và khoá thiếu bản
dịch thì rơi về mẫu tiếng Anh. Một khoá có `#one` ở EN mà không có ở VI vì thế hiện tiếng Anh
**chỉ đúng vào ngày con số bằng 1**: hôm qua 2 lượt thì đúng, hôm nay 1 lượt thì sai, mai 3 lượt
lại đúng. Mắt chỉ bắt được nhờ tình cờ mở đúng hồ sơ đó.

Vì nó là một cái bẫy chứ không phải một lần gõ nhầm, chỗ sửa là một **cổng máy**:
`scripts/check-lang-plural.mjs` (trong `npm run check:code`) đòi mọi `#one` phải có đủ các thứ
tiếng. Bật lên lần đầu nó tìm ngay **ba chỗ cũ** mắc đúng lỗi này trong scorecard gia đình
(SRC-626) — đã sửa luôn. Đó là dấu hiệu cổng này đáng tồn tại: lỗi đã nằm sẵn trong mã nhiều tuần
mà không ai thấy.

Nhân đó, "0 ngày trước" — một câu không ai nói — được thay bằng khoá riêng (`…​.today`). Chọn
**khoá** là việc của server, ghép **câu** vẫn là việc của client.

### 7.8 Bàn SAT: chạy với những gì có thật, nói thẳng phần chưa có (SRC-802)

Bàn nay nhận **cohort** thay vì cứng IELTS: `/desk/<cohort>` với bộ chọn lấy từ API, `/ielts` cũ
đổi lặng sang `/desk/ielts-learners` để link đã gửi không chết. Thêm chương trình thứ ba vẫn là
thêm một dòng trong registry.

**Chỗ khó không nằm ở kỹ thuật.** Năm bước IELTS không do kỹ thuật nghĩ ra — chúng là đúng năm
bước đang đứng trên trang công khai `nemo12.com/ielts/journey` (SRC-726), và learner đọc trang đó
trước khi đăng nhập. SAT **chưa có** hành trình nào được chốt. Chép năm bước IELTS sang, hoặc bịa
năm bước mới nghe hợp lý, là **tự quyết một chuyện sản phẩm** rồi giấu nó trong một file kỹ thuật:
ngày chủ dự án chốt hành trình SAT thật, thứ đang chạy sẽ mâu thuẫn với thứ được chốt, mà không ai
nhớ nó từ đâu ra.

Nên `journeys.ts` khai thẳng `"sat-learners": null`, và cả hệ đọc `null` đó cho đúng:

| Thứ | IELTS | SAT (chưa có hành trình) |
| --- | --- | --- |
| Bước đang đứng | 1..5 | `null` — màn hình không nói gì về bước |
| Xác suất · thời điểm | có | `null`, kèm một câu nói rõ vì sao chưa có |
| Trục `progress` | có | **bỏ hẳn**, không chấm 0 |
| Thẻ "sắp đi tiếp" | có | **bỏ hẳn**, không hiện số 0 |
| Việc phải làm | đủ | vẫn có — các việc thật sự cần không phụ thuộc hành trình |

Ba dòng giữa là cùng một luật, và là luật đã dùng cho trục `attention` khi Nemo12 chưa gửi thư
nào: **không chấm 0 cho thứ mình chưa làm**. Một số 0 ở đó đọc như tin xấu về learner, trong khi
nó nói về khoảng trống của chính Nemo12. "Chưa định nghĩa" và "chưa đi được bước nào" là hai
chuyện khác nhau, và màn hình nói khác nhau.

Còn những việc vẫn ra được cho SAT — chưa từng đăng nhập thì gọi; ba tuần im thì kéo lại; ta chưa
gửi gì thì đó là lỗi của ta — đều không cần biết hành trình. Đó là lý do bàn SAT có ích ngay hôm
nay, trước khi ai kịp chốt năm bước SAT.

> **Việc còn chờ chủ dự án:** hành trình SAT. Khi có, thêm một dòng vào `journeys.ts` là bàn SAT
> có đủ dự báo như IELTS — không phải sửa màn hình, không phải sửa API.

**Và bàn SAT vừa mở ra đã bắt được một lỗi của bàn IELTS.** Học sinh IELTS hiện trong danh sách
SAT, vì đường vào thứ ba — "ai đã đặt mục tiêu IELTS", thêm ở SRC-796 để không bỏ sót người nóng
nhất — chạy cho **mọi** cohort chứ không riêng IELTS. Đúng loại lỗi §2.4 đã cảnh báo (dashboard âm
thầm đếm thêm người), chỉ khác là nó đến từ một câu truy vấn quên điều kiện chứ không từ một tiền
tố khớp rộng. Nay đường vào riêng khai theo **tên cohort** (`EXTRA_SOURCES`); cohort không có tên
ở đó chỉ dùng ba đường chung — thà thiếu một người nóng còn hơn đếm nhầm cả một chương trình.

Test cũ cho SAT chỉ kiểm `rows.length > 0` nên không thấy gì. Nay khoá **đúng tập người**. Đó là
khác biệt giữa "có dữ liệu" và "đúng dữ liệu", và chỉ cái thứ hai mới là một cổng.

## 8. Bàn theo dõi trình bày lại (SRC-892)

Chỉ đạo chủ dự án 2026-09-20, kèm ảnh chụp `/desk/ielts-learners`: *"Trình bày lại thông tin, nhìn cho dễ nhìn, đẹp đẽ, minimal. Từ trang HOME cần có link tới trang này."*

Nội dung không đổi một trường nào; API không đổi. Đổi bốn thứ về cách nó hiện ra.

**Đầu trang gom làm một.** Bản trước xếp ba tầng rời: một ô chọn cohort, một hàng ba thẻ có viền, rồi một dòng chữ xám đếm số learner. Gần một phần ba chiều cao màn hình trôi qua trước khi thấy tên người đầu tiên. Ba con số ấy là **chú thích** cho danh sách bên dưới chứ không phải một dashboard riêng, nên nay chúng viết như chú thích: số đậm, chữ nhạt, một hàng, không khung.

**Bước đang đứng vẽ thành vạch, không thành chữ.** `Bước 5/5 · Sẵn sàng` thành một dãy vạch nhỏ kèm tên bước. Cùng lẽ với thanh điểm của scorecard: chiều dài mang thông tin, và mắt so được sáu người trong một lượt quét dọc, thứ mà sáu dòng chữ "Bước 2/5" không làm được.

**Xác suất tách ra cột phải, cỡ lớn.** Nó là con số mentor quét dọc để xếp hạng cả cohort, nên nó phải thẳng hàng với nhau. Bản trước để nó nằm lẫn trong một dòng chữ, mỗi người lệch một chỗ theo độ dài tên.

**Việc phải làm xuống dòng riêng, không cắt cụt.** Đây là câu duy nhất trên cả dòng nói cho mentor biết phải làm gì, mà bản trước để nó `truncate` ở cuối cùng: thứ quan trọng nhất là thứ bị cắt trước nhất.

Và mười cái thẻ có viền thành **một cột phẳng chia bằng nét mảnh**. Mười khung cạnh nhau thì việc đầu tiên mắt phải làm là tách khung khỏi nội dung; một cột phẳng thì tên người là thứ đầu tiên nhìn thấy, đúng như nó phải thế.

**Link từ trang chủ cổng.** Hai lối tắt trên đầu danh sách gia đình: Bàn theo dõi và Live Session ([SDD-040](sdd-040-live-sessions.md)). Cả hai vẫn nằm trong menu khu ở thanh trên, và đó chính là vấn đề: một menu phải bấm ra mới thấy thì chỉ người đã biết nó chứa gì mới dùng được. Hai thẻ chứ không tám: một hàng lối tắt liệt kê đủ mười khu thì nó thành bản sao thứ hai của menu, và bản sao ấy sẽ lệch vào ngày có khu thứ mười một.
