---
url: https://docs.nemo12.com/architecture/sdd-010-lab-platform/unit-path.md
description: >-
  Learner đi qua một Unit ở Phòng Lab thế nào: thứ tự Experience, luật mở khoá,
  sửa câu sai, bỏ qua câu và Đo cuối.
---

# SDD-010 · Đường đi của Unit ở Phòng Lab

Một phần của [SDD-010](./index.md).

## 9. Đường đi của Unit ở Phòng Lab (REQ-LRN-16 — SRC-102)

Trước SRC-102, mỗi **node** (bài) là một card kèm 2–3 dòng mô tả → một Unit có 4 node là 4 card × 3 dòng chữ, learner không biết bắt đầu từ đâu. Chủ dự án chỉ đạo học cách Duolingo: **Unit là đơn vị đường đi**, mỗi bước chỉ một hình.

**Cấu trúc 9 ô** (client dựng bằng `unitPath()`, không lưu bảng riêng — nội dung vẫn là node + item của unit):

```text
Học vượt → Khám phá/Luyện tập ×3 → Đo giữa → Khám phá/Luyện tập ×3 → Đo cuối
assessment          learning        assessment        learning         assessment
```

**Hình trong ô** (SRC-116) — art SVG procedural của `apps/learn/src/N12Art.tsx`, **cùng một style 3D Pixar** (gradient radial, rim light, mắt to), ô tròn cùng kích thước 80px và hình chiếm 56px cho dễ nhìn:

| Ô | Con vật | Vì sao |
| --- | --- | --- |
| Học vượt | **chim mòng biển mỏ rất to** | bay vượt lên trên, qua cả unit trong một lần |
| Khám phá | **cá ngựa** | bơi chậm, tò mò nhìn ngó |
| Luyện tập · Luyện thêm | **cua** (dùng chung một icon) | cùng một việc, khác số lần |
| Đo giữa · Đo cuối | hải đăng · phao tiêu | mốc để định vị mình đang ở đâu |

Ba cỡ ô (SRC-138): ô **đang làm** 96px, ô mở bình thường 80px, ô **khoá** 48px + xám mờ — cùng một đường đi nhưng đọc ra "một việc bây giờ" thay vì "mười lăm việc". Màu viền ô vẫn nói trạng thái (chưa làm · đã xong · làm ngay) nhưng **không làm mờ art nữa** — mờ thì hình 3D mất hết khối. Bỏ luôn bộ đếm "0/9" ở cuối hàng (SRC-092: Phòng Lab không đếm số).

* **Học vượt** (Assessment, `kind=skip`): 10 câu discriminator/boundary trải trên **mọi node của unit**, **1 bong bóng** — sai một câu là trượt lượt đó, làm lại từ đầu. Đúng trọn vẹn thì cả unit ghi nhận là đã qua (mọi ô hiện màu "đã xong"). Học vượt là **lựa chọn**, không phải đường mặc định: ô "làm ngay" luôn là ô học chưa xong đầu tiên.
* **Đo giữa / Đo cuối** (`kind=check`): 6 câu; đo giữa lấy nửa đầu node của unit, đo cuối lấy cả unit.
* **Learning Experience** (viết lại 2026-08-15, SRC-138): mỗi node cho các ô có NỘI DUNG THẬT — 🔬 Lab (nếu bài có lab), 🧪 Khám phá (nếu có nội dung khám phá), ✏️ Luyện tập. **Không bù cho đủ 6 ô nữa**: vòng 🔁 Luyện thêm chỉ mọc ra *sau khi* learner làm xong lượt trước, tối đa 4 lượt. Vẽ sẵn ba lượt lặp trên cùng một kho câu hỏi chỉ làm đường đi trông dài còn learner thì ngợp — một unit thật đo được đã giảm từ 15 ô xuống 4 ô. **Đo giữa** chỉ xuất hiện khi có từ 4 ô học trở lên, không thì chỉ còn Học vượt + học + Đo cuối.

**Sai thì gặp lại ở CUỐI hàng** (chỉ đạo 2026-08-15 sửa lại luật ngày 14): trả lời sai thì learner xem đáp án + giải thích rồi **đi tiếp**, còn câu đó được **chèn xuống cuối danh sách** — 10 câu, sai 1 thành 11 lượt, sai nữa thành 12. Bắt làm lại ngay tại chỗ chỉ là chép lại đáp án vừa hiện ra; gặp lại sau vài câu mới là nhớ thật (spaced retrieval). Mỗi lần sai vẫn mất một bong bóng; **hết bong bóng thì làm lại cả Experience**. Học vượt chỉ có 1 bong bóng nên sai một câu là trượt luôn.

**Skip đi thẳng câu sau** (SRC-117): bấm Skip = "con chưa biết câu này" → **không chấm, không hiện đáp án, không mất bong bóng, không chèn lại xuống cuối**, nhảy luôn câu kế. Tín hiệu "chưa biết" vẫn được ghi ngầm một lần làm evidence (SDD-002 §18 — thành thật là dữ liệu quý), nhưng learner không bị dừng lại vì nó.

**Chỉ lần trả lời đầu tiên của mỗi câu là bằng chứng.** Lượt gặp lại được chấm ở client bằng `correct_index` của feedback lần đầu và **không gửi lên `/answer`** — nếu gửi thì mọi learner đều kết thúc ở 100% và Assessment Experience mất hết giá trị đo (SDD-002 §5). Bảng ôn cuối bài và mastery vẫn phản ánh lần đầu; việc sửa lại là để học, không phải để làm đẹp điểm.

> **Cập nhật SRC-384 (2026-08-19).** Trình tự học ở cấp trên Unit (cửa vào, hàng chờ, các lần đo)
> chuyển sang [SDD-021](../sdd-021-learning-entry-sequencing.md). Phần dưới đây vẫn là nguồn đúng cho
> mọi thứ BÊN TRONG một Unit. Một điểm đổi: Học vượt và "đo khi nhảy vào vùng chưa có bằng chứng"
> nay là **một** Experience (SDD-021 §7), khác nhau chỉ ở chỗ ai khởi xướng.

### 9.1 Luật hiển thị và mở khoá — bản hiện hành (2026-08-19)

> Phần này là **nguồn đúng** cho câu hỏi "màn Học hiển thị như thế nào". Các đoạn phía trên mô tả
> bản 2026-08-14/15 (ba màu, accordion một-mở-một, Package làm tag) — giữ lại làm lịch sử vì chúng
> giải thích vì sao từng luật ra đời, nhưng khi lệch nhau thì lấy phần này.

#### Bốn trạng thái của một ô Experience

| Trạng thái | Điều kiện | Vẽ thế nào |
| --- | --- | --- |
| **chưa mở** | ô đứng sau ô chưa-động-tới đầu tiên, hoặc thuộc unit còn khoá | bong bóng mờ, không quầng, hình màu chữ mờ; bấm vào chỉ mở popover giải thích |
| **sẵn sàng** | đã mở, chưa đụng tới | bong bóng trong, viền sáng |
| **đang làm** | ô chưa-động-tới đầu tiên **của unit đang học** | bong bóng sáng nhất + quầng rộng, kèm chữ **BẮT ĐẦU** nảy nhẹ phía trên |
| **đã xong** | `status = completed` | viền và quầng ánh xanh lá, dấu ✓ ở góc |

Ba thứ này nói bằng **độ sáng**, không bằng màu tô (SRC-348). Bảng ba màu cũ (`--border` / `--success` /
`--accent`) đã nghỉ hưu cùng với giao diện nền trắng.

#### Chỉ MỘT chữ "Bắt đầu" trên cả màn (SRC-359)

Mỗi lúc chỉ **một** ô mang chữ "Bắt đầu": ô chưa-động-tới đầu tiên của **unit đang học** — tức unit
đầu tiên đang mở mà chưa đi qua Đo cuối. Các unit đang mở khác không có chữ này. Trước đây mỗi unit
tự gắn một cái, nên cuộn xuống là gặp ba bốn chữ "Bắt đầu" và learner hết biết đâu là chỗ mình đứng.

#### Học vượt: cửa vào, không phải lối tắt luôn mở (SRC-363)

Trong một Unit, **"Học vượt" và đường thường không bao giờ cùng mở**:

| Tình trạng Unit | Ô Học vượt | Ô đường thường |
| --- | --- | --- |
| **còn khoá** (chưa tới lượt) | có, và là ô **duy nhất** bấm được | hiện mờ, bấm vào chỉ mở popover nói cần gì để mở |
| **đang mở** (đang học) | **không hiện** | mở theo luật tuần tự bên dưới |
| **đã học vượt xong** | có, mang dấu ✓ | tất cả đều mang dấu ✓ |

Bày cả hai lối cùng lúc là mời learner bỏ dở đường đang đi. Học vượt là cách **chứng minh một lần
thay vì đi từng bước**, nên nó chỉ có nghĩa ở cửa vào.

#### Bố cục màn Học vượt: luật nói lúc có ích, phản hồi tách khỏi điều khiển (SRC-453)

Hai luật bố cục, cùng một nguyên tắc — **mỗi chữ trên màn phải tới đúng lúc nó dùng được**:

1. **Điều kiện đạt (9/10 câu, sai từ hai câu là làm lại từ đầu) KHÔNG hiển thị dưới tiêu đề màn
   làm bài.** Đứng ở đó, trước khi learner làm câu nào, nó chỉ là một câu đe: biết ngưỡng khắt khe
   mà chưa biết đề dễ hay khó thì learner chần chừ chứ không chuẩn bị được gì. Luật vẫn nguyên hiệu
   lực ở **server** (§12) và được **màn kết quả nói ra khi trượt** — đúng lúc con số ấy giải thích
   được chuyện vừa xảy ra. Đây là luật hiển thị, không phải nới điều kiện.
2. **Cụm "Quá dễ · Quá khó · Báo cáo" là một khu RIÊNG ở đáy màn**, căn giữa, ngăn bằng một đường
   kẻ, **không đứng chung hàng với Skip và nút trợ giúp**. Ba lối đó nói về **chất lượng câu hỏi**
   (phản hồi cho hệ), còn Skip/trợ giúp là **điều khiển của lượt làm bài**. Trộn chúng vào một hàng
   khiến learner đọc "Quá khó" như một nước đi trong bài — bấm để thoát câu — thay vì một lời nhận
   xét về đề.

Áp ở `apps/learn/src/Learning.tsx`, dùng chung cho mọi Experience có ba lối phản hồi này.

#### Học vượt xong ⇒ cả Unit là **đã xong**, không phải "được mở" (SRC-102, khoá bằng test ở SRC-362)

Khi `exp_key = skip` đạt `completed`:

* **mọi ô** trong unit chuyển sang **đã xong** (dấu ✓), không phải chỉ được bỏ khoá;
* unit tính vào **"Đã vững"** ở cột phải và vào `units_solid` của API `/xp`;
* unit kế tiếp mở ra.

Đánh đổi cần biết: học vượt **không sinh bằng chứng cho từng node** bên trong — Learner Model chỉ
nhận 10 câu của bài học vượt. Vì thế một unit học vượt sẽ có **mức vững mỏng hơn** một unit đi đủ
đường, dù trên đường đi cả hai đều xanh.

#### Mở khoá TRONG một Unit — "đã làm" mở đường (SRC-354)

Ô kế tiếp mở khi ô trước đã **được làm**, tức `learner_experience_state` có một dòng cho nó — kể cả
`failed`. Trước đây phải `completed` mới mở, nên hết bong bóng là learner kẹt: làm xong rồi mà đường
đi vẫn khoá như chưa đụng vào.

Tách bạch hai việc, và đây là chỗ dễ lẫn nhất của cả khu vực này:

| Việc | Điều kiện |
| --- | --- |
| **mở đường** (ô sau, unit sau) | đã làm — `completed` **hoặc** `failed` |
| **tô ✓, tính "Đã vững", cộng XP** | chỉ `completed` |

#### Mở khoá GIỮA các Unit — phải **đi qua** Đo cuối (SRC-356)

Unit sau mở khi unit trước đã có một dòng cho `exp_key = final` (đạt hay chưa cũng được), **hoặc**
đã học vượt. Cửa gác vẫn còn — **phải làm Đo cuối mới đi tiếp**, không nhảy cóc — nhưng không bắt
phải đạt, vì đứng mãi một chỗ không làm learner giỏi lên.

#### Phần (Section) và chỗ learner đang đứng (SRC-321, SRC-329, SRC-330)

* Đường học trải **cả môn**, không lọc theo lớp; lớp chỉ quyết định learner **bắt đầu ở Phần nào**.
* Số Phần suy ra từ số unit: **tối thiểu 10 unit một Phần**, tối đa 10 Phần. Dưới 10 unit thì gộp
  vào một Phần.
* Learner **đang đứng** ở Phần chứa unit đầu tiên chưa đi qua Đo cuối; ai chưa học gì thì đứng ở
  Phần có unit đúng lớp mình. Phần đó ghi "con đang ở đây" và **không có nút dẫn tới chính nó**.

#### Lưu trạng thái

`learner_experience_state` (migration 0028), khoá `(learner_id, subject_id, unit_key, exp_key)`:

* `unit_key = "<strand>|<module>|<unit>"` — client dựng, khớp cách gom nhóm ở đường học.
* `exp_key ∈ {skip, mid, final, explore:<node>, practice:<node>, practice<n>:<node>, lab:<id>}`.
  Vào bài bằng **deep link** cũng phải mượn đúng khoá slot này, không được bịa `practice:<node_id>`
  — bịa thì ghi xong không ô nào đọc được và ô vẫn hiện như chưa làm (SRC-341).
* `POST /v1/learners/{id}/experience/complete` ghi `completed`/`failed`; upsert **chỉ nâng, không
  hạ** — trượt Học vượt không xoá dấu đã hoàn thành cũ.
* `GET /labs` trả kèm mảng `experiences` để đường đi tô đúng trạng thái.

#### Bảng luật — tra nhanh

| Luật | Nguồn | Test khoá |
| --- | --- | --- |
| Ô sau mở khi ô trước **đã làm** | SRC-354 | `Làm xong một ô thì ô kế tiếp mở, kể cả khi chưa đạt` |
| Unit sau mở khi đã **đi qua** Đo cuối | SRC-356 | `Unit sau mở khi unit trước đã làm Đo cuối, dù chưa đạt` |
| Học vượt xong ⇒ cả unit **đã xong** | SRC-102 · SRC-362 | `Học vượt xong thì mọi ô trong unit đều là đã xong` |
| Học vượt **không** đứng cạnh đường thường | SRC-363 | `Học vượt chỉ có ở unit còn khoá…` |
| Cả màn chỉ **một** chữ "Bắt đầu" | SRC-359 | `Chỉ một chữ Bắt đầu trên cả màn…` |
| Popover đóng khi bấm ra ngoài | SRC-310 · SRC-361 | cùng test trên |
