Skip to content

SDD-029 · Oceanic, vòng sửa SRC-776 tới SRC-817 ​

Một phần của SDD-029. Logo, trang đội ngũ, popover, vệt đường dẫn, năm bước, band theo CEFR, trang học phí, giá theo band, trang sự kiện IELTS.

Lịch sử quyết định ​

11.13 Logo về gốc, kỳ thi lên trên, và bớt chữ (SRC-776, SRC-777) ​

Logo luôn về /. Trước đó nó về trang gốc của khu đang đứng, với lý lẽ "người đang đọc IELTS bấm logo là muốn về đầu phần IELTS". Lý lẽ ấy sai ở một điểm không sửa được: logo là quy ước chung của cả web, và quy ước đó nói "về trang chủ". Một logo đưa người ta tới chỗ khác thì người dùng không học được nó, họ chỉ thấy cú bấm không làm điều mình tưởng. Lối về khu vẫn còn và còn rõ hơn logo: nhãn khu ngay cạnh logo là menu đổi khu.

Cụm kỳ thi lên ngay sau đầu trang của trang mục tiêu. Đó là việc người đọc tới trang này để làm, nên nó không được nằm dưới một khối "sắp mở". Khối đặt trên dải tối, và môn đã dựng đầu tiên được phóng to thành lối vào chính (ở mục tiêu đại học là IELTS).

Chọn thẻ nổi bật bằng "môn đã dựng đầu tiên" chứ không bằng một cờ trong dữ liệu: thứ tự trong goals.ts vốn đã là thứ tự ưu tiên, nên thêm một cờ nữa là thêm một chỗ để hai nguồn nói khác nhau.

Thẻ môn phải trông ra thứ bấm được. Bản trước chỉ có tiêu đề và một dòng chữ; cả thẻ là link nhưng không có dấu hiệu nào nói điều đó. Nay mỗi thẻ đã dựng mang một dòng mời bấm có mũi tên, và mũi tên nhích sang phải khi rê chuột.

Lỗi mép hai dải. Khối CTA "Xem chi tiết năm bước" trước đó không màu và kéo lên bằng -mt-4, nên nó vắt ngang đúng ranh giới: câu chữ rơi vào nền tối còn cái nút rơi xuống nền sáng. Kéo âm qua ranh giới hai dải là một cách hỏng im lặng - nó chỉ lộ ra đúng ở mép. Nay khối nằm hẳn trong dải tối.

Khối "Bạn nhận được gì" còn hai dòng mỗi thẻ: đang mắc gì, và thứ thay vào chỗ nó. Vế proof bị bỏ, và cái kỷ luật nó từng giữ chuyển vào chính get: get phải gọi tên một thứ cầm được, không được là một tính từ. "Chẩn đoán theo từng tiêu chí chấm" thì được; "lộ trình tối ưu cho bạn" thì không.

11.14 Trang đội ngũ IELTS theo vai (SRC-778) ​

/{vai}/university/ielts/mentors. Nút "Xem đầy đủ đội ngũ IELTS" ở khối Mentors trên trang môn nay dẫn tới đây thay vì /ielts/team.

Tách vai với /ielts/team: bên kia là trang đội ngũ của khu nội dung, không phân vai, sống trong họ trang SEO cũ. Trang này nằm trong mạch Ai -> Mục tiêu -> Môn nên nó nói theo giọng của vai và đứng cùng họ với lộ trình, lịch học, dạng bài, học phí. Hai trang dùng chung một nguồn (mentor_profiles qua /v1/public/mentors) và chỉ khác cách bày.

Dấu xác minh có mặt ở đây, khác với khối teaser trên trang môn (§11.12): trang này tồn tại để người đọc kiểm chứng từng hồ sơ, nên im lặng sẽ bị đọc thành "tất cả đều đã xác minh".

Website cá nhân đọc từ links.website (cột mentor_profiles.website_url), hiện dưới dạng tên miền và mở ở tab riêng kèm rel="noreferrer noopener" - đó là trang của người khác, không phải trang của Nemo12. Không chép URL vào mã: một địa chỉ cá nhân chép tay là một lời khẳng định về người thật, và ngày nó sai thì trang công khai đang gửi người đọc tới một chỗ không phải của họ. Hồ sơ chưa khai website thì thẻ không có link, không có chỗ trống nào lộ ra.

Bản prerender không liệt kê tên người: danh sách tới từ API lúc chạy, chép tên vào bản tĩnh là dựng một danh sách thứ hai sẽ cũ đi ngay lần đổi người đầu tiên.

11.15 Popover bị cắt, hover dòng, và vệt đường dẫn (SRC-779) ​

Popover bị cắt mất nửa dưới. Nguyên nhân không nằm ở z-index: bảng tiêu chí nằm trong một thẻ có overflow-x-auto để cuộn ngang trên màn hẹp, và overflow khác visible tạo ra một vùng cắt - mọi thứ con tràn ra ngoài đều bị xén, z-index cao tới đâu cũng vậy.

Cách chữa: popover đổi sang position: fixed với toạ độ đo từ nút bằng getBoundingClientRect(). fixed đứng ngoài mọi vùng cắt. Cái giá là nó không trôi theo khi trang cuộn, nên popover tự đóng khi người dùng cuộn hoặc đổi cỡ cửa sổ - thà đóng hẳn còn hơn nằm lệch khỏi cái nút vừa mở ra nó.

Phép kẹp toạ độ có một cái bẫy đã đo được: window.innerWidth có thể bằng 0 khi khung chưa vẽ (tab ẩn, ảnh chụp tự động), và khi đó min(right + 12, innerWidth - w - 12) cho ra số âm, popover chạy ra ngoài màn. Nên có giá trị đỡ (|| 360), popover co lại theo bề rộng màn, và max(12, ...) đứng NGOÀI cùng để lề 12px luôn được bảo đảm.

Hover từng dòng đổi nền rất nhẹ (hover:bg-muted/50 kèm transition-colors): bảng mười sáu dòng thì mắt dễ trượt hàng, và một vệt nền mờ là cách rẻ nhất để giữ hàng.

Vệt đường dẫn thêm cho cả bảy trang con của khu IELTS, không riêng trang chẩn đoán: chúng đều thiếu, nên người vào thẳng từ Google không biết mình đứng ở đâu và không có lối lùi nào ngoài nút back. Gom thành SubjectCrumbs trong Kit.tsx vì bảy trang phải nói cùng một chuỗi - gõ tay bảy lần là bảy chỗ để lệch.

11.16 Vệt đường dẫn chỉ để định vị, và ba chỗ gọn lại (SRC-780) ​

Vệt đường dẫn: luôn mở đầu bằng Home trỏ về /, và chỉ Home bấm được. Các cấp giữa là chữ thường.

Đó là một quyết định đi ngược quy ước quen thuộc nên ghi rõ lý do: breadcrumb ở đây làm đúng một việc - nói người đọc đang đứng ở đâu. Việc đi lại đã có thanh điều hướng, chân trang theo khu và các nút trong thân trang lo; ba chỗ ấy nói rõ mình dẫn đi đâu, còn một cấp breadcrumb thì không.

Hệ quả kỹ thuật: Crumbs bỏ hẳn href khỏi kiểu, không giữ lại rồi lờ đi - một tham số được nhận mà không dùng là một cái bẫy cho người sửa sau. 28 chỗ gọi trong bảy file đã bỏ theo, và hai tham số đường dẫn của Trail (khu SAT, khu AP) cũng biến mất cùng lý do.

Cấp mục tiêu dùng goal.short ("Vào đại học") chứ không dùng title ("Vào được trường đại học mình muốn"): một câu nằm trong vệt đường dẫn thì vệt ấy dài hơn cả tiêu đề trang.

Menu đổi khu cạnh logo đã gỡ. Nó là thứ thứ ba tranh chỗ trong khối thương hiệu, sau logo và tên trang đang xem, mà đổi khu là việc hiếm - người ta chọn khu một lần rồi ở lại trong đó. Lối đổi khu vẫn còn ở hai chỗ dùng nhiều hơn: chân trang và menu trên khổ điện thoại.

Sáu ô CEFR cao bằng nhau. Bản trước cho mỗi bậc chiếm số hàng đúng bằng số band nằm trong nó, nên C2 (2 band) lùn hơn C1 (3 band) còn A1 và A2 lại cao bằng ba band dù không có band nào. Ý định là "chiều cao nói độ rộng của bậc", nhưng thứ người đọc thấy chỉ là sáu ô so le. Cột band bên phải vẫn giữ đúng tỉ lệ thật, và đó mới là chỗ tỉ lệ có nghĩa.

Hai đoạn giải thích quy đổi CEFR bị bỏ theo chỉ đạo.

11.17 Năm bước trên trang môn: năm cái tên, hết (SRC-781) ​

Khối "Năm bước, theo đúng thứ tự" trên trang môn rút về năm ô, mỗi ô một số và hai cái tên, cộng một nút sang trang chi tiết. Không thân bài, không tab, không nút qua lại.

Bản trước bày một thẻ lớn kèm bộ điều khiển: người đọc phải bấm năm lần mới thấy hết, mà ở chỗ này họ chưa cần biết chi tiết - họ cần biết có năm bước và chúng theo thứ tự. Chi tiết nằm trọn ở /{vai}/university/ielts/journey, cách đúng một cú bấm, và ở đó mỗi bước còn nói thêm ai làm gì, mất bao lâu, xong thì cầm cái gì.

Khối đi từ khoảng 700 ký tự xuống 198.

Tên tiếng Anh giữ nguyên: đó là tên gọi của bước trong hệ khái niệm Nemo12 (Diagnosis, Competency Gap, Learning Plan, Readiness), và người học sẽ gặp lại đúng những chữ ấy trong app - gọi đúng tên ngay từ trang giới thiệu là dạy trước một từ vựng họ sắp cần.

Bản prerender vẫn mang chữ của cả năm bước (routes.ts), dù trang thật nay không hiển thị thân bài nào. Đó là chủ ý: phần chữ ấy là nội dung khác biệt nhất giữa hai vai, và nó vẫn đúng - chỉ là người đọc gặp nó ở trang chi tiết thay vì trang môn.

11.18 Band nằm cùng hàng với bậc CEFR (SRC-782) ​

Mỗi bậc CEFR nay chiếm một hàng, và hai tới ba band của bậc ấy nằm ngay trên cùng hàng đó:

BậcBand
C29.0 · 8.5
C18.0 · 7.5 · 7.0
B26.5 · 6.0 · 5.5
B15.0 · 4.5 · 4.0
A2, A1không có band nào

Bản trước xếp mười một band thành một dải dọc riêng bên phải. Dải ấy giữ đúng tỉ lệ (bậc rộng thì chiếm nhiều ô hơn), nhưng người đọc phải tự bắc cầu bằng mắt từ ô C1 sang đúng ba ô 8.0/7.5/7.0 nằm đâu đó bên cạnh - mà đó lại chính là việc cả khối này tồn tại để làm.

Cái mất là tỉ lệ: C2 có hai band còn C1 có ba, nhưng hai hàng vẫn cao bằng nhau. Chấp nhận, vì tỉ lệ ấy không phải thông tin người đọc tới đây để tìm.

A2 và A1 để trống, không vẽ ô giả: thang trên trang này bắt đầu từ 4.0, đúng bảng quy đổi chính thức.

Bốn ô kỹ năng trong phiếu điểm bỏ icon và căn giữa. Icon emoji ở góc phải kéo mắt ra khỏi con số, mà con số mới là thứ duy nhất trong ô đó đáng nhìn.

11.19 Trang chi tiết năm bước, bản tối giản (SRC-788) ​

/{vai}/university/ielts/journey dựng lại theo cùng phong cách với trang môn (§11.17), nhưng ở đây tối giản không có nghĩa là bớt nội dung - đây đúng là trang chi tiết, nên chi tiết phải còn nguyên. Thứ bị bỏ là phần khung:

  • Bỏ dải tab và nút qua lại. Thứ tự là nội dung của trang này, mà một dải tab bày năm bước thành năm thứ ngang hàng nhau. Năm bước nay xếp dọc, đúng thứ tự, nhìn một cái là thấy cả năm.
  • Mỗi bước mặc định chỉ hiện số, hai cái tên và mục đích một dòng. Phần nặng (bạn làm gì, Nemo12 làm gì, cầm cái gì, mất bao lâu, chỗ hay hỏng) nằm sau một cú bấm. Năm bước mở sẵn cùng lúc là một bức tường chữ, mà người đọc thường chỉ cần soi kỹ một bước.
  • Mở được nhiều bước cùng lúc - người đọc hay so bước này với bước kia. Khác với popover ở bảng tiêu chí (§11.15): cái đó che mất bảng nên chỉ cho mở một.
  • Đầu trang bỏ hai nút hành động. Trang này là chỗ ĐỌC, và hai nút ngay trên đầu là lời mời rời đi trước khi đọc. Lối đi tiếp nằm ở cuối trang, đúng lúc.

Trang đóng lại còn khoảng 1.350 ký tự; mở một bước thêm khoảng 1.000.

11.20 Trang Học phí trả lời hai câu, theo đúng thứ tự người hỏi (SRC-790) ​

Chỉ đạo chủ dự án 2026-09-17: dựng lại toàn bộ /{vai}/university/ielts/tuition quanh hai câu người học và phụ huynh thật sự hỏi - ôn bao lâu, và hết bao nhiêu tiền.

Bản trước chỉ trả lời câu thứ hai, bằng một bảng giá đọc từ /v1/public/pricing. Câu thứ nhất không có chỗ nào trên trang, mà đó mới là câu chặn quyết định: không ai chốt một khoản tiền khi chưa biết mình phải bỏ ra bao nhiêu tháng.

Thứ tự trên trang là thứ tự người hỏi. Máy tính đứng ngay sau tiêu đề, trước cả phần giải thích con số ở đâu ra. Ai muốn biết nguồn thì đọc tiếp; ai chỉ cần câu trả lời thì đã có nó rồi. Người đọc không phải để lại số điện thoại mới biết được cả hai con số.

Một nguồn cho câu "bao lâu". HOURS_PER_HALF_BAND = 100 là hằng số duy nhất; số tháng, ngày dự kiến xong và bảng đòn bẩy đều tính ra từ nó. Con số dựa trên tài liệu Cambridge "How long does it take to learn a foreign language": khoảng 200 giờ học có hướng dẫn cho một bậc CEFR, mà một bậc CEFR rộng khoảng 1.0 band IELTS. Trang dẫn thẳng link nguồn.

Hai điều buộc phải nằm ngay cạnh con số, không được đẩy xuống chân trang:

  • Đây là mức tham chiếu của một nhóm, không phải cam kết cho một người.
  • Giờ có hướng dẫn khác giờ tự học. Hiểu nhầm hai thứ này là người đọc tính sai gấp đôi.

Số tháng luôn làm tròn LÊN, và làm tròn về tháng tròn. Mười tuần đọc ra "khoảng 3 tháng" chứ không phải "2,3 tháng": người ta xếp lịch bằng tháng tròn, một số lẻ ở đây chỉ giả vờ chính xác hơn thứ mình biết, và một lời hứa về thời gian mà sai thì phải sai theo hướng Nemo12 chịu thiệt. Cách làm tròn này khớp đúng ví dụ chủ dự án đưa: 100 giờ, mỗi tuần 10 giờ, khoảng 3 tháng.

Giá nằm trong mã, và đó là ngoại lệ có ghi lý do. Luật chung là giá sống ở API (§ bảng giá). Mức giá theo band là dòng sản phẩm mới chưa có trong /v1/public/pricing, nên nó nằm tạm trong BAND_PRICING - một chỗ duy nhất, có ngày hiệu lực. Khi dòng này vào được API thì xoá khối hằng số và đọc từ API, không để hai nguồn cùng tồn tại.

Khuyến mãi tự hết hạn. Mỗi mức giảm mang một mốc until; hết mốc thì nó tự rụng khỏi trang. Một dòng "giảm 50% trong năm 2026" còn nằm đó ngày 01.01.2027 là một lời chào hàng sai, và nó sai im lặng: không cổng nào kêu, chỉ có người đọc tin rồi hỏi lại.

Bản tĩnh mang cách tính, không mang con số tiền (routes.ts). Cùng lý do với bảng giá cũ: chép một mức giá có hạn dùng vào bản prerender là dựng một bản giá thứ hai sẽ lệch đúng ngày khuyến mãi hết hạn.

Bẫy giao diện đã vấp và đã sửa: trong dải tối, nút được chọn không được dùng bg-white kèm text-ocean-900 - tầng .n12-band-dark đã kéo text-ocean-900 về màu sáng, nên đó đúng là chữ trắng trên nền trắng. Nút chọn dùng nền mờ bg-white/10 kèm viền sáng, đọc được ở cả hai dải.

11.21 Giá theo band là dữ liệu, sửa từ admin (SRC-791) ​

Chỉ đạo chủ dự án 2026-09-17: "Đưa giá theo band vào API để sửa từ admin." §11.20 để mức giá trong một hằng số ngay trong IeltsTuitionPage.tsx, có ghi rõ đó là ngoại lệ tạm và ngày nào vào được API thì xoá đi. Đây là ngày đó.

TrướcBây giờ
Con số sống ở đâuhằng số BAND_PRICING trong apps/webbảng D1 band_pricing (migration 0227)
Đổi giá bằng cách nàosửa mã, chờ một vòng CI và deploysửa ô trong màn Học phí của admin
Ai đổi đượcngười biết codengười vận hành

Bảng riêng, không nhét vào pricing_items (0177). Hai bảng trả lời hai câu khác nhau: pricing_items là MỘT KHOẢN ĐÁNG BAO NHIÊU (học phí khoá, gói bài tập, combo), còn band_pricing là MỘT ĐƠN GIÁ CÓ HẠN DÙNG cho mỗi 1.0 band tăng thêm. Điểm khác thật sự là cột ends_at: bảng cũ không có khái niệm hết hạn, còn ở đây khuyến mãi phải tự rụng đúng ngày.

Tầng API quyết đợt nào đang chạy, không phải giao diện. GET /v1/public/band-pricing trả về một câu trả lời đã chốt theo thời điểm gọi: đơn giá đang áp dụng, mức thường để gạch ngang, và điều kiện. Ba lý do, lý do thứ ba là lý do thật:

  1. Hạn khuyến mãi tính theo giờ Việt Nam, đồng hồ máy người đọc thì không, và có máy chạy sai ngày.
  2. Web, admin và mọi client về sau phải ra cùng một con số.
  3. Bản prerender tĩnh sinh lúc build; nếu nó tự lọc theo đồng hồ lúc build thì con số đóng băng sai ngay hôm sau. Vì vậy bản tĩnh không mang con số tiền nào, chỉ mang cách tính.

Đợt rẻ nhất còn hạn thắng, không xếp theo seq hay ngày tạo: người nhập không phải nhớ thứ tự mình gõ, và người học luôn nhận mức tốt nhất đang có.

Thiếu dòng base thì API trả null và trang ẩn hẳn phần tiền, thay vì hiện "5.000.000đ, thay vì undefined". Trang vẫn trả lời được câu "ôn bao lâu", và đó là nửa quan trọng hơn. Cùng lý do, không có giá dự phòng trong mã: một con số dự phòng là một bảng giá thứ hai, và nó sẽ lệch đúng vào ngày người ta quên nó tồn tại.

Admin chỉ LIST và PATCH, không tạo và không xoá. Người vận hành cần đổi con số và đổi hạn; thêm một dòng base thứ hai hay xoá mất dòng nền là hai cách làm hỏng khối giá công khai mà không ai thấy ngay. Ba thứ bị chặn ở cả API lẫn giao diện: ngày thiếu múi giờ, ngày kết thúc trước ngày bắt đầu, và tắt dòng base. Mỗi lần ghi vào logAccess kèm giá trị trước và sau, vì đây là giá công khai và "hôm qua trang hiện bao nhiêu" phải trả lời được.

Migration dùng ON CONFLICT DO NOTHING, khác 0177 và khác có chủ đích: từ nay ba dòng này sửa được từ admin, nên chạy lại migration mà đè lại số cũ là xoá thầm việc người vận hành vừa làm.

11.22 Máy tính học phí chỉ hỏi MỘT câu (SRC-791) ​

Chỉ đạo chủ dự án 2026-09-17, sau khi xem bản §11.20: cố định mục tiêu ở tăng 1.0 band, và chỉ cho chọn số giờ mỗi tuần với đúng ba mức 10, 15 và 20.

Bản trước hỏi hai câu. Câu "bạn muốn tăng bao nhiêu band" nghe hợp lý nhưng người mới tìm hiểu chưa trả lời được - họ còn chưa biết mình đang ở band nào, nên nó là một cửa ải đứng trước con số họ vào trang để tìm. 1.0 band vừa là đích thường gặp nhất vừa là đơn vị của bảng giá, nên lấy nó làm mặc định là bỏ được một câu hỏi mà không mất gì.

Ba mức giờ thay vì bốn: mức 5 giờ mỗi tuần cho ra "khoảng 10 tháng" cho 1.0 band, tức là một con số không ai chọn sau khi nhìn thấy. Bày ra một lựa chọn chỉ để nó bị loại là làm bảng dài thêm.

Bảng đòn bẩy đi theo đúng ba mức ấy. Bảng không được bày ra một mức mà máy tính không cho chọn: hai chỗ nói khác nhau trên cùng một trang là chỗ người đọc mất lòng tin vào cả hai.

Khối "con số ở đâu ra" rút còn một đoạn, trỏ thẳng tới trang 11 của tài liệu Cambridge chứ không trỏ chung vào cả file. Ba thẻ lớn giảng lại phép chia đôi là chữ phải lướt qua: người muốn kiểm chứng sẽ mở tài liệu gốc, người không muốn thì không đọc ba thẻ ấy.

11.23 Trang môn: hero hai cột, và bốn thứ bị bỏ (SRC-793) ​

Chỉ đạo chủ dự án 2026-09-17, sau khi xem trang IELTS đang chạy.

Phiếu điểm gộp vào hero. Trước đó hero là một màn chữ căn giữa, còn phiếu điểm nằm dưới một màn cuộn trong một dải tối riêng, kèm một cột chữ giảng luật tính điểm. Hai cái sai cùng lúc: thứ nói "trang này về IELTS" nhanh nhất thì người đọc không thấy ngay, và cột chữ bên cạnh dài ngang ngửa phiếu nên mắt phải chọn đọc bên nào trước.

Nay PageHead nhận thêm aside và chuyển sang hai cột. Tác dụng phụ đáng mong: cột chữ chỉ còn nửa bề ngang, nên câu dẫn buộc phải ngắn lại - nó rút từ hai câu kể tên ba câu hỏi xuống một câu nói việc trang làm. Ba luật đọc phiếu rút thành ba dòng gạch đầu dòng dưới phiếu, bỏ số thứ tự và bỏ ô cảnh báo đóng khung đỏ: chúng là chú thích cho phiếu, không phải một bài đọc riêng.

IeltsScoreCard vì thế không còn dựng <section> của riêng nó - một component vừa dựng phiếu vừa dựng cả một dải thì không nhét được vào hero của trang khác. Chỗ gọi quyết định nó nằm ở đâu.

Kiểu dáng phiếu chuyển từ .n12-band-dark .n12-scorecard sang .n12-scorecard.n12-scorecard. Phiếu nay đứng trên nền SÁNG, mà toàn bộ màu đỏ và nền giấy trắng trước đây chỉ có hiệu lực bên trong dải tối - trên nền sáng nó thành một thẻ trắng trên giấy trắng. Lặp lớp để đạt độ chọn lọc (0,2,0), đủ thắng .n12-band-dark [data-slot="card"], mà không phải dùng !important. Dải tối chỉ còn thêm đúng thứ nó cần: một cái bóng sâu hơn để phiếu nổi khỏi nền đậm.

Breadcrumb dựng lại. Luật SRC-780 giữ nguyên (chỉ Home bấm được), đổi hình thức: Home thành một viên có biểu tượng nhà, dấu phân cách là mũi tên nhỏ, và cấp cuối in đậm vì đó là chỗ người đọc đang đứng. Bản trước cho cả vệt cùng một sắc độ nên nó đọc thành một câu dài đều đều, không chỗ nào nói "bạn ở đây". Biểu tượng vẽ bằng SVG chứ không dùng emoji: emoji đổi hình theo hệ điều hành.

Bỏ khối "Bạn nhận được gì". Nó trả lời "đi với Nemo12 thì khác gì tự học" bằng bốn thẻ hai dòng, tức là bằng LỜI HỨA - trong khi phần còn lại của trang đã trả lời bằng thứ bấm được: phiếu điểm, thang CEFR, năm bước, trang chẩn đoán. Một khối lời hứa đứng cạnh những thứ đó làm loãng chúng. deep.payoffs và deep.gets giữ lại trong subjectCopy.ts vì chúng là chữ đã biên tập và khó viết lại, nhưng deep.gets bỏ khỏi paras của bản tĩnh: bản tĩnh mang chữ mà bản thật không có là hai bản nói khác nhau.

Bỏ nhãn gói học ở cuối trang môn ("Môn này thuộc Squid..."), và xoá luôn bảng SCHOOL_TAG vì đây là chỗ duy nhất đọc nó. Tên linh vật là từ vựng NỘI BỘ; một dòng bắt người đọc học một khái niệm mới ngay lúc họ vừa đọc xong thứ họ cần, kèm một link mời họ rời đúng trang vừa thuyết phục được họ. Tên linh vật vẫn sống ở các trang khu riêng, đúng chỗ nó là chủ đề chứ không phải chú thích.

"Đọc tiếp" thành lưới thẻ đều nhau. Bản trước là chín nút viền giống hệt nhau, độ dài lệch nhau, xếp ba hàng căn giữa - trông như một đám thẻ rơi vãi. Nay mỗi thẻ cùng bề rộng, một dòng chữ và một mũi tên, nên mắt đi theo cột thay vì nhảy theo độ dài chữ.

11.24 Trang sự kiện IELTS (SRC-816, SRC-817) ​

Chỉ đạo chủ dự án 2026-09-18: "Tạo trang events tại students/ielts/events, trang đó liệt kê các event liên quan tới IELTS. Trong đó có event vào sáng chủ nhật, ngày 20 tháng 9 này, online." Rồi ngay sau đó: "Tạo thêm 2 events khác, cũng vào các buổi sáng chủ nhật tiếp theo, nội dung cũng tương tự."

/{vai}/university/ielts/events, đứng cùng họ với lộ trình, lịch học, dạng bài, câu hỏi nên hỏi, học phí và chẩn đoán. Buổi 20.09.2026 đã nằm trong CSDL từ trước (migration 0236, SRC-811) — việc của SRC-816 là mở một cửa công khai để người chưa có tài khoản nhìn thấy nó, và SRC-817 thêm hai buổi 27.09 và 04.10 để nó thành một chuỗi hàng tuần.

Danh sách buổi đọc từ community_events, không viết trong mã. Cùng luật với khung giờ ở trang lịch học: chép ngày giờ vào mã là dựng nguồn thứ hai cho một cái lịch, và ngày nó lệch thì trang công khai mời người ta tới một giờ không còn ai mở. Admin đổi buổi qua app.

Endpoint mới /v1/public/events, và ba thứ nó cố ý KHÔNG trả về:

Bỏ raVì sao
meeting_urlLink phòng họp chỉ người đã giữ chỗ được thấy (REQ-SEC-02, cùng luật với courses.meeting_url). Công khai nó là mở cửa phòng họp cho bất kỳ ai đọc JSON, và một buổi có người lạ vào giữa giờ thì không sửa lại được
capacity"Còn 2 chỗ" là một con số gây áp lực mua, không phải một thông tin người đọc cần
Số người đã ghi tênThông tin nội bộ của một buổi chưa diễn ra

Câu SELECT vì thế liệt kê cột tường minh, và test public.test.ts kiểm bằng cách đọc toàn văn phản hồi: một SELECT * lọt vào đây không hiện triệu chứng nào trên màn hình, nên chỉ test giữ được.

Lọc theo series ở phía API, không lọc ở client. Bảng community_events còn giữ buổi cafe của bố mẹ và buổi ôn thi vào 10; một trang IELTS liệt kê chúng là một trang nói sai chủ đề của mình. Khớp không phân biệt hoa thường và cắt khoảng trắng hai đầu, vì series do người gõ từ admin.

Giữ chỗ không làm trên trang này. Trang công khai không có phiên đăng nhập nên nó không ghi tên ai vào buổi nào được; nút dẫn sang marlins.nemo12.com/#/events/{id}, nơi có phiên và có luật chỗ ngồi. Chữ trên nút vì thế là "Xem và giữ chỗ", không được là "Đã giữ chỗ" — cùng ràng buộc câu chữ đã ghi ở trang lịch học.

Giờ hiện theo giờ Việt Nam, cố định. starts_at mang offset +07:00 nên mốc thời gian là đúng với mọi máy; phần chọn múi giờ là chuyện khác. Trang này ghim Asia/Ho_Chi_Minh vì đây là buổi ở Việt Nam: một người đọc ở Đức thấy "5h sáng thứ bảy" sẽ không nhận ra đó là buổi sáng chủ nhật mà cả nhà đang nói tới. Bù lại mọi chỗ hiện giờ đều ghi rõ "giờ Việt Nam". Ngày theo DD.MM.YYYY và giữ tên thứ (SRC-748), vì buổi này được nói tới bằng "sáng chủ nhật", không bằng con số ngày.

Ba buổi là một chuỗi, không phải ba bản sao (SRC-817). Cùng khung 10h00 sáng chủ nhật, cùng mục tiêu, nhưng mỗi buổi có một trọng tâm riêng ghi ngay trong summary và agenda: 20.09 giới thiệu toàn hệ thống, 27.09 đi sâu vào một tuần học thật, 04.10 đi sâu vào cách hệ thống đọc số liệu và chấm bài. Ba buổi giống hệt nhau thì buổi thứ hai và thứ ba không có lý do tồn tại, và người đã dự buổi đầu không có lý do quay lại. Giữ nguyên series để cả ba nằm trên cùng một trang.

Bản prerender mang CÁCH một buổi diễn ra, không mang ngày giờ của buổi cụ thể — cùng lẽ với bản tĩnh của trang học phí (§11.8): bản tĩnh chỉ dựng lại lúc build, nên một ngày cụ thể chép vào đó sẽ còn mời người đọc tới một buổi đã qua từ lâu.

Rỗng thì nói ra bằng một câu. Khác lưới tuần ở trang lịch học — ở đó phần lưới biến mất là xong vì phần còn lại vẫn đủ nghĩa. Ở đây danh sách buổi LÀ cả trang, nên không có buổi nào thì trang hiện một thẻ nói điều đó kèm đường đi tiếp (hai buổi học thử không phụ thuộc lịch sự kiện). Lỗi mạng ra cùng màn hình ấy: với người đọc, hai tình huống đều là "hôm nay không có gì để ghi tên", và một thông báo lỗi kỹ thuật trên trang công khai chỉ làm họ nghĩ Nemo12 đang hỏng.

7.6 Chỗ gắn home nối vào trang chủ (SRC-1224) ​

Rà ngày 03.10.2026 (mở từng trang đích trên nemo12.com, chụp làm bằng chứng): 11/12 cảm nhận hiện đúng trang, nhưng chỗ gắn home không trang nào đọc tới. Khối cảm nhận ở /students và /parents không truyền placement, nên hiện mọi cảm nhận theo đối tượng đọc; cảm nhận chỉ gắn about và home mà dành cho học sinh (Khánh Vy) vì thế không hiện ở đâu cả, vì /about chỉ lấy lời dành cho phụ huynh.

Nay StudentsHome và ParentsHome truyền placement="home": trang chủ chỉ hiện cảm nhận được chọn cho trang chủ trong dolphin, chưa gắn cái nào thì khối tự ẩn (cùng luật với mọi khối có placement).

Bẫy khi kiểm bằng trình duyệt tự động: trang được dựng sẵn rồi mới nạp ứng dụng sau requestAnimationFrame. Một khung trình duyệt đang ẩn không vẽ khung hình nào, nên ứng dụng không khởi động và mọi khối gọi API lúc chạy đều vắng mặt. Phải buộc khung vẽ (chụp màn hình) trước khi kết luận "trang không hiện".