Skip to content

SDD-038 · Speak together ​

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.

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 độngPhút (buổi 90')Giờ buổi thứ Tư
1Today's agenda0-520:00-20:05
2Meet your hosts5-1020:05-20:10
3About NEMO SPEAK (3 job, 3 pain)10-1520:10-20:15
4About NEMO START (job, pain, solution)15-2020:15-20:20
5Demo by the hosts20-2720:20-20:27
6Practice round 1 (nhóm 2-3, 4 bước)27-4520:27-20:45
7Public demo45-5220:45-20:52
8Reflection (2 câu hỏi)52-5720:52-20:57
9Practice round 2 (lặp 6, 7, 8)57-8720:57-21:27
-Thank you and goodbye (dòng kết, không đánh số)87-9021: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.