Skip to content

SDD-043 · Journey-first: hai tầng hành trình ​

Một phần của SDD-043. hành trình hai tầng của course và unit, journey-first thay cho danh sách bài (SRC-1251).

9. Journey-first: hai tầng hành trình (SRC-1251) ​

Chỉ đạo chủ dự án 06.10.2026: dựng lại learn.nemo12.com/ai-teen theo kiến trúc Journey-first, với thanh Journey dính trên cùng như NEMO IELTS. Learner luôn biết: đang ở đâu, đã qua đâu, còn gì phía trước. Nguyên tắc chốt kèm: a Journey is not a page, a Journey is a learner state transition: chặng không có URL riêng, nó là trạng thái của learner.

TầngChặngMỗi chặng hỏi
Chương trình (/ai-teen)Goal → Where am I? → LearningEm muốn đi đâu? Em đang ở đâu? Em sẽ đi thế nào?
Unit (/ai-teen/{course}/units/{n})Goal → Learn → Practice → Assess → ReflectUnit muốn em làm được gì? Cần hiểu gì? Làm được chưa? Làm được tới đâu? Đã đổi gì?

Progress không phải một chặng (chủ dự án chọn phương án ba chặng): nó là một lớp hiện xuyên suốt, thanh skill Now → Destination ở bước Destination, Your gap và trong Learning. Level hiện tại của một skill = max(level tự đánh giá ở Where am I?, level mà task của các unit ĐÃ XONG yêu cầu).

9.1 Chặng cấp chương trình ​

  • Goal, 5 bước: Discover (AI làm được gì, 8 khả năng) → Imagine (6 tình huống đời sống) → Choose (1-3 hướng: Create better things · Learn with AI · Build with AI, chọn sẵn theo bước trước) → Define (một đích quan sát được) → Destination. ✍️ Danh sách đích lấy thẳng từ 18 Performance Task ("Em có thể tự dùng AI làm ra X"): mỗi task đã là một mục tiêu quan sát được, soạn tay lần hai là mở đường cho hai bên lệch nhau.
  • Where am I?, 5 bước: Try (một việc thật: nhờ AI viết đoạn giới thiệu về em, không phải 50 câu trắc nghiệm) → Show (prompt, câu trả lời, bản sửa) → Understand (6 mục tự soi, chia "đã làm tốt" và "đang thiếu") → Your level (gợi ý từ số mục đạt, learner chỉnh lại theo thang SKILL_LEVELS) → Your gap (tối đa 3 skill thiếu nhiều nhất, và unit đầu tiên = unit sớm nhất có đo một skill còn thiếu). Nút "Vào unit đầu tiên" ghi danh course trước nếu chưa.
  • Learning: lộ trình ba course, mỗi unit hiện Compelling Question và trạng thái (Xong / Đang ở chặng nào / Đích), nút "Tiếp tục" tới unit kế. Trang course (Skills) và trang Sản phẩm nằm trong chặng này, cùng thanh Journey.

9.2 Chặng cấp unit ​

  • Goal: Compelling Question làm tiêu đề, "Em có thể tự dùng AI làm ra X", Em sẽ làm ra (task), Em sẽ học (key concept), Em sẽ chứng minh (bảng skill/level). Chưa ghi danh thì nút Ghi danh thay cho "Bắt đầu học". Từ SRC-1278 màn này mở đầu bằng khối skill / level / chỉ báo của §13.
  • Learn: danh sách lesson; trang lesson chỉ còn phần đọc và trắc nghiệm. Lesson qua khi mọi trắc nghiệm đúng; qua hết thì mở Practice.
  • Practice: mọi bài thực hành của các lesson gom về một chỗ, xếp theo hỗ trợ giảm dần (bài có gợi ý trước, bài chỉ còn checklist sau). ✍️ Dùng bài đã có, không soạn thêm (chủ dự án chọn): vì vậy chưa đủ 5 bậc "có mẫu → transfer"; nhãn bậc là sự thật của bài (có hint hay không), không gán nhãn "transfer" cho bài không soạn như vậy. Tick hết checklist mọi bài thì mở Assess.
  • Assess: Performance Task. Learner nộp sản phẩm (đường dẫn hoặc mô tả) và tự chấm từng tiêu chí rubric (task không có rubric thì tự chấm theo skill, thang 1-4). ✍️ Tự đánh giá trước (chủ dự án chọn); AI nhận xét để đợt sau.
  • Reflect: 4 câu hỏi, trả lời ít nhất 3 thì "Hoàn tất unit"; xong thì hiện "Sẵn sàng cho Unit kế". Bằng chứng vào Evidence Registry và Learner Model: §11.

9.3 Luật thanh Journey ​

Từ SRC-1263 thanh này là NavBar của JourneyShell (§9.6); JourneyBar.tsx đã gỡ. Các luật dưới vẫn đứng.

  • Dính top-0, thay chỗ thanh menu chung (mang theo logo và menu tài khoản ở cấp chương trình); hai thanh sticky top-0 thì thanh này đè lên thanh kia. Trong unit là chế độ tập trung: chỉ còn nút Thoát.
  • Chặng đã xong và chặng hiện tại bấm được; chặng phía trước khoá thật (disabled, kèm biểu tượng khoá), để cả bàn phím và máy đọc màn hình cũng không vào được.
  • Nhãn chặng là từ khoá tiếng Anh chủ dự án chốt (Goal, Where am I?, Learning, Learn, Practice, Assess, Reflect); dòng phụ và mọi chữ khác tiếng Việt.

9.4 Lưu trạng thái ​

  • Bảng ai_teen_journeys (migration 0333): một dòng mỗi (learner, scope), scope = 'program' hoặc '<course>:u<n>', nội dung JSON. Server kiểm hình dạng theo scope bằng zod (workers/api/src/modules/aiTeen/journey.ts) trước khi ghi.
  • API: GET /v1/ai-teen/journeys?learner_id=... (không truyền scope thì trả mọi hành trình một lượt, trang chương trình cần cả 1 + 18 trạng thái) và PUT /v1/ai-teen/journeys. Kiểm canAccessLearner (QG-008); hành trình unit chỉ ghi được khi đã ghi danh course (409).
  • Tiến độ Learn/Practice không lưu riêng: suy ra từ ai_teen_exercise_results (§8), một sự thật chỉ một chỗ ghi.
  • Client lưu có độ trễ 600 ms theo từng scope (gõ reflection là mỗi phím một lần đổi).
  • Test: journey.test.ts (API) và apps/learn/e2e/aiTeen.spec.ts (cả hai tầng; từ SRC-1259 có thêm các ca nhiều journey, xem §9.5).

9.5 Trang unit và trang lesson: nhiều journey, chọn bằng menu (SRC-1259) ​

Chủ dự án 06.10.2026: trong một unit có 2-3 journey, mỗi journey vài bước; bước của journey đang chọn hiện bằng thanh chấm cam dính dưới thanh trên cùng, learner chọn journey bằng menu. Trang lesson cùng cấu trúc. Mẫu UI chung: Guided Journey §8.

Không thêm loại nội dung, không đổi chỗ lưu. Năm chặng của §9.2, khoá của §9.3 và trạng thái D1 của §9.4 giữ nguyên; apps/learn/src/aiTeen/journeys.ts chỉ gom lại:

TrangJourney (slug)Bước (slug)Xong khi
UnitKhởi động (start)Goal (goal) → Where am I? (where, danh sách lesson)đã bấm Bắt đầu học, và đã chạm một lesson hoặc đã qua Learn
UnitHọc (learn)Lesson 1 (lesson-1) → ... → Lesson ntừng lesson qua trắc nghiệm (hoặc unit đã qua Learn)
UnitChứng minh (prove)Practice → Assess → Reflectđúng chặng D1 như §9.2
LessonBắt đầu (understand)Câu hỏi (question) → Bài học (read)learner đã trả lời ít nhất một câu của lesson
LessonTìm hiểu (check)Câu 1 (q1) → ... → Câu k, mỗi câu trắc nghiệm một bướccâu ấy đúng
LessonĐúc kết (review)Mang theo (output, trường Lesson.output) → Tiếp theo (next)mọi câu đúng
  • ✍️ Bài học giữ nguyên thứ tự khối (đọc, ví dụ, prompt, lời dặn xen nhau) trong MỘT bước, không tách "đọc" khỏi "ví dụ mẫu" như gợi ý ban đầu: các khối text tham chiếu prompt ngay trước nó, tách ra là gãy mạch bài.
  • ✍️ Bài thực hành KHÔNG vào trang lesson: luật "LEARN tách khỏi PRACTICE" của §9.2 vẫn đứng, nên journey Tìm hiểu của lesson chỉ có trắc nghiệm; checklist nằm ở bước Practice của unit.
  • Bước sau khoá đúng như chặng: chưa Bắt đầu học thì chỉ Goal mở, journey Học và Chứng minh khoá (tab disabled). Trang lesson không khoá bước nào, như trước.
  • Mặc định là journey chưa xong đầu tiên, ở bước chưa xong đầu tiên; xong hết thì bước cuối (unit xong mở Reflect như cũ).
  • URL: /ai-teen/{course}/units/{n}/{journey}/{step} và /ai-teen/{course}/units/{n}/lessons/{m}/{journey}/{step}. Link cũ không có hai đoạn cuối vẫn chạy và về mặc định; slug lạ hoặc bước còn khoá cũng về mặc định. Bấm tab là GHIM bước vào URL, để bước không tự nhảy khi learner vừa trả lời đúng một câu.
  • Component: JourneyMenu.tsx (từ SRC-1263 dựng trên JourneyShell, §9.6; trước đó là shadcn Tabs) và apps/learn/src/components/JourneyProgressBar.tsx (bản learn của thanh chấm cam www, node là <button>, có trạng thái khoá).
  • Test: apps/learn/e2e/aiTeen.spec.ts thêm ba ca (menu đổi journey kể cả phím mũi tên, mặc định journey chưa xong, thanh dính khi cuộn) và ba URL điện thoại (Học, Tìm hiểu, Đúc kết).

9.6 Bố cục JourneyShell: NavBar, thanh bước có tên, Status Card (SRC-1263) ​

Luật bố cục chủ dự án 06.10.2026 cho mọi trang AI Teen (mẫu chung: Guided Journey §9). Không đổi chỗ lưu, không đổi khoá: chỉ đổi cách hiện.

TrangMenu của NavBarThanh bước (luôn có tên)Status Card
Chương trình /ai-teenGoal · Where am I? · Learning (khoá như §9.3)Goal: Discover → Imagine → Choose → Define → Destination; Where am I?: Try → Show → Understand → Your level → Your gap; Learning: AI Teen 1 → 2 → 3Đích (course · unit), Level (thấp nhất hiện tại → cao nhất đích, "Chưa đo" khi chưa có), Đã xong (n/18 unit · %), Bước tiếp
Course, Sản phẩmnhư trên, Learning sángba course, course đang xem là bước hiện tạinhư trên
UnitKhởi động · Học · Chứng minhbước của journey đang xem (§9.5)Course, Unit n/N · sản phẩm, Lesson đã qua, Chặng, Bước tiếp
LessonBắt đầu · Tìm hiểu · Đúc kếtbước của journey đang xem (§9.5)Unit, Lesson n/N, Câu đúng k/K, Lesson này (đã qua / chưa), Bước tiếp
  • Bộ đếm "1/3", dải "5/5 · Destination" dưới các vạch không tên, và dòng tiêu đề "Unit 1 · ..." trên thanh đã bỏ: menu nói journey, thanh nói bước, Status Card nói unit và tiến độ.
  • Cột Status Card của bảng trên. Bỏ ở SRC-1272 (chủ dự án 07.10.2026): không trang AI Teen nào còn Status Card; breadcrumb hàng 1 (§9.7) nói unit/lesson, menu nói journey, thanh nói bước, và nội dung dùng hết bề ngang (JourneyShell không nhận status thì không dựng cột phải). Các hàm programStatusRows, unitStatusRows, lessonStatusRows và test của chúng đã xoá cùng.
  • Cấp chương trình: bấm một bước đã qua của Goal/Where am I? là quay lại bước ấy (như nút "Quay lại"); bước phía trước khoá. Bấm Goal hay Where am I? trên menu của trang course mở đúng journey ấy.
  • Unit và lesson vẫn là chế độ tập trung: NavBar chỉ có Thoát (và "Về unit" ở lesson), không logo, không menu tài khoản. Thay bởi §9.7 (SRC-1264): mọi cấp có logo, breadcrumb cấp và tài khoản.
  • Luật thuần, có test: programSteps, programMark (và tới SRC-1272 programStatusRows, unitStatusRows, lessonStatusRows) trong apps/learn/src/aiTeen/journeys.ts (journeys.test.ts); e2e apps/learn/e2e/aiTeen.spec.ts thêm ca Status Card ở ba tầng và đổi các ca menu sang NavBar (aria-current="page", phím mũi tên chuyển focus, Enter mở).

9.7 Header ba hàng, Mục lục, trang course một cột (SRC-1264) ​

Chủ dự án 06.10.2026. Header của MỌI trang AI Teen thành ba hàng (mẫu chung: Guided Journey §9): cấp (breadcrumb "em đang ở đâu") · các journey của cấp ấy · các bước của journey đang xem. Status Card giữ ở góc trên phải (tới SRC-1272; nay đã bỏ, xem §9.6).

CấpHàng 1: breadcrumbHàng 2: journeyHàng 3: bước
Chương trình /ai-teenAI TeenGoal · Where am I? · Learningbước của journey đang xem (§9.6)
Course /ai-teen/{course}AI Teen · AI Teen 2Goal · Where am I? · Learning, Learning sángAI Teen 1 → 2 → 3, course đang xem là bước hiện tại
Sản phẩm /ai-teen/showcaseAI Teen · Sản phẩmnhư coursenhư course
UnitAI Teen 2 · Unit 1Khởi động · Học · Chứng minhbước của journey đang xem (§9.5)
LessonAI Teen 2 · Unit 1 · Lesson 1Bắt đầu · Tìm hiểu · Đúc kếtbước của journey đang xem (§9.5)
  • ✍️ Cấp course không có journey riêng: course nằm TRONG chặng Learning của chương trình (§9.6), nên hàng 2 là ba journey chương trình với Learning sáng, hàng 3 là ba course. Dựng thêm journey cấp course là tạo loại nội dung mới chỉ để lấp hàng.
  • Mỗi đoạn trước của breadcrumb là một nút đổi cấp: "AI Teen" về chương trình, "AI Teen 2" về trang course, "Unit 1" (từ lesson) về unit ở bước Học · Lesson m của chính lesson vừa rời. Đoạn cuối là cấp hiện tại (aria-current="page"), không bấm. Logo bên trái về /ai-teen, tài khoản bên phải.
  • Bỏ "← Thoát" và "Về unit" (trang unit/lesson) cùng "← Lộ trình của em" (trang course): breadcrumb thay chúng. Unit và lesson thôi là chế độ tập trung không logo của §9.6.
  • Mục lục (từ SRC-1272, chủ dự án 07.10.2026). Từ cấp course trở xuống, đoạn cuối của breadcrumb (cấp hiện tại) mang một chevron và mở một CÂY các unit và lesson của riêng course đang xem: AI Teen 2 · Unit 1 · Lesson 1 v. Unit ở gốc, lesson bên dưới; unit hiện tại mở sẵn, mục hiện tại sáng, được chọn và nhận focus; ở cấp course không mục nào là hiện tại. Không còn nút "Mục lục" riêng ở cuối hàng 1, không còn cây ba course, không còn mục "Tổng quan" trong cây. ✍️ Tương tác chốt: đoạn ĐỔI CẤP vẫn chỉ đổi cấp ("AI Teen" về chương trình, "AI Teen 2" về trang course, "Unit 1" từ lesson về unit ở bước Học · Lesson m); chỉ đoạn cuối, trước đây là chữ trơn không làm gì, nay mở cây. Vì thế không cú bấm đổi cấp nào bật ra khung bất ngờ (lý do của luật "hai việc, hai nút" ở SRC-1264 vẫn giữ), và cây đứng ngay cạnh logo, đúng chỗ learner đọc "em đang ở đâu". Cấp chương trình và trang Sản phẩm không có cây (đoạn cuối là chữ trơn). Từ sm cây nằm trong shadcn Popover neo vào đoạn cuối; dưới sm là Sheet dưới đáy (components/ui/sheet.tsx, Radix Dialog). Cây theo WAI-ARIA role="tree": lên/xuống đi giữa các mục, phải mở hoặc vào mục con, trái đóng hoặc về mục cha, Home/End, Enter hoặc Space đi tới; chọn một mục là đi và đóng cây, Escape đóng mà không đi. Bản SRC-1264: nút Mục lục riêng, cây cả ba course.
  • Trạng thái trong cây chỉ từ dữ liệu đã có: unit xong khi chặng D1 là done; lesson khoá cho tới khi unit được bắt đầu (khoá §9.3), vẫn hiện, nói lý do ("Ghi danh AI Teen 2 để mở lesson" hoặc "Bấm Bắt đầu học ở Unit n"), aria-disabled, không đi được; lesson xong khi qua trắc nghiệm.
  • 375px. Có từ ba đoạn trở lên thì mọi đoạn trừ đoạn cuối gấp thành một nút biểu tượng ... đi lên một cấp ("Về Unit 1"); [...] · Lesson 1 v, cấp hiện tại luôn đọc trọn và vẫn là nút mở cây (gấp mạnh hơn vì hàng 1 có thêm công tắc VI/EN, §9.8); hàng 2 và 3 cuộn ngang trong chính nó và kéo mục hiện tại vào tầm nhìn.
  • Trang course: danh sách unit một cột, mỗi unit một hàng hết bề ngang; thẻ unit bỏ dòng "Performance Task: ..."; khu Skills nằm trong một thẻ thu gọn mặc định (shadcn Accordion, tức nút aria-expanded + vùng nội dung), bấm để mở.
  • Bỏ skill "API integration using AI" (genai-3.3-2) khỏi Performance Task của AI Teen 2 · Unit 2 (apps/learn/src/aiTeen/courses/aiTeen2.ts) và bản chép trên www (apps/web/src/site/aiTeenProgram.ts). Skill vẫn nằm trong danh mục CCS chép nguyên (data/ccs-skills.json, 153 skill) vì đó là khung, không phải course. Không có gì ở D1, nên không có seed. Rubric của product learning-website không đổi.
  • Hiện thực: JourneyShell nhận thêm levels (hàng 1, LevelBreadcrumb); không truyền thì header giữ hai hàng như cũ, nên /ielts chạy nguyên và có thể nhận hàng 1 sau. Cây: CourseTree.tsx (buildTree({ course, ... }), currentOf thuần, có courseTree.test.ts; nút mở là TreeCrumb, đặt vào đoạn cuối qua LevelCrumb.node). e2e apps/learn/e2e/aiTeen.spec.ts thêm sáu ca: trang course một cột + không còn Performance Task + Skills thu gọn; vắng skill đã bỏ; breadcrumb và journey từng cấp, bấm đoạn đổi cấp; Mục lục mở từ hàng 1, mục hiện tại sáng, khoá có lý do, chọn lesson khác thì đi; Mục lục bằng bàn phím; 375px gấp breadcrumb và mở sheet.

9.8 Gọn lại lần hai: thẻ Discover có hình, màn Goal của unit, công tắc VI/EN (SRC-1272) ​

Chủ dự án 07.10.2026, cùng đợt với §9.7 (Mục lục) và §12.2 (Overview gộp vào màn đầu).

GOAL · Discover (/ai-teen, "AI có thể giúp em làm được gì?"): bỏ dòng phụ dưới tiêu đề. Tám thẻ (Create, Learn, Research, Make websites, Make images, Make videos, Solve problems, Build projects) mỗi thẻ mở đầu bằng một hình minh hoạ sinh bằng thuật toán của máy vẽ bìa sách (@nemo12/books, khung tile, chuẩn bìa của books.md): hình chính có nghĩa, bảng màu có tên chọn theo nghĩa, hạt giống cố định, aria-hidden. Bốn hình chính mới: browser, easel, clapper, gamepad. Mỗi thẻ giữ lời tả ảnh chi tiết prompt ngay cạnh dữ liệu (CAPABILITIES trong journey.ts, sinh bằng composeTilePrompt). Bố cục: ảnh trên, chữ dưới, hai cột cả ở 375px, bốn cột từ lg.

Màn Khởi động · Goal của unit (§9.5), theo thứ tự từ trên xuống, mỗi khối một hàng hết bề ngang:

  1. Compelling Question (tiêu đề).
  2. "Em sẽ học được gì?" (các key concept).
  3. "Em sẽ làm ra sản phẩm nào?" (Performance Task và mô tả sản phẩm).
  4. Unit Outcomes (Unit Overview cũ, §12.2).
  5. Nút Bắt đầu học / Tiếp tục học, hoặc Ghi danh.
  • Bỏ dòng "Em có thể tự dùng AI làm ra ..., đúng với điều em muốn." và hai khối thu gọn "Em sẽ chứng minh điều gì?" (bảng skill) và "Câu hỏi lớn của unit này là gì?" (Essential Question, Big Idea). Dữ liệu của chúng vẫn nằm trong file course.
  • ✍️ Unit Outcomes đứng SAU hai hàng, không gộp vào "Em sẽ học được gì?": outcome là câu "Tôi có thể..." nối cả concept lẫn sản phẩm (ví dụ "Tôi có thể dựng Smart Study Hub..."), nên nó là câu tóm hai hàng trên; nhét vào khối concept thì trộn câu với chip từ vựng và câu nhắc sản phẩm đứng trước khi learner biết sản phẩm là gì.

Công tắc VI/EN ở hàng 1, ngay trước tài khoản (công tắc LangSwitch của Design System, radiogroup, phím mũi tên). Nó đổi chữ của ba hàng header: đoạn breadcrumb (Sản phẩm/Showcase, Tổng quan/Overview), tên journey ở hàng 2 và tên bước ở hàng 3 (cả aria-label của từng bước, và chữ trạng thái cho máy đọc màn hình). Nội dung trang giữ nguyên. Mặc định tiếng Việt; lựa chọn lưu trong localStorage (nemo12.aiTeen.navLang, mọi lần đọc ghi bọc try/catch: kho bị chặn thì vẫn chạy, bằng tiếng Việt).

Nhãn trong dữ liệuVIEN
Goal · Where am I? · Learning (chương trình)Mục tiêu · Mình đang ở đâu? · HọcGoal · Where am I? · Learning
Discover · Imagine · Choose · Define · DestinationKhám phá · Hình dung · Chọn · Xác định · Đíchnhư cột trái
Try · Show · Understand · Your level · Your gapThử · Cho xem · Hiểu ra · Level của em · Khoảng cáchnhư cột trái
Khởi động · Học · Chứng minh (unit)như cột tráiGet started · Learn · Prove
Goal · Where am I? · Practice · Assess · Reflect (bước unit)Mục tiêu · Mình đang ở đâu? · Thực hành · Đánh giá · Suy ngẫmnhư cột trái
Bắt đầu · Tìm hiểu · Đúc kết (lesson, SRC-1279; trước đó Hiểu · Luyện · Nhìn lại)như cột tráiMake Meaning · Investigate · Synthesize and Reflect
Câu hỏi · Bài học · Phần k · Câu k · Kiểm tra · Mang theo · Tiếp theonhư cột tráiQuestion · Lesson · Part k · Check k · Check · Take away · Next
  • ✍️ Một bảng nhãn duy nhất (NAV_LABELS trong apps/learn/src/aiTeen/navLang.ts), khoá bằng đúng chuỗi dữ liệu đang dùng, nên dữ liệu journey không phải mang hai trường; nhãn không có trong bảng (tên course, "Lesson 2", tên phần đọc) giữ nguyên ở cả hai thứ tiếng. Tag Learning Cycle giữ tiếng Anh ở cả hai.
  • ✍️ Hệ quả của mặc định tiếng Việt: các từ khoá tiếng Anh trước đây hiện thẳng trên thanh (Goal, Discover...) nay mặc định hiện bản tiếng Việt; tiêu đề nhỏ trong nội dung ("Goal · Discover") là nội dung nên không đổi.
  • 375px: hàng 1 thêm công tắc (hai ô 40px), nên dưới sm breadcrumb gấp MỌI đoạn trừ đoạn cuối thành ... (đi lên một cấp: từ lesson về unit); đoạn cuối "Lesson n v" luôn đọc trọn.
  • Test: navLang.test.ts (đủ nhãn hai thứ tiếng cho mọi journey và bước, đúng chữ chủ dự án, nhãn đánh số và tiền tố aria, đọc lựa chọn khi kho bị chặn), capabilities.test.ts; e2e aiTeen.spec.ts: Discover có tám hình trang trí ổn định và hai cột ở 375px, thứ tự màn Goal của unit, công tắc đổi nhãn ba hàng và giữ lựa chọn qua tải lại, về tiếng Việt bằng bàn phím.