What's New: mỗi tuần có gì mới
Hai repo (16.10.2026)
Từ 16.10.2026 trang /whats-new của www nằm ở repo conan-web. scripts/whats-new-draft.mjs đọc thêm lịch sử repo đó (CONAN_WEB_DIR hoặc thư mục anh em), khoá mục web-pr-<số>. Xem Hai repo.
Trang theo domain (PRD, SDD, tests): Communications / What's New. Trang này giữ nguyên làm hướng dẫn soạn.
Trang ghi lại những thay đổi người học thấy được, gom theo tuần ISO. Một nguồn, hai nơi hiện:
| Nơi | Đường | Ai thấy gì |
|---|---|---|
| www.conan.school | /whats-new, /en/whats-new | Khách: chỉ mục audience: public |
| tokyo.conan.school | /whats-new + chấm "chưa đọc" ở chân thanh bên | Thành viên đăng nhập: public + member |
Đường đi của một mục
content/whats-new/2026-W41.json (nguồn sự thật, viết tay, song ngữ)
-> scripts/whats_new_to_sql.py (biên dịch, --check dùng chung luật với gác)
-> migrations/2026xxxx_whats_new_*.sql (CI áp, TRƯỚC deploy)
-> D1 whats_new_entries
-> GET /api/public/whats-new (gom theo tuần, lọc audience Ở ĐÂY)
-> www (SSR, không cookie) | tokyo (cùng origin, có cookie)Repo trước, D1 sau (CLAUDE.md): không chèn mục bằng wrangler d1 execute.
Soạn một tuần
# 1. Nháp từ các PR đã merge vào main trong tuần (mặc định: tuần hiện tại)
node scripts/whats-new-draft.mjs 2026-W42 > api.conan.school/content/whats-new/2026-W42.json
# 2. Xoá mục không đổi gì người dùng thấy (dọn mã, CI, tài liệu nội bộ), gộp các PR cùng một
# thay đổi thành một mục, viết lại tiêu đề + mô tả bằng giọng người học, cả vi lẫn en.
# 3. Kiểm rồi biên dịch ra một migration MỚI
python3 api.conan.school/scripts/whats_new_to_sql.py --check api.conan.school/content/whats-new/2026-W42.json
python3 api.conan.school/scripts/whats_new_to_sql.py api.conan.school/content/whats-new/2026-W42.json \
> api.conan.school/migrations/20261113a_whats_new_2026_w42.sql
npm run check:whats-newBản nháp mang chữ TODO ở mọi chỗ cần người viết, và --check từ chối file còn TODO. Không có đường nào để tiêu đề PR ("Dọn 74 file chết") lọt thẳng lên trang.
Định dạng
{
"week": "2026-W41",
"entries": [
{
"key": "book-interactive-blocks",
"audience": "member",
"category": "feature",
"published_at": "2026-10-09",
"url": "https://tokyo.conan.school/books",
"title": { "vi": "...", "en": "..." },
"description": { "vi": "...", "en": "..." }
}
],
"retired": []
}audiencebắt buộc, không có mặc định. Mặc địnhpubliclà lộ tin nội bộ cho khách; mặc địnhmemberlà giấu tin công khai khỏi www. Cả hai đều im lặng.category:feature,improvement,content,fix.urllà https tuyệt đối: một mụcpublichiện ở cả www lẫn tokyo, đường tương đối trỏ sai site ở một trong hai. Tokyo tự đổi URLhttps://tokyo.conan.school/...thành<Link>nội bộ.keytạoid(uuid5 củaweek/key). Đổikeylà tạo mục mới, mục cũ nằm lại D1; muốn bỏ một mục thì khaikeyvàoretired, bộ biên dịch sinhDELETE.- Không dấu gạch dài (LUẬT CHỮ),
--checkbắt.
Vì sao một bảng mới, không phải changelogs
Trước tính năng này có hai bảng changelog, cả hai đều rỗng: GET /api/public/changelogs đọc legacy_changelogs (qua getD1Client, tự thêm tiền tố), còn chỉ mục của Ran đọc SQL thô changelogs. Không xác minh được cả hai cùng tồn tại trên production, và một ALTER TABLE vào bảng không tồn tại làm hỏng lượt áp migration, mà migration chạy TRƯỚC deploy, nên cả api đóng băng. CREATE TABLE IF NOT EXISTS whats_new_entries không phụ thuộc trạng thái nào đoán trước. /changelogs cũ để nguyên; Ran đọc thêm whats_new_entries (kind whats_new).
Ranh giới audience nằm ở API
src/domains/public/whatsNew.ts khai optionalAuthMiddleware trên chính router của nó: router public được mount trước các app.use(optionalAuth) của src/index.ts, khai ở đó thì không bao giờ chạy tới. Phản hồi đổi theo người đọc nên thành viên nhận Cache-Control: private, no-store, khách nhận public, max-age=300 kèm Vary: Cookie. Từ 17.10.2026 header này tới được người xem: trước đó middleware chung của api ghi đè mọi phản hồi thành no-store (d1-performance).
Trang www không lọc gì: loader chạy ở máy chủ, không mang cookie, nên API chỉ trả mục public.
Chấm "chưa đọc" ở tokyo
app/lib/whats-new.ts: GET /api/public/whats-new/latest trả khoá tuần mới nhất người đọc được thấy, so với localStorage['conan.whatsNew.lastSeenWeek']. Mở trang /whats-new ghi mốc và bắn sự kiện để thanh bên tắt chấm ngay. Mốc ở localStorage là cố ý: mất nó thì chấm hiện lại một lần, không hỏng gì. Lỗi mạng thì không hiện chấm, vì báo "có tin mới" khi không biết là nói sai.
Gác
npm run check:whats-new (job guards của CI): gọi --check, rồi biên dịch lại từng tuần và đòi mọi câu sinh ra có mặt trong một migration. Sửa chữ trong JSON mà quên biên dịch lại thì PR xanh và trang vẫn hiện bản cũ; gác này bắt đúng chỗ đó. Không có tuần nào thì gác đỏ.
Skill: whats-new.