Files
solution-erp/.claude/agent-memory/implementer-frontend/MEMORY.md
2026-08-01 13:17:52 +07:00

77 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# Implementer-Frontend Agent — Persistent Memory
> **Persistent diary cross-session.** Auto-injected @spawn (L1 HOT).
> Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 `archive/` on-demand · L3 RAG `search_memory` just-in-time. Keep entry ≤ 1.5K chars (gotcha #53).
> **Compact @S166 (2026-08-01)** — L1 chạm trần đọc (26.9KB) ⇒ S113→S166 verbatim dời `archive/2026-H2.md`, dưới đây chỉ còn móc 1 dòng.
> **NEW agent S39 (2026-05-29)** — split từ implementer (FE 2 app half). Backend scaffold history ở `implementer-backend`.
---
## 🧠 Bài học SỐNG (đọc trước khi nhận việc — KHÔNG archive)
1. **ĐO ĐĨA TRƯỚC KHI GÕ.** Tiền-đề lead sai **5 lượt liên tiếp** (S164 · S166-K4a ×2 · K4b · K5) theo 2 hướng: *"tưởng chưa có"* (bảo tạo `KHKK_GROUP_LABELS` khi map đã tồn tại 5 call-site ⇒ tạo thêm là **phá đúng acceptance single-source mà nó phục vụ**) và *"tưởng có"* (bảo đi qua `lines[].contractId` — field **không tồn tại ở cả FE lẫn BE**). 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á. Bịa field cho khớp đề bài = tệ hơn.**
2. **Đề bài mô tả CÁCH LÀM có thể giết feature đang sống.** "`phase` derive từ searchParams thay `useState`" nguyên văn ⇒ **6 chip lọc thành nút chết** (hỏng CÂM, build xanh). Đọc kỹ *còn ai ghi vào state đó* trước khi đổi nguồn.
3. **Phép đo phải có RĂNG — luôn kèm CONTROL-ÂM.** 0-hit trần / "0 cảnh báo" vô nghĩa nếu chưa chứng phép đo bắt được cái sai (im-vì-đúng ⟂ im-vì-câm). Chuẩn đã dùng: tiêm lỗi vào **chính khối text đã ship** rồi xác nhận FLAG nổ.
4. 🔴 **Mô phỏng framework thì phải mô phỏng CẢ vòng đời.** Harness K4b bản-1 đo nhầm **cái render React đã VỨT** (setState trong render ⇒ React huỷ render đó + render lại trước khi commit) ⇒ nhánh thật và control-âm ra CÙNG kết quả = phép đo mất khả năng phân biệt, **trông y hệt PASS**. Lặp tới khi state đứng yên mới đúng.
5. **Chứng bằng cách CHẠY text đã ship**, không chép tay: `indexOf('<neo>')` → slice → `new Function`; dữ liệu thử sinh từ nguồn BE thật (`MenuKeys.cs` / `DbInitializer.cs`). Mọi phép thế text phải assert-khớp trước, không khớp thì script tự chết.
6. **Bất-biến nên là CẤU TRÚC, không phải kỷ luật.** Giữ 1 danh sách phẳng canonical để đếm, phần chia nhóm chỉ **chia lại chính nó** ⇒ Σ luôn khớp; assert chỉ là chuông cho người sửa sau.
7. **Forward-provision = đổi HÌNH DẠNG (đọc-1-ô → gom-một-TẬP), KHÔNG bịa field.** Thiếu nguồn thì đánh dấu chỗ cắm + trích nguyên văn bằng chứng + FLAG cho lead.
8. **Sai-thầm-lặng → đổi thành thiếu-nhìn-thấy-được.** BE `/deleted` không nhận `group` ⇒ KHÔNG cho UI claim nhóm ở màn đó. **Rỗng-vì-lọc ⟂ rỗng-vì-chưa-có-gì phải khác câu** (dùng chung ⇒ user nhóm N5 tưởng cả module chưa cấu hình). Phiếu nhóm lạ gom vào "(chưa phân nhóm)" thay vì rơi ra ngoài.
9. **`npm run build` = `tsc -b && vite build` + `noUnusedLocals`** ⇒ build LÀ phép thử TS6133 thật; **đừng tin IDE diagnostic** (gotcha #68).
10. **Mirror ×2 app: cái KHÔNG đổi cũng là bằng chứng.** File không sửa mà hash BẰNG baseline = **đối chứng dương** cho phép đo hash. Lệch có chủ đích (fe-admin không có `WorkflowMatrixViewPage`) phải KHAI tại chỗ, không mirror mù.
11. **Validate query-param bằng REGEX CHUỖI, không `Number()`.** `Number("03")=3` lọt lưới; `/^[1-8]$/` chặn. Và chốt chặn kép khi param chỉ có nghĩa với 1 ngữ cảnh (`type===10 && regex`).
12. **Lọc client-side thì KHÔNG thêm trục vào `queryKey`** nếu payload đã tải trọn — thêm = N lần gọi mạng cho cùng một payload + N ô cache trùng. Ngược lại, trục làm ĐỔI request mà thiếu = cache dùng chung sai CÂM.
13. **Muốn ĐẾM cái UI hiển thị thì DỰNG LẠI dữ liệu thật rồi chạy chính bộ lọc đã ship** — đừng đếm bằng mắt/suy luận. Và cho phép đo **tự kiểm**: cây menu dựng lại được đối chứng bằng invariant T6 (mọi key `MenuKeys.All` phải có menu-row) — dựng sai thì chỉ số đó vỡ trước.
14. **Route-resolver có nhánh ≠ thứ đó tồn tại.** `resolvePath` nhận code `'Contract'` không có nghĩa menu-row `AwV2_Contract` tồn tại (0 hit repo). Cùng họ với "bịa field": đọc nhánh code rồi suy ra dữ liệu là suy ngược.
15. 🔴 **Khuôn chỉ port được khi VỎ TRANG cùng hình dạng — đo BỀ NGANG bằng SỐ trước khi mirror.** Khuôn cột-trái `19rem` sinh ra cho trang GIẤY-CUỘN; trang 3-PANEL cao-cố-định (`h-[calc(100vh-4rem)]` + `grid-cols-[320px_1fr_360px]`, grid KHÔNG `gap`) không có 304px dư: sidebar `xl:w-80`@1366 panel chi tiết 346px→**42px**. Mirror-y-hệt ở đây = hỏng CÂM (build xanh). Cộng bề ngang ra số TRƯỚC, và nhớ cơ chế thu gọn của component có thể là `lg:hidden` = chỉ sống ở dải mà panel kia đã ẩn ⇒ vô dụng ở desktop.
---
## 📌 Nhật ký (móc 1 dòng — verbatim → `archive/2026-H2.md`, cũ hơn → `archive/2026-H1.md` + `_INDEX.md`)
- **S167 (08-01) GĐ3** cây 4-folder lên cụm Duyệt-HĐ → **BLOCKED @§0, 0 file chạm**. 7 loại HĐ = 1 component trang (`?type=`), KHÔNG 7 file. 3 lệch tiền-đề: vỏ 3-panel không nhận khuôn cột-19rem (@1366 còn 42px) · fe-user có 3 ứng viên trang (`MyContracts`/`Inbox`/`WfMatrix`) vs admin 1 · cặp trang KHÁC FILE (`ContractsListPage``MyContractsPage`) ⇒ SHA-pair identical bất khả thi (đối chứng dương: 4 cặp component/hook/HardCopies BẰNG nhau). +K6 đã ẩn `Contracts` khỏi sidebar admin ⇒ nửa admin là trang deep-link-only. `[s167, khuon-can-cung-vo-trang, sub-implfe-gd3.md]`
- **S166 (08-01) K6** admin thu gọn về vai NƠI-CONFIG (fe-admin ONLY 1 file, fe-user 0-diff). `filterForAdmin` lọc ĐỆ QUY ⇒ chỉ liệt 12 ROOT; **dựng lại cây seed 200 node rồi chạy bộ lọc đã ship ⇒ 166 lá → 15 lá liệt đủ tên**, tự-kiểm bằng invariant T6 (113 key `All` → 0 thiếu row). Comment vá lineage **2 lần đảo**. `[s166, k6-admin-thu-gon, route-resolver-khac-menu-row]`
- **S166 (08-01) K4c** WfView lọc nhóm (chốt cụm-2, fe-user ONLY 1 file, fe-admin 0-diff). Lọc `wf.code === khkkGroupWorkflowCode(n)` client-side ⇒ **không đổi queryKey** (khai rõ); rỗng-vì-lọc tách câu riêng. 21/21 proof + 2 control-âm. `[s166, khkk-k4c-wfview-loc-nhom]`
- **S166 (08-01) K5** cây toàn-trình GĐ2 mở **8 sub-folder nhóm** + forward-provision B3-A5. `groups` chia lại `leaves` phẳng ⇒ Σ theo cấu trúc; hiện đủ 8 kể cả rỗng + "(chưa phân nhóm)"; `lines[].contractId` KHÔNG tồn tại → union 1-nguồn + FLAG. 15/15 proof (CA4 tiêm lỗi). SHA 4/4 pair (2 đổi + **2 đứng yên = đối chứng dương**). `[s166, khkk-k5-8-sub-folder]`
- **S166 (08-01) K4b** nối `?group=` + vá-3/5/6. Override 3-trạng-thái `chipPhase` + reset-trong-render theo `navKey` (KHÔNG useEffect); queryKey +`group`; staticMap G1 user 6/6 · admin 5/6. **Harness đo nhầm render bị vứt.** `[s166, khkk-k4b-group-wire, harness-do-nham-render-bi-vut]`
- **S166 (08-01) K4a** regex 42 leaf `Khkk_G{2..8}_*` + DERIVE `khkkGroupMenuLabel` (không đẻ map-2) + `khkkGroupWorkflowCode`. 42/42 resolve + 3 control-âm. `[s166, khkk-k4a-regex-42-leaf, tien-de-lead-sai-x2]`
- **S164/S165 (07-31) K2** KHKK nhóm-duyệt N1N8 — badge trong ô (không đẻ cột) · **`colSpan` 7→8 hai chỗ = lỗi CÂM** · việc thật ≈1/4 mô tả. `[s164, do-dia-truoc-khi-go, colspan-loi-cam]`
- **S161 (07-29)** ARC W1+W2+W3đ1 KHKK FE 3 lượt — containment **2-LỚP** render+payload (chỉ `disabled` = giả) · types-theo-ENTITY · FE tự suy ✓/●/○ từ con-trỏ đôi. `[s161, containment-2-lop]`
- **S153 (07-26)** ctx-adopt T2+T5 — **writeGuard là 2 ĐIỂM**, `:235` (vào prompt) mới có RĂNG · `node --check` file thô luôn SyntaxError. `[s153, t5-writeguard-2-diem]`
- **S134 (07-17)** PE lũy kế TẠM TÍNH — **`sub?: ReactNode` slot-prop** chèn sub-dòng vào N render-site không nhân bản markup. `[s134, sub-slot-prop]`
- **S133 (07-17)** PE budget-freeze badge — className header LẶP 2 chỗ ⇒ **anchor phải kèm text visible**. `[s133, anchor-by-visible-text]`
- **S117 (07-14)** PE duyệt-theo-KHOẢNG radio-5 — spread conditional **TRƯỚC** `as T[]` cast · `??undefined` = omit-key axios. `[s117, spread-conditional-before-as-cast]`
- **S116 (07-13)** PE nhập số ÂM — toggle dấu phải **commit NGAY** (chỉ flip cờ = mất dấu thầm lặng) · quote ở `details[].quotes` KHÔNG `suppliers[].quotes`. `[s116, sign-toggle-immediate-commit]`
- **S113 (07-12)** Supplier publish/draft — publish qua endpoint riêng, **KHÔNG qua UpdateSupplier** (#73). `[s113, publish-not-update]`
- **S152 (07-25)** 2 ranh auto-refuse: việc <30min/trivial = em-main solo · **KHÔNG hạ chuẩn** bị giục (thiếu mirror-SHA hoặc build ×2 = trả lại). `[s152, no-lower-bar]`
- hơn (S65S97: office-dashboard · dept-parent · capability-flag · hosolink…) `archive/2026-H1.md`.
---
## 🎯 Role baseline
WRITE specialist FE 2 app (fe-admin + fe-user). Cookie-cutter mirror SHA256 IDENTICAL + Pattern 16-bis 4-place + declarative KIND_CONFIG + npm build ×2. Case 1+2 only. Tools: Read, Edit, Write, Bash, Skill, Grep, Glob + 5 RAG. Skill: `permission-matrix`.
### 🚫 Split boundary
- MINE: `fe-admin/src/**` + `fe-user/src/**`
- NOT: `src/Backend/**` `implementer-backend` (chỉ Read DTO shape) · `tests/` `test-specialist`
- NOT: UX flow decision (drawer/tab/modal, mở-sẵn hay đóng) em main solo
### 📋 Patterns proven
- **Pattern 16-bis 4-place mirror (9× cumulative):** page/types · `App.tsx` Route · `lib/menuKeys.ts` · `Layout.tsx` `resolvePath` staticMap (**DỄ MISS** silent sidebar drop, gotcha #50). Spec MUST list 4 places explicit.
- **SHA256 IDENTICAL ×2 app:** viết 1 bên `cp` `sha256sum`. Khác UX thì mirror tay + `diff` + KHAI do tại chỗ.
- **Declarative KIND_CONFIG Record (3× proven):** `:kind` URL + `Record<Kind,{fields,columns,icon,label}>`. ADD-KIND = union + entry + KINDS + renderCells branch + icon; **0 route mới**.
- **Pattern 14 Tailwind JIT:** class động bị purge full literal `as const`, cycle `index % length`.
- **TS6:** `erasableSyntaxOnly` cấm `enum` `const X = {...} as const` + `type X = typeof X[keyof typeof X]`. Tuple `as const` cần `as readonly number[]` mới `.includes(n: number)` được.
- Named export only (trừ `App`). UI 100% tiếng Việt. fe-user thiếu Card/Badge shadcn fallback inline `<div className="rounded-lg border bg-card">`.
- **PageHeader KHÔNG một chuẩn:** bản `@/components/PageHeader` = `title/description/actions`; bản `@/components/ui/PageHeader` (pipeline/KHKK) thêm `eyebrow/subtitle/icon/accent`. **Đọc bản đang import trước khi truyền prop** (bài S37, lặp lại @K4c).
### ✅ Verify protocol
`npm run build` ×2 app BOTH 0 TS error + `sha256sum` mirror proof + proof-script control-âm cho mọi khẳng định "đủ/đúng/không sót". Bundle >500KB + `INEFFECTIVE_DYNAMIC_IMPORT realtime.ts` = warning PRE-EXISTING, OK.
### ⚠️ Anti-patterns (DO NOT)
1. ❌ Touch BE files · 2. ❌ Miss 4th Layout staticMap · 3. ❌ Skip npm build ×2 · 4. ❌ `git add -A` · 5. ❌ Push/commit — em main commits · 6. ❌ UX decision autonomous → REFUSE · 7. ❌ Bịa field cho khớp đề bài → FLAG.
### 🔄 Curate trigger
L1 > ~30KB **hoặc chạm trần đọc** → archive verbatim sang `archive/<period>.md`, L1 giữ móc 1 dòng. Commit scope (em main commits): `FE-Admin` · `FE-User`.