wal: flush 20260729T1352
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
# Reviewer Agent — Persistent Memory
|
# Reviewer Agent — Persistent Memory
|
||||||
|
|
||||||
- **S155-đợt2 (07-27) PE delete-approver DIFF-review #2 (12 FLAG 2H/4M/6L · ĐỪNG-DEPLOY):** 🔴 class MỚI **`hợp-đồng-đứt-giữa-2-bờ`** — FE type chép tay khai `allowApproverDelete: boolean`, BE record chỉ 8-member KHÔNG có cờ ⇒ nút CHẾT mà `tsc` + `dotnet build` + 561-test đều XANH (mọi thước đo đo chỗ khác) ⇒ **phép rẻ nhất = đếm member DTO vs khoá FE + `grep -c <cờ>` ở file dựng DTO**. · policy-gate `X.Delete` chặn ĐÚNG nhóm cần dùng (đo DB: 11/13 vai `CanDelete=0`, 2 seeder KHÔNG BAO GIỜ nâng) ⇒ owner gỡ policy; **verify "gỡ authz" = truy TỪNG dòng chặn thành bảng + grep `.First()` fallback + grep `Admin` trong chính file** (gỡ policy sinh LOW mới: endpoint thành máy-dò 4-loại-phản-hồi cho mọi tài khoản). · **mã ĐỔI giữa lượt soi ⇒ re-đo đĩa TRƯỚC verdict**; test đổi chiều (`==X` → `==null`) = **ĐỔI-SPEC hợp lệ ≠ nới-assert** vì 2 mệnh đề loại trừ nhau (nới = mệnh đề mới SUY RA được từ cũ). · cascade `Add con → Remove cha → 1 SaveChanges`: chứng "site duy nhất" bằng liệt-kê-vét-cạn 34 `Remove` → lọc 8 → xét cha-con từng cái.
|
- **S159 (07-29) dashboard "toàn trình" đợt-4 FE-only (fe-user 984+/337−, PWF 7 FLAG 0H/2M/5L):** 🔴 **`tsc` XANH đo ÍT hơn vẻ ngoài** — `fe-user/tsconfig*.json` KHÔNG có `strict`/`strictNullChecks` (chỉ `noUnusedLocals`) ⇒ **tra cờ tsconfig TRƯỚC khi trích tsc làm bằng chứng**; ngược lại `noUnusedLocals`+pass = bằng chứng MẠNH HƠN grep cho claim "đã gỡ khối X" (compiler chặn nếu sót import). · class **`nhãn khẳng định chiều-ngược`**: dot-map `none='chưa tới'` — không-suy-diễn "đã xong" là đúng, nhưng "chưa tới" CŨNG là khẳng định ⇒ sai cho GĐ ĐỨNG TRƯỚC + sai khi `contractId != null` **có sẵn trong payload**. · pager tính `data.page±1` (keepPreviousData ⇒ stale) thay vì state ⇒ double-click = no-op. · **verify đích điều hướng phải đọc CẢ breakpoint trang đích** (panel `hidden lg:block` ⇒ ngõ cụt mobile; trang đích tự có nhánh `matchMedia` mà caller mới không copy). · regression đo HEAD⟂work bằng `grep -c` từng token: count 5→2 mà KHÔNG mất nhãn (inline-prop → `STAGES[i].title`) ⇒ **đếm lệch phải truy CẤU TRÚC trước khi kêu hồi quy**. · tự tính lại 4 tỉ-số contrast + tra `@theme` mọi `var(--color-*)` (0 dòng `--color-*: initial` ⇒ nấc mặc định còn) — 3/3 số designer ĐÚNG. · lời khai sub-file "766→1.130 dòng" SAI (đĩa 1.413 = 766−337+984). [→ `runs/2026-07-29-S159-tong-quan-pipeline-menu/sub-reviewer-diff-dot4.md` 28,5KB]
|
||||||
|
- **[→ archive/2026-07.md @S159-curate] S155-đợt2 (07-27) PE delete-approver DIFF-review #2 (12 FLAG 2H/4M/6L · ĐỪNG-DEPLOY):** class **`hợp-đồng-đứt-giữa-2-bờ`** (FE type khai cờ, BE DTO không có ⇒ nút chết mà tsc+build+561-test đều xanh) · policy-gate đo DB 11/13 vai `CanDelete=0` · mã ĐỔI giữa lượt soi ⇒ re-đo đĩa TRƯỚC verdict · đổi-chiều-assert ≠ nới-assert.
|
||||||
- **[→ archive/2026-07.md @S155-curate] S155 ×2 (07-27) PE delete-approver — DIFF-review đợt 1 tiền-deploy (14 FLAG 4H/6M/3L/1I, DEPLOY-CÓ-RỦI-RO) + SPEC-review (PWF 4H/8M/5L):** 🔴 class MỚI **`HIGH nằm ở git chứ không ở mã`** — 2/3 file migration UNTRACKED mà snapshot ĐÃ tracked ⇒ `commit -a` nạp model-có-cột nhưng bỏ migration ⇒ `Invalid column name` cả 7 module, build+test vẫn xanh ⇒ **gate deploy phải chấm `git status ^??` cho `Migrations/`** · `ExecuteDelete` ngoài transaction TRƯỚC `SaveChanges` = mất dữ liệu không hoàn tác · purge xoá CỨNG chữ ký phiếu XOÁ MỀM ⇒ **test đang KHOÁ chiều ngược, vá xong test đỏ ≠ hồi quy, CẤM nới assert** · ~600 dòng BE = mã chết (`grep api.put`=0) · **xanh-giả bắt bằng phép 2-thế-giới** (còn-gate ⟂ bỏ-gate cùng cho 0 ⇒ 0 thông tin) · CLEAN chứng bằng **liệt kê vét cạn** write-site + sweep gián-tiếp (#81-EXT) · 2 lỗi TỰ BẮT: trích line-number **từ diff** (diff không mang số dòng file) · `grep -v Snapshot` ăn nhầm tên migration · **file bị sửa TRONG LÚC soi ⇒ neo bằng NỘI DUNG DÒNG + ghi bản-đồ-trôi**.
|
- **[→ archive/2026-07.md @S155-curate] S155 ×2 (07-27) PE delete-approver — DIFF-review đợt 1 tiền-deploy (14 FLAG 4H/6M/3L/1I, DEPLOY-CÓ-RỦI-RO) + SPEC-review (PWF 4H/8M/5L):** 🔴 class MỚI **`HIGH nằm ở git chứ không ở mã`** — 2/3 file migration UNTRACKED mà snapshot ĐÃ tracked ⇒ `commit -a` nạp model-có-cột nhưng bỏ migration ⇒ `Invalid column name` cả 7 module, build+test vẫn xanh ⇒ **gate deploy phải chấm `git status ^??` cho `Migrations/`** · `ExecuteDelete` ngoài transaction TRƯỚC `SaveChanges` = mất dữ liệu không hoàn tác · purge xoá CỨNG chữ ký phiếu XOÁ MỀM ⇒ **test đang KHOÁ chiều ngược, vá xong test đỏ ≠ hồi quy, CẤM nới assert** · ~600 dòng BE = mã chết (`grep api.put`=0) · **xanh-giả bắt bằng phép 2-thế-giới** (còn-gate ⟂ bỏ-gate cùng cho 0 ⇒ 0 thông tin) · CLEAN chứng bằng **liệt kê vét cạn** write-site + sweep gián-tiếp (#81-EXT) · 2 lỗi TỰ BẮT: trích line-number **từ diff** (diff không mang số dòng file) · `grep -v Snapshot` ăn nhầm tên migration · **file bị sửa TRONG LÚC soi ⇒ neo bằng NỘI DUNG DÒNG + ghi bản-đồ-trôi**.
|
||||||
- **[→ archive/2026-07.md @S155-curate] S146·S147·S152-D2 (digest):** S146 lead sửa 1 phép quét 4 lần/phiên vì mã-hoá ý-định NGỮ-NGHĨA vào regex ⇒ nấc đúng = lưới-soát-cho-người + allowlist CÓ TÊN + use⟂mention · S147 **decouple 1 gate ⇒ truy xem prop dùng chung đã mang carve-out chưa** (`itemsReadOnly` ≠ raw `readOnly`); "same fix" cho surface anh-em có thể trúng gate KHÁC · S152-D2 census thay spot 307/307; **3 THƯỚC-HỎNG** (md5 disk vs `git show` = FAIL giả do CRLF⟂LF · `grep -c` dính citation-trap · `grep -iF` MSYS trả 0-hit IM LẶNG trên UTF-8) + máy-trích mù đuôi `{.yml,.json,.sql}`.
|
- **[→ archive/2026-07.md @S155-curate] S146·S147·S152-D2 (digest):** S146 lead sửa 1 phép quét 4 lần/phiên vì mã-hoá ý-định NGỮ-NGHĨA vào regex ⇒ nấc đúng = lưới-soát-cho-người + allowlist CÓ TÊN + use⟂mention · S147 **decouple 1 gate ⇒ truy xem prop dùng chung đã mang carve-out chưa** (`itemsReadOnly` ≠ raw `readOnly`); "same fix" cho surface anh-em có thể trúng gate KHÁC · S152-D2 census thay spot 307/307; **3 THƯỚC-HỎNG** (md5 disk vs `git show` = FAIL giả do CRLF⟂LF · `grep -c` dính citation-trap · `grep -iF` MSYS trả 0-hit IM LẶNG trên UTF-8) + máy-trích mù đuôi `{.yml,.json,.sql}`.
|
||||||
- **[→ archive/2026-07.md @S145-curate] S145 4-lane (Axis-A/D/E + S145b GATE) — digest:** bất-biến chia-đôi giữa 2 axis nằm ĐÚNG SEAM ⇒ chỉ cross-cut sweep bắt (hook 3→4 path orphan; `session_ctx_kb` ghost-wire #H18) · 'vai-có-sổ' acceptance ⇒ MUST `ls agent-memory/<role>/` (folder≠sổ, ring1-audit RỖNG) · stale-fix phải verify caller-TRÊN-ĐĨA — grep-"4 surface"=0-hit là ANTI-TEETH Goodhart; 2-script-same-suffix = conflation-vector · đếm VALID_ROLES = per-file grep bidirectional KHÔNG regex (comment lừa parser).
|
- **[→ archive/2026-07.md @S145-curate] S145 4-lane (Axis-A/D/E + S145b GATE) — digest:** bất-biến chia-đôi giữa 2 axis nằm ĐÚNG SEAM ⇒ chỉ cross-cut sweep bắt (hook 3→4 path orphan; `session_ctx_kb` ghost-wire #H18) · 'vai-có-sổ' acceptance ⇒ MUST `ls agent-memory/<role>/` (folder≠sổ, ring1-audit RỖNG) · stale-fix phải verify caller-TRÊN-ĐĨA — grep-"4 surface"=0-hit là ANTI-TEETH Goodhart; 2-script-same-suffix = conflation-vector · đếm VALID_ROLES = per-file grep bidirectional KHÔNG regex (comment lừa parser).
|
||||||
|
|||||||
@ -156,3 +156,9 @@
|
|||||||
- **Tự bắt 2 lỗi của CHÍNH mình trước khi báo:** "Mig 47/50/42/46 MISS" = artifact chuẩn-hoá của tao (`Mig42` liền không dấu cách, có ĐỦ 2 phía) · context-regex `.{70}` trả RỖNG đọc nhầm thành sạch → phải `.{0,70}`. ⇒ **LUẬT: mọi MISS phải literal-grep XÁC-NHẬN 2 phía bằng công-cụ THỨ HAI trước khi vào report.**
|
- **Tự bắt 2 lỗi của CHÍNH mình trước khi báo:** "Mig 47/50/42/46 MISS" = artifact chuẩn-hoá của tao (`Mig42` liền không dấu cách, có ĐỦ 2 phía) · context-regex `.{70}` trả RỖNG đọc nhầm thành sạch → phải `.{0,70}`. ⇒ **LUẬT: mọi MISS phải literal-grep XÁC-NHẬN 2 phía bằng công-cụ THỨ HAI trước khi vào report.**
|
||||||
- D re-ground từ đĩa: `docs/gotchas.md` count=83/max=83 ⇒ 0 nhãn `gotcha #N>83`; soi NGƯỢC 24 dòng gotcha ≤83 tìm mirror-error (run-number đội lốt gotcha) → 0 ca, mọi #N đều là gotcha thật trong ngữ-cảnh nguồn. Tag `[s152, d2-fidelity-gate, coverage-diff, extractor-ext-blindspot, thuoc-hong-x3]`
|
- D re-ground từ đĩa: `docs/gotchas.md` count=83/max=83 ⇒ 0 nhãn `gotcha #N>83`; soi NGƯỢC 24 dòng gotcha ≤83 tìm mirror-error (run-number đội lốt gotcha) → 0 ca, mọi #N đều là gotcha thật trong ngữ-cảnh nguồn. Tag `[s152, d2-fidelity-gate, coverage-diff, extractor-ext-blindspot, thuoc-hong-x3]`
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## @S159-curate — moved from L1 (S155-đợt2 verbatim, moved-not-cut)
|
||||||
|
|
||||||
|
- **S155-đợt2 (07-27) PE delete-approver DIFF-review #2 (12 FLAG 2H/4M/6L · ĐỪNG-DEPLOY):** 🔴 class MỚI **`hợp-đồng-đứt-giữa-2-bờ`** — FE type chép tay khai `allowApproverDelete: boolean`, BE record chỉ 8-member KHÔNG có cờ ⇒ nút CHẾT mà `tsc` + `dotnet build` + 561-test đều XANH (mọi thước đo đo chỗ khác) ⇒ **phép rẻ nhất = đếm member DTO vs khoá FE + `grep -c <cờ>` ở file dựng DTO**. · policy-gate `X.Delete` chặn ĐÚNG nhóm cần dùng (đo DB: 11/13 vai `CanDelete=0`, 2 seeder KHÔNG BAO GIỜ nâng) ⇒ owner gỡ policy; **verify "gỡ authz" = truy TỪNG dòng chặn thành bảng + grep `.First()` fallback + grep `Admin` trong chính file** (gỡ policy sinh LOW mới: endpoint thành máy-dò 4-loại-phản-hồi cho mọi tài khoản). · **mã ĐỔI giữa lượt soi ⇒ re-đo đĩa TRƯỚC verdict**; test đổi chiều (`==X` → `==null`) = **ĐỔI-SPEC hợp lệ ≠ nới-assert** vì 2 mệnh đề loại trừ nhau (nới = mệnh đề mới SUY RA được từ cũ). · cascade `Add con → Remove cha → 1 SaveChanges`: chứng "site duy nhất" bằng liệt-kê-vét-cạn 34 `Remove` → lọc 8 → xét cha-con từng cái. Tag `[s155-dot2, hop-dong-dut-2-bo, policy-gate-do-DB, re-do-dia-truoc-verdict]`
|
||||||
|
|||||||
@ -76,7 +76,20 @@
|
|||||||
· ⚠️ **Lưu ý còn hiệu lực:** nhánh grant seeder = **skip-existing, KHÔNG tự nâng row false** — prod đúng nhờ re-grant SQL + revoker thôi lật; prod nào sau này rơi false thì re-grant tay, đừng chờ seeder.
|
· ⚠️ **Lưu ý còn hiệu lực:** nhánh grant seeder = **skip-existing, KHÔNG tự nâng row false** — prod đúng nhờ re-grant SQL + revoker thôi lật; prod nào sau này rơi false thì re-grant tay, đừng chờ seeder.
|
||||||
· Baseline mới: admin js `CC1pGE1F`/css `DWDbm5As` · user js `DeAg7MWm`/css `BQIZ-rUe` · 38-key 13/13.
|
· Baseline mới: admin js `CC1pGE1F`/css `DWDbm5As` · user js `DeAg7MWm`/css `BQIZ-rUe` · 38-key 13/13.
|
||||||
|
|
||||||
<!-- ARC-2 ĐÓNG @S159 12:5x — 3 đợt · 3 commit (`b5799fc`·`1a47a61`·`2a72695`) · 3 cicd (PASS 9/9 · FAIL 8/9 bắt revoker · PASS 9/9 restart-proof) · prod-data 5 lượt SQL (backup+verify đủ) -->
|
<!-- moc: 3 đợt đầu đóng @12:5x — arc MỞ LẠI đợt-4 theo 3 lệnh mid-turn anh -->
|
||||||
|
|
||||||
|
## ĐỢT-4 — "bỏ hết mấy cái này, hiện list chi tiết đàng hoàng" + tab strip menu + "Bảng cứng"
|
||||||
|
|
||||||
|
**Owner verbatim:** ① *"bỏ hết mấy cái này đi, hiện theo list chi tiết đàng hoàng luôn. Nghĩ cách bố trí sao cho nó có danh sách phiếu mà User nhìn vào vẫn thấy đc tổng quan liên kết."* (ảnh khoanh 5 KPI + 2 list gần-đây) ② *"Thêm chữ bảng cứng vào nhé"* (ảnh: `01. HĐ Thầu phụ - Bảng cứng`) ③ *"vậy là tròn 1 quy trình của Solution rồi đấy."* (ghi nhận, không phải lệnh).
|
||||||
|
|
||||||
|
- [x] **Đ4-a — designer redesign** (`sub-frontend-designer-dot4.md`, §5 lead on-behalf): BỎ đúng khối khoanh · THÊM tab 4-GĐ mang count + 2 bảng chi tiết (PE 7 cột + HĐ) + cột **Hành trình 4-chấm** per-row + phân trang + tìm kiếm (param BE verify thật §2) · trang **59,8 KB**, build PASS · 🔴 nỗi lo CSS-token của vai (chết #53 đúng lúc verify): **lead verify hộ SẠCH** — 0 class ghép động, 3 lookup-map tĩnh
|
||||||
|
- [x] **Đ4-b — reviewer** `PASS_WITH_FLAGS — 7 FLAG (0H/2M/5L)` (`sub-reviewer-diff-dot4.md` 28.510B; vì designer chưa kịp tự-review FD4 ⇒ reviewer là lớp duy nhất, trục 6 a11y đo 10/10 mục). **Disposition TỪNG DÒNG:**
|
||||||
|
· F1[M] mobile HĐ ngõ-cụt → **FIX** mirror matchMedia trang đích · F2[M] dot "chưa tới" nói chắc điều không biết (3 ca — ca-3: HĐ DangDongDau/DaPhatHanh = GĐ4 thật mà chấm GĐ3) → **FIX** per-dot states + `linked` (contractId THẬT) + `contractDots` suy diễn tuyến-tính-CÙNG-module + aria chỉ kể chấm CÓ thông tin · F3[L] pager race → **FIX** page-state · F4[L] aria-live biến mất khi 0 kết quả → **FIX** role=status sr-only · F5[L] Hành trình mất trên mobile → **FIX** 4-chấm vào sub-line · F7[L] nút X chờ debounce → **FIX** clearSearch() · **F6[L] tab-count vs search → PARK** (chờ UAT; 🔴 **ERRATA commit-message `a2bbcb9`**: câu "co chu thich o code" cho F6 là **SAI** — không có chú thích nào được thêm; disposition thật = PARK. Khai tại đây, không amend)
|
||||||
|
- [x] **Đ4-c — nhãn "Bảng cứng"**: prod `suffix=7` (UTF-16 file, idempotent NOT-LIKE guard) + seeder tree.Add + labelBackfill 7 entry. 🔸 Theo VERBATIM anh vẽ "Bảng cứng" (nếu ý anh là "Bản cứng" — 1 lệnh đổi được, guard NOT-LIKE vẫn idempotent)
|
||||||
|
- [x] **Đ4-d — build PASS 495ms · Infra 517 PASS · commit `a2bbcb9` push** (2 file, 1052+/338−)
|
||||||
|
- [!] **Đ4-e — cicd verify `a2bbcb9`** đang chạy (marker bản mới + control âm "chưa tới"/"HĐ ĐANG SOẠN" vắng + restart-durability lần 2 của 38-key)
|
||||||
|
|
||||||
|
<!-- ARC-2: 4 đợt · 4 commit (`b5799fc`·`1a47a61`·`2a72695`·`a2bbcb9`) · prod-data 7 lượt SQL -->
|
||||||
|
|
||||||
- [x] **T2 — Prod menu data** ✅ **LIVE + VERIFY TRỌN** (`prod-backup-pre-apply.txt` → apply `hide=3 upd=99 ins=376` → `prod-verify-post-apply.txt`):
|
- [x] **T2 — Prod menu data** ✅ **LIVE + VERIFY TRỌN** (`prod-backup-pre-apply.txt` → apply `hide=3 upd=99 ins=376` → `prod-verify-post-apply.txt`):
|
||||||
· Inspect lộ thực-trạng ≠ giả định: Master subtree có matrix 13-row/key (canread 2/13) nhưng **`Ct_*` 28 key = 0 ROW** (chưa từng seed), `Contracts` root 1 row ⇒ INSERT 376 + UPDATE 99, số khớp toán từng đồng (9×11=99 · 28×13+12=376)
|
· Inspect lộ thực-trạng ≠ giả định: Master subtree có matrix 13-row/key (canread 2/13) nhưng **`Ct_*` 28 key = 0 ROW** (chưa từng seed), `Contracts` root 1 row ⇒ INSERT 376 + UPDATE 99, số khớp toán từng đồng (9×11=99 · 28×13+12=376)
|
||||||
|
|||||||
@ -100,3 +100,106 @@ Nền so sánh: `HEAD` = 766 dòng và ĐÃ chứa hero + các bản vá vòng-1
|
|||||||
`fe-user/src/pages/UserDashboardPage.tsx:606` đặt `aria-live="polite"` lên dòng "Hiển thị x–y trong N", nhưng chính khối chứa nó bị gỡ khỏi cây khi không có kết quả (`:601` — `if (!data || data.total === 0) return null`). Nghĩa là đúng lúc cần thông báo nhất — gõ một từ khoá không khớp gì — vùng thông báo biến mất, và khối trạng thái rỗng thay thế nó (`:704-731`) thì không nằm trong bất kỳ vùng `aria-live` nào. Người dùng bàn phím/trình đọc gõ xong sẽ không nghe gì cả và không biết bảng vừa đổi.
|
`fe-user/src/pages/UserDashboardPage.tsx:606` đặt `aria-live="polite"` lên dòng "Hiển thị x–y trong N", nhưng chính khối chứa nó bị gỡ khỏi cây khi không có kết quả (`:601` — `if (!data || data.total === 0) return null`). Nghĩa là đúng lúc cần thông báo nhất — gõ một từ khoá không khớp gì — vùng thông báo biến mất, và khối trạng thái rỗng thay thế nó (`:704-731`) thì không nằm trong bất kỳ vùng `aria-live` nào. Người dùng bàn phím/trình đọc gõ xong sẽ không nghe gì cả và không biết bảng vừa đổi.
|
||||||
|
|
||||||
**Fix đề nghị:** giữ vùng `aria-live="polite"` luôn tồn tại (khi rỗng thì thông báo "Không tìm thấy phiếu nào khớp"), hoặc gắn `role="status"` cho khối trạng thái rỗng.
|
**Fix đề nghị:** giữ vùng `aria-live="polite"` luôn tồn tại (khi rỗng thì thông báo "Không tìm thấy phiếu nào khớp"), hoặc gắn `role="status"` cho khối trạng thái rỗng.
|
||||||
|
|
||||||
|
### FLAG-5 [L] — tính năng chủ đạo của đợt-4 biến mất trên điện thoại
|
||||||
|
|
||||||
|
Cột "Hành trình" — thứ mà chính đầu bài gọi là tầng liên kết thứ hai, và là câu trả lời cho yêu cầu "nhìn vào vẫn thấy được tổng quan liên kết" — được đặt `hidden … md:table-cell` ở cả hai bảng (`fe-user/src/pages/UserDashboardPage.tsx:672` và `:866`, kèm ô dữ liệu `:812` và `:977`). Dưới 768px cột này không tồn tại, nên trên điện thoại người dùng chỉ còn dải hero ở trên, không còn thông tin "từng phiếu đang đứng đâu".
|
||||||
|
|
||||||
|
Chọn ẩn cột trên màn hẹp là quyết định hợp lý về mặt bố cục — tôi không đòi hiện nguyên bảng 7 cột trên điện thoại. Điều đáng ghi là **phần bị hy sinh lại đúng là phần được giao làm**, và dòng phụ gộp cho mobile (`:765-767`) chỉ gộp Dự án + Ngày, không gộp hành trình.
|
||||||
|
|
||||||
|
**Fix đề nghị:** đưa cụm 4 chấm vào dòng phụ trên mobile (cùng chỗ với Dự án · Ngày), hoặc đổi ngưỡng ẩn của cột Người soạn/Ngày để nhường chỗ cho Hành trình.
|
||||||
|
|
||||||
|
### FLAG-6 [L] — hai con số đếm cùng một thứ, hiện cạnh nhau, không khớp nhau khi đang tìm kiếm
|
||||||
|
|
||||||
|
Số trên thẻ tab lấy từ `peTotalQuery` / `contractTotalQuery` (`:1333`) — đây là tổng **không kèm từ khoá**. Bảng ngay bên dưới lại lọc theo `search` (`:1147`, `:1159`), và dòng phân trang in tổng **có kèm từ khoá** (`:608`). Gõ một từ khoá xong, cùng một màn hình sẽ có thẻ tab ghi "Giai đoạn 1 · 57" trong khi dòng dưới ghi "Hiển thị 1–3 trong 3 phiếu".
|
||||||
|
|
||||||
|
Không sai dữ liệu — hai số đo hai thứ khác nhau — nhưng đặt cạnh nhau mà không chú thích thì người đọc phải tự đoán.
|
||||||
|
|
||||||
|
**Fix đề nghị:** khi `search` khác rỗng thì cho thẻ tab đang chọn lấy số từ chính kết quả danh sách (`peList.data.total`), hoặc thêm một chữ nhỏ "trên tổng 57" cạnh dòng phân trang.
|
||||||
|
|
||||||
|
### FLAG-7 [L] — hai nút cùng nghĩa "xoá tìm kiếm" nhưng phản hồi khác nhau
|
||||||
|
|
||||||
|
Nút chữ X trong ô tìm (`:556-558`) chỉ gọi `onChange('')`, tức chỉ đổi `searchInput`, rồi phải chờ hết 300ms gỡ nhịp mới thực sự bỏ lọc. Nút "Xoá tìm kiếm" ở khối trạng thái rỗng (`:717`, `:911`) gọi `clearSearch()` (`:1136-1140`) đặt lại cả `searchInput`, `search` và `page` **ngay lập tức**. Cùng một hành động, một đường có độ trễ, một đường không.
|
||||||
|
|
||||||
|
**Fix đề nghị:** cho nút X gọi thẳng `clearSearch()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TRỤC 6 — bảng có tử tế với bàn phím / trình đọc không, và mỗi tab có đủ 3 trạng thái không
|
||||||
|
|
||||||
|
Đo trên mã, từng mục:
|
||||||
|
|
||||||
|
| Mục | Có / Không | Chỗ đo |
|
||||||
|
|---|---|---|
|
||||||
|
| `<caption>` cho bảng | CÓ, cả 2 bảng, `sr-only`, câu nói rõ cột Hành trình dùng để làm gì | `:660-663`, `:854-857` |
|
||||||
|
| `scope="col"` mọi ô tiêu đề | CÓ, 7/7 mỗi bảng | `:666-672`, `:860-866` |
|
||||||
|
| `scope="row"` cho ô đầu dòng | CÓ (ô Mã phiếu / Mã HĐ là `<th scope="row">`) | `:740`, `:934` |
|
||||||
|
| Bấm dòng bằng bàn phím | CÓ — `<tr onClick>` chỉ là tiện cho chuột; điểm dừng bàn phím THẬT là `<button>` trong ô Mã, có `stopPropagation` nên không kích hoạt hai lần | `:735-751`, `:929-945` |
|
||||||
|
| Không lồng nút trong nút | ĐÚNG — `PeUrgentChips` và `PeFinalizeChip` chỉ dựng `<span>` + `title`, không có phần tử bấm được | `components/pe/PeUrgentChips.tsx`, `components/pe/PeFinalizeChip.tsx` |
|
||||||
|
| Trạng thái đang tải, theo từng tab | CÓ — `TableSkeleton` dùng một ô `colSpan` nên không lệch khi cột bị ẩn theo bề rộng | `:574-590`, dùng ở `:677`, `:871` |
|
||||||
|
| Trạng thái lỗi, theo từng tab | CÓ — khối lỗi riêng + nút "Thử lại" gọi đúng `refetch()` của query đó, KHÔNG gộp vào băng lỗi của hero | `:686-702`, `:880-896`, nối ở `:1366`, `:1394` |
|
||||||
|
| Trạng thái rỗng, theo từng tab | CÓ, và tách hai ca: rỗng vì chưa có dữ liệu (nút tạo mới) ⟂ rỗng vì tìm không ra (nút xoá tìm kiếm) | `:704-731`, `:898-925` |
|
||||||
|
| Đang tải lại thì báo gì | `aria-busy` trên `<tbody>` + làm mờ 60%, có tôn trọng `motion-reduce` | `:680-684`, `:874-878` |
|
||||||
|
| Thanh tab đúng khuôn WAI-ARIA | CÓ — `role="tablist"` + 4 `role="tab"` mang `id`/`aria-selected`/`aria-controls`, tabIndex luân phiên, panel `role="tabpanel"` + `aria-labelledby`; phím ←/→ cuộn vòng, Home/End nhảy biên | `:1323-1327`, `:466-480`, `:1346`, `:1123-1134` |
|
||||||
|
| Vùng bấm đủ lớn | CÓ — nút X 24×24, dòng số liệu hero cao ~32px, đều có chú thích lý do trong mã | `:560`, `:266` |
|
||||||
|
| Tương phản chữ nền | ĐẠT — tôi **tự tính lại** từ mã màu trong `index.css`, không lấy số của người làm: `brand-700 #1b6aa3` + trắng = **5,77:1**; `teal-700 #0a7170` + trắng = **5,82:1**; `brand-800 #175685` trên trắng = **7,76:1**; chữ `text-white/90` trên nền `brand-700` = **5,01:1**. Cả bốn đều qua ngưỡng AA 4,5:1, và ba số đầu khớp đúng con số người làm khai. |
|
||||||
|
| Màu dùng có thật trong bảng màu | CÓ — mọi `var(--color-…)` gọi trong `dotVisual`/`STAGE_TONE` (`brand-500/600`, `teal-500/600`, `amberx-500`, `accent-500`) đều được khai trong `@theme` (`fe-user/src/index.css:5-52`), và file **không** có dòng `--color-*: initial` nào nên các nấc mặc định của Tailwind (`slate`, `red`, `emerald`, `violet-800`…) vẫn còn. Không có màu ma. |
|
||||||
|
|
||||||
|
Một khoản nhỏ không tính FLAG: khung cuộn ngang của bảng (`:658`, `:852` — `overflow-x-auto`) không nhận được tiêu điểm bàn phím, nên người chỉ dùng bàn phím không cuộn ngang được bằng phím mũi tên. Vì mọi cột tràn đều đã bị ẩn theo bề rộng nên gần như không xảy ra; ghi lại cho đủ.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TRỤC 1 — khối bị owner khoanh đã biến hẳn chưa
|
||||||
|
|
||||||
|
Quét cây làm việc bằng các chuỗi nhận dạng của khối cũ (`Việc của tôi`, `gần đây`, `StatCard`, `STAT_TONE`, `SlaTimer`, `SectionTitle`, `Tổng giá trị nháp`, `đang soạn`, `Sắp quá hạn`, `my-contracts-recent`, `my-pe-recent`): chỉ còn **hai dòng chú thích** kể lại việc đã gỡ (`:12`, `:1034`) và ba trường của kiểu `MyDashboard` (`:94`, `:96`, `:98`) mô tả hình dạng dữ liệu backend trả về. **Không còn một dòng giao diện nào.** Đây là phân biệt dùng ⟂ nhắc-đến, không phải bỏ nửa vời.
|
||||||
|
|
||||||
|
Bằng chứng phụ mạnh hơn cả grep: `fe-user/tsconfig.app.json` bật `noUnusedLocals` và `noUnusedParameters`, mà `npx tsc --noEmit -p tsconfig.app.json` chạy sạch ⇒ **không còn import hay biến mồ côi nào** của khối đã gỡ (`StatCard`, `SlaTimer`, `SectionTitle`, các icon `Pencil`/`Inbox`/`Clock`/`Coins`). Nếu sót, trình biên dịch đã chặn.
|
||||||
|
|
||||||
|
**Hero còn nguyên:** 4 thẻ giai đoạn, mũi tên nối, 6 dòng số liệu, băng lỗi + nút "Thử lại", và toàn bộ 8 đích điều hướng cũ — đối chiếu HEAD ⟂ cây làm việc đều khớp. Thay đổi duy nhất là nhãn/biểu tượng nay đọc từ mảng `STAGES` thay vì viết tay trong JSX.
|
||||||
|
|
||||||
|
## TRỤC 4 — bản đồ trạng thái sang chấm có ca nào rơi không
|
||||||
|
|
||||||
|
Liệt kê vét cạn, không lấy mẫu:
|
||||||
|
|
||||||
|
- **Phiếu (`peDot`, `:401-408`)** đi qua `getPeDisplayStatus` (`types/purchaseEvaluation.ts:104-110`). Toàn bộ 10 giá trị phase khai trong `PurchaseEvaluationPhase` (`:21-32`) đều có chỗ đáp: `1` → nháp; `7` → xong; `98` → bị trả lại; `99` → dừng; `2,3,4,5,6,10` → đang chạy. **Không ca nào rơi**, nhánh mặc định là "đang chạy" — an toàn, vì mọi phase còn lại đều thật sự là phase trung gian.
|
||||||
|
- **Hợp đồng (`contractDot`, `:410-416`)** so thẳng với `ContractPhase` (`types/contracts.ts:1-14`): `9` → xong; `99` → dừng; `98` → bị trả lại; `1,2` → nháp; `3,4,5,6,7,8,10` → đang chạy. **Không ca nào rơi.**
|
||||||
|
|
||||||
|
Trục này ĐẠT về mặt "không rơi ca". Phần sai nằm ở chỗ khác — xem FLAG-2 (chấm của các giai đoạn KHÔNG phải giai đoạn của dòng đó bị gán nghĩa "chưa tới").
|
||||||
|
|
||||||
|
## Quan sát KHÔNG tính FLAG (ghi để khỏi bị đào lại)
|
||||||
|
|
||||||
|
- **Không có dấu vết giả lập.** Rà toàn bộ dòng thêm mới: 0 lần `// Mock`, 0 `alert(`, 0 `TODO/FIXME`, 0 `console.`. Bảy lần khớp chữ `placeholder` đều là thuộc tính ô tìm kiếm (dùng thật), không phải đánh dấu chỗ làm dối.
|
||||||
|
- **An toàn.** Không `dangerouslySetInnerHTML`, không `eval`, không địa chỉ máy chủ hay khoá cứng trong mã. Dữ liệu hai bảng lấy từ hai endpoint đã lọc theo người dùng ở tầng backend (`ContractFeatures.cs:297-303` và khối IDOR trong `PurchaseEvaluationFeatures.cs`) ⇒ bảng mới **không** mở rộng phạm vi dữ liệu người dùng được thấy so với trước, chỉ hiển thị nhiều dòng hơn của cùng phạm vi đó.
|
||||||
|
- **Tật cũ từ vòng-1 chưa xử (không tính là mới):** hai ô của giai đoạn 3 vẫn lấy số từ `/reports/my-dashboard` nhưng dẫn sang `/inbox` — hai nơi đếm theo hai luật khác nhau (vòng-1 FLAG-4). Owner đã biết, để lại là lựa chọn của owner.
|
||||||
|
- **Ba trường `draftsInProgress` / `dueSoon` / `draftsTotalValue`** trong kiểu `MyDashboard` không còn nơi nào đọc. Vô hại (nó tả hình dạng dữ liệu backend trả về, không phải mã chết chạy được). Nhưng đáng nói một câu: "Sắp quá hạn" nay không còn xuất hiện ở đâu trên trang chủ — đúng ý owner đã yêu cầu bỏ, chỉ ghi lại để owner biết mình đã mất cảnh báo đó.
|
||||||
|
|
||||||
|
## Chỗ chịu được soi (ghi lại vì có giá trị đối chứng lần sau)
|
||||||
|
|
||||||
|
- **Gom nhãn 4 giai đoạn về một mảng `STAGES`** khiến hero và thanh tab không thể lệch tên nhau nữa — đây là cách chữa đúng gốc cho một thứ đã trôi qua ba đợt sửa, không phải chép tay cẩn thận hơn.
|
||||||
|
- **Cách dùng lại `['pe-inbox']`**: giữ nguyên khoá và hàm tải, chỉ đếm bằng `select` ở phía quan sát. Đúng chỗ đúng cách — nếu đổi hàm tải thành trả `.length` dưới cùng khoá thì đã phá cache của trang Hộp thư. Trong mã có ghi rõ lý do cấm làm vậy (`:1066-1069`).
|
||||||
|
- **Bốn con số tương phản và toàn bộ mã màu**: tôi tự tính lại và tự tra `@theme`, cả hai đều đứng.
|
||||||
|
- **Khung xương khi đang tải dùng một ô `colSpan`** thay vì dựng đủ 7 ô — tránh được lỗi lệch cột khi cột bị ẩn theo bề rộng màn hình.
|
||||||
|
|
||||||
|
## Sai lệch giữa lời khai và đĩa (không phải lỗi mã, nhưng phải nói)
|
||||||
|
|
||||||
|
`sub-frontend-designer-dot4.md` §4 khai *"766 → **1.130 dòng**"*. Đo lại trên đĩa: **1.413 dòng** (`wc -l`), và phép cộng từ diff cũng ra đúng số đó: 766 − 337 + 984 = 1.413. Con số 1.130 lệch 283 dòng. Không ảnh hưởng gì tới mã, nhưng ai đọc sổ sau này mà trích lại con số đó thì trích phải số sai.
|
||||||
|
|
||||||
|
## Giới hạn của lượt soi này — khai thẳng
|
||||||
|
|
||||||
|
1. **Không kiểm chứng trực tiếp trên máy chủ.** Chưa triển khai, `/dashboard` lại nằm sau đăng nhập. Mọi kết luận về bố cục, tương phản thực tế, hành vi khi bấm đều là **suy luận từ mã**. Chưa có một khung hình nào được nhìn bằng mắt.
|
||||||
|
2. **Bản chạy `tsc` sạch của tôi yếu hơn vẻ ngoài của nó.** `fe-user/tsconfig.app.json` **không** bật `strict` cũng không bật `strictNullChecks` (tôi đã tra cả `tsconfig.json` gốc — chỉ có `references`, không có cờ nào). Nên "biên dịch sạch" ở đây chứng minh được: không import thừa, không biến mồ côi, tên thuộc tính có thật. Nó **không** chứng minh được an toàn null. Đây là cấu hình sẵn có của dự án, không phải chuyện của diff này.
|
||||||
|
3. **Hai điểm tôi không bác được bằng cách soi tĩnh:** mẹo `w-full max-w-0` để cắt chữ trong ô bảng có ăn đúng ở mọi bề rộng không, và dải 4 chấm có đọc ra "hành trình" bằng mắt thường không. Cần người nhìn màn hình thật.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## KẾT LUẬN
|
||||||
|
|
||||||
|
**Verdict: PASS_WITH_FLAGS — 7 FLAG (0 Nặng / 2 Vừa / 5 Nhẹ).**
|
||||||
|
|
||||||
|
Nói rõ vì sao không có mục nào xếp mức Nặng, để không ai hiểu là tôi hạ chuẩn cho hợp ý: tôi đã tìm đúng bốn lớp lỗi thuộc mức đó và không thấy lớp nào — dữ liệu hiển thị sai (đã đối chiếu từng tham số với controller và từng trường với DTO), lỗ hổng phân quyền (không có endpoint mới; hai endpoint đang dùng đều lọc theo người dùng ở backend), vỡ biên dịch (tự chạy `tsc`, sạch), và hồi quy so với HEAD (đã đo từng bản vá của vòng-1, còn nguyên cả sáu). Hai mục mức Vừa đều là chuyện đúng-sai về hành vi và câu chữ, sửa được trong vài dòng, và một trong hai là tật kế thừa chứ không phải do đợt này gây ra.
|
||||||
|
|
||||||
|
**Thứ tự nên sửa:** FLAG-1 (ngõ cụt trên điện thoại) → FLAG-2 (bỏ khẳng định "chưa tới") → FLAG-3 (phân trang tính từ state) → còn lại tuỳ owner.
|
||||||
|
|
||||||
|
**Bốn yêu cầu của đầu bài, đối chiếu từng cái:** khối bị khoanh đã bỏ hết (không sót dòng giao diện nào) · hero giữ nguyên (8 đích điều hướng khớp HEAD) · bảng chi tiết có đủ 7 cột cho giai đoạn 1 gồm cột Hành trình, có bảng hợp đồng cho giai đoạn 3 kèm nút "Tạo HĐ mới" ở trạng thái rỗng, giai đoạn 2 và 4 dẫn sang `/coming-soon?stage=N` (route có thật, và `STAGE_INFO` có đủ cả khoá `'2'` lẫn `'4'` nên không rơi về nhầm giai đoạn) · phân trang và tìm kiếm đều dựa trên tham số backend CÓ THẬT, không phải làm cho có.
|
||||||
|
|
||||||
|
<!-- END sub-reviewer-diff-dot4 · TOTAL=7 FLAG (0H/2M/5L) · verdict=PASS_WITH_FLAGS -->
|
||||||
|
|||||||
Reference in New Issue
Block a user