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 domainlearn.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/grammarvà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_sessiongắnDomain=.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-learnphục vụ như cũ.
Các bước
| Bước | Việc | Trạng thái |
|---|---|---|
| 0 | Gom shell dùng chung vào packages/learn-shell: đăng nhập, HTTP, NavBar, menu, avatar, Motion, ngôn ngữ, màn Join, NemoLogo, PayCard | Xong 18.10.2026 |
| 1 | Pilot GRAMMAR: apps/grammar, Worker nemo12-grammar, docs cạnh code, khu design/ | Xong 18.10.2026, đang chạy trên production |
| 2a | SAT: apps/sat, Worker nemo12-sat | Xong 18.10.2026 |
| 2b | ESSAY: apps/essay, Worker nemo12-essay | Xong 18.10.2026 |
| 2c | STYLE (/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ì |
| 2d | WALK | Chưa: một phiên khác đang sửa các trang walk; tách khi khu ấy rảnh |
| 3 | IELTS 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
- Hai bộ đăng nhập và hai API client song song trong learn (bộ A:
metrongApp.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. - Import chéo giữa program (grammar và walk dùng
ielts/, speak dùngwalk/, essay dùngielts/PayCard) phải hết trước khi tách program ấy. - 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.