⌨️ Thế Giới Keycap Docs Protocol

TASK_PROTOCOL.md — thegioikeycap.vn

Quy trình bắt buộc trước / trong / sau khi code. Đọc trước mọi task.


Before Coding

  1. Đọc CLAUDE.md — coding rules, những quyết định đã chốt.
  2. Đọc docs/PROJECT_MAP.md phần liên quan tới miền đang sửa.
  3. Tra docs/FUNCTION_REGISTRY.md — hàm cần viết có thể đã có sẵn.
  4. Đọc đầy đủ file sắp sửa. Không sửa mù dựa trên kết quả grep.
  5. Tìm hook trước khi viết lại. Đây là WooCommerce — gần như mọi thứ đều có hook. Viết lại luồng thương mại là sai hướng.
  6. Nếu task đụng tới đơn hàng, tồn kho, email hoặc tracking → đọc tiếp mục Quy tắc test trước khi làm bất cứ gì.

During Coding

  • Escape mọi output. esc_html(), esc_attr(), esc_url(), wp_kses_post().
  • Kiểm tra số tham số của hook trước khi khai callback. Khai thừa gây ArgumentCountError → HTTP 500. Ví dụ đã sập thật: woocommerce_add_notice chỉ truyền 1 tham số, khai 2 là trắng trang giỏ hàng.
  • Dùng ??, không dùng ?: cho key mảng có thể không tồn tại.
  • Không dùng :has() để bắt phần tử của WP core.
  • Sửa CSS cho nội dung dài phải sửa đủ 3 container: .post__content, .cat-seo__body, .woocommerce-tabs .panel.
  • Đổi PHP xong đợi tới 60 giây (OPcache) rồi mới kết luận.
  • Đổi mô tả danh mục xong phải xoá cache sitemap Rank Math.

After Coding

  1. Kiểm chứng bằng quan sát thật, không suy luận. - Đổi giao diện → chụp màn hình xem (xem docs/OPS.md §Chụp màn hình). - Đổi tracking → xem event thật trong Events Manager hoặc DB TGK_Store. - Đổi email → gửi thử tới thegioikeycap+test@gmail.com. - Đổi tính toán tiền → đặt một đơn test thật rồi đọc số.
  2. Kiểm tra log: logs/php/error.log. ⚠️ Nhớ là không phải lỗi nào cũng vào đây — ArgumentCountError từng gây 500 mà log trống trơn.
  3. Cập nhật docs theo bảng ở AGENTS.md §4, trong cùng commit.
  4. Commit với message tiếng Việt giải thích vì sao.

New File Policy

Chỉ tạo file mới khi nó là một mối quan tâm riêng. Đặt tên theo miền (checkout.php), không theo kỹ thuật (utils.php, misc.php).

Vị trí file mới theo loại

Loại Đặt ở đâu
Logic PHP theo miền themes/thegioikeycap/inc/<miền>.php + require_once trong functions.php
Template WordPress themes/thegioikeycap/ theo template hierarchy
Override WooCommerce themes/thegioikeycap/woocommerce/<đúng đường dẫn gốc>
Mảnh template dùng lại themes/thegioikeycap/template-parts/
CSS Không tạo file mới — thêm khối vào assets/css/main.css
Dữ liệu tĩnh themes/thegioikeycap/data/*.json, đọc bằng tgk_data()
Logic tracking plugins/tgk-tracking/includes/class-*.php
Tài liệu docs/

Tạo thư mục mới thì phải khai vào .gitignore (whitelist), nếu không git lặng lẽ bỏ qua.


Reuse Policy

  • Tra docs/FUNCTION_REGISTRY.md trước.
  • Cần hằng số về shop → tgk_site(), không hardcode.
  • Cần HTML email → tgk_email_html().
  • Cần Content ID → TGK_Events::content_id().
  • Viết hàm mới thì thêm ngay vào FUNCTION_REGISTRY trong cùng commit.

Quy tắc Test (RULE CỨNG)

Website này bán hàng thật. Test sai là bắn thông báo cho khách thật và đẩy chuyển đổi giả vào Meta.

Quy trình test đơn hàng — BẮT BUỘC đủ 6 bước

cd /srv/thegioikeycap.vn/docker
WP="docker compose exec -T -u www-data php wp --path=/srv/thegioikeycap.vn/www"
  1. Tắt đẩy đơn sang POS: $WP plugin deactivate send-app
  2. Bật cờ test event: mở TGK_TEST_EVENT_CODE trong wp-config.php
  3. Đặt đơn test — email phải là thegioikeycap+test@gmail.com. ⚠️ Tuyệt đối không dùng @example.com: gây hard bounce, hại uy tín gửi mail.
  4. Dọn đơn: HUỶ TRƯỚC rồi mới XOÁ. Xoá thẳng không hoàn kho. Xong thì bật lại send-app và comment lại TGK_TEST_EVENT_CODE.
  5. Huỷ đơn test bên Pancake nữa. Webhook đẩy đơn sang POS ngay khi tạo, nhưng order.deleted chưa từng bắn lần nào — xoá bên Woo là Pancake vẫn giữ đơn và vẫn trừ kho: PUT /shops/{shop}/orders/WOO{id} với {"status":6} (6 = Đã huỷ).
  6. ĐỐI CHIẾU LẠI TỒN KHO HAI BÊN.

Bước 6 không được bỏ — đây là lý do

Gặp thật 06/09/2026: đơn test #1732 làm tồn Capybara bên Woo nhảy từ 2 lên 4. Huỷ đơn hoàn kho một lần, rồi $order->delete(true) hoàn thêm lần nữa. Woo lệch +2 so với Pancake, không có cảnh báo nào.

Đừng tin "huỷ rồi xoá thì kho tự đúng".

# Đối chiếu nhanh sau mỗi lần test đơn
$WP eval '$o=[]; foreach(wc_get_products(["limit"=>-1,"return"=>"ids"]) as $i){
  $p=wc_get_product($i);
  foreach(($p->is_type("variable")?$p->get_children():[$i]) as $v){
    $x=wc_get_product($v); if($x) $o[strtoupper($x->get_sku())]=$x->get_stock_quantity(); }}
  echo json_encode($o);'
# rồi so với: GET /shops/{shop}/products/variations
#             (đối chiếu display_id ↔ SKU, remain_quantity ↔ tồn Woo)

Test tracking

  • curl và headless Chrome mặc định bị should_track() coi là bot → trang trả về không có hàng đợi event, không có cookie _fbp. Phải đổi User-Agent, nếu không sẽ kết luận nhầm là pixel hỏng.
  • Đã mất một vòng chẩn đoán vì chuyện này.

Test email

  • Gửi tới thegioikeycap+test@gmail.com.
  • Container không có MTA → wp_mail() luôn trả false nếu FluentSMTP hỏng. false ở đây nghĩa là "cấu hình SMTP sai", không phải "nội dung email sai".

Checklist nhanh trước khi commit

  • [ ] Đã tra FUNCTION_REGISTRY, không viết trùng hàm đã có?
  • [ ] Số tham số callback khớp với số WordPress thật sự truyền?
  • [ ] Output đã escape?
  • [ ] Đã đợi qua OPcache rồi mới kiểm chứng?
  • [ ] Đã nhìn thấy kết quả (screenshot / event / email / số tiền), không chỉ suy luận?
  • [ ] Nếu có test đơn: đã đủ 6 bước, kể cả đối chiếu tồn kho?
  • [ ] send-app đã bật lại? TGK_TEST_EVENT_CODE đã comment lại?
  • [ ] Đã cập nhật docs theo bảng AGENTS.md §4?
  • [ ] Không có secret nào lọt vào file git theo dõi?

Bẫy — các lỗi đã sập thật, đừng vấp lại

Bẫy Hậu quả Cách tránh
Khai thừa tham số cho hook HTTP 500, log trống Đếm tham số WordPress thật sự truyền
$order->delete(true) sau khi huỷ Hoàn kho hai lần, lệch tồn im lặng Đối chiếu tồn kho sau mỗi test đơn
pre_term_description lọc kses cấp bình luận Mô tả danh mục bị xoá sạch, không báo lỗi Giữ nguyên đoạn nới thẻ trong inc/category-content.php
should_track() chặn bot Tưởng pixel hỏng Đổi User-Agent khi debug
OPcache 60 giây Tưởng code không chạy Chờ đủ rồi mới kết luận
wp db query không có mysql client Lệnh báo lỗi khó hiểu Dùng wp eval với $wpdb
Container không có MTA wp_mail() trả false Kiểm tra FluentSMTP trước
:has() bắt phần tử WP core Gãy im lặng khi WP cập nhật Thêm class riêng
Sót container thứ 3 khi style bảng Bảng mất viền đúng một chỗ Sửa đủ .post__content, .cat-seo__body, .woocommerce-tabs .panel
woocommerce_add_notice tưởng là filter chung Khai 2 tham số → ArgumentCountError, HTTP 500 Nó là filter của RIÊNG loại notice. Bộ filter đúng là woocommerce_add_{success\|notice\|error}, mỗi cái 1 tham số
Chặn thông báo bằng cách trả chuỗi rỗng wc_print_notice() KHÔNG lọc rỗng → in ra hộp viền trống không chữ Lọc ở template woocommerce/notices/*.php, xem inc/notices.php
Payload REST dùng key name 400, client_fired không được ghi Key đúng là event_name
.gitignore whitelist Thư mục mới bị bỏ qua im lặng Khai đường dẫn mới vào .gitignore
Sửa main.css/main.js mà quên bump TGK_VERSION Khách cũ chạy bản cache cũ; máy mình vẫn đúng nên không ai phát hiện Bump TGK_VERSION trong functions.php mỗi lần đụng asset
Bọc <div> bằng hook before_add_to_cart_quantity external.php bắn hook đóng mà không bắn hook mở → thừa </div> Dùng cờ trạng thái, xem inc/product-buybox.php
wp_kses_post() bọc quanh woocommerce_quantity_input() Ngữ cảnh 'post' không cho phép thẻ <input> → ô số lượng trong giỏ biến mất hoàn toàn, không lỗi Echo thẳng như core (templates/cart/cart.php) — markup đã tự esc_attr() rồi, không cần kses lần hai. Xem woocommerce/cart/cart.php
Test đổi số lượng vượt tồn kho rồi tưởng là bug HTML5 checkValidity() chặn submit im lặng (input có max="<tồn kho>") — trông giống nút "không hoạt động" Trước khi kết luận bug, form.checkValidity() / đọc validationMessage; test bằng số lượng còn trong hạn mức tồn kho
Sửa giá/tồn ở Woo Bị Pancake ghi đè Sửa ở POS
Bật Turnstile toàn cục qua woocommerce_process_login_errors nhưng sót form checkout Khách bấm đăng nhập ở checkout bị lỗi "Vui lòng hoàn tất xác minh Tôi là người" trong khi không có captcha Phải chèn tgk_captcha_field() vào mọi form login, override checkout/form-login.php, và hook vào woocommerce_login_form
Turnstile trong form ẩn display:none Cloudflare Turnstile tự bỏ qua container ẩn khi quét trang → mở form ra captcha không chạy JS phải lắng nghe sự kiện mở form (a.showlogin) và gọi window.turnstile.render()/reset() để kích hoạt
Khách vãng lai nhập email đã có tài khoản Cho phép checkout là bị mạo danh email của người khác tạo đơn ảo woocommerce_after_checkout_validation phải kiểm tra email_exists() và chặn, yêu cầu đăng nhập
Bật method ship khi mode=test Thu tiền thật của khách bằng giá giả lập Simulator (như đơn #1743) Kiểm tra TGK_ALLINGO_MODE phải là 'live'; không để khách chịu phí giả lập
AllinGo delivery_payment_method='cash' Chỉ trả Viettel Post và Nhất Tín — SPX, GHN, J&T, Best, VNPost cần 'wallet' Dùng "wallet" khi truy vấn AllinGo để lấy đủ danh sách đối tác
AllinGo inquiries cần địa chỉ 3 cấp cũ Gửi địa chỉ 2 cấp mới (34 tỉnh) là tất cả quote báo unavailable Không gọi API AllinGo lúc checkout; dùng bảng giá SPX đồng giá toàn quốc theo cân nặng nội bộ
Tự dựng HTML thẻ sản phẩm .pcard trong bài viết wpautop chèn <p>, <br> làm giãn nát card; .pcard__icon có font-size: 0 làm mất text ➔ thành vòng tròn đỏ rỗng Dùng shortcode chuẩn WooCommerce [products ids="..." columns="4"] tự động nạp template content-product.php có SVG icon
Bàn giao thay đổi UI mà không chụp kiểm tra thực tế Khách thấy giao diện vỡ, lệch cache hoặc hiển thị sai BẮT BUỘC dùng Playwright chụp ảnh thực tế cả Desktop (1440px) và Mobile (390px), tự xem ảnh trước khi báo xong

Quy Trình Bắt Buộc: Visual Inspection Bằng Playwright Trước Khi Bàn Giao UI

BẤT CỨ KHI NÀO sửa CSS, markup template, shortcode hoặc layout bài viết / trang / sản phẩm, AI Agent PHẢI thực hiện quy trình sau trước khi trả lời người dùng:

  1. Bump TGK_VERSION trong functions.php và chạy opcache_reset() để đảm bảo file asset mới được nhận diện.
  2. Khởi chạy Playwright (sử dụng /opt/screenshot-tool/node_modules/playwright) để chụp ảnh màn hình trang live: - Desktop viewport: 1440 x 900 - Mobile viewport: 390 x 844
  3. Gọi công cụ xem ảnh trực tiếp (view_image) trên các file ảnh vừa chụp để tự kiểm tra bằng mắt: - Layout có bị vỡ, dãn khoảng trắng bất thường không? - Các icon, nút bấm, badge có bị biến mất hoặc thành vòng tròn rỗng không? - Văn bản có bị ngắt dòng sai hoặc thiếu thẻ heading không? - Mobile có bị thanh cuộn ngang (horizontal overflow) không?
  4. Chỉ khi ảnh chụp màn hình hiển thị hoàn hảo mới được phép báo cáo hoàn thành cho người dùng. Tuyệt đối không suy luận dựa trên mã HTTP 200 hay curl log.

Nguyên tắc Test Ma trận (Matrix Scenario Testing)

Mọi tính năng liên quan Auth / Cart / Checkout / Account bắt buộc phải được kiểm thử đầy đủ các nhánh trong ma trận:

  1. Happy path: Khách điền đúng, đăng nhập thành công, đơn tạo thành công.
  2. Rejection & Conflict path: - Email đã tồn tại trong database (phải báo lỗi, link mở form đăng nhập, giữ nguyên dữ liệu đã điền). - Mật khẩu sai / yếu / không khớp xác nhận. - Thêm giỏ vượt quá số lượng tồn kho (bắt toast báo lỗi AJAX, không reload trang). - Địa chỉ Phường/Xã không thuộc Tỉnh/Thành đã chọn. - Captcha Turnstile: verify thành công, hết hạn token, hoặc thiếu token.
  3. Cross-viewport testing: - Mobile: 375px (iPhone SE) — kiểm tra layout 1 cột, input full width, không vỡ card. - Laptop: 1366px — kiểm tra layout không bị thanh cuộn ngang (đặc biệt bàn phím cơ Fullsize). - Desktop: 1440px+ — canh giữa chuẩn, khoảng đệm cân đối.