Skip to content

Tách learn thành web app riêng cho từng program ​

Chủ dự án 18.10.2026 chọn "Cách 3" trong ba phương án: mỗi program là một web app riêng, cùng repo, cùng tên miền learn.nemo12.com, chung đăng nhập, API (workers/api), D1 và các package. Mục tiêu: sửa một program chỉ test và deploy program ấy, lỗi một program không làm hỏng program khác, và mỗi phiên Claude làm trong một thư mục riêng. Cách 2 (subdomain và repo riêng) bị loại vì ba program chung tài khoản, dữ liệu và code; tách repo chỉ hợp với phần không chung dữ liệu hay đăng nhập (như nemo12-www, nemo12-videos). Tên miền giữ nguyên theo chỉ đạo 18.09.2026 (mọi URL trên learn.nemo12.com, cổng check-one-domain).

Cách định tuyến ​

Route của Cloudflare thắng custom domain trên cùng hostname, và route cụ thể hơn thắng route chung. Nên:

  • nemo12-learn (shell) giữ custom domain learn.nemo12.com: trang chủ, /programs, hồ sơ, chứng chỉ, lịch, các redirect 301 (/math, /ai-teen, /speaking/*) và bản catch-all.
  • Mỗi program một Worker với hai route, ví dụ learn.nemo12.com/grammar và learn.nemo12.com/grammar/*.
  • Mỗi app build với base: "/<program>/", nên asset của nó nằm dưới tiền tố riêng, không đụng /assets/* của app khác.
  • Cookie nemo12_session gắn Domain=.nemo12.com; Path=/, đăng nhập Google là nút trong trang (GIS), không có redirect URI: tách theo path giữ nguyên phiên đăng nhập và localStorage.
  • Link sang app khác phải là tải lại trang (full page load), không pushState: router của app này không biết trang của app kia.
  • Rollback: gỡ route của program là trang lại do nemo12-learn phục vụ như cũ.

Các bước ​

BướcViệcTrạng thái
0Gom shell dùng chung vào packages/learn-shell: đăng nhập, HTTP, NavBar, menu, avatar, Motion, ngôn ngữ, màn Join, NemoLogo, PayCardXong 18.10.2026
1Pilot GRAMMAR: apps/grammar, Worker nemo12-grammar, docs cạnh code, khu design/Xong 18.10.2026, đang chạy trên production
2aSAT: apps/sat, Worker nemo12-satXong 18.10.2026
2bESSAY: apps/essay, Worker nemo12-essayXong 18.10.2026
2cSTYLE (/style)Giữ trong learn: đó là bảng mẫu giao diện của chính learn (27 component), không phải chương trình học; tách ra phải chép cả bộ component mà không được gì
2dWALKChưa: một phiên khác đang sửa các trang walk; tách khi khu ấy rảnh
3IELTS cùng SPEAK (chung router, shell và auth nên tách cùng nhau, làm sau cùng)Chưa

Bước 0 không đổi hành vi: file chuyển nguyên văn, import trong apps/learn trỏ sang package. Primitive shadcn không chép vào package: component trong shell import @/components/ui/..., phân giải về bản sao của chính app dùng nó (cổng check-ds-copies giữ các bản sao giống hệt nhau); app nào dùng shell phải có các primitive ấy và khai @source của package trong CSS để Tailwind quét class.

Docs và artifact cạnh code ​

Chủ dự án 18.10.2026 chọn đặt tài liệu của mỗi web app cạnh code. Mỗi app có:

  • apps/<app>/docs/: PRD, SDD, runbook, quyết định UI, ảnh trước/sau. Docs dùng chung (platform, API, D1) ở docs/.
  • apps/<app>/design/prototypes/: prototype UI (nháp HTML/React, link Artifact, link Figma), không vào build.
  • apps/<app>/design/assets.json: danh mục file nguồn và media trên R2 (khoá, nguồn, giấy phép, ghi công).
  • apps/<app>/CLAUDE.md: luật riêng của app, Claude Code nạp khi làm trong thư mục ấy.

Nơi lưu từng dạng file: quy tắc lưu trữ.

Phiên Claude theo làn ​

Mỗi web app một làn: phiên của làn ấy chỉ sửa apps/<app>/. Làn platform (một phiên một lúc) sửa workers/api, migrations/, packages/* và shell. Việc cần cả API lẫn giao diện: làn platform làm API và migration (expand/contract) trước, push lên main, rồi làn app làm giao diện. Làm thẳng trên main (hệ thống test và QC).

Rủi ro đã biết ​

  1. Hai bộ đăng nhập và hai API client song song trong learn (bộ A: me trong App.tsx, LaunchNotice, Join; bộ B: ielts/auth). App tách ra phải tái tạo đúng cổng đăng nhập; E2E của program là cổng cho việc này.
  2. Import chéo giữa program (grammar và walk dùng ielts/, speak dùng walk/, essay dùng ielts/PayCard) phải hết trước khi tách program ấy.
  3. Một bộ mock E2E chung (e2e/fixtures.ts) cho 53 spec; mỗi app tách ra cần phần mock của mình.