# Implementer-Frontend Agent — Persistent Memory

- **S171 (2026-08-04) lane FE — 🔴 **CHẾT Ở LƯỢT TRẢ VỀ** (`API Error: Connection closed mid-response`, engine nhận `verdict: null`) `[LEAD HARVEST HOI-TO @S172 — no mo tu closeout truoc; owner slot (71) chot 'gom va sleep thi auto, tu quyet'. Nguon = artifact tren dia, KHONG suy dien]; nguồn `runs/2026-08-04-S171-khkk-ui-mirror-pe/sub-implementer-frontend-s171.md` 13.132 B = **BIÊN BẢN LEAD-ON-BEHALF**, không phải lane tự khai`:** 🔴 **Nhật-ký GỐC = 642 B, đúng 7 heading, RUỘT RỖNG HOÀN TOÀN** — lane ghi khung ở lượt 1-2 **đúng luật** nhưng **KHÔNG append dòng nào** sau đó. 🔴 **Đây là ĐỐI CHỨNG SẠCH trong cùng một phiên, và nó SIẾT LẠI luật chống-#53:** 3 lane `reviewer` (khung ✅ + append ✅) cứu **74.070 B** dù cả 3 chết hết-giới-hạn · `implementer-backend` (khung ✅ + append ✅) return SẠCH · lane này (khung ✅ + append ❌) **mất trắng**. ⇒ **khung-rỗng = CẦN, KHÔNG ĐỦ; APPEND-sau-mỗi-đơn-vị-việc mới là cái cứu.** Câu cũ *'ép ghi khung rỗng lượt 1-2'* từ nay **cấm trích cụt**. 🔴 **Ranh phải giữ — MẤT CODE ⟂ MẤT NGHĨ:** *cái lane LÀM* đo được từ đĩa (20/20 acceptance tĩnh ×2 app · mirror 8/8 · build 3/3, lead tự đo sau khi agent dừng hẳn); *cái lane NGHĨ* **mất trắng** — 0 bản ghi trade-off/lý-do. Đó là lý do **§4 (nợ E-7) TRỐNG** và **không ai dựng lại được**. Đừng hứa 'recover trọn' cho ca write-lane. ⚠️ Kèm: MFE @S172 đo vai này **11/13 = 85%**, role **duy nhất** dưới 100% trên 23 role. Tag `[s171, chet-o-luot-tra-ve, khung-rong-can-khong-du, mat-code-vs-mat-nghi, e7-khong-dung-lai-duoc, mfe-85]`

> **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 N1–N8 — 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** dù bị giục (thiếu mirror-SHA hoặc build ×2 = trả lại). `[s152, no-lower-bar]`
- Cũ hơn (S65→S97: 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 lý 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 có 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`.
