Files
solution-erp/.claude/agent-memory/implementer-frontend/archive/2026-H2.md
2026-08-01 11:35:35 +07:00

82 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# archive/2026-H2 — implementer-frontend (verbatim L2, dựng @S166 2026-08-01)
> Chuyển verbatim từ `MEMORY.md` khi L1 chạm trần đọc. Con-trỏ 1 dòng ở `MEMORY.md`; Ctrl-F tag để nhảy.
> Thứ tự: mới → cũ.
---
## S166 (2026-08-01) — K6-FE admin thu gọn về vai NƠI-CONFIG `[s166, k6-admin-thu-gon, dung-lai-cay-seed-de-dem, route-resolver-khac-menu-row]`
Artifact `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k6.md`. fe-admin ONLY, **1 file** (+52/19), fe-user 0-diff.
**Giải ẩn-số spec để mở acceptance:** spec treo "N = 7 hay 8 tuỳ `AwV2_Contract` có tồn tại không". Đo: **0 HIT toàn repo** ⇒ 2 seed leaf + 8 designer. 🔴 **Bẫy suýt lẫn: `resolvePath:173` CÓ nhánh nhận code `'Contract'` — nhưng route-resolver có nhánh ≠ menu-row tồn tại.** Chốt **N = 1 Dashboard + 4 System-leaf + 2 AwV2 + 8 designer = 15** (spec tự quên cộng 8 designer vào công thức của chính nó).
**Khuôn giữ lại — ĐẾM BẰNG CÁCH DỰNG LẠI CÂY THẬT rồi chạy bộ lọc đã ship:** parse 72 tuple literal từ `DbInitializer` + sinh 128 key từ vòng lặp (hằng đọc thẳng `MenuKeys.cs`) = 200 node → chạy `ADMIN_HIDDEN_KEYS`+`isAdminHidden`+`filterForAdmin` cắt từ file → **166 lá → 15 lá**, liệt đủ tên. **Đối chứng tính đúng của cây dựng lại: 113 key trong `MenuKeys.All` → 0 key thiếu menu-row (invariant T6)** — cây dựng sai thì chỉ số này vỡ ngay, tức phép đo tự kiểm chính nó. **CONTROL-ÂM:** bỏ `'KeHoachKyKet'` khỏi Set ⇒ 15→**63** (+48 = đúng 8 nhóm × 6 leaf).
**Cơ chế đáng nhớ:** `filterForAdmin` lọc **ĐỆ QUY** ⇒ ẩn 1 ROOT là cả cây con chết theo ⇒ tập chỉ liệt 12 ROOT (+`Workflows`/`PeWorkflows` vì chúng là CON của `System`, không chết theo root nào khác). Liệt từng leaf = thừa + phải bảo trì mỗi lần BE thêm key. `System`/`ApprovalWorkflowsV2` là GROUP nên không tính vào số LÁ.
**Vá-14 lineage 2 LẦN ĐẢO** (S29 ẩn → S57 bỏ-ẩn → QĐ7 S164 ẩn LẠI rộng hơn): comment cũ S57 đang nói NGƯỢC luật đang chạy ⇒ gỡ + viết lại kèm câu "cả hai đều từng đúng, mốc sau thắng mốc trước — đừng khôi phục S57 vì tưởng gặp bug". **Display-only #82** khai rõ: route+quyền còn nguyên, deep-link vẫn 200, thu gọn = bớt lối đi menu KHÔNG phải thu hồi quyền. Build fe-admin PASS 1961mod `index-B0OrpamE` 620ms.
## S166 (2026-08-01) — K4c-FE WfView lọc theo nhóm (chốt cụm-2) `[s166, khkk-k4c-wfview-loc-nhom, rong-vi-loc-khac-rong-that]`
Artifact `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k4c.md`. fe-user ONLY, **1 file** (+68/5), `git diff -- fe-admin` = RỖNG.
Đóng FLAG-3 lượt K4b (`?group=` trên workflow-matrix chưa có consumer ⇒ 8 leaf mở ra 8 trang hệt nhau).
**Đo trước khi gõ (4 thứ, không đoán cái nào):**`AwDefinitionDto.code` là tên field THẬT (`types/approvalWorkflowV2.ts:41`, đang render ở `:165`) ② trang **tải trọn bộ theo type** rồi `summary.history` ⇒ lọc CLIENT-side, **KHÔNG thêm trục `group` vào queryKey** (thêm = 8 lần gọi mạng cho cùng payload + 8 ô cache trùng) — khai rõ theo yêu cầu ③ `DbInitializer.cs:609` `code = $"KHKK-N{n}"` + `:623` **`IsUserSelectable = true`** ⇒ 8 wf THỰC SỰ lọt bộ lọc `isUserSelectable:true` của trang (seed `false` thì lọc kiểu gì cũng trang trắng) ④ `PageHeader` bản `@/components/PageHeader``actions` — repo có ≥2 bản PageHeader khác chữ ký, **đọc bản đang import** (lặp bài S37).
**Khuôn giữ lại:** chốt chặn KÉP `typeInt === 10 && /^[1-8]$/``group` là khái niệm riêng KHKK, type khác/rác thì BỎ QUA hoàn toàn (thà hiện đủ còn hơn lọc theo số vô nghĩa ra trang trắng); regex chuỗi bắt được `"03"``Number()` sẽ nuốt. Lọc giữ **đủ VERSION cùng code** (nghĩa là "quy trình của nhóm", không phải "1 bản ghi"). Link "xem tất cả" bám `useLocation().pathname` vì trang gắn **3 route**. 🔴 **Rỗng-vì-lọc ⟂ rỗng-vì-chưa-có-gì phải khác câu** — dùng chung thì user nhóm N5 tưởng cả module chưa cấu hình trong khi 7 nhóm kia đang chạy (cùng họ bài "sai-thầm-lặng → thiếu-nhìn-thấy-được").
**Chứng 21/21** chạy chính 2 khối đã ship + helper cắt từ types (assert khuôn trước khi bỏ annotation), code sinh theo khuôn đọc thẳng `DbInitializer.cs`: 8 URL → mỗi cái đúng 1 wf khớp số · trần → cả 8 · `group=0/9/abc/rỗng/"03"` → không lọc · `type=1|2|3` kèm group → bỏ qua · 2 version cùng code → giữ đủ · **2 CONTROL-ÂM** (gỡ vị-ngữ lọc ⇒ ra 8 = phép đo phân biệt được · helper sai khuôn `KHKK_N3` ⇒ 0 wf, không âm thầm khớp). Build fe-user PASS 1950mod `index-DBtbmn5s` 531ms.
## S166 (2026-08-01) — K5-FE cây toàn-trình: GĐ2 mở 8 sub-folder NHÓM `[s166, khkk-k5-8-sub-folder]`
Artifact `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k5.md`.
🔴 **BÀI: forward-provision đúng nghĩa = đổi HÌNH DẠNG, KHÔNG bịa field.** Đề bài đòi GĐ3/4 đi qua `lines[].contractId pe.contractId`. Đo 4 nơi → **cả 4 âm**: `PeListItem` 0 hit chữ `lines` · `PurchaseEvaluation.cs:31` chỉ `Guid? ContractId` đơn · `ContractListItem` không có `purchaseEvaluationId` · `Contract.cs` 0 khoá ngược. Bằng chứng đắt nhất là BE **tự khai**: `CreateContractFromEvaluationFeatures.cs:143-145` `pe.ContractId = contracts[0].Id` + comment "N HĐ liên-danh truy qua changelog" — endpoint TRẢ `contractIds[]` (`PeDetailTabs.tsx:2186`) nhưng chỉ HĐ ĐẦU được ghi ngược ⇒ HĐ 2+ vô hình. ⇒ Wire vế làm được: `contractIdsOfPackage = new Set<string>()` (union) thay vì đọc 1 ô, chỗ cắm nguồn-2 đánh dấu sẵn + trích nguyên văn BE; FLAG 2 đường cho lead (PE list DTO trả `contractIds[]` — rẻ, dữ liệu đã có; hoặc K7 `Line.ContractId`).
**Khuôn giữ lại:** `stage.leaves` GIỮ vai danh sách PHẲNG canonical (nguồn đếm duy nhất), `groups` chỉ **chia lại chính nó** ⇒ bất-biến Σ là chuyện **CẤU TRÚC**, không phải kỷ luật nhớ-cộng-cho-khớp; `stageCount`/`hideEmpty`/chip số 0 phải sửa. Tách `LeafList` dùng chung thân-folder ⟂ thân-ngăn (0 nhân bản markup). Nút tên ngăn trong `<summary>` phải `preventDefault`+`stopPropagation`, không thì mỗi lần đi trang kèm 1 nhịp đóng/mở nhấp nháy. Ngăn mặc định ĐÓNG (8 ngăn × N gói trên cột trái 19rem = tường chữ). Hiện đủ 8 kể cả rỗng = `.map` trên hằng, KHÔNG `.filter` — mirror tiền lệ `SoonChip`; ngăn "(chưa phân nhóm)" CHỈ mọc khi thật có phiếu lạc (8 nhóm = khung nghiệp vụ cố định ⟂ đây = chỗ chứa bất thường).
**Chứng:** chạy chính khối đã ship qua `new Function`, **15/15 OK** — CA1 rải đều (8 ngăn, Σ 5=5, nhóm-3 rỗng vẫn hiện, route `?group=1..8` khớp URL leaf K4b) · CA2 phiếu lạc (9 ngăn, gom đủ 4 ca null/9/undefined/thiếu-field) · CA3 gói rỗng (vẫn 8, không đẻ ngăn lạc) · **CA4 CONTROL-ÂM tiêm lỗi nhân-đôi ⇒ `console.warn` NỔ** (không có CA4 thì "0 cảnh báo" ở CA1-3 vô nghĩa: im-vì-đúng ⟂ im-vì-câm). 4 phép thế text đều assert-khớp trước khi chạy, không khớp thì script tự chết.
**SHA 4/4 pair ×2 app:** `usePipelineStages` `7144eeb7`→**`edff9931`** · `PipelineStageFolders` `d449cc8d`→**`bed52f39`** · `PipelineTreePanel` `d70ac315` KHÔNG ĐỔI · `PePipelineStrip` `6fb7337e` KHÔNG ĐỔI ⇒ **2 hash đứng yên = đối chứng dương** cho phép đo. Build ×2 PASS (user `index-CZUcTUUg` 19.8s · admin 1961mod `index-BZWvx0Y8` 10.9s). 0 BE, 0 K4b file, 0 commit. 3 FLAG (lines[].contractId · ngăn mặc-định-đóng · `truncated` không lặp ở từng ngăn).
## S166 (2026-08-01) — K4b-FE nối `?group=` + vá-3/5/6 `[s166, khkk-k4b-group-wire, harness-do-nham-render-bi-vut]`
Artifact `…/sub-implfe-k4b.md`.
🔴 **BÀI TO NHẤT — HARNESS ĐO NHẦM CÁI RENDER BỊ REACT VỨT, suýt kết luận NGƯỢC.** `setState` gọi **trong lúc render** ⇒ React **huỷ render đó + render lại ngay với state mới TRƯỚC khi commit**; harness bản-1 chỉ chạy 1 pass nên đọc ra `phase=3` (giá trị của render đã bị vứt) — **control-âm cũng ra 3 ⇒ 2 nhánh giống nhau = phép đo mất khả năng phân biệt, trông y như PASS**. Vá = lặp tới khi state đứng yên (đúng ngữ nghĩa React) → A ra `phase=2`, control-âm giữ `3`. Khuôn: *mô phỏng hành vi framework thì phải mô phỏng CẢ vòng đời, không chỉ 1 lượt gọi hàm.*
🔴 **Tiền-đề đề-bài sai lần 3 liên tiếp** (S164→K4a→K4b): "`phase` derive từ searchParams thay useState" nguyên văn sẽ **GIẾT 6 chip lọc** (`KpiCard onClick setPhase :153`) — hỏng CÂM, build xanh. Giải = **override 3-trạng-thái** `chipPhase: T|null|undefined` (`undefined`="nghe URL" ≠ `null`="user chọn Tất cả"; gộp 2 cái là chọn Tất-cả bị URL kéo ngược) + **reset trong render theo `navKey`** (KHÔNG `useEffect` — effect vẽ 1 lượt dữ-liệu-cũ rồi mới sửa = nháy). Cùng khuôn cho CreatePage (8 leaf chung 1 route ⇒ leaf→leaf không remount; so `lastPresetGroup` **KHÔNG** so `approvalGroup` — so nhầm thì user bấm đổi nhóm bị kéo về, select thành nút chết).
Đo ra BE `/deleted` **thiếu `group`** (`Controller:61-66` vs `List:42`) ⇒ không lọc client-side được (server đếm `total` trên tập chưa lọc → số trang nói dối) ⇒ xử bằng **không cho UI claim nhóm ở màn đã-xóa** (sai-thầm-lặng → thiếu-nhìn-thấy-được) + FLAG lane BE. staticMap G1: fe-user **6/6** mang `group=1`, fe-admin **5/6** (WfView admin trỏ Designer, không nhận group) — lệch có chủ đích. SHA-pair ×2: List `80ebd79d`→**`fc3944df`** · Create `677f4ab8`→**`545b4c2f`** · types `7a99b865` không đổi = đối chứng dương. Build ×2 PASS (user `index-C4F7kHKT` 3.6s · admin `index-kv90BBYQ` 0.9s). 4 FLAG.
## S166 (2026-08-01) — K4a-FE regex 42 leaf KHKK + single-source nhãn nhóm `[s166, khkk-k4a-regex-42-leaf, tien-de-lead-sai-x2]`
Artifact `…/sub-implfe-k4a.md`.
🔴 **Khuôn S164 "đo đĩa TRƯỚC khi gõ" ăn tiếp 2 lần trong CÙNG 1 đề bài** — cả 2 tiền-đề lead sai theo hướng *"tưởng chưa có / tưởng có"*: ① đề bài bảo export `KHKK_GROUP_LABELS` **mới**, đĩa đã có `KHKK_APPROVAL_GROUP_LABELS` (`types/khkk.ts:129`, K2 land, **5 call-site**) ⇒ đẻ map-2 là **phá đúng acceptance-5 (single-source) mà nó phục vụ**; K2 còn để sẵn uỷ-quyền trong comment `:125-127`. Đo thêm: nhãn BE ≡ `` `${n}. ${short}` `` cho **8/8** ⇒ giải bằng **DERIVE `khkkGroupMenuLabel(n)`, KHÔNG hardcode lần 2, 0 call-site cũ bị đụng** (hardcode lại = double-numbering "N2 - 2. …" ở select). ② comment vá-8 bảo "gỡ `KeHoachKyKet` khỏi ẩn-admin ⇒ phải nới regex trước", nhưng `isAdminHidden:194` = `key.startsWith('Ct_')` — KHKK **chưa bao giờ bị ẩn** ⇒ ràng buộc ĐANG hiệu lực chứ không phải tương lai; viết comment theo ĐĨA + FLAG "7 nhóm rỗng" (`MenuGroup` chỉ xét `children.length>0`, không xét con resolve được ⇒ header hiện, thân rỗng).
**BÀI MỚI: chứng regex bằng `new Function()` chạy CHÍNH khối text đã ship** (`indexOf('const khkkMatch')` → slice → eval), key sinh từ `KhkkGroupNumbers`/`KhkkLeafActions` **đọc thẳng MenuKeys.cs** ⇒ 42/42 resolve, 0 drop, **+3 control-âm** (G9→null · action bogus→null · `Khkk_List` nhóm-1-không-infix→null vì chặn ở staticMap trước). Nhãn cũng kèm control-âm (chèn 'x' vào N4 → phép so FLAG) — 0-hit trần vô nghĩa.
Khuôn: `Khkk_G1` + 6 leaf **KHÔNG-infix** (S159, cấm đổi = mồ côi permission-row), 42 leaf infix chỉ là nhóm **2..8**; giữ `[1-8]` theo spec = rộng hơn tập seed 1 nấc, cố ý. `group`/`filter`/`view` ∉ `TRANSIENT_QUERY_KEYS:311` ⇒ active-state phân biệt (bài `view=approved` S155). SHA-pair types `eba649cc`→**`7a99b865`** ×2; Layout 2 app **cố ý KHÔNG mirror** (user +34 regex · admin +13 comment-only). Build ×2 PASS (user 1950mod `index-B2v0kn5F` 20.8s · admin 1961mod `index-84zDBHE-` 8.2s).
## S164/S165 (2026-07-31) — K2-FE KHKK nhóm-duyệt N1N8 `[s164, khkk-nhom-duyet, do-dia-truoc-khi-go, colspan-loi-cam]`
Relaunch p2 sau p1 chết #53 — chi tiết `runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k2.md` §1-§5.
🔴 **BÀI CHÍNH — tiền-đề lead SAI, đo `git diff` TRƯỚC khi gõ:** lead khai CreatePage "mid-wire, 8 khai báo chưa vào JSX" (TS6133 sẽ đỏ) ⇒ đo đĩa thật thì **8/8 ĐÃ có call-site**; Detail cũng land đủ. Việc thật ≈ **1/4** mô tả — chỉ ListPage dở (p1 kịp thêm mỗi dòng import). Khuôn S114 "WRITE-lane garble ≠ mất code" xác nhận lần nữa: lead đọc transcript dở-dang → kết luận theo Ý ĐỊNH đang chạy, không theo đĩa. **Viết lại phần đã đúng = phá.**
⇒ p2 chỉ sửa ListPage 3 điểm: badge `N{g}` NHÉT VÀO ô "Mã kế hoạch" (không đẻ cột — bảng đã cuộn ngang @1366 do cây GĐ 19rem) · +cột "Tổng duyệt" Σ`totalApprovedAmount` (BE trả **0** cho phiếu chưa duyệt ⇒ ép `` kẻo đọc nhầm "đã duyệt 0 đ") · **`colSpan` 7→8 hai chỗ** (loading + empty row — quên = lỗi CÂM tới lúc bảng rỗng). `npm run build` = `tsc -b && vite build` + `noUnusedLocals:true` ⇒ build LÀ phép thử TS6133 thật, không cần tin IDE (#68). cp 4 file → SHA-pair **5/5 IDENTICAL** (Panel không sửa = đối chứng dương, hash `2B64144EF6E8` BẰNG baseline). diff-stat đối xứng 269+/36 ×2 app.
## S161 (2026-07-29) — ARC W1+W2+W3đ1 KHKK FE, 3 lượt `[s161, khkk-fe-3-wave, containment-2-lop, types-theo-entity]`
Chi tiết = sub-md `runs/2026-07-29-S161-khkk-*/` (mọi lượt #53, đĩa cứu trọn).
**W1** Designer type-10: containment **2-LỚP render+payload** (`levelFlagsPayload(e, lock)` ép false 2/2 call-site — chỉ `disabled` = containment GIẢ vì `copyFromDefinition` tuồn state); 2 tiền-đề đề-bài SAI tự bẻ (FE không có bảng nhãn — nhãn từ BE `applicableTypeLabel`; khuôn type-9 không tồn tại → mirror `Contract:3`).
**W2** 3 page + types ×2 SHA-pair; types-theo-ENTITY khi BE song song; header types = HỢP-ĐỒNG 12-endpoint (reviewer đối chiếu ra 6 đứt → lead vá; bài: hợp-đồng phải LEAD-định-nghĩa literal); WfView 2-app-2-đường (user matrix?type=10 whitelist+10 · admin Designer deep-link); place-3 menuKeys SKIP-có-khai (0 consumer).
**W3đ1** `KhkkWorkflowPanel` mirror PE-panel 1063-dòng theo bảng **GIỮ/BỎ tường minh** (BỎ đủ 2 đường cấm + price-picker/budget/4-radio); cây workflow THÔ — **FE tự suy ✓/●/○ từ con-trỏ đôi** (StepIndex 0-based vs Level.Order giá-trị); bổ `approverUserId` opinion (W2 khai thiếu).
## S153 (2026-07-26) — arc ctx-adopt T2 + T5 `[s153, t5-writeguard-2-diem-235-co-rang]`
Lead seed on-behalf @closeout M9; T2 return mất schema-fail (#53 +1) → derive-from-body, template VẪN LAND 8.819B; T5 return CLEAN.
**T2 template** `.claude/templates/session-mind-template.md` — 3 marker (RULES-START/END · MIND-TOP) · **0 dòng key `ts:`** (ts sống trong HEADING sau em-dash ⇒ không phạm rào-2) · 6 hit `rào-` (3 rào × tên+lý-do) · chiều chèn **NGƯỢC** `_context` khai TO trong khối luật · placeholder `<k>` trong schema-comment chống parser false-match · kiểm `_SECRET_RES` để ví-dụ rào-3 không tự khớp mẫu.
**T5 `hmw.js`** 3/3 acceptance: VALID_ROLES 20→21 (sau T10/T11 = **23**) + 🔴 **writeGuard là 2 ĐIỂM không phải 1** — `:228` JS-comment doc **và** `:235` template-literal ĐI VÀO PROMPT sub; chỉ sửa `:235` ⇒ doc lệch trong CÙNG file, chỉ sửa `:228` ⇒ **prompt runtime KHÔNG đổi** ⇒ vai read-only bị hướng-dẫn như write-sub ⇒ Bash-write MD ⇒ mojibake G-009. `:235` = **điểm có RĂNG**. Vùng CẤM đo bằng diff: `WIDTH_CAP` 0 hit · `TIER2_EXPECTED_FULL_ID` 0 hit.
**2 kẽ báo lead:** ① **naming-collision cùng-role 2-invocation** (index `i` reset mỗi invocation ⇒ đợt-2 suýt đè `sub-implementer-frontend-1.md` của đợt-1) — kẽ CÒN LẠI sau S134 ② `node --check` file thô **luôn** SyntaxError (top-level await) ⇒ acceptance phải là **wrapped-parse + đối-chứng HEAD**.
## S134 (2026-07-17) — PE lũy kế TẠM TÍNH FE 2-app `[s134, pe-luyke-tam-tinh, sub-slot-prop]`
wf_58302bd5-b9f INV-2.B · return sạch 0-garble · engine opus-worker.
types ×2 +5 field `pending*` (DIVERGE — edit riêng từng app cùng old_string sau `budgetFrozen`) · PeDetailTabs `5534addb`→**`d28eaa16`** ×2: `BudgetSharedNumCell:1184`+`BudgetColValue:1197` +**`sub?: ReactNode` prop** → sub-dòng amber "Tạm tính" row1/2 (withDetail "+X từ N phiếu chưa duyệt", N8 count-TƯƠNG-ỨNG delta KHÔNG list.length) + row5/6 + block liệt kê `pendingPriorPes` dưới matrix (N9 KHÔNG link — 403 S89) + gate `showPending = !bs.budgetFrozen && (...)` · PeWorkflowPanel `a83a58e1`→**`bebc0ada`** ×2: banner M2-gate `!readOnly && phase === ChoDuyet` + reminder trong Dialog TRÊN price-picker. npm ×2 PASS.
Learned: (1) **`sub?: ReactNode` slot-prop trên cell-component dùng chung = chèn sub-dòng vào N render-site không nhân bản markup**; (2) hmw đánh index sub-file GLOBAL theo taskList — hết collision S133 khi 2 role KHÁC nhau cùng invocation.
## S133 (2026-07-17) — PE budget-freeze FE types+badge `[s133, budgetfrozen-badge, anchor-by-visible-text]`
4 file — types 2-app +`budgetFrozen: boolean` (fe-user `:333` · fe-admin `:331`, 2 file KHÔNG mirror nhưng block PeBudgetSummary nội-dung identical → 1 old_string dùng chung); PeDetailTabs 2-app byte-identical: badge amber `🔒 Ngân sách chốt tại thời điểm duyệt` @ header `PeBudgetSummaryTable` `:1350-1359` (`normal-case tracking-normal` OVERRIDE `uppercase tracking-wide`). 🔴 Gotcha: className header LẶP 2 chỗ (`:1350` budget + `:1931` winners) → Edit anchor PHẢI kèm text visible "Tổng hợp ngân sách trình ký" mới unique. Hash `5534addb` ×2; build ×2 PASS.
## S117 (2026-07-14) — PE "duyệt theo KHOẢNG" radio-5 ProMinMax ×2 app `[s117, prominmax-5th-radio, spread-conditional-before-as-cast]`
Ô "CHỌN GIÁ CHỐT" cấp cuối +radio-5 "PRO — cả Min và Max" (source=ProMinMax) CHỈ khi `proSuggestedMinPrice!=null&&proSuggestedMaxPrice!=null`. types DIVERGE (`PeDetailBundle` L433 +`approvedPriceMaxAmount`, anchor same-string khác line admin488/user490). PeWorkflowPanel `011ee8cf`: `priceCandidates` +`amountMax?`; entry qua `...(hasBothProPrices?[{...}]:[])` **spread TRƯỚC `as T[]` cast**; submit `??undefined` (axios bỏ key). PeDetailTabs `617b0aa0`: label-map + value ternary `min đ max đ`.
**LESSON:** conditional array entry = spread trước cast (né inference lỗi); `??undefined` = omit-key axios chuẩn; Max KHÔNG cần state riêng (derive từ selectedCandidate) → 0 reset-site. Build ×2 PASS.
## S116 (2026-07-13) — PE cho nhập SỐ ÂM báo giá ×2 app `[s116, sign-toggle-immediate-commit, hasRealSelectedQuote]`
PeDetailTabs BYTE-IDENTICAL, cp fe-user→fe-admin. Âm = phát-sinh-giảm/hoàn tiền NCC = giá THẬT. 5 change.
Sign-toggle 2 dialog (AddSupplier :2379 + QuoteDialog :2948); `neg` = useState RIÊNG (QuoteDialog `setForm({thanhTien})` thay CẢ object → nuốt neg). 🔴 2 dialog POST commit `form.thanhTien` THẲNG (:2247/:2906, no nút "Lưu") → toggle PHẢI ghi giá-CÓ-DẤU NGAY, chỉ flip cờ = **MẤT DẤU thầm lặng**. GLOBAL `parseVnd/formatVndInput :51-52` KHÔNG sửa (share ngân sách) → `Math.abs` wrap. Gate `!==0` (KHÔNG `>0`). **2 FE gate ĐỔI (KHÔNG 1):** `missingForApproval:212` + `submitChecklist:243` → helper `hasRealSelectedQuote`. 🔴 GOTCHA quote ở `details[].quotes` KHÔNG `suppliers[].quotes`. Cũ SUM winnerQuoteTotal: net-zero +100/100=0 chặn oan. SHA `3e16dba5` ×2; build ×2 PASS.
## S113 (2026-07-12) — Supplier import v2 FE ×2 app `[s113, supplier-publish-draft, publish-not-update]`
3 file×2app: 2 SHA-pair + types DIVERGED (edit-identical KHÔNG cp). types/master: Supplier +`isPublic`. SupplierImportDialog `78403bd8`: +"Tải file mẫu" (blob `GET /suppliers/import/template`) · cột "Mã" `<td>`→`<Input>` editable (updateRow map-patch) · +`mstMissing` amber warn. 3.d disabled ĐÃ đúng SẴN → NO change. SuppliersPage `9947b121`: +cột "Trạng thái" badge full-class-literal(P14) · Công bố/Ẩn → `POST /suppliers/{id}/publish` (**KHÔNG qua UpdateSupplier** #73) · filter Select `published` · actions w-32→w-40. NO 4-place mirror (page ĐÃ routed). Build ×2 PASS.
DEVIATION flagged: "Tải file mẫu" đặt CHỈ trong dialog (② canonical + ③ acceptance) — parent 2.d loosely list page-btn nữa.
## S152 (2026-07-25) — R1 retention-note 2 ranh auto-refuse `[s152, auto-refuse-trivial-30min, no-lower-bar-smart-friend]`
M9 on-behalf lead — queue memory-ops.
**(a) Việc < 30 min / trivial:** fix 1-dòng CSS / label-polish = em-main solo nhanh hơn spawn-cost REFUSE nhận đúng role-file; nhận việc trivial = đốt spawn ích.
**(b) KHÔNG hạ chuẩn "lower bar" kiểu Smart-Friend nể-nang:** bị giục "làm nhanh" vẫn giữ sàn mirror-SHA ×2-app + npm build ×2 PASS; thiếu 1 trong 2 = trả lại chứ không ship.