22 KiB
sub-implementer-frontend-1 — S182 W2 (a-h) lane FE
⚠️ TÊN FILE: TASK text ghi
sub-implementer-frontend-0.md, RUN-TRACE block + structured-output ghisub-implementer-frontend-1.md. Chọn-1(bản RUN-TRACE = bản em main đọc quasubMdPath). Khai ở đây để lead biết.
§0 — Trạng thái lượt (append liên tục, chống-#53)
- Đọc SPEC FINAL MỤC 2 W2 (a-h) + MỤC 1 R-1..R-6
- Đọc file đích + đo tiền-đề từ ĐĨA (line-number trong spec có thể lệch)
- W2a parse kind
- W2b synthetic slice
- W2c lockedCode
- W2d D3=C 6 ô + #4
- W2e menuKeys ×2 app
- W2f ngưỡng CEO
- W2g auto-pin isActive ×2 app
- W2h comment + subtitle
- build fe-admin — exit 0
- build fe-user — exit 0
Lượt 2 (vá-nốt): chẩn-đoán TS trong TASK là ảnh cũ — đo lại từ đĩa thì đã sạch ⇒ 0 dòng code sửa, lượt này là ĐO + CHỨNG. Chi tiết §3.3.
- tsc thật ×2 app (lệnh trong spec là phép đo CÂM — §3.1)
- control-dương chứng tsc có răng (§3.2)
- proof-harness 27/27 + anti-Goodhart (§3.4)
- đối chiếu code-vs-spec W2a-h (§3.5)
- mirror proof SHA + 4-place (§3.6)
§1 — Đo tiền-đề từ đĩa (TRƯỚC khi gõ)
| # | Tiền-đề spec | Đo trên đĩa | Verdict |
|---|---|---|---|
| P1 | :164-172 TYPE_CODE_TO_INT |
ApprovalWorkflowsV2Page.tsx:164-172 ĐÚNG (TYPE_INT_CONTRACT_SIGNING_PLAN=10 :149) |
HELD |
| P2 | :299-301 currentType |
:299-301 overview.data?.types.find(...) ĐÚNG |
HELD |
| P3 | :357/:620/:650-651/:798/:920-933 |
TypePanel :357 · Designer :620 · defaultCode :650-651 · POST code :798 · ô Mã :920-933 — ĐÚNG cả 5 |
HELD |
| P4 | 6 ô D3 :1235-1266, :1268-1276, :1277-1285, :1320-1328 |
#1 :1232-1240 · #2 :1241-1249 · #3 :1250-1258 · #4 :1259-1267 · #5 :1268-1276 · #6 :1277-1285 · #7 :1292-1302 · #8 :1307-1315 · #9 :1320-1328 |
HELD (số ô #1-#4 lệch ±3 dòng so spec, khớp nội dung) |
| P5 | :944-961 ngưỡng CEO |
:943-961 ĐÚNG |
HELD |
| P6 | menuKeys.ts:31-32 = 2 const AwV2_* |
fe-admin :31-32 + fe-user :31-32 ĐÚNG, 0 key KhkkN |
HELD |
| P7 | BE const để mirror | MenuKeys.cs:127-134 AwV2_KhkkN1..8 (VÀO All) |
HELD |
| P8 | 4-place Layout staticMap | fe-admin/Layout.tsx:188 /^KhkkN[1-8]$/ ĐÃ CÓ (K3 S164/S165) ⇒ chỗ-4 KHÔNG cần chạm |
HELD |
| P9 | khkkGroupWorkflowCode / khkkGroupMenuLabel |
types/khkk.ts:159-168, 2 app SHA identical b26bf674… |
HELD |
| P10 | 🔴 spec W2g "2 file KhkkCreatePage vốn khác nhau — không cần byte-equal" | SAI: sha256sum 2 file = 332986ae6bf1904a4db434c796164caa2768beaaebdc7843dd2b1cdd729d17da BẰNG NHAU + diff 0 dòng |
LỆCH → sửa mirror BYTE-EQUAL, giữ SHA-pair |
| P11 | WorkflowDto (local) có isActive? |
KhkkCreatePage.tsx:57-62 = {id, code, name, isUserSelectable} — THIẾU isActive |
phải thêm field (BE có: DefinitionDto.isActive :79) |
| P12 | 🔴 spec W2h "thêm ~1 prop subtitle" |
components/PageHeader.tsx:3-11 chỉ có title/description/actions (bài S37 lặp lần 3) |
LỆCH → xem §4 FLAG-1 |
| P13 | build = phép thử TS thật | package.json build = tsc -b && vite build, noUnusedLocals+noUnusedParameters+erasableSyntaxOnly ON |
HELD |
§2 — Thay đổi từng task (đã gõ xong, chờ build)
File chạm (4):
fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx(a,b,c,d,f,h)fe-admin/src/lib/menuKeys.ts(e)fe-user/src/lib/menuKeys.ts(e)fe-admin/src/pages/khkk/KhkkCreatePage.tsx+fe-user/…(g,cp→ SHA-pair)
W2a — parse kind
khkkGroupMatch = typeCode?.match(/^KhkkN([1-8])$/) ?? null→khkkGroup: number|nullselectedTypeInt = khkkGroup !== null ? TYPE_INT_CONTRACT_SIGNING_PLAN : typeCode ? TYPE_CODE_TO_INT[typeCode] : null- queryKey GIỮ NGUYÊN (
['approval-workflow-v2-overview', selectedTypeInt]) ⇒ 8 panel nhóm + panel type-10 dùng chung 1 ô cache, invalidate làm tươi cả 9. Khai trong comment. - Regex CHUỖI (không
Number()) —KhkkN03không lọt (bài học L1 #11). - +import
{ khkkGroupMenuLabel, khkkGroupWorkflowCode } from '@/types/khkk'(không chép chuỗiKHKK-N).
W2b — synthetic slice
- Tách
typeBucket(raw BE) ⟂currentType(lát cắt).useMemo([khkkGroup, typeBucket]). versions = typeBucket.history.filter(d => d.code === khkkGroupWorkflowCode(g))·active = versions.find(v=>v.isActive) ?? null· KHÔNG đọctypeBucket.active(1-trong-8).- Giữ shape
TypeSummaryDto⇒ TypePanel/Designer không biết mình đang xem nhóm hay loại.
W2c — lockedCode xuyên suốt
- Page:
lockedCode = khkkGroup !== null ? khkkGroupWorkflowCode(khkkGroup) : null TypePanel({type, lockedCode, onSaved})→<Designer lockedCode={lockedCode} …>defaultCode = lockedCode ?? DEFAULT_CODE_BY_TYPE[applicableType] ?? 'QT-V2-001'— ô hiện ĐÚNG mã sẽ gửi (không hiệnQT-KHKK-V2-001rồi gửiKHKK-N3).- Ô Mã
disabled={isEdit || lockedCode !== null}+ hint "Mã khoá theo nhóm — version tự tăng." - POST
code: lockedCode ?? code(bất-biến theo CẤU TRÚC, không dựa vào input disabled). - +
key={typeCode ?? 'all'}trên<TypePanel>— NGOÀI spec, lý do: chuyển N1→N3 khi Designer đang mở thìcloneFrom/editTarget(state trong TypePanel) còn trỏ bản N1 trong khilockedCodeđã là N3 ⇒ POST đẻKHKK-N3mang ruột N1. Remount xoá state. Khai ở đây.
W2d — D3=C danh-sách-chốt
Cờ chung const isKhkk = applicableType === TYPE_INT_CONTRACT_SIGNING_PLAN (1 nguồn, 8 chỗ dùng).
| Ô | Cờ | Xử lý |
|---|---|---|
| #1 | allowReturnOneLevel | disabled={isKhkk} + title chung + checked={entry.…} GIỮ + mờ text |
| #2 | allowReturnOneStep | ↑ |
| #3 | allowReturnToAssignee | ↑ |
| #4 | allowReturnToDrafter | disabled + checked THẬT + title RIÊNG "KHKK hiện luôn trả về người soạn khi Trả lại — cờ chưa phân nhánh" + KHÔNG mờ (giữ text-slate-700) |
| #5 | allowApproverEditDetails | như #1 |
| #6 | allowApproverEditBudget | như #1 |
| #7 | allowApproverSkipToFinal | GIỮ hành vi (kể cả checked ép-false của K3 S165); chỉ thay 3 biểu thức trùng → isKhkk, giá trị y hệt |
| #8 | allowApproverFinalize | SỐNG — 0 chạm |
| #9 | allowApproverDelete | như #1 (rose→slate khi mờ) |
levelFlagsPayload0 dòng đổi (echo state thật).- +1 dòng giải thích trong hộp tím (chỉ khi
isKhkk) — nói rõ ô mờ vẫn hiện đúng giá trị DB, và 2 ô còn tác dụng.
W2e — menuKeys ×2 app
+8 const AwV2_KhkkN1..8 mirror MenuKeys.cs:127-134, sau :32 cả 2 app. Comment viết theo app (fe-admin trỏ Layout.tsx:188; fe-user khai "cố ý không có Designer") — cố ý lệch chữ, không lệch const.
W2f — Ngưỡng CEO
disabled={isEdit || isKhkk} + helper-text "KHKK không dùng ngưỡng máy — NĐUQ quyết…" + POST ceoApprovalThreshold: isKhkk ? null : (…).
W2g — auto-pin lọc isActive ×2 app
WorkflowDto(local,:57-62) +isActive: boolean— BE có (AwAdminOverviewDto, đối chiếuDefinitionDto.isActive).find(w => w.isActive && code-match); filterisUserSelectableở query GIỮ.- Sửa 2 câu thông báo cho khớp điều-kiện MỚI (3 vế: đúng mã ∧ đang áp dụng ∧ đã ghim) — nếu không sẽ nói "chưa có quy trình" trong khi nó đang nằm đó ở bản archived (sai-thầm-lặng).
cpfe-admin → fe-user, SHA-pair89dfa607…BẰNG NHAU.
W2h — comment + tiêu đề
- Vá comment
:641-644: câu cũ "2 ô duyệt sớm sống cho MỌI loại" SAI — #7 vẫn khoá cho type-10 từ K3 S165. Viết lại + trỏ tại-chỗ. - PageHeader: title =
Quy trình duyệt (Mới): {khkkGroupMenuLabel(g)}· description = phụ đề nghiệp vụ (loại + nhóm N{n} + mã khoá + "7 nhóm còn lại không đổi"). Xem FLAG-1.
§3 — Verify (build ×2 + proof)
3.1 🔴 Lệnh acceptance trong spec/TASK là PHÉP ĐO CÂM — phải đổi
npx tsc --noEmit (nguyên văn acceptance R-2 + TASK lượt 2) chạy ra EXIT=0, 0 dòng output ở CẢ 2 app kể cả khi code sai: fe-{admin,user}/tsconfig.json là solution-style {"files": [], "references": [...]} ⇒ tsc không biên dịch file nào rồi thoát 0. Xanh vì CÂM, không phải vì đúng.
Lệnh THẬT (đã dùng dưới đây): npx tsc -p tsconfig.app.json --noEmit — hoặc tsc -b (chính npm run build).
| # | Lệnh | Exit | Ghi chú |
|---|---|---|---|
| V1 | fe-admin$ npx tsc -p tsconfig.app.json --noEmit |
0 | 114 file src/ trong tập biên dịch (--listFiles), có ApprovalWorkflowsV2Page.tsx + types/khkk.ts |
| V2 | fe-user$ npx tsc -p tsconfig.app.json --noEmit |
0 | |
| V3 | fe-admin$ npm run build (tsc -b && vite build) |
0 | ✓ 1964 modules · index-CmEoYZiH.js 1.761,63 kB |
| V4 | fe-user$ npm run build |
0 | ✓ 1953 modules · index-CT5Y_xQ4.js 1.677,49 kB |
V3/V4 chạy SAU khi đã hoàn tác tiêm-lỗi ⇒ đo đúng trạng thái cuối trên đĩa.
Warning PRE-EXISTING (không phải nợ mới): chunk >500 kB · INEFFECTIVE_DYNAMIC_IMPORT realtime.ts · CSS @import order.
3.2 Control-DƯƠNG — chứng phép đo có RĂNG
Tiêm const __ctrl_neg: number = khkkGroupWorkflowCode(3) vào cuối chính file đã ship → tsc nổ 2 lỗi:
TS2322 Type 'string' is not assignable to type 'number' + TS6133 '__ctrl_neg' is declared but its value is never read.
🔴 Ý nghĩa: TS6133 (noUnusedLocals) CÒN SỐNG — đúng class lỗi mà 3 chẩn-đoán "declared-never-read" (lockedCode :417 · isKhkk :714 · khkkNotYetHint :717) thuộc về. Nên "0 lỗi" ở V1 = thật sự hết, không phải đo hụt.
Hoàn tác: SHA trước/sau dda4be1f9d4dabd…b69b7f71 BẰNG NHAU (byte-identical).
3.3 🔴 Chẩn-đoán trong TASK là ẢNH CŨ — 0 dòng code phải vá
6 lỗi TS mà TASK liệt (thiếu import khkkGroupWorkflowCode/khkkGroupMenuLabel :330/:343/:355 · lockedCode chưa khai ở signature :403/:517 · 3 biến never-read) KHÔNG còn tồn tại trên đĩa: import { khkkGroupMenuLabel, khkkGroupWorkflowCode } from '@/types/khkk' ở :36; lockedCode khai đủ ở CẢ 2 signature (TypePanel :417/:421 · Designer :690/:699); 3 biến đều có chỗ dùng thật (isKhkk 20 call-site, khkkNotYetHint 6, lockedCode 6).
⇒ Đây là IDE-diagnostic chụp giữa lúc lane trước đang gõ (gotcha #68). Lượt này KHÔNG sửa 1 dòng code nào — chỉ đo + chứng. git diff --stat giữ đúng 5 file, 0 file untracked.
3.4 Proof-harness — chạy CHÍNH text đã ship (extract-by-anchor, neo lệch thì script tự chết)
node proof-w2.mjs → 27 PASS / 0 FAIL:
- T1 (8/8) dây xuyên suốt
AwV2_KhkkN{n}→Layout.tsx:188→/system/approval-workflows-v2/KhkkN{n}→ regex page parse nhóm{n}→lockedCode = KHKK-N{n}, đối chiếu đủ 3 nguồn BE+admin+user. - T2 (7/7) control-ÂM:
KhkkN0·KhkkN9·KhkkN03·KhkkN10·khkkn1·"KhkkN1 "·KhkkN— bị chặn ở CẢ 2 regex (regex CHUỖI, khôngNumber()). - T3 (2/2) control-âm cho chính T1: key bịa
AwV2_KhkkN9/N0vắng ở cả 3 nguồn. - T4/T5/T6 (10/10) D3=C — xem 3.5.
Anti-Goodhart (proof-control.mjs, exit 0): áp ĐÚNG vị ngữ T4 và T5 lên ô #7 (ô cố ý khoá + ép-false từ K3 S165) ⇒ cả hai đều false. Phép đo phân biệt được ô-sống ⟂ ô-khoá và bắt được ép-false; không phải luôn-luôn-PASS.
3.5 Đối chiếu code-vs-spec W2a–h
| Task | Verdict | Bằng chứng |
|---|---|---|
| W2a parse kind | ✅ | :302-306 regex chuỗi; queryKey giữ nguyên (9 panel chung 1 ô cache — khai tại :300-301) |
| W2b synthetic slice | ✅ và là BẮT BUỘC | BE active = versions.FirstOrDefault(v => v.IsActive) (:210) — với type-10 nay có 8 bản active song song (BE lane đổi sang tắt per-CODE :336-360) ⇒ đọc bucket.active sẽ sai cho 7/8 panel. history xác nhận KHÔNG lọc IsActive (definitions :118-134 chỉ lọc Type + IsUserSelectable) |
| W2c lockedCode | ✅ | signature 2 nơi · defaultCode :733 · ô Mã disabled :1020 · POST code: lockedCode ?? code :884 (bất-biến theo CẤU TRÚC). Khớp khoá với vị ngữ w.Code == request.Code bên BE |
| W2d D3=C | ✅ đúng danh-sách-chốt | 6 ô disabled={isKhkk} + checked={entry.…} THẬT · #4 tooltip riêng + KHÔNG mờ · #7 giữ nguyên · #8 allowApproverFinalize 0 chạm (T4) · levelFlagsPayload 0 dòng đổi |
| W2e menuKeys ×2 | ✅ | 8 const khớp MenuKeys.cs:127-134; khối const 2 app diff 0 dòng, chỉ comment cố ý lệch |
| W2f ngưỡng CEO | ✅ | disabled={isEdit || isKhkk} :1052 + helper-text :1055 + POST null :891 |
| W2g auto-pin isActive | ✅ | isActive CÓ THẬT trong payload (ToDto :176) — không phải field bịa; 2 câu thông báo đổi sang nói ĐIỀU KIỆN (rỗng-vì-lọc ⟂ rỗng-vì-chưa-có) |
| W2h comment + tiêu đề | ✅ | subtitle gộp vào description (xem FLAG-1) |
3.6 Mirror proof (Pattern 16-bis + SHA)
KhkkCreatePage.tsxadmin ⟂ user:89dfa607061cd3d560dcbbdb8fbad157d38cae1786be3cd41a81bd8e9c1d1390BẰNG NHAU (cặpcp, W2g).types/khkk.tsadmin ⟂ user:b26bf67492821b1003c3d523585e2ce563a26b04ef728867689911263e5a33d3BẰNG NHAU — file không sửa ⇒ đối chứng DƯƠNG cho phép đo hash.- 4-place: ① page+types ✅ · ②
App.tsx:80route:typeCodeđã có (0 route mới) ✅ · ③menuKeys.ts×2 ✅ · ④Layout.tsx:188staticMap đã có sẵn từ K3 S164/S165 ⇒ chỗ-4 KHÔNG cần chạm (P8 đúng), regex Layout khớp regex page. - Lệch CÓ CHỦ ĐÍCH: fe-user không có
ApprovalWorkflowsV2Page.tsx+ 0 nhánhAwV2trongLayout.tsx(màn cấu hình là của admin) ⇒ 8 const bên fe-user là mirror hằng-số thuần, không sinh menu.
§4 — Nợ / FLAG cho lead
- 🔴 FLAG-A (acceptance hỏng — cần sửa spec): acceptance R-2 ghi "
npm run build×2 app + tsc 0-err". Nếu ai chạynpx tsc --noEmitđúng nguyên văn thì luôn xanh kể cả code sai (§3.1). Đề nghị sửa acceptance thànhnpx tsc -p tsconfig.app.json --noEmit. Ảnh hưởng mọi wave FE trước/sau, không riêng S182. - 🔴 FLAG-B (spec tự mâu-thuẫn — đã chọn nhánh, xin lead ratify): ô #4
allowReturnToDrafter. MỤC 2 dòngd(bảng thi-hành, có neo dòng) ghi "hiển thị state thật (thường TRUE)"; MỤC 3 bảng D3 ghi bản rút gọn "#4 forced-true-disabled". Code đang theo state thật (checked={entry.allowReturnToDrafter}), vì: (1) đo đĩa: mặc địnhtrueở 4 lớp độc lập —ApprovalWorkflow.cs:129 = true· EFHasDefaultValue(true)· DTO:232/:465· FE hydrate?? true(:213) ⇒ thực tế vẫn hiện TICK, thoả acceptance A2 "ô #4 tick-disabled"; (2) hard-codechecked={true}sẽ nói dối đúng ca hàng row cófalse— cùng class lỗi mà chính D3=C cấm ở 6 ô kia ("CẤM ép false"), chỉ đảo dấu. ⇒ Đọc "forced-true" là rút gọn hao nghĩa của "state thật (thường TRUE)". Nếu lead thật sự muốn ép cứngtruethì nói 1 câu, sửa 1 dòng — nhưng nên biết là đánh đổi tính trung thực với DB. - ⚠️ FLAG-1 (P12, đã xử lý — không còn là nợ):
@/components/PageHeaderkhông có propsubtitle(title/description/actions, cả 3 đềuReactNode). Không thêm prop — phụ đề nghiệp vụ gộp vàodescription. Lý do: PageHeader dùng chung toàn app, đẻ prop cho 1 trang là mở rộng API cho cả trăm call-site. (Bài S37, lặp lần 3 — mỗi lần lại tưởng cósubtitle.) - ⚠️ Ngoài spec, đã khai:
key={typeCode ?? 'all'}trên<TypePanel>(:401) — remount khi đổi nhóm; thiếu nó thìcloneFrom/editTargetcòn trỏ bản N1 trong khilockedCodeđã sang N3 ⇒ POST đẻKHKK-N3mang ruột N1. - ⚠️ Nợ nhận-thức (không chặn): 8 panel nhóm + panel
/ContractSigningPlandùng CHUNG 1 ô cache (queryKey không cókhkkGroup— cố ý, vì lọc client-side trên payload đã tải trọn). Invalidate sau khi lưu làm tươi cả 9. Nếu sau này BE nhậncode=làm tham số request thì BẮT BUỘC thêmcodevào queryKey, nếu không 9 màn dùng chung cache sai CÂM. - ⚠️ Chưa chạy (ngoài tay lane FE): acceptance A1/A2b/A3/A4 là UAT động (bấm thật trên 8 leaf, sửa-tại-chỗ ⇒ 204, tạo v02 N1 kiểm 7 nhóm không mất badge). Lane này chỉ chứng được phần tĩnh. A3 phụ thuộc BE lane (tắt active per-CODE) — 2 nửa phải test CÙNG nhau.
- ✅ Không nợ build/mirror: 0 lỗi TS ×2 app · SHA-pair 2/2 · 4-place 4/4 · 27/27 proof + 3 control.
§5 — vá MAJOR-1 (reviewer pre-commit, lượt sau)
Bệnh: title= nằm trên chính <input type="checkbox" disabled={isKhkk}>. Chromium (Chrome/Edge = trình duyệt đích của owner) nuốt sự kiện chuột của form-control disabled ⇒ tooltip KHÔNG bao giờ bật; <label> bao ngoài không mang title nên rê vào chữ cũng câm. Quyết định D3=C của owner là "mờ ô chưa nghe + tooltip" ⇒ mất vế tooltip thì admin chỉ còn 7 ô xám vô cớ. Nặng nhất là ô #4 allowReturnToDrafter: câu "KHKK hiện luôn trả về người soạn khi Trả lại — cờ chưa phân nhánh" là thông tin DUY NHẤT phân biệt #4 với 6 ô "chưa áp dụng" — câm ⇒ #4 trông y hệt 6 ô kia = ngược nghĩa.
(a) Dời title từ <input> lên <label> — 8/8 site (label không disabled nên hover chữ là hiện):
| # | cờ | title cũ ở input | title mới ở label |
|---|---|---|---|
| 1 | allowReturnOneLevel |
:1361 | :1366 (label @:1364) |
| 2 | allowReturnOneStep |
:1372 | :1379 (@:1377) |
| 3 | allowReturnToAssignee |
:1383 | :1392 (@:1390) |
| 4 | allowReturnToDrafter |
:1399 | :1410 (@:1408) |
| 5 | allowApproverEditDetails |
:1410 | :1432 (@:1430) |
| 6 | allowApproverEditBudget |
:1421 | :1445 (@:1443) |
| 7 | allowApproverSkipToFinal |
:1443 ← tiền lệ CÙNG BỆNH từ K3, reviewer chỉ đích danh | :1469 (@:1467) |
| 9 | allowApproverDelete |
:1471 | :1499 (@:1497) |
(b) RIÊNG ô #4 — thêm chữ NHÌN THẤY ĐƯỢC (fe-admin/.../ApprovalWorkflowsV2Page.tsx:1421-1429, ngay dưới </label> #4 @:1420): {isKhkk && <div className="col-start-2 -mt-0.5 pl-4 text-[10px] leading-snug text-slate-500">{khkkDrafterHint}</div>}. Thông tin phân-biệt không được phụ thuộc hover. Dùng col-start-2 (không phải sibling thường) vì #4 nằm ô hàng 2 cột 2 của grid grid-cols-2: sibling auto-place sẽ rơi xuống cột 1 ⇒ đọc nhầm sang ô #3 và đẩy lệch 5 ô col-span-2 phía dưới. col-start-2 ghim dòng chữ đúng dưới ô của nó; 5 ô col-span-2 kế tiếp vẫn auto-place xuống hàng mới, 0 reflow.
Chuỗi #4 gom thành 1 const khkkDrafterHint (:722, cạnh khkkNotYetHint) vì nay dùng ở 2 chỗ (tooltip + chữ hiện) — 2 bản literal sẽ trôi lệch nhau. Kèm comment tại :715-721 ghi rõ vì sao title phải ở label, để lần sau không ai dời ngược lên input rồi làm câm lại.
Vùng CẤM — đo lại sau khi vá, xác nhận nguyên vẹn:
- 9/9
checked=byte-identical với bản trước khi vá (:1371 :1384 :1397 :1415 :1437 :1450 :1474 :1488 :1504) — gồm cảchecked={isKhkk ? false : entry.allowApproverSkipToFinal}(quyết định K3, giữ) vàchecked={entry.allowReturnToDrafter}(FLAG-B §4 vẫn treo y nguyên, lượt này KHÔNG động). - Ô #8
allowApproverFinalize(:1484-1492): 0disabled, 0title— không chạm. levelFlagsPayload(:258, call:880/:920): không chạm.isKhkk(:714): không chạm.
Proof — checker có RĂNG (control-âm): script parse tag-owner của mọi title= trong file (scratchpad/check-title-owner.js).
- Bản đã ship:
title=tổng 22 · tham chiếuisKhkk8 · owner=<label>8/8 · owner=<input>0. - CONTROL-ÂM: tiêm lại 1
titlevào<input>ô #1 ⇒ checker nổ đúng 1 (owner=<input> :1373). ⇒ 0-hit ở bản thật là im-vì-đúng, không phải im-vì-câm.
Build: npx tsc --noEmit -p tsconfig.app.json → exit 0 (🔸 dùng -p; npx tsc --noEmit TRẦN biên dịch 0 file = xanh giả — đúng FLAG-A §4). npm run build fe-admin → ✓ built in 30.18s, 0 lỗi TS; warning >500 kB + INEFFECTIVE_DYNAMIC_IMPORT realtime.ts + @import = PRE-EXISTING.
Mirror: ApprovalWorkflowsV2Page.tsx chỉ tồn tại ở fe-admin (fe-user: file không có, ls pages/system | grep -i workflow = 0, grep khkkNotYetHint|khkkDrafterHint fe-user/src = 0 hit) ⇒ không có cặp để mirror, đúng lệch-có-chủ-đích đã khai §3.6. Lượt này chạm đúng 1 file.
⚠️ FLAG-C (cùng lớp, KHÔNG chạm — xin lead quyết): grep cả file thấy 1 site nữa cùng bệnh ngoài phạm vi MAJOR-1: nút "+ Thêm NV" (:1226-1248) có disabled={addDisabled} + title= giải thích lý do bị khoá ("Cấp N-1 phải có ≥1 NV trước" / "Hết NV khả dụng…") đặt trên chính <button disabled> ⇒ cùng Chromium suppress, admin không biết vì sao nút xám. Đây là bug có sẵn từ trước S182, không thuộc D3=C, và TASK ghi rõ "1 việc DUY NHẤT — KHÔNG làm gì khác" ⇒ để nguyên, chỉ khai. (Hai nút title="Lên"/"Xuống" :1155/:1164 cũng disabled ở rìa nhưng title chỉ là nhãn, không mang thông tin lý-do ⇒ không cùng lớp thiệt hại.)