---
url: https://docs.nemo12.com/architecture/sdd-038/speak-together.md
description: >-
  Luyện nói theo cặp qua Zoom, cách đáp gợi ý, icon đọc to, trang bài dạng thẻ
  gập, chế độ band, About Me
---

# SDD-038 · Speak together

> 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.

## 126. Speak together: luyện nói theo cặp qua Zoom (SRC-1078, 27.09.2026)

Chủ dự án: vài learner học cùng nhau qua Zoom, mở cùng một bài nói, đóng vai A (thiên về hỏi) và
B (thiên về trả lời) với bối cảnh cực kỳ đơn giản và thân thuộc; mỗi người có 5 câu gợi ý. Lượt 1
đọc theo mẫu, lượt 2 đổi vai, lượt 3 người hỏi dựa vào câu mẫu còn người trả lời nói theo đời thật,
lượt 4 đổi vai, cuối cùng mỗi người chia sẻ điều thú vị thu được. Hệ thống phải ghi nhận "A và B đã
luyện xong bài này" và cộng thời gian cho cả hai. Mười quyết định chủ dự án chốt "OK hết":

* URL `/ielts/speaking/pair` (danh sách 12 bài) và `/ielts/speaking/pair/<bài>`; lối vào ở trang
  Speaking (ô "Speak together") và thẻ "Learn with others" ở `/ielts`.
* 12 bài: chào bạn mới, gia đình, đồ ăn, cuối tuần, mua sắm, hỏi đường, thời tiết, sở thích, trường
  học, kỳ nghỉ, điện thoại và ứng dụng, kế hoạch; band 4.0-5.5. Nội dung là JSON trong bundle
  (`apps/learn/src/ielts/speakPair/content/`), soạn bằng workflow 8 agent (soạn + duyệt độc lập).
* Mỗi bài: tên, bối cảnh Anh + Việt, vai A và B, 5 lượt (câu hỏi A, câu mẫu B, gợi ý tiếng Việt để
  trả lời theo đời thật), 3-5 cụm từ đáng học.
* Năm bước trên một thanh, ai bấm cũng đổi cho cả hai: chủ buổi là A ở lượt 1 và 3. Lượt 3-4 ẩn câu
  mẫu của B, chỉ còn gợi ý tiếng Việt.
* Nhóm 3-4 người thì xoay cặp; hệ thống không tự ghép nhóm.
* Ghi nhận: một người bấm "Bắt đầu buổi" nhận mã 4 số (bảng `speak_pair_sessions`, migration 0302),
  người kia nhập mã; hai màn hình hỏi trạng thái mỗi 3 giây. Ở bước Chia sẻ mỗi người bấm Xong; chỉ
  khi CẢ HAI đã bấm mới cộng.
* Cộng cố định 15 phút mỗi người vào `learning_events` (skill 'speaking', ref `pair:<bài>`, items 0),
  nên tổng giờ, chuỗi ngày, xếp hạng và thưởng tự cộng.
* Chống cộng khống: buổi phải đủ 8 phút từ lúc tạo mã; mỗi người mỗi bài tối đa một lần mỗi ngày;
  đóng buổi bằng UPDATE có điều kiện nên hai người bấm Xong cùng lúc không cộng hai lần.
* Ngôn ngữ theo SRC-953: tên bài, câu mẫu, vai tiếng Anh; hướng dẫn từng lượt và nút tiếng Việt.
* Bản đầu không có video.

### 126b. Speak together bản 2 (SRC-1079, 27.09.2026)

Chủ dự án, cùng ngày, trước khi bản 1 kịp lên production:

* **Ba cách hỏi cho mỗi câu** (`a_variants`, cách đầu trùng `a`): người hỏi tự bấm chọn một cách để
  đọc. Soạn bằng workflow 8 agent; người duyệt sửa 7 chỗ, phần lớn là một cách hỏi mà câu trả lời
  mẫu của B không còn khớp (hỏi "khi nào" mà B trả lời "bao lâu").
* **Chỉ đúng hai người được cộng giờ**: chủ buổi và người vào đầu tiên. Ai vào sau bằng mã hay link
  là quan sát viên (`speak_pair_observers`): xem cùng bài, không bấm Bắt đầu hay Xong, không cộng.
* **Rời phòng**: nút "Rời phòng" luôn hiện. Người thứ hai rời trước khi bắt đầu là trả chỗ cho người
  khác; đã bắt đầu mà rời, hoặc chủ buổi rời, thì buổi đóng (`ended_at`), không ai được cộng.
* **Cả hai bấm Bắt đầu mới tính giờ**: bước 0 là cùng xem bài và các gợi ý; `started_at` đặt đúng
  một lần khi cả hai đã sẵn sàng, và ngưỡng 8 phút đo từ mốc ấy chứ không từ lúc tạo mã. Màn hình
  hiện thời gian đã luyện.
* **Tự mở bài nào cũng được rồi mời**: bấm "Bắt đầu buổi" ở bất kỳ bài nào, rồi đọc mã hoặc bấm
  "Chép link mời" (`/ielts/speaking/pair/<bài>?code=1234`); người nhận đã đăng nhập mở link là tự vào.
* Migration 0302 sửa tại chỗ thay cho thêm migration mới, vì chưa từng được áp ở môi trường nào
  (Actions bị chặn từ trước khi bản 1 lên main).
* Video giới thiệu: chủ dự án hẹn làm sau.

## 139. Speak together gọn: lịch ra trang riêng, trang hướng dẫn một buổi (SRC-1128, 29.09.2026)

Chủ dự án: chuyển cụm "Weekly sessions" ra trang riêng; trang chính chỉ một buổi sắp diễn ra và số người đăng ký;
trang danh sách chỉ xem, bấm từng buổi mới xem chi tiết và đăng ký; trang chính chỉ 3 bài đầu, bấm mới mở hết;
thêm trang hướng dẫn một buổi (phòng breakout 2 người, đổi cặp, cùng chủ đề, mỗi người 3 lượt).

* **Trang chính** (`/ielts/speaking/pair`, `/speaking`): `NextSessionCard` (một buổi, "n going") dẫn sang danh sách;
  3 bài đầu và nút "See all 11 topics".
* **Danh sách buổi** (`.../sessions`, `/speaking/sessions`): `WeeklySchedule` cũ, nay `SlotRow` chỉ xem (số người,
  trạng thái Open / You are in / Closed), không còn nút đăng ký hay huỷ. Đăng ký và huỷ chỉ còn ở trang chi tiết buổi.
* **Hướng dẫn một buổi** (`.../how-a-session-works`): 90 phút, năm chặng (chào, bạn 1, đổi cặp, bạn 2, tổng kết) và
  luật trong phòng: A hỏi B đáp rồi đổi, mỗi người 3 lượt. Tiếng Anh đơn giản như cả khu Speak together (SRC-1084).
  Link tới trang này ở trang chính, trang danh sách và trang chi tiết buổi.
* Router: hai địa chỉ mới đứng TRƯỚC regex bài `/speaking/pair/<bài>` để không bị đọc thành tên bài.

## 147. Mỗi lượt Speak together có 3 cách đáp gợi ý (SRC-1137, 29.09.2026)

Mỗi lượt trong `apps/learn/src/ielts/speakPair/content/*.json` (21 chủ đề) có thêm `b_variants`: đúng 3 câu trả lời,
câu đầu trùng `b`. Ba câu khác nhau về NỘI DUNG chứ không chỉ về chữ (một ý thuận, một ý nghịch hoặc khác, một ý có lý do
hay ví dụ), để learner vai B thấy một câu hỏi có nhiều đường trả lời. `scripts/audit-content.mjs` chấm lượt thiếu
`b_variants` hoặc `b_variants[0] !== b` là hỏng; kiểu `PairTopic` có trường `b_variants`. Giao diện hiển thị chúng là việc riêng.

## 148. Icon đọc to cho mọi câu Speak together (SRC-1142, 29.09.2026)

Chủ dự án 29.09.2026, áp cho trang bài ở `/speaking/<bài>` và `/ielts/speaking/pair/<bài>` (21 bài Speak together).

**Bố cục.** Mỗi lượt không còn chia hai cột hỏi/đáp. Xếp dọc: 3 câu hỏi (`a_variants`), rồi 3 câu trả lời
(`b_variants`), rồi dòng gợi ý `hint_vi` ở cuối, dòng gợi ý không có icon.

**Giọng.** Một hàm thuần `clusterVoices(order, turn)` ở `apps/learn/src/ielts/speakVoices.ts` quyết cho mỗi lượt:
3 câu hỏi là một người, 3 câu trả lời là người kia; luôn một nam một nữ; cả hai cùng chất giọng US hoặc UK.
Chất giọng xen kẽ theo lượt, bài có `order` lẻ bắt đầu US, chẵn bắt đầu UK; người hỏi là nam ở lượt chẵn, nữ ở
lượt lẻ. Trang và API cùng gọi hàm này (API import file theo đường dẫn tương đối), nên không thể lệch nhau, và
URL không mang chất giọng: server tự suy ra, không ai xin được một lượt trộn US với UK.

**Icon.** Đầu mỗi câu có hai nút tròn 40px: cậu bé (`Play male voice`) và cô bé (`Play female voice`), SVG vẽ tay.
Nút của người được phân vai đậm, nút kia nhạt nhưng vẫn bấm được. Cả trang một trình phát: bấm câu khác thì câu
đang đọc dừng; nút đang đọc đổi màu `accent` và `aria-pressed`.

**API.** `GET /v1/speak-audio/{topic}/{turn}/{line}/{variant}/{voice}?h=<textTag>` (`line` là `q` hoặc `a`,
`voice` là `male` hoặc `female`), sau `requireSession` như các route speak khác. Câu được tra theo VỊ TRÍ trong 21
file nội dung mà worker đóng gói cùng (`workers/api/src/modules/speakAudio/content.ts`), không nhận chữ tự do, nên
không phải một proxy TTS mở.

* R2: bucket `nemo12-content` (binding `CONTENT`, chung với audio dictation và video), khoá
  `speak-audio/v1/<topic>/t<turn>-<q|a><variant>-<us|uk>-<male|female>-<sha16(text)>.wav`. SHA-256 của chữ nằm
  trong khoá nên sửa câu là ra file mới. Tối đa 21 × 5 × 6 × 2 = 1.260 file.
* Lần nghe đầu: không có trong R2 thì gọi Workers AI `@cf/deepgram/aura-2-en` (hàm `speak()` của dictation,
  PCM 16 kHz, đóng WAV), `put` vào R2 với `audio/wav`, trả về. Lần sau đọc thẳng R2, không gọi model.
* Giọng Aura-2 (đã được dictation SRC-669 gọi thật với binding AI): US nam `apollo`, US nữ `asteria`, UK nam
  `draco`, UK nữ `pandora`. Nhãn chất giọng lấy từ bảng Deepgram công bố, chưa ai nghe xác nhận.
* Hai lượt nghe đầu cùng lúc: trong một isolate dùng chung một lượt sinh; khác isolate thì cùng lắm sinh hai lần
  và `put` đè cùng một khoá, vô hại.
* Trần `speakAudioGenerate`: 120 lượt SINH mỗi giờ mỗi tài khoản; lượt đọc từ R2 không đếm.
* `Cache-Control: private, max-age=31536000, immutable` khi `h` (FNV-1a của chữ, tính ở trình duyệt) khớp chữ của
  worker; không khớp (learn và api deploy lệch nhau) thì `max-age=300`.

Kiểm chứng: `workers/api/src/modules/speakAudio/routes.test.ts` (cần phiên; miss sinh và lưu WAV; hit không gọi AI;
UK dùng giọng UK; ba lượt đồng thời gọi TTS một lần; câu không có thật bị từ chối và `?text=` bị bỏ qua; `h` cũ không
cache một năm; khoá đổi khi chữ đổi), `apps/learn/src/ielts/speakVoices.test.ts` (mọi lượt của 21 bài một nam một
nữ cùng chất giọng), `apps/learn/e2e/speakAudio.spec.ts` (thứ tự hỏi, đáp, gợi ý; 2 icon mỗi câu; bấm icon xin đúng
giọng; gợi ý không icon; 40px ở 375px). `mobileAudit.spec.ts` đã phủ `/ielts/speaking/pair/family`.

## 149. Trang bài Speak together thành các thẻ gập; thêm Grammar và Collocations (SRC-1147, 30.09.2026)

Chủ dự án 30.09.2026. Trang `/speaking/<bài>` (`SpeakPairTopicPage`, `apps/learn/src/ielts/SpeakPair.tsx`) bỏ bố cục trải dài:

* **Mỗi cụm là một thẻ gập** (`FoldCard`): header là một `<button aria-expanded aria-controls>` thật, nên Enter/Space
  và focus có sẵn; mở nhiều thẻ cùng lúc được; Motion (`AnimatePresence` + `m.div`) lo phần mở ra. Header gồm câu
  tiếng Anh `hint_en` ở trên, `hint_vi` nhạt ngay dưới, và nhãn **US/UK** ở góc phải lấy từ `clusterVoices` (cùng hàm
  API dùng để chọn giọng, nên nhãn và giọng không lệch nhau). Thẻ gập sẵn.
* **Trong thẻ**: nhóm "Questions" rồi nhóm "Answers", dòng thoáng, icon đọc to thẳng hàng. Bỏ nhãn "A · Question n",
  bỏ nhãn "Answer 1/2/3", bỏ dòng gợi ý cuối thẻ (đã lên header). Icon và cách chọn giọng (SRC-1142) giữ nguyên.
* **Bỏ dòng vai** "A: ... · B: ...".
* **Sau 5 cụm**: Useful phrases, Grammar, Collocations, cả ba cũng là thẻ gập.
* **Grammar**: mỗi file nội dung có mảng `grammar` đúng 3 điểm `{name_en, name_vi, form, explain_vi, examples[2], lesson?}`.
  Điểm được chọn là điểm có thật trong câu trả lời của bài; bài 1-11 dùng điểm cơ bản, bài 12-21 (NEMO SPEAK 2) dùng
  điểm nâng cao hơn. `lesson` là `<level>/<bài>` trong `packages/grammar-catalog/catalog.json`, hiện thành link
  `/grammar/<level>/<bài>`.
* **Collocations**: mảng `collocations` 6-8 cụm `{en, vi, example}`, ví dụ trong bối cảnh bài.
* **Ngôn ngữ** theo SRC-953: tên thẻ, câu mẫu tiếng Anh; dòng phụ của thẻ, giải thích ngữ pháp, nghĩa cụm từ tiếng Việt.

Cổng: `scripts/audit-content.mjs` đòi `grammar` đủ 3 điểm, đủ trường, `lesson` (nếu có) trỏ tới bài có thật, và
`collocations` 6-8 cụm đủ trường; khu speak phải giữ 100. Worker `speakAudio/content.ts` chỉ đọc `turns`, bỏ qua trường
mới. e2e: `apps/learn/e2e/speakCards.spec.ts` (gập sẵn, mở bằng chuột và bàn phím, header EN + VI, nhãn US/UK, không còn
dòng A/B, ba thẻ cuối theo thứ tự, icon vẫn gọi đúng giọng sau khi mở, ảnh chụp desktop và 375px ở cả hai trạng thái);
`mobileAudit.spec.ts` rà thêm `/speaking/family#open-all` (mọi thẻ mở).

## 152. Trang bài Speak together: tiếng Anh toàn bộ, icon nét mảnh, chế độ Band 5.0 / 6.0 (SRC-1155, 30.09.2026)

Chủ dự án 30.09.2026, bốn việc trên trang một bài (`SpeakPairTopicPage` trong `apps/learn/src/ielts/SpeakPair.tsx`,
URL `/speaking/<bài>` và `/ielts/speaking/pair/<bài>`):

1. **Tiếng Anh toàn bộ.** Trang không còn hiện `context_vi`, sub-heading `hint_vi`, `vi` của Useful phrases và
   Collocations, tên và giải thích ngữ pháp tiếng Việt. Mỗi điểm ngữ pháp có thêm `explain_en` (tiếng Anh đơn
   giản), cổng audit đòi trường này. Các trường tiếng Việt vẫn nằm trong JSON, chỉ thôi hiển thị. Dòng phụ của
   ba thẻ cuối và link "See the NEMO GRAMMAR lesson" cũng tiếng Anh. Ranh giới ghi vào bảng ngôn ngữ ở CLAUDE.md.
   Chỉ trang MỘT BÀI; trang khoá và trang chủ Speak together không đổi.
2. **Icon nét mảnh.** Hai icon giọng đọc, mũi tên gập vẽ lại bằng SVG nội tuyến: stroke 1.5, đầu nét tròn, không tô,
   `currentColor` cho mặt. Cậu bé tóc ngắn nét xanh `blue`, cô bé tóc dài buông quá vai nét đỏ cam `red`
   (cùng màu nhân vật PairScene). Trạng thái: phát là `ring-2` màu accent, vai được phân là ring 1.5px, vai kia
   mờ 60% với ring mảnh. Nút giữ 40px, aria-label giữ nguyên. Mọi icon có `data-style="line"` để e2e kiểm.
3. **Công tắc Band 5.0 | Band 6.0** đầu trang, dưới đoạn bối cảnh: `radiogroup` hai nút `radio`, mũi tên đổi nấc.
   Mặc định 5.0. Thứ tự ưu tiên: `?band=6` (hoặc `?band=5`) trên URL, rồi `localStorage["nemo12.speakBand"]`
   (bọc try/catch), rồi 5.0. Đổi nấc thì ghi `localStorage` và cập nhật `?band=` bằng `replaceState`.
   * Band 5.0: câu gốc (`a_variants`, `b_variants`), không đổi chữ nào.
   * Band 6.0: trường mới `band6: { a_variants[3], b_variants[3] }` ở mỗi lượt của 21 bài. Câu hỏi cùng ý, tự nhiên
     hoặc phức tạp hơn; câu trả lời ứng với từng câu gốc nhưng dài hơn, có lý do và ví dụ, từ nối (although,
     because, so, which), từ vựng rộng hơn và một hai cấu trúc phức. Bài SPEAK 2 (order 12-21) chạm tới 6.5.
     Chữ mới không có tên riêng hay thương hiệu (câu gốc có vài tên, bản band6 thay bằng cách gọi chung).
   * Tiêu đề thẻ (`hint_en`), Useful phrases, Grammar, Collocations dùng chung cho hai nấc.
   * Cổng audit (`scripts/audit-content.mjs`) đòi `band6` đủ 3 + 3 câu không rỗng ở mọi lượt; `PairTopic` có trường mới.
4. **Cột giữa.** Tiêu đề, ảnh, bối cảnh, công tắc và mọi thẻ nằm trong một cột `mx-auto max-w-3xl`
   (`data-part="topic-column"`); ảnh rộng bằng cột. Header và breadcrumb giữ nguyên. Mobile vẫn full width.

**Âm thanh Band 6.0.** `GET /v1/speak-audio/{topic}/{turn}/{line}/{variant}/{voice}?h=&level=b6`. `level` là
`b5` (mặc định, bỏ trống) hoặc `b6`; giá trị khác trả 400. Worker tra câu `band6` THEO VỊ TRÍ như b5, nên vẫn
không thành proxy TTS mở. Giọng và chất giọng của lượt vẫn lấy từ `clusterVoices`, giống hệt b5. Khoá R2 của b5
không đổi (`t1-q1-us-male-<hash>.wav`), file cũ vẫn dùng được, không sinh lại; b6 thêm đoạn `-b6`
(`t1-q1-b6-us-male-<hash>.wav`). Trần file: 21 × 5 × 6 × 2 giọng × 2 nấc = 2.520.

**Kiểm chứng.** `workers/api/src/modules/speakAudio/routes.test.ts` (b6 đọc đúng câu band6, giữ giọng, khoá tách
riêng, level lạ và vị trí ngoài khoảng bị từ chối mà không gọi AI). `apps/learn/e2e/speakBand.spec.ts`: không còn
chữ tiếng Việt ở cả 21 bài và cả hai nấc (so với chuỗi `*_vi` đã biết và regex dấu tiếng Việt), icon là SVG nét
mảnh không tô, 5.0 là mặc định và hiện câu gốc, 6.0 hiện câu band6 và lượt gọi âm thanh mang `level=b6`, lựa chọn
còn sau khi tải lại, `?band=6`, cột giữa; ảnh chụp desktop và 375px ở cả hai nấc.
`speakCards.spec.ts`, `speakAudio.spec.ts` đổi theo (không còn sub-heading).

**Cùng đợt, các trang /speaking khác (chủ dự án 30.09.2026, gộp vào SRC-1155).** Luật chung: mọi trang
`/speaking/**` tiếng Anh toàn bộ, một cột giữa (`max-w-3xl`; trang lưới và trang hai cột dùng `max-w-5xl`), và
một kiểu gập sẵn duy nhất là `FoldCard` (`apps/learn/src/ielts/FoldCard.tsx`, tách ra từ trang bài).

* **Trang khoá** (`SpeakCoursePage`): mặc định chỉ tên, một dòng tóm tắt (`3 topics · 2 sessions · Free`) và thẻ học
  phí có Register khi áp dụng. Kế hoạch buổi (kèm dòng lịch cuốn chiếu và buổi kế tiếp) gập vào `Sessions (n)`;
  đoạn "Two topics in each session..." thành một link nhỏ "How a session works". Lưới bài giữ lại, 2 cột trong
  cột giữa: ảnh từng bài là thứ learner nhận ra nhanh nhất, gập nó lại thì trang khoá mất phần đáng xem nhất.
* **How a session works** (`SpeakGuidePage`): ảnh SVG kiểu PairScene (laptop chia ô Zoom, ba phòng nhỏ hai người,
  mũi tên đổi bạn); năm bước là năm hàng gập (số, tên, giờ); thẻ "In each breakout room" cũng gập.
* **Sessions** (`WeeklySchedule`, `SlotRow`): tab Upcoming / My sessions, một cột hàng gọn một dòng (ngày giờ ·
  bài · số người · trạng thái), cả hàng là link tới trang buổi. Dòng học thử tiếng Việt và `helper` bỏ; luật học thử
  nằm sau "Free trial rules": `Wed 20:00-21:30 · Speaking Practice · Free trial: 1 of 3 used` và
  `Free trials in 09.2026 and 10.2026. Join at least 2 hours before.`
* **Chỉ buổi tối thứ Tư.** Giao diện chỉ hiện buổi `wed` (Speaking Practice) ở Upcoming, ở dòng học thử và ở thẻ
  buổi kế tiếp trên trang chủ. Lọc ở GIAO DIỆN (`shownSlot` trong `SpeakSchedule.tsx`); API, lịch và đăng ký đã lưu
  không đổi. Quyết định: buổi Chủ nhật learner ĐÃ đăng ký vẫn hiện (ở Upcoming và My sessions), đánh dấu "You are
  in" như mọi buổi khác, để không ai mất dấu một chỗ đã giữ.
* **Trang chủ** (`SpeakPairHome`, `max-w-5xl`): NEMO START là một thẻ riêng cả hàng, mang buổi tối thứ Tư gần nhất
  (ngày giờ, bài, số người) và nút "View session" tới thẳng `/speaking/sessions/<mã>`; trang vẫn chỉ xem, nút chỉ
  điều hướng. Thẻ "Next session" riêng đã gỡ (`NextSessionCard` thay bằng hook `useNextWedSession`). Hàng dưới:
  NEMO SPEAK 1, NEMO SPEAK 2, NEMO WALK, 3 cột trên desktop.
* **Trang một buổi** (`SpeakSessionPage`, `max-w-5xl`, hai cột 2/3 + 1/3 trên desktop): cột phải mở đầu bằng thẻ
  giờ (khối lịch thứ/ngày/tháng, khung giờ, "Vietnam time", nhãn trạng thái, số người), rồi "Who is coming (n)" mỗi
  người một hàng (ảnh hoặc chữ cái đầu, tên, "(you)"). Cột trái: tên bài, ảnh, bối cảnh, khối hành động (Join /
  Cancel / You are in / Closed, thẻ Zoom khi đã đăng ký), link "See the lesson" và "How a session works". Mobile:
  thẻ giờ lên đầu, "Who is coming" xuống cuối. Lượt gọi API giữ nguyên.

Kiểm chứng: `apps/learn/e2e/speakPages.spec.ts` (từng trang ở desktop và 375px: cột giữa, gập sẵn, chỉ buổi thứ Tư,
buổi Chủ nhật đã đăng ký vẫn hiện, không chữ tiếng Việt, bố cục hai cột và thứ tự trên mobile, ảnh chụp);
`speakingProgram.spec.ts` đổi theo.

## 154. Bài About Me và phần khởi động mở đầu mọi buổi (SRC-1157, 30.09.2026)

Chủ dự án 30.09.2026: ngoài 3 bài của NEMO START (My Family, Favourite Food, Your Weekend), thêm một bài
giới thiệu bản thân để hai bạn cặp quen nhau; mọi buổi đều mở đầu bằng phần About Me. Số mục là 154 vì
PR #282 (Speaking Mentors, SRC-1156) đang giữ §153.

**Quyết định của điều phối viên (chủ dự án chưa duyệt từng dòng, đánh dấu ✍️):**

1. ✍️ Bài mới `about-me`, tên "About Me", `order: 0`, cùng khuôn đầy đủ như 21 bài kia: bối cảnh EN/VI,
   5 lượt (tên và tên gọi, tuổi/lớp/trường, nơi ở, việc thích làm, một điều thú vị ít ai biết), mỗi lượt
   3 cách hỏi, 3 cách đáp, gợi ý EN/VI và `band6`; 5 cụm từ; 3 điểm ngữ pháp (be/have ở hiện tại đơn,
   like/love/enjoy + -ing, trạng từ tần suất, mỗi điểm trỏ tới một bài NEMO GRAMMAR có thật); 8 collocation.
   Ảnh `PairScene`: hai bạn vẫy tay, thẻ tên trên áo, bóng chữ "Hi!" và "Hello!". Để vẽ tay và thẻ tên
   ĐÈ lên người, `Scene` có thêm lớp `front` (vẽ sau hai người); các cảnh cũ không đổi.
2. ✍️ Thứ tự không được làm gãy lát cắt cũ. `SPEAK_COURSES` trước đây cắt `PAIR_TOPICS.slice(...)` theo
   `order`, `FREE_ROTATION_TOPICS = slice(0, 11)`: thêm một bài order 0 sẽ đẩy mọi bài lệch một ô. Nay
   các khoá đọc `SPEAK_TOPIC_IDS` và vòng học thử đọc `FREE_ROTATION_IDS`, đều là danh sách ID tường
   minh; `TOPICS` ở `speakPair/schedule.ts` và `topics` ở `speakCourses/cohort.ts` vốn đã là ID. `order: 0`
   giữ nguyên order 1-21 của các bài khác, nên giọng đọc (`clusterVoices(order, turn)`) của chúng cũng
   không đổi. About Me KHÔNG vào vòng học thử hằng tuần.
3. ✍️ NEMO START thành 4 bài, About Me là bài 1: 1 About Me, 2 My Family, 3 Favourite Food, 4 Your Weekend.
   Buổi 1 = About Me + My Family + Favourite Food; buổi 2 = About Me + My Family + Your Weekend. Trong kế
   hoạch buổi, About Me hiện là "Warm-up: About Me" ở đầu, đúng một lần.
4. ✍️ "Mọi buổi mở đầu bằng About Me" áp dụng ở mọi nơi:
   * NEMO SPEAK 1 và NEMO SPEAK 2: trường `warmup: "about-me"` trên khoá (cả learn lẫn bản sao ở worker
     `cohort.ts`); kế hoạch buổi, dòng "Next session" và danh sách buổi ở trang đăng ký hiện
     "Warm-up: About Me + 1. ... + 2. ...". Bài và số thứ tự trong khoá KHÔNG đổi: đây là phần khởi động
     dùng chung, không phải một bài của khoá. Hàm chung `sessionParts` trong `SpeakPair.tsx`.
   * Trang một buổi thứ Tư (`/speaking/sessions/<id>`): link "Warm-up: About Me" nằm trên tên bài chính.
   * Trang "How a session works": bước Partner 1 và Partner 2 nhắc một vòng About Me nhanh với mỗi bạn mới.
     Vẫn năm bước.
   * Audio: `speakAudio/content.ts` import thêm `about-me.json`, nên câu của bài mới qua đúng phép kiểm
     "câu có trong nội dung" như mọi bài.
5. Rà mobile có thêm `/speaking/about-me` và `/speaking/about-me#open-all`.

Ngôn ngữ: toàn bộ chữ mới trên `/speaking` là tiếng Anh (SRC-1155); trường `_vi` vẫn nằm trong JSON như các bài khác.

Kiểm chứng: `apps/learn/src/ielts/cohortAgrees.test.ts` (vòng học thử vẫn đúng 11 bài order 1-11; about-me
order 0 đứng đầu; SPEAK 1/2 giữ bài, order và plan; NEMO START 4 bài và nhãn khởi động; `warmup` khớp worker);
`speakVoices.test.ts` (22 bài); `workers/api/src/modules/speakAudio/routes.test.ts` (câu của about-me ở cả
hai mức phát được, lượt không có bị từ chối); `apps/learn/e2e/speakAboutMe.spec.ts` (trang NEMO START 4 bài
và khởi động, SPEAK 1/2 có khởi động ở mọi buổi, trang about-me đủ nội dung, trang hướng dẫn; ảnh chụp
desktop); `speakPages.spec.ts` (link khởi động trên trang buổi), `speakingProgram.spec.ts` đổi theo;
`scripts/audit-content.mjs` chấm about-me 100.

## 157. Trang chi tiết buổi: ảnh thấp một nửa, hành động sang cột phải, hai cụm gập (SRC-1161, 30.09.2026)

Chủ dự án 30.09.2026 chỉnh trang `learn.nemo12.com/speak/sessions/<id>` (`SpeakSessionPage`):

* **Ảnh** dùng bản `banner` của `PairScene`, khung `aspect-[4/1]`; bản cũ khung 2:1, nên cùng bề rộng ảnh thấp còn đúng một nửa.
* **Khối hành động** ("You are in." + Cancel, Join this session, hoặc Sign-up closed / This session is over) sang cột phải, **ngay dưới thẻ giờ**. Chọn thứ tự này vì hành động nói về đúng giờ vừa đọc: đọc giờ rồi quyết định. Cột phải: thẻ giờ, hành động, Zoom, Who is coming, link How a session works.
* **Cột giữa**: tên bài, ảnh, rồi hai cụm gập sẵn `SessionTopicFold`: "About Me" (nhãn Warm-up) và bài chính (nhãn Topic). Header có nhãn, tên bài, bối cảnh `context_en`. Mở ra là 5 cụm hỏi-đáp dùng chung component `TopicTurns` với trang bài, theo lựa chọn Band 5.0/6.0 và "1 answer / 3 answers" đã lưu (localStorage hoặc `?band`/`?view`), có nút đọc to; cuối cụm là link "Open full lesson". Link "Warm-up: About Me" và "See the lesson" cũ đã gỡ.
* **Mobile**: thẻ giờ, hành động, tên, ảnh, About Me, bài chính, Zoom, Who is coming, How a session works.
* Kiểm chứng: `apps/learn/e2e/speakPages.spec.ts` (desktop và 375px), `mobileAudit.spec.ts`.

## 159. Trang buổi Speak: 9 hoạt động, bỏ chữ lặp ở cột phải, nút Join on Zoom (SRC-1167, 30.09.2026)

Chủ dự án 30.09.2026: mỗi buổi Speak (trang `learn.nemo12.com/speak/sessions/<id>`) hiện danh sách hoạt động.
Dữ liệu một nguồn ở `apps/learn/src/ielts/speakSessionPlan.ts`, dùng chung cho trang buổi và trang
"How a session works". Chữ trên `/speak` là tiếng Anh đơn giản (SRC-1155).

| # | Hoạt động | Phút (buổi 90') | Giờ buổi thứ Tư |
| --- | --- | --- | --- |
| 1 | Today's agenda | 0-5 | 20:00-20:05 |
| 2 | Meet your hosts | 5-10 | 20:05-20:10 |
| 3 | About NEMO SPEAK (3 job, 3 pain) | 10-15 | 20:10-20:15 |
| 4 | About NEMO START (job, pain, solution) | 15-20 | 20:15-20:20 |
| 5 | Demo by the hosts | 20-27 | 20:20-20:27 |
| 6 | Practice round 1 (nhóm 2-3, 4 bước) | 27-45 | 20:27-20:45 |
| 7 | Public demo | 45-52 | 20:45-20:52 |
| 8 | Reflection (2 câu hỏi) | 52-57 | 20:52-20:57 |
| 9 | Practice round 2 (lặp 6, 7, 8) | 57-87 | 20:57-21:27 |
| - | Thank you and goodbye (dòng kết, không đánh số) | 87-90 | 21:27-21:30 |

* Khối "Activities" nằm ở cột giữa, trên hai cụm gập About Me và Topic; mỗi hoạt động là một `FoldCard` gập
  sẵn (số, tên, giờ đồng hồ tính từ giờ bắt đầu buổi + phút tương đối). Hoạt động 1 mở ra là cả lịch; hoạt
  động 6 mở ra là 4 bước, đúng chữ của checklist "Practice steps (4 rounds)" (hằng `PRACTICE_ROUNDS`, trang bài
  import lại từ đây), kèm gợi ý "Start a practice room" dẫn tới phòng luyện.
* Trang "How a session works": bỏ kế hoạch 5 bước cũ, thay bằng 9 hoạt động trên (vẫn gập sẵn) + dòng kết.
* Cột phải bỏ chữ lặp: thẻ giờ chỉ còn ngày, giờ, "Vietnam time"; trạng thái ("You are in" + Cancel / Join /
  Closed) chỉ ở khối hành động; số người chỉ ở hàng gập "Who is coming · n going", mở ra mới thấy danh sách.
* Thẻ Zoom: nút "Join on Zoom" (accent, full width, icon video, tab mới, `rel=noreferrer`) thay link dài;
  không hiện URL thô. Chưa có link thì giữ câu "The Zoom link will be here before the session."

**Cờ chờ chủ dự án:**

* ✍️ Giờ từng hoạt động là ĐỀ XUẤT của phiên, chưa được duyệt.
* ✍️ Chưa có dữ liệu host theo từng buổi: hoạt động 2 tạm hiện bốn Speaking Mentors (tên + vai) dưới nhãn
  "Hosts". Host theo buổi chờ chủ dự án.
* ✍️ Buổi NEMO SPEAK 1/2 cuốn chiếu (120 phút) dùng cùng 9 hoạt động, phút co giãn theo tỉ lệ độ dài buổi
  (`scaleSlots`, làm tròn tới phút); trang "How a session works" vẫn ghi theo buổi 90 phút.

Kiểm: `apps/learn/src/ielts/speakSessionPlan.test.ts`, e2e `speakPages.spec.ts` (9 hoạt động gập đúng thứ tự,
giờ 20:00 tới 21:30, hoạt động 6 có 4 bước, "You are in" và số người mỗi thứ một lần, danh sách ẩn tới khi
mở, nút Join on Zoom không lộ URL) và `speakAboutMe.spec.ts`.
