wal: flush 20260812T1858

This commit is contained in:
pqhuy1987
2026-08-12 18:58:46 +07:00
parent 1cc16a2db3
commit 3addd373e2
5 changed files with 203 additions and 14 deletions

View File

@ -23,9 +23,13 @@ chain:
non (be2 phủ anchor 9/9 — khai, không relaunch) · be2 FIXES-15 (style-list tự đánh nhau non (be2 phủ anchor 9/9 — khai, không relaunch) · be2 FIXES-15 (style-list tự đánh nhau
spec v2.1 → LẬT; BS-3 số thật 12/13 CanDelete=0; T-matrix 13 ca; ghost-wire spec v2.1 → LẬT; BS-3 số thật 12/13 CanDelete=0; T-matrix 13 ca; ghost-wire
AuditBehavior). → STYLE-GATE-V2 land (style-gate-v2.md, delta-thắng-nền) + spec v2.2. AuditBehavior). → STYLE-GATE-V2 land (style-gate-v2.md, delta-thắng-nền) + spec v2.2.
[!] B5 /fable-real reviewer — tổng hợp cuối → plan wave FINAL. wf: wf_bc763d7d-aca · [x] B5 GO_WITH_NOTES — 9 mâu thuẫn (6 CHỐT 0 treo; X-5 comment stale file-2 · KHKK :1763
artifact sub-b5-plan-final.md (mâu-thuẫn-chéo 3 tầng + plan per-wave + Đ-dừng) thiếu nấc-0 null-guard "chép nguyên khuôn cũng là bẫy") · plan 31.9KB sub-b5-plan-final.md
[ ] B6 hmw fan-out Opus 5 MAX execute toàn plan (per-wave gate + cicd 2 app + prod verify) (5 wave block + Đ-1..Đ-10) · spec v2.3 (W5(i) → T-matrix + STATUS 2-ô)
[!] B6 fan-out Opus 5 MAX — TUẦN TỰ: ✅W4 commit 1cc16a2d (4/4 ô máy · 7 dòng · 0 Đ ·
lead-gate PASS — nhánh đúng khối + comment khuôn; 3 ô hành-vi DEFERRED-TO-LOCAL-VERIFY
gộp đo ④ cuối) → 🔄 W1 wf_d3466c9d-0fc (bug menu A+B + slice HD-*, 2 file fe-user)
→ W2 → W3 → W5. Commit per-wave, PUSH GOM CHỜ (CI runner P0). W2/W5 + reviewer-gate.
[!] TỒN CŨ — CỬA NGOÀI-GIỜ (anh gật giờ): dọn workspace runner + dispatch verify CI · [!] TỒN CŨ — CỬA NGOÀI-GIỜ (anh gật giờ): dọn workspace runner + dispatch verify CI ·
(3B) Gitea cron + compact Vietreport_Master + báo VIETREPORT · đo lại A/059 (3B) Gitea cron + compact Vietreport_Master + báo VIETREPORT · đo lại A/059
[ ] chờ anh (10+3 tồn): (76)(77)(78)(79) · (89) · (74) · value_protect · lật ô YC-008↔YC-010 [ ] chờ anh (10+3 tồn): (76)(77)(78)(79) · (89) · (74) · value_protect · lật ô YC-008↔YC-010

View File

@ -118,11 +118,11 @@ nào phụ thuộc layout mới; W5 cuối — BE + test-before).
bật `pendingMe=1` list **ÍT HƠN** list thường + không-đến-lượt **VẮNG**; control-dương: user bật `pendingMe=1` list **ÍT HƠN** list thường + không-đến-lượt **VẮNG**; control-dương: user
ngoài luồng **0 dòng** (không phải cả list); ✅(ii) leaf "Đang duyệt" lại `/my-contracts`; ngoài luồng **0 dòng** (không phải cả list); ✅(ii) leaf "Đang duyệt" lại `/my-contracts`;
✅(iii) build fe-user PASS. ✅(iii) build fe-user PASS.
- **W5:** ✅(i) `dotnet test SolutionErp.slnx` PASS **680 → 680+N** (pin số; cập nhật ĐÚNG 1 row test - **W5:** ✅(i) 🔄 v2.3 (B5 X-1/X-2): `dotnet test SolutionErp.slnx` PASS **680 → 680+N** ca test =
`docs/STATUS.md`), test-before liệt đủ ca: TraLai(98)/TuChoi(99) Drafter xoá được · DangSoanThao **T-matrix T-1..T-13 + phản-ca T-14 trong `style-gate-v2.md §C` (NGUỒN DUY NHẤT danh-sách ca
Drafter xoá được (như ) · **ChoDuyet(10) CẤM kể cả Drafter** (khuôn KHKK NGƯỢC v1) · của v2 đã RETIRED tại đây, đừng đọc bản git-history)**; STATUS lật **2 ô**: dòng session
**DaPhatHanh(9) CẤM mọi đường** · user thường xoá người khác **403** (sửa lỗ khai ) · DELTA `680→680+N` tại commit + row canonical `:476` = DEFERRED-TO-DEPLOY (chỉ chép từ
V1 nháp (2) vẫn xoá được (chống quá tay); ✅(ii) changelog: xoá xong đọc-ngược DB ra **đúng 1 cicd-verify deploy đang CHỜ); ✅(ii) changelog: xoá xong đọc-ngược DB ra **đúng 1
dòng** Action=Delete (0 dòng = dẫm T26); ✅(iii) 🔄 v2.1: owner-check trong handler test dòng** Action=Delete (0 dòng = dẫm T26); ✅(iii) 🔄 v2.1: owner-check trong handler test
stranger-403 (khuôn `ContractSigningPlanCrudTests` stranger-case) + KHÔNG thêm stranger-403 (khuôn `ContractSigningPlanCrudTests` stranger-case) + KHÔNG thêm
`[Authorize(Policy` mới vào DELETE (`grep` DELETE-action giữ nguyên shape policy = W8); `[Authorize(Policy` mới vào DELETE (`grep` DELETE-action giữ nguyên shape policy = W8);

View File

@ -1,20 +1,130 @@
# B5 — PLAN FINAL "SOLTUON-12-08-2026" (reviewer /fable-real, tổng hợp cuối YC-029) # B5 — PLAN FINAL "SOLTUON-12-08-2026" (reviewer /fable-real, tổng hợp cuối YC-029)
> Nguồn: spec v2.2 (`spec-soltuon-dieu-chinh-hd-12-08-2026.md`) ⟂ `style-gate-v2.md` ⟂ `anh-mo-ta.md` + bối cảnh B1/B2/B3. > Nguồn: spec v2.2 (`spec-soltuon-dieu-chinh-hd-12-08-2026.md`) ⟂ `style-gate-v2.md` ⟂ `anh-mo-ta.md` + bối cảnh B1 §PROPOSE / B2-synthesis; đối chiếu nền `sub-style-{fe,be}-b3.md` chỗ delta chạm. Mọi neo `file:line` trong file này B5 ĐÃ MỞ ĐĨA ĐO TƯƠI 2026-08-12 (không chép từ artifact).
> Trạng thái: ĐANG GHI — append từng phần (chống #53).
## Verdict tổng ## Verdict tổng
(đang ghi) **B5-PLAN: GO_WITH_NOTES — 9 mâu thuẫn/lỗ soi ra (6 CHỐT tại chỗ, 3 ghi-nhận, 0 treo 50/50), plan 5 wave đủ worker-cầm-làm.**
| Wave | Verdict | Ghi chú 1 dòng |
|---|---|---|
| W4 | **GO** | Neo :176/:188/:189 đo tươi ĐÚNG; 1 file ~6 dòng; C-13 RANGE có răng |
| W1 | **GO** | Gate `ct` phải ĐỦ 3 vế (X-4); +1 việc mới comment stale thứ 2 (X-5) |
| W2 | **GO** | Q1/Q2 nhánh-đã-chọn; bổ quyết định `bodyMaxHClass` (X-3) + cơ chế lọc cây `stage3TypeFilter` opt-in |
| W3 | **GO** | Đổi-endpoint `/contracts/inbox` (0-param, đo đĩa :41-43); CẤM gửi param `pendingMe` lên server |
| W5 | **GO** (test-before) | T-matrix nguồn duy nhất (X-1); T-13 Fact riêng (X-9); STATUS 2-ô, canonical treo cicd-verify (X-2) |
Điểm mấu chốt cho lead: (i) spec §③ W5(i) danh-sách ca + câu "ĐÚNG 1 row STATUS" là **dư-lượng v2.1** — plan này thay bằng T-matrix + 2-ô; đề nghị lead đánh dấu 2 chỗ đó trong spec khi rảnh (B5 không sửa spec — ranh). (ii) Toàn bộ Q3/Q4/Q5 + W8/W9 nằm NGOÀI plan — Đ-8 chặn worker chạm. (iii) Deploy CHỜ ⇒ mọi ô prod-only = DEFERRED-TO-DEPLOY, wave dừng ở local-verify + commit, push gom sau.
## Mâu thuẫn còn sót (X-1..) ## Mâu thuẫn còn sót (X-1..)
(đang ghi) > Soi CHÉO 3 tầng {spec v2.2 ⟂ style-gate-v2 ⟂ T-matrix} + đối chiếu ĐĨA (mọi neo dưới đây B5 đã mở file đo lại, không chép từ artifact). 9 mục: **6 CHỐT** (đổi hành-động, đã nhập vào PLAN) · **3 ghi-nhận** (không đổi hành-động).
- **X-1 [CHỐT — T-matrix thắng] spec §③ W5(i) còn giữ danh-sách ca CŨ đã bị §C thay.** Spec :121-125 vẫn liệt "V1 nháp (2) vẫn xoá được…" trong khi chính spec §② :87-88 tuyên *"Ca test W5 = T-matrix T-1..T-13 + phản-ca T-14 (thay danh sách ca ở ✅W5(i) cũ — ca 'V1 nháp 2' đã bị chứng TRÙNG)"* và be2-R7 (style-gate §B.5) chứng phase-2 TRÙNG T-1 → thay bằng **T-4 DangGopY(3)**. Chốt: worker W5 CHỈ dùng T-1..T-14; câu-chữ §③ W5(i) coi là dư-lượng v2.1. *(Trả lời câu hỏi giao việc: T-4 phase 3 KHÔNG đá allow-list — đo đĩa `ContractPhase.cs:17-27`: DangGopY=**3** < DangInKy=**5** nằm TRONG `<DangInKy {TraLai=98, TuChoi=99}`; T-5 ChoDuyet=10 · T-6 DaPhatHanh=9 đều NGOÀI dải 409 khớp. T-matrix allow-list: 0 ca đá nhau.)*
- **X-2 [CHỐT style-gate thắng] spec §③ W5(i) "cập nhật ĐÚNG 1 row test `docs/STATUS.md`" style-gate §C:77-78 + §D C16 "pin **2 ô**".** Nguyên văn be2 `sub-style-review-b4-be2.md:221`: *"lật 2 ô: dòng session mới (DELTA 680→680+N) VÀ row canonical Tests `:476` (số tuyệt đối, **chỉ chép từ cicd-verify**)"* + R-12 :164-170. Chốt: 2 ô; DEPLOY CHỜ (CI runner P0) commit W5 ghi **dòng session DELTA ngay**, row canonical `:476` **KHÔNG lật đợt này** ghi `DEFERRED-TO-DEPLOY` vào `run.md` theo đúng chế spec §④.4 (đây không phải quên: row `:476` tự khai kỷ luật chỉ chép số đã cicd-verify).
- **X-3 [CHỐT lỗ spec, delta SF-26 chưa được W2 nhắc] spec §② W2 KHÔNG quyết `bodyMaxHClass` cho host mới.** style-gate §A.1 (fe1-R1/SF-26): đặt cây vào host PHẢI quyết tường minh mặc định panel đẻ "2 cuộn lồng" (`PipelineTreePanel.tsx:112-115` tự khai: *"Host 3-panel (Panel-3 TỰ CUỘN) truyền class trần riêng — vd `lg:max-h-none`"*). Host mới = cột trái 400px trong grid `overflow-hidden` (đúng ca host-3-panel S168 F-1). Chốt: panel-1 truyền `bodyMaxHClass="lg:max-h-none"` + cột wrapper tự cuộn; ô đo THÊM: `grep -c "bodyMaxHClass" fe-user/src/pages/contracts/MyContractsPage.tsx` 1 sau khi xoá render `:242` + mắt-đo local "đúng 1 thanh cuộn trong panel cây".
- **X-4 [CHỐT delta fe1-R2 chưa được spec chép đủ] spec §② W1(b) chỉ ghi "clamp `/^[1-7]$/`" THIẾU 2 vế `typeInt === 3 && rawCt !== null`.** Khuôn thật `WorkflowMatrixViewPage.tsx:85-86` gate 3 vế (`typeInt === 10 && rawGroup !== null && /^[1-8]$/`); thiếu vế `typeInt === 3` thì `?type=1&ct=5` lọc HD-* trên bộ Duyệt-NCC = trang trắng (style-gate §A.2). Chốt: PLAN W1 ghi ĐỦ 3 vế.
- **X-5 [CHỐT phát hiện MỚI của B5] comment stale THỨ HAI ngoài tầm C-6.** Spec W1(d) + C-6 chỉ /đo comment `Layout.tsx:141-142`; nhưng `WorkflowMatrixViewPage.tsx:58-59` cũng đang khai *"HĐ dùng CHUNG 1 bộ workflow V2 cho mọi ContractType ⇒ 7 leaf 'Luồng duyệt' … đều trỏ về matrix này"* sau W1 câu này thành nói dối (mỗi leaf slice per-loại). Cùng file worker đang đụng (nhánh `ct` nằm ngay dưới). Chốt: W1 thêm việc sửa comment này; ô đo THÊM: `grep -ci "chung 1 bộ" fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` = 0.
- **X-9 [CHỐT mâu thuẫn NỘI BỘ style-gate §C] T-13 ghi "(gộp vào T-1 được)" chú cuối bảng đòi "`[Fact]` 13".** Gộp T-13 vào T-1 thì chỉ còn 12 Fact (T-14 phản-ca không viết) vỡ ô 13. Chốt: **T-13 viết Fact RIÊNG** thoả cả hai câu, giữ được assert trim-to-null tách bạch (ContextNote==null khi Reason toàn khoảng trắng).
- **X-6 [ghi nhận] spec §② W5(d) viện "tiền lệ PE S155 đo 11/13 role CanDelete=0" be2-R2 đo tươi key `Contracts` LocalDB = 12/13, Drafter=0.** Hai phép đo KHÁC key/thời điểm, CÙNG chiều kết luận (không gắn policy đợt này). PLAN cite số tươi 12/13 (be2-R2); điều kiện mở W8 giữ nguyên: policy + grant Drafter CÙNG commit + đo lại **prod** trước khi gắn.
- **X-7 [ghi nhận] các ô grep §③ W1(iv)/W2(i) ô SF-17 W5(iv) đã bản SIẾT trong §D** (C-8 2 vế + nhãn "canh-thoái-lui" cho vế đã-xanh-baseline; C-11 thêm vế hoán-đổi `=0` baseline 1 B5 đo `MyContractsPage.tsx:242` đúng 1 hit; C-MIRROR-KHAI baseline 0/0 B5 đo cặp `ContractDetailContent` SHA `cd81508f97cb` identical 2 app; W4 thêm ô RANGE C-13 §③ không ). Không mâu thuẫn giá trị PLAN hợp nhất: **§D chuẩn ô-máy, §③ giữ vai ô HÀNH-VI local ④**.
- **X-8 [ghi nhận] wave order W4W1W2W3W5 KHÔNG bị delta nào đổi tiền-đề.** B5 đo tươi neo W4: `fe-admin/Layout.tsx` open `:176` · KhkkN `:188` · close `:189` = ĐÚNG mốc C-13. W3 đứng SAU W2 (chỗ hiển thị list pendingMe = panel-3 do W2 quyết) giữ. W1 W3 đụng cùng vùng `Layout.tsx:148-153` tuần tự (đã đúng thứ tự). SHA-pair baseline W2(iv) khớp đĩa: `usePipelineStages` = `1b9b5c611663…` · `PipelineTreePanel` = `4ffc96beded5…` (KHỚP 2 app, đo sha256sum hôm nay).
## PLAN FINAL SOLTUON-12-08-2026 (per-wave) ## PLAN FINAL SOLTUON-12-08-2026 (per-wave)
(đang ghi) > **Thứ tự BẮT BUỘC: W4 → W1 → W2 → W3 → W5** (X-8: không delta nào đổi tiền-đề). Mỗi wave: local-verify + commit riêng; **PUSH GOM SAU** (deploy CHỜ — CI runner P0). Q1/Q2 = nhánh-đã-chọn (làm theo, anh bác thì đổi); **Q3/Q4/Q5 CẤM đụng** (chờ anh). Ô hành-vi đo theo spec §④ nấc-đo-local; ô không dựng được dữ liệu ⇒ `DEFERRED-TO-DEPLOY` ghi `run.md`, CẤM tick. Ô máy: nối `;` không `&&`, `grep -c … || true`, exit qua `${PIPESTATUS[0]}` (§D đầu bảng).
### W4 — Admin Designer 7 leaf (fe-admin, ≈6 dòng)
- **INPUT đọc:** spec §② W4 + style-gate §D ô C-13, C-1a, C-2, C-5. Không cần file B3.
- **File đích (DUY NHẤT):** `fe-admin/src/components/Layout.tsx`.
- **Việc theo thứ tự:**
1. Import named `findContractWfByTypeCode` từ `@/lib/contractWorkflowFamily` (hàm SẴN `fe-admin/src/lib/contractWorkflowFamily.ts:77-80`; family const `:45`).
2. TRONG khối `if (awV2Match) {` (baseline `:176`), SAU nhánh KhkkN `:188` TRƯỚC `}` `:189`, thêm: ``if (findContractWfByTypeCode(code)) return `/system/approval-workflows-v2/${code}` `` — `code` = phần sau `AwV2_` (vd `HdTP`), GIỮ NGUYÊN casing.
3. Kèm comment 2-3 dòng kiểu Pattern 16-bis như khối `:181-187` sẵn (BE seed leaf resolvePath thiếu nhánh MenuLeaf DROP im lặng gotcha #50).
4. KHÔNG đụng: nhánh `code === 'Contract'` `:178` (nhánh chết, 0 seed) · `DbInitializer` · họ `Khkk_*` (fe-admin cố ý không WorkflowMatrixViewPage `:185-187`).
- **Ô acceptance:** C-13 RANGE (`open < hit < close` `hit > dòng KhkkN`; mốc 176/188/189) · spec W4(i) sidebar đủ **17 leaf** (2 NCC + 8 KHKK + 7 "QT Duyệt …" Order 11..17, đo local ④) · (ii) click "QT Duyệt Thầu phụ" Designer khoá `HD-TP`, history CHỈ HD-TP · (iii) control-âm: `KhkkN1` + `DuyetNcc` mở đúng trang · (iv) C-2 build fe-admin PASS + C-1a eslint fe-admin 42 (37E/5W) KHÔNG TĂNG.
- **Bẫy né:** chèn NGOÀI khối awV2Match = leaf chết câm (#50) · đừng "tiện tay" xoá nhánh chết `:178` (ngoài lệnh).
- **DoD:** build + eslint delta 0 + commit `[CLAUDE] FE-Admin: YC-029 W4 — sidebar Designer 7 leaf QT Duyệt HĐ (resolvePath AwV2_Hd*)`.
### W1 — Danh-tính menu + slice per-loại (fe-user)
- **INPUT đọc:** spec §② W1 (a-d) + style-gate §A.2 (fe1-R2), §A.7 (fe1-R9) + §D ô C-1a/b, C-2, C-6, C-7, C-8, C-9 + X-4/X-5 trên đây.
- **File đích (2):** `fe-user/src/components/Layout.tsx` · `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx`.
- **Việc theo thứ tự:**
1. **(Layout accordion `:470-485`)** GIỮ nhánh Pe (`startsWith('/purchase-evaluations')`, `:476-478`) NGUYÊN VỊ TRÍ ĐẦU. THÊM ưu tiên `ct`: đọc `params.get('ct')`, nếu khớp `/^[1-7]$/` `INT_TO_TYPE_CODE[Number(ct)]` (map SẴN `:27-29`) `setExpandedCtCode` rồi return. Nhánh `?type=` (`:479-484`) CHỈ còn chạy khi `['/my-contracts','/contracts/new','/inbox'].includes(location.pathname)` allowlist EXACT (diệt bug A `?type=3` của workflow-matrix + cross-talk `Hdc_*` `/hard-copies?type=1..7`).
2. **(Layout `:152`)** Approved: `?type=${typeInt}&phase=9` thêm `&view=approved` (khuôn PE; trang bỏ qua param lạ H-4 HELD).
3. **(Layout `:141-142`)** viết lại comment stale: "cả 7 loại đều ?type=3 ( chung 1 bộ workflow V2)" nêu 7 bộ per-loại `HD-*` @S188, `ct` = danh-tính leaf (ô dương C-6 cần chứa 1 token `7 bộ|per-loại|S188`).
4. **(Layout)** 🔴 KHÔNG thêm `ct`/`view`/`pendingMe` vào `TRANSIENT_QUERY_KEYS` `:350` 3 param DANH-TÍNH leaf, cho vào set leaf mất phân biệt active (C-7 canh =0). Đây chính chế diệt bug B: `view` nằm NGOÀI set leaf List tắt khi URL `view=approved`.
5. **(WfViewPage)** const cục-bộ `INT_TO_HD_CODE: Record<number, string>` 7-entry **MỞ `src/Backend/SolutionErp.Domain/Contracts/ContractApprovalWorkflowFamily.cs:33-42` CHÉP ĐÚNG** mapping ContractType (⚠ bản-sao-thứ-3 của họ; token gạch-nối `HD-NT-NCC`/`HD-NT-DV`; comment ghi nguồn + "drift-check = C-9"). KHÔNG import từ fe-admin (`contractWorkflowFamily.ts:16-18` tự khai fe-admin-ONLY).
6. **(WfViewPage)** nhánh `ct` mirror khuôn `group` `:84-99`: `const rawCt = searchParams.get('ct')`; gate **ĐỦ 3 VẾ (X-4)**: `typeInt === 3 && rawCt !== null && /^[1-7]$/.test(rawCt)` `hdCode = INT_TO_HD_CODE[Number(rawCt)]`, else `null`; `workflows = khkkGroup !== null ? …(giữ nguyên)… : hdCode !== null ? allWorkflows.filter(wf => wf.code === hdCode) : allWorkflows`. `ct` rác/thiếu KHÔNG lọc (giữ hành vi ). 🔴 KHÔNG thêm `ct` vào `queryKey` (lý-do ghi sẵn `:88-91` client-side slice của cùng payload).
7. **(WfViewPage `:58-60` X-5)** sửa comment stale thứ 2 " dùng CHUNG 1 bộ workflow V2…" 7 bộ per-loại, `ct` slice.
- **Ô acceptance:** spec W1(i) tái-hiện A local (mở nhóm 01 "Luồng duyệt" accordion GIỮ 01; control-âm `ct=5` nhóm 05) · (ii) tái-hiện B (`?type=1&phase=9&view=approved` CHỈ leaf Đã-duyệt sáng) · (iii) vòng `ct ∈ 1..7` 1 quy trình ĐÚNG HỌ + 0 dòng khác họ + `ct=1` VẮNG `QT-HD-V2-001` + control `ct=99` KHÔNG lọc *(mapping int→code SAI thì C-9 vẫn xanh — chính vòng (iii) này mới khoá mapping)* · C-8 vế-1 `get('ct')` 1 (mới) + vế-2 `workflow-matrix?type=3&ct=` 1 (canh-thoái-lui, đã xanh baseline) · C-9 = 7 distinct · C-6 2 vế · ô X-5: `grep -ci "chung 1 bộ" fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` = 0 · C-7 = 0 · C-1a/b eslint fe-user 47 (41E/6W), `Layout.tsx` 4E **0 ruleId mới** (lỗi sẵn :478 nằm đúng khối accordion đổi hình thì lỗi phải ) · C-2 build fe-user.
- **Bẫy né:** allowlist pathname phải EXACT `startsWith('/contracts')` sẽ nuốt luôn `/contracts/workflow-matrix` (tái sinh bug A) · KHÔNG sửa `:151` (WfView ĐÃ đúng `type=3&ct=` v2 GIỮ type=3, chỉ THÊM logic đọc) · grep `?` phải `-F` (B2 cụm 5).
- **DoD:** build + eslint + 3 ô hành-vi local + commit `[CLAUDE] FE-User: YC-029 W1 — danh-tính menu 7 loại HĐ (accordion ct + view=approved + WfView slice HD-*)`.
### W2 — HOÁN ĐỔI 2 khung: cây trái 400px + list phải 360px (host fe-user; cặp mirror ×2 app)
- **INPUT đọc:** spec §② W2 + Q1/Q2 (nhánh-đã-chọn) + style-gate §A.1 (SF-26), §A.3 (fe1-R3 JSDoc), §A.4 (fe1-R4 Select native), §A.5 (fe1-R5 header) + §D ô C-2, C-3, C-5, C-10, C-11 + X-3 trên đây.
- **File đích (3 + 2 copy):** host `fe-user/src/pages/contracts/MyContractsPage.tsx` (CHỈ fe-user fe-admin không trang này) · cặp mirror `fe-user/src/hooks/usePipelineStages.ts` + `fe-user/src/components/pipeline/PipelineTreePanel.tsx`, sửa xong **COPY NGUYÊN BYTE** sang `fe-admin/src/...` cùng đường dẫn.
- **Việc theo thứ tự:**
1. **(hook cặp mirror)** `BuildStagesOptions` thêm 2 opt-in **FLAT trong opts** (CẤM bịa tầng `stage3Options` F-4 lens-1):
- `onOpenContract?: (id: string) => void` JSDoc theo khuôn `:185-192` sẵn, BẮT BUỘC mệnh đề *"hông truyền ⇒ giữ nguyên `navigate('/contracts/…')` — 6 host còn lại 0 đổi hành vi"* (fe1-R3, gate `grep -cE "hông truyền"` 1 per prop mới);
- `stage3TypeFilter?: number` lọc leaf GĐ3 theo ContractType; JSDoc cùng chuẩn ("hông truyền đủ mọi loại").
Site leaf GĐ3 `:368-378`: `onOpen: opts.onOpenContract ? () => opts.onOpenContract!(contractId) : () => navigate(\`/contracts/${contractId}\`)` (khuôn `:228-230`/`:244`); điều kiện push leaf thêm vế `(!opts.stage3TypeFilter || !c || c.type === opts.stage3TypeFilter)` — **`c` vắng (HĐ ngoài chỉ mục 200, `:371-373`) thì leaf VẪN HIỆN khi filter bật** (không đủ dữ liệu phân loại — thà thừa; ghi comment quyết định này).
2. **(panel — cặp mirror)** `PipelineTreePanel.tsx:111` mở rộng Pick: `'stage2Content' | 'stage2Count' | 'onOpenKhkk' | 'onOpenPe' | 'onOpenContract' | 'stage3TypeFilter'` (GIỮ tên prop `stage2Options`).
3. **(host — grid)** `MyContractsPage.tsx:153`: `lg:grid-cols-[320px_1fr_360px]` → `lg:grid-cols-[400px_1fr_360px]` (khuôn host `KhkkListPage.tsx:171`). **Panel-1 TRÁI = khối MỚI:** thanh đầu = Select loại HĐ (wrapper `ui/select.tsx` NATIVE `:4-19` — fe1-R4, CẤM kéo radix; option đầu "Tất cả loại HĐ" cho ca URL trần; default = `?type=N` của URL, sync khi URL đổi; đổi Select CHỈ đổi state lọc cây — KHÔNG navigate/không đổi list) + `<PipelineTreePanel currentStage={3} hideHeader hideSearch bodyMaxHClass="lg:max-h-none" stage2Options={{ onOpenContract: id => setSelectedId qua ?id=, stage3TypeFilter: selectLoại || undefined }} />`; cột wrapper TỰ CUỘN (X-3/SF-26 — mặc định panel = 2 cuộn lồng). Nếu panel-1 cần header bar riêng: dùng đúng chuỗi class `KhkkListPage.tsx:157` (fe1-R5).
4. **(host — panel-3 PHẢI 360px)** = khối DANH-SÁCH HĐ hiện tại CHUYỂN CHỖ NGUYÊN CHỨC NĂNG (search + empty-state + lọc `phase=9`/`deleted=1` hiện có — Q1: KHÔNG xoá khối UI anh không bảo xoá; HĐ tạo-trực-tiếp-ngoài-gói luôn thấy được ở đây). **Search ĐỔI client→server**: nối ô tìm vào param `search` của `GET /contracts` (SẴN `ContractsController.cs:19` — nói thẳng: đây là ĐỔI hành-vi, F-10 lens-2).
5. **(host — panel-2 GIỮA)** giữ nguyên detail/placeholder; click leaf cây → chọn vào panel-2 qua `?id=` — URL Ở LẠI `/my-contracts`. Xoá render cây cũ `:242` (`{!selectedId && <PipelineTreePanel …}`) — C-11 vế-2 về 0.
6. Copy 2 file cặp sang fe-admin (fe-admin KHÔNG truyền 2 opt-in ⇒ control-âm: hành-vi cũ giữ nguyên, kể cả `ContractsListPage.tsx:265` fe-admin).
- **Ô acceptance:** C-11 2 vế (`grid-cols-[400px` ≥1 **VÀ** `{!selectedId && <PipelineTreePanel` = 0 — baseline B5 đo `:242` = 1) · ô X-3: `grep -c "bodyMaxHClass" MyContractsPage.tsx` ≥1 + mắt-đo local đúng 1 thanh cuộn trong panel cây · C-3 vòng 4 cặp + guard MISSING, 2 cặp W2 SHA MỚI khớp nhau 2 app (baseline cũ `1b9b5c61`/`4ffc96be` SẼ đổi — khớp-cặp là chuẩn, không phải khớp-baseline) · C-10 = 0 **+ ô THÊM cùng dạng cho token `stage3TypeFilter`** (đổi token trong đúng lệnh C-10) = 0 · spec ✅W2(ii) click leaf → panel-2, URL ở lại (local ④) · (iii) Network tab `/contracts?search=` (server-side) · (v) control-âm fe-admin: leaf GĐ3 vẫn `navigate('/contracts/{id}')`; leaf GĐ1/GĐ2/GĐ4 không đổi ở CẢ 2 app · (vi) deep-link `/contracts/{id}` dán thẳng vẫn mở detail · (vii) HĐ ngoài-gói hiện panel-3 · (viii) C-2 build ×2 app + eslint C-1a/b KHÔNG TĂNG.
- **Bẫy né:** SF-26 hai-thanh-cuộn-lồng (S168 F-1) · `hideHeader` nuốt nút "Hiện/Ẩn cây" mobile — panel đã tự ép thân hiện (`PipelineTreePanel.tsx:117-122`), vẫn PHẢI kiểm viewport `<lg` local còn thấy cây · sửa tay 2 bản mirror thay vì copy-byte = SHA lệch · Select đổi loại mà navigate = đổi cả list (SAI — chỉ lọc cây).
- **DoD:** build ×2 + SHA cặp khớp + hành-vi local + commit `[CLAUDE] FE-User: YC-029 W2 hoán đổi cây toàn trình sang trái + Select loại + list phải (mirror cặp pipeline ×2 app)`.
### W3 — Đang-duyệt ở lại trang (fe-user, FE-only — khuôn PE đổi-endpoint)
- **INPUT đọc:** spec §② W3 + §D ô C-2, C-7. Khuôn: `fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx:91-98`.
- **File đích (2):** `fe-user/src/components/Layout.tsx` (`:150`) · `fe-user/src/pages/contracts/MyContractsPage.tsx`.
- **Việc theo thứ tự:**
1. Layout `:150`: `if (action === 'Pending') return \`/my-contracts?type=${typeInt}&pendingMe=1\`` (thay `/inbox?type=`). GIỮ route `/inbox` + `InboxPage` nguyên trạng — hộp thư tổng.
2. MyContractsPage: đọc `pendingMe` từ URL. Khi `= '1'` ⇒ panel-3 ĐỔI ENDPOINT: `GET /contracts/inbox` (0-param — SẴN `ContractsController.cs:41-43`, trả `List<ContractListItemDto>`) + lọc `type` CLIENT-SIDE (`c.type === Number(typeParam)`), ép shape về dạng list thường (khuôn PE `:98`: `{ items, total: length, page: 1, pageSize: length }`); ẩn/khoá phân trang + search-server ở chế độ này (inbox không phân trang — payload nhỏ, đúng hiện trạng `InboxPage.tsx:103`). Comment khai lệch-W7 CHẤP NHẬN + `[carry:w7b-inbox-param]` (nâng BE là việc khác đợt).
- **Ô acceptance:** spec ✅W3(i) so-số 2 chiều local ④ (user có ≥1 HĐ đến-lượt + ≥1 không-đến-lượt: bật `pendingMe=1` ⇒ list ÍT HƠN + HĐ không-đến-lượt VẮNG; control-dương: user ngoài luồng ⇒ **0 dòng**, không phải cả list) · (ii) leaf "Đang duyệt" ở lại `/my-contracts` · C-7 = 0 (pendingMe KHÔNG vào TRANSIENT — danh-tính leaf) · C-2 build fe-user + eslint không tăng.
- **Bẫy né:** 🔴 CẤM gửi `?pendingMe=1` lên `GET /contracts` — BE KHÔNG có param này, ASP.NET NUỐT im lặng ⇒ list y hệt list thường (B2 BLOCKER ❷; đổi-endpoint là khuôn đúng) · queryKey phải tách trục `pendingMe` (2 nguồn dữ liệu khác nhau — đừng dùng chung key gây cache lẫn).
- **DoD:** build + đo 2 chiều + commit `[CLAUDE] FE-User: YC-029 W3 — leaf Đang duyệt ở lại /my-contracts (pendingMe đổi-endpoint /contracts/inbox)`.
### W5 — Đường XOÁ đồng-khuôn KHKK (BE nhỏ + FE, 0-mig, TEST-BEFORE)
- **INPUT đọc:** spec §② W5 (a)-(e) + style-gate §B TRỌN (be2-R1/R2/R7/R13/R14) + §C T-matrix + §D khối BE (**MỞ `sub-style-review-b4-be2.md` mục "Ô đo phải sửa" `:198` chạy đúng bảng C1-C21 đã sửa**) + §D ô FE C-12, C-MIRROR-KHAI, C-3, C-5 + X-1/X-2/X-9 trên đây.
- **File đích (4):** `src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs` (khối DELETE `:885-902`) · `src/Backend/SolutionErp.Api/Controllers/ContractsController.cs` (`:78-81`) · test MỚI `tests/SolutionErp.Infrastructure.Tests/Application/ContractDeleteGuardTests.cs` (tên CỤ THỂ — CẤM glob) · cặp FE `fe-{user,admin}/src/components/contracts/ContractDetailContent.tsx` (sửa fe-user → copy-byte fe-admin).
- **Hiện trạng đã đo (B5, đừng đo lại):** `DeleteContractCommand(Guid Id)` KHÔNG Reason/owner-check/changelog; guard cũ `:896` `Phase >= DangInKy`; controller DELETE `:78` KHÔNG policy (class `[Authorize]` trần `:13`); `Remove` = SOFT-delete qua `AuditingInterceptor.cs:54-62` (H-6 — chỉ assert); FE DTO SẴN `drafterUserId` (`fe-user/src/types/contracts.ts:154`); EF `ContractChangelogConfiguration.cs:20` ContextNote HasMaxLength(2000).
- **Việc theo thứ tự (test-before):**
1. **Viết `ContractDeleteGuardTests.cs` TRƯỚC** — fixture private-per-file theo nếp Contract-family (`ContractSigningPlanCrudTests.cs:77` `KhkkFixture` / `ContractCatalogTests.cs:83` — be2-R14, cái thứ 6 cùng nếp là ĐÚNG khuôn), fixture **PHẢI wire `AuditingInterceptor`** (thiếu = assert soft-delete/changelog RỖNG-NGHĨA). Ca = **T-1..T-13 ĐÚNG BẢNG §C, mỗi ca 1 `[Fact]` — T-13 viết RIÊNG (X-9: gộp vào T-1 là vỡ ô `[Fact] ≥13`)**. Chi tiết bẫy per-ca: T-2/T-3 Dev 0-row TraLai/TuChoi ⇒ fixture seed THẲNG `Phase` (§D dữ-liệu đo tay) · T-9 fake `ICurrentUser` UserId=null ⇒ expect `UnauthorizedException` (nấc-0) · T-10/T-11 khoá THỨ TỰ ném exception 404→403→409 · T-1 assert 3-trong-1 (khuất query thường · `IgnoreQueryFilters` thấy IsDeleted/DeletedBy · changelog đúng 1 dòng ContextNote/PhaseAtChange/UserId). Chạy chứng ĐỎ (guard chưa có) — KHÔNG commit lúc đỏ. **T-14 phản-ca: KHÔNG VIẾT test reflection-authz cho DELETE** (2 chiều đều cấm — chiều phủ-định pin lỗ, ĐỎ oan khi W8 mở).
2. **BE handler viết lại (`ContractFeatures.cs:889-902`):** thứ tự trong handler =
(0) nấc-0 actor-guard khuôn `PeSoftDeleteFeatures.cs:56-58`: `!currentUser.IsAuthenticated || currentUser.UserId is null → UnauthorizedException` — 🔴 khuôn KHKK `:1763` KHÔNG có nấc này (`DrafterUserId == UserId` để null==null lọt), ĐỪNG chép nguyên hình;
(1) load + `NotFoundException` (giữ `:893-894`);
(2) owner-check khuôn KHKK `:1763-1766`: `isOwner = entity.DrafterUserId == currentUser.UserId` (an toàn vì nấc-0 đã chặn UserId null) · `isAdmin = currentUser.Roles.Contains(AppRoles.Admin)` · `!isOwner && !isAdmin → ForbiddenException` (T-7/T-8; KHAI trong comment: sửa lỗ sẵn có #82 — trước đây user thường xoá được HĐ người khác);
(3) phase guard THAY `:896-897`: allow `Phase < DangInKy(5)` **** `{TraLai(98), TuChoi(99)}`, còn lại `ConflictException` message liệt rõ trạng thái cấm (chặn ChoDuyet=10 kể cả Drafter — khuôn KHKK, approver-delete = Q4; chặn DaPhatHanh=9 mọi đường — đã gen mã; T-4/T-5/T-6; 🔴 bẫy vá-quá-tay: ĐỪNG chép pattern `is not (DangSoanThao or TuChoi or TraLai)` của KHKK — allow-list HĐ RỘNG HƠN, gồm cả 1/3/4 legacy `<DangInKy`, T-4 bắt);
(4) command nở `DeleteContractCommand(Guid Id, string? Reason = null)` + validator MỚI khuôn `:1743-1752` (`RuleFor(Reason).MaximumLength(2000)` MATCH EF `:20` — T-12) + trim-to-null khuôn `:1785` (T-13);
(5) 🔴 `db.Contracts.Remove(entity)` **TRƯỚC** → `db.ContractChangelogs.Add(new ContractChangelog { ContractId, EntityType = Contract, EntityId = null, Action = Delete, PhaseAtChange = <phase snapshot>, UserId, UserName, ContextNote = reason })` **SAU** (bẫy T26 — đảo là cascade DETACH ⇒ 0 row im lặng, nguyên lý ghi tại KHKK `:1787-1790`; field-set mirror 2 site ghi-tay `CreateContractFromSigningPlanFeatures.cs:247` / `CreateContractFromEvaluationFeatures.cs:121`). 🔴 **GHOST-WIRE be2-R13:** comment `ContractChangelog.cs:10-12` nói "AuditBehavior tự populate" — **file KHÔNG TỒN TẠI**, changelog là GHI TAY; nhân tiện sửa/chú thích comment đó (cùng lượt chạm, hết nói dối).
3. **Controller `:78-81`:** thêm `[FromQuery] string? reason = null` → `new DeleteContractCommand(id, reason)` (khuôn KHKK controller `:153-159`) — 🔴 **KHÔNG chép `[Authorize(Policy = "….Delete")]`** từ khuôn (be2-R1 LẬT: GIỮ shape class-`[Authorize]`-trần, C10 kỳ vọng **= 0**; SB-14 → comment khai VÌ SAO CHƯA gắn: chốt (82) W8 HOÃN + BS-3/be2-R2 đo LocalDB key `Contracts` = 12/13 role CanDelete=0, Drafter=0 ⇒ gắn = khoá oan mất nút xoá nháp; đánh `[carry:w8-delete-policy]`; điều kiện mở W8 = policy + grant Drafter CÙNG commit + đo lại prod).
4. **FE `ContractDetailContent.tsx` (fe-user rồi copy-byte fe-admin):** port khuôn THANH NÚT ĐÁY `fe-user/src/components/khkk/KhkkDetailContent.tsx:1076-1097` (sticky bottom-0 + `window.confirm` message CÓ ĐÍCH nhắc chuyển sang mục "Đã xóa" + `Trash2` + `isPending` disable) — 🔴 KHÔNG lấy `KhkkWorkflowPanel` làm khuôn (phần xoá trong đó = CODE CHẾT S175). Điều kiện render nút = `phase ∈ allow-list` (mirror BE: `<5 || 98 || 99`) **VÀ** (`isDrafter` khuôn null-safe `PeDetailTabs.tsx:141`: `currentUser?.id != null && contract.drafterUserId === currentUser.id` **||** `isAdmin`). Mutation `DELETE /contracts/{id}?reason=`; xong: toast + rời detail về list. GIỮ `<Dialog` hiện có = 1 (C-12 vế 3).
5. **Tự-khai MIRROR (SF-17):** thêm 1 dòng header `// File MIRROR SHA256 identical ` CÙNG BYTES 2 bên cặp `ContractDetailContent` (C-MIRROR-KHAI baseline 0/0 → 1/1; SHA cặp B5 đo hôm nay `cd81508f97cb` identical — sau sửa SHA mới phải tiếp tục khớp-cặp).
6. **STATUS (X-2):** dòng session DELTA `680680+N` ghi ngay tại commit; row canonical `Tests` `:476` **KHÔNG lật** (chỉ chép từ cicd-verify — be2 C16 `:221`); ghi `DEFERRED-TO-DEPLOY: STATUS row :476` vào `run.md`.
- **Ô acceptance:** `dotnet test SolutionErp.slnx` PASS **680 → 680+N, N ≥ 13** (baseline 680 đo lại TRƯỚC khi sửa — Đ-9) · 13 `[Fact]` T-1..T-13 xanh + T-14 tôn trọng · bảng BE be2 C1-C21 đã sửa (scope `awk '/class DeleteContractCommandHandler/,/^}/'` TRƯỚC grep — C3/C5 từng dương-giả 7-hit/4-hit; **C10 = 0**; C13 BỎ; C14 neo tên file cấm glob; C17 viết `"A|B|C"` + control-dương 292; C18 union ls-files; C19 `--include=*.cs`; C21 sqlcmd ma-trận CanDelete trước mọi kết luận policy) · changelog đọc-ngược DB đúng 1 dòng Action=Delete (0 dòng = dẫm T26) · C-12 (`window.confirm` ≥1 + message 'Đã xóa' ≥1 + `<Dialog` = 1) · C-MIRROR-KHAI = 1 mỗi bên + SHA cặp khớp (C-3 guard MISSING) · C-2 build ×2 app · C-5 union danh-sách file (git diff MÙ untracked — test file MỚI phải hiện qua `ls-files -o`).
- **Bẫy né (gom):** T26 thứ-tự Remove/Add · ghost-wire AuditBehavior (be2-R13) · chép policy khuôn KHKK controller (NGƯỢC be2-R1) · chép owner-check KHKK thiếu nấc-0 (T-9 bắt) · vá-quá-tay pattern phase KHKK (T-4 bắt) · fixture thiếu AuditingInterceptor (T-1 rỗng-nghĩa) · sửa STATUS row canonical (X-2).
- **DoD:** test 680+N xanh local + build ×2 + commit **MỘT phát** BE+FE+tests+STATUS-delta: `[CLAUDE] Contract: YC-029 W5 đường xoá đồng-khuôn KHKK (owner-check + phase allow-list + changelog, test-before 13 ca)`.
## Điều kiện dừng/leo thang cho worker (Đ-1..) ## Điều kiện dừng/leo thang cho worker (Đ-1..)
(đang ghi) Gặp bất kỳ điều nào dưới đây: **DỪNG wave đang làm, ghi hiện trạng vào `run.md`, hỏi lead** — KHÔNG tự chế nhánh xử lý.
- **Đ-1 — eslint delta vỡ:** `npx eslint .` app đụng TĂNG so baseline (fe-user >47 · fe-admin >42) hoặc file-wave `Layout.tsx` >4E hoặc xuất hiện **ruleId MỚI** ⇒ DỪNG. CẤM `eslint-disable` để ép xanh (rời tập-đo).
- **Đ-2 — SHA-pair không khớp được:** copy-byte rồi mà 2 bản mirror vẫn lệch SHA (nghi BOM/CRLF/format-on-save) ⇒ DỪNG báo kèm `sha256sum` 2 bên. CẤM sửa `.gitattributes`/editor-config để ép.
- **Đ-3 — T-matrix FAIL không rõ nguyên nhân:** đặc biệt T-1 changelog 0-row DÙ đã Remove-trước-Add-sau (nghi cascade timing khác KHKK) hoặc T-9 không ném đúng exception ⇒ DỪNG kèm log. CẤM nới assert cho xanh.
- **Đ-4 — đụng file NGOÀI danh sách wave:** kể cả "tiện tay" refactor/format file lân cận ⇒ DỪNG hỏi trước khi commit (reviewer gate đánh scope-drift = FAIL).
- **Đ-5 — spec/plan LỆCH so đĩa:** neo `file:line` lệch >±15 dòng, hàm/khuôn không tồn tại, hình khuôn khác mô tả ⇒ DỪNG báo (mọi neo trong plan này B5 đã đo tươi 2026-08-12 — lệch nghĩa là cây đã trôi giữa chừng).
- **Đ-6 — mất control W1(iii):** Dev VẮNG `QT-HD-V2-001` (ô "vắng-vì-lọc" mất nghĩa) ⇒ đánh dấu ô mất-control trong `run.md` + DỪNG hỏi (dữ liệu này dính Q5 đang chờ anh — CẤM tự tạo/xoá quy trình tay).
- **Đ-7 — ô hành-vi không dựng được dữ liệu local:** áp đúng spec §④.4: đánh `DEFERRED-TO-DEPLOY` công khai trong `run.md`, CẤM tick, CẤM lặng lẽ bỏ ô. (Không phải leo thang — nhưng là đường thoát HỢP LỆ duy nhất.)
- **Đ-8 — chạm ranh CHỜ-ANH:** mọi việc dính Q3 (port 3 món KHKK/DTO nở) · Q4 (approver-delete `AllowApproverDelete`) · Q5 (QT-HD-V2-001) · W8 (policy DELETE/authz diện rộng) · W9 (đã HỦY) ⇒ DỪNG ngay — spec đã tách các món này KHỎI wave.
- **Đ-9 — baseline test lệch:** `dotnet test` TRƯỚC khi sửa ra số ≠ 680 ⇒ DỪNG báo (số pin từ B4 có thể trôi bởi commit khác chen giữa; cần lead chốt lại mốc trước khi tính 680+N).
- **Đ-10 — build/test đỏ vì nguyên nhân NGOÀI wave** (localdb/seed hỏng, package restore fail) ⇒ DỪNG báo, CẤM `--no-verify` mọi tình huống.

View File

@ -0,0 +1,16 @@
# sub-b6-w1-worker — implementer-frontend (B6, wave W1, YC-029)
Run: 2026-08-12-S190-soltuon-dieu-chinh-hd · plan SOLTUON-12-08-2026
Ranh: Write/Edit CHỈ {fe-user/src/components/Layout.tsx, fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx, file này}
## Việc đã làm (7 mục)
(đang chạy)
## Ô acceptance (kết quả từng ô)
(đang chạy)
## DEFERRED/DỪNG
(đang chạy)

View File

@ -0,0 +1,59 @@
# sub-b6-w4-worker — W4 (YC-029, plan SOLTUON-12-08-2026)
Vai: implementer-frontend · File đích DUY NHẤT: `fe-admin/src/components/Layout.tsx`
Nguồn lệnh: `sub-b5-plan-final.md` block `### W4` (:37-48) + `## Điều kiện dừng` (:117-131).
INPUT phụ đã đọc: spec `:28-34` (§② W4) + `:98-100` (§③ W4) · style-gate §D `:83-87` (C-1a/C-2), `:101-103` (C-13), `:106-107` (C-5).
## Việc đã làm
**Tiền-đề đo tươi TRƯỚC khi sửa (Đ-5 — 0 lệch):**
- `fe-admin/src/components/Layout.tsx``if (awV2Match) {` = **:176** · nhánh KhkkN = **:188** · `}` đóng khối = **:189** ⇒ khớp mốc plan `:43` + C-13 `:103`.
- `fe-admin/src/lib/contractWorkflowFamily.ts``findContractWfByTypeCode` = **:77-80** (named export) · `CONTRACT_WF_FAMILY` = **:45** (7 member `HD-TP…HD-NT-DV`) · `contractWfTypeCode` = **:69** ⇒ khớp plan `:42`. Không lệch dòng nào ⇒ KHÔNG kích Đ-5.
- Baseline eslint fe-admin đo TRƯỚC sửa: **42 problems (37 errors, 5 warnings)**, exit 1 — đúng số pin của C-1a.
**Việc 1 — import named** (`Layout.tsx:9`, ngay sau `import { cn } from '@/lib/cn'`):
```
import { findContractWfByTypeCode } from '@/lib/contractWorkflowFamily'
```
**Việc 2 — nhánh TRONG khối `awV2Match`** (`Layout.tsx:195`, SAU nhánh KhkkN `:189` và TRƯỚC `}` `:196` — số dòng dịch +1 do import):
```
if (findContractWfByTypeCode(code)) return `/system/approval-workflows-v2/${code}`
```
`code` = phần sau `AwV2_` (vd `HdTP`, `HdNTNCC`), GIỮ NGUYÊN casing — không đụng chuỗi.
**Việc 3 — comment Pattern 16-bis** (`Layout.tsx:190-194`, 5 dòng, khuôn khối KhkkN `:182-188` cũ): nêu họ `AwV2_Hd*` 7 leaf seed @S188 Order 11..17 · "chỗ thứ 4" Pattern 16-bis · thiếu nhánh ⇒ MenuLeaf DROP IM LẶNG (gotcha #50) · tra bằng BẢNG (nhà mã `lib/contractWorkflowFamily.ts` mirror BE `ContractApprovalWorkflowFamily.cs`), KHÔNG regex đoán ngược (`HdNTNCC``NT-NCC` hay `N-TNCC`).
**Việc 4 — 4 thứ CẤM: KHÔNG đụng** (xác nhận bằng diff 1-file):
- nhánh `code === 'Contract'` (`:178` cũ → `:179` nay) — nguyên trạng, không xoá "tiện tay";
- `DbInitializer` — 0 file BE trong diff;
- họ `Khkk_*` — 0 thay đổi (staticMap `Khkk_WfView:40` nguyên trạng);
- không thêm/sửa file nào khác (C-5 union = đúng 1 file).
## Ô acceptance (kết quả từng ô)
| Ô | Lệnh/nguồn | Số đo THẬT | Verdict |
|---|---|---|---|
| **C-13 RANGE** | style-gate `:101-103` | `open=177` · `khkk=189` · `hit=195` · `close=196` · `import=1` | **PASS**`177 < 195 < 196` ✔ và `195 > 189` ✔ (mốc baseline 176/188/189 dịch +1 do dòng import, quan hệ thứ tự giữ nguyên) |
| **C-2 build** | `npm run build` fe-admin | `✓ built in 20.33s`, **BUILD_EXIT=0**, `dist/assets/index-DUBars48.js 1,776.57 kB` | **PASS** (2 warning chunk>500kB + INEFFECTIVE_DYNAMIC_IMPORT = pre-existing, không phải lỗi) |
| **C-1a eslint** | `npx eslint .` fe-admin, exit qua `${PIPESTATUS[0]}` | sau sửa **42 problems (37 errors, 5 warnings)** — baseline trước sửa **42 (37E/5W)****delta = 0** | **PASS** (≤42, KHÔNG TĂNG; exit 1 = baseline-đỏ-sẵn, không phải hồi quy) |
| C-1a file-wave | `npx eslint src/components/Layout.tsx` | **2 errors**`290:10` + `364:8`, ruleId **`react-hooks/static-components`** (cả 2 CÁCH XA vùng sửa `:9` / `:190-195`) | **PASS** — ≤4E, **0 ruleId mới** (tổng app không đổi ⇒ không đẻ lỗi ở đâu) |
| **C-5 file list** | `{ git diff --name-only HEAD; git ls-files -o --exclude-standard; } \| sort -u \| grep -E '^fe-'` | `fe-admin/src/components/Layout.tsx`**đúng 1 dòng** | **PASS** — 0 file lạ (Đ-4 sạch) |
| spec ✅W4(i) 17 leaf sidebar | cần dev-server + BE seed | — | **DEFERRED-TO-LOCAL-VERIFY** |
| spec ✅W4(ii) click "QT Duyệt HĐ — Thầu phụ" → Designer khoá `HD-TP` | cần dev-server | — | **DEFERRED-TO-LOCAL-VERIFY** |
| spec ✅W4(iii) control-âm `KhkkN1` + `DuyetNcc` | cần dev-server | — | **DEFERRED-TO-LOCAL-VERIFY** |
**Ô máy: 4/4 PASS** (C-13 · C-2 · C-1a app + file-wave · C-5). Ô hành-vi: 0/3 đo — DEFERRED (xem dưới).
## DEFERRED/DỪNG (nếu có)
- **DEFERRED-TO-LOCAL-VERIFY — 3 ô hành-vi W4 (i)(ii)(iii)** (spec §③ `:98-100`): worker KHÔNG tự dựng dev-server (tránh chiếm port, theo lệnh giao). Lead đo ở nấc ④ (`npm run dev` fe-admin + BE LocalDB seed 7 `HD-*`). Đây là đường thoát HỢP LỆ theo Đ-7/spec §④.4, **CẤM tick** cho tới khi đo thật.
- Cần đo: (i) nhánh "Quy trình duyệt (Mới)" đủ **17 leaf** = 2 NCC + 8 KHKK + 7 "QT Duyệt HĐ — …" Order 11..17; (ii) leaf "QT Duyệt HĐ — Thầu phụ" → `/system/approval-workflows-v2/HdTP`, panel khoá mã `HD-TP`, history CHỈ HD-TP; (iii) control-âm: `KhkkN1` + `DuyetNcc` vẫn mở đúng trang cũ.
- **Đ-1..Đ-10: 0 điều kiện dừng kích hoạt.** (Đ-5 neo khớp tuyệt đối · Đ-1 eslint delta 0 · Đ-4 diff đúng 1 file · Đ-10 build xanh.)
- **DEPLOY CHỜ** — dừng ở local-verify + commit, KHÔNG push (theo lệnh giao + plan `:35`).
## Commit
`1cc16a2d``[CLAUDE] FE-Admin: YC-029 W4 — sidebar Designer 7 leaf QT Duyệt HĐ (resolvePath AwV2_Hd*)`
(subject ĐÚNG DoD từng ký tự; + trailer `Co-Authored-By` theo nếp repo — 18/30 commit gần nhất có).
`1 file changed, 7 insertions(+)``git add` đích danh `fe-admin/src/components/Layout.tsx`. Staged set đo `git diff --cached --name-only` = **đúng 1 dòng**; 8 file dirty khác (`.claude/WAL.md`, `docs/HANDOFF.md`, spec/plan trong run-folder…) là của em main, **KHÔNG stage**. **KHÔNG push** (deploy CHỜ).