# sub-implementer-frontend-s171 — Nhật-ký lane FE (Nhóm 2+3)

> Run: 2026-08-04-S171-khkk-ui-mirror-pe · Vai: `implementer-frontend` (Opus 5 + effort:max)
> Luật giao cho lane: ghi khung TRƯỚC, append SAU MỖI file sửa xong. Không dồn cuối.

---

## 🔴 BIÊN BẢN LEAD-ON-BEHALF (đọc trước)

Lane **chết ở lượt TRẢ VỀ** — `API Error: Connection closed mid-response`, engine nhận `verdict: null`.
**Nhật-ký gốc = 642 B, đúng 7 heading dưới đây, 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 đó.

⇒ Toàn bộ số trong file này **do LEAD tự đo từ ĐĨA sau khi agent đã dừng hẳn** — không phải lane tự khai. Khuôn `(3) lead-on-behalf-từ-sub-file sau verify-đĩa` (`feedback_agent_return_garble_recover`).

🔴 **Ranh phải giữ:** *cái lane LÀM* thì đo được từ đĩa; *cái lane NGHĨ* thì **mất trắng**. Không có bản ghi trade-off/lý-do nào từ phía nó. Đó là lý do **§4 (E-7) TRỐNG** và không ai dựng lại được — xem §6.

### Chống-#53 chỉ cứu được MỘT NỬA ở lane này

| Lane | Khung rỗng lượt 1-2 | Append liên-tục | Kết quả |
|---|---|---|---|
| 3 lane `reviewer` (Stage B) | ✅ | ✅ | **cứu 2,95/3 — 74.070 B** dù cả 3 chết vì hết giới-hạn phiên |
| `implementer-backend` (Stage D) | ✅ | ✅ | return **SẠCH** + nhật-ký 11.650 B |
| `implementer-frontend` (lane này) | ✅ | ❌ | **642 B rỗng** — mất trọn phần NGHĨ |

🔴 **Bài học đo được:** *ghi khung rỗng* là điều-kiện **CẦN, KHÔNG ĐỦ**. Khung không ruột vẫn mất. Cái cứu thật là **append sau MỖI đơn-vị việc** — lane này bỏ đúng vế đó.
🟢 **Nhưng CODE không mất byte nào** — lane chết ở *return*, không phải giữa lúc ghi file (`ngược-#53`).

---

## 0. Tiền-đề đọc được (spec + lane BE)

3 build chạy **SAU khi agent dừng hẳn** (gotcha #68):

| Build | Kết quả |
|---|---|
| `dotnet build SolutionErp.slnx` | ✅ **Build succeeded, 0 Error(s)** |
| `npm --prefix fe-admin run build` | ✅ **built in 4.04s** — 1963 modules, `index-BMZrYl33.js` 1.750,70 kB |
| `npm --prefix fe-user run build` | ✅ **built in 3.62s** — 1952 modules, `index-BW4K-ESl.js` 1.668,44 kB (`tsc -b` trong script, exit 0) |

🔴 **IDE diagnostic ĐÃ ĐÁNH LỪA — đúng gotcha #68 / `feedback_stale_diagnostic_background_agent`.** Lúc lane còn chạy, IDE báo **6 lỗi** trông như vỡ nặng:
`ContractSigningPlansController.cs:93,95` CS0246 *không tìm thấy* `ContractSigningPlanChangelogDto` / `ListContractSigningPlanChangelogsQuery` · `fe-user/.../PipelineTreePanel.tsx:97` TS2304 *Cannot find name* `BuildStagesOptions` · +3 cảnh-báo biến không dùng.
**Cả 6 đều là snapshot DỞ.** Build thật: **0 lỗi, cả 3**. Tin diagnostic mà rollback thì đã vứt một mẻ việc đúng.

## 1. Nhật-ký thay-đổi (append-only, 1 dòng / file)

> 🔴 Lane KHÔNG ghi mục này. Dưới đây là **danh-sách file lead đọc từ `git status`**, KHÔNG kèm lý-do (lý-do đã mất cùng return).

**Tạo mới** (×2 app, SHA256 trùng khít): `components/khkk/KhkkListPanel.tsx` · `components/khkk/KhkkDetailContent.tsx` · `components/khkk/KhkkWorkflowPanel.tsx`
**Xoá**: `pages/khkk/KhkkWorkflowPanel.tsx` (dời sang `components/`)
**Sửa** (×2 app): `pages/khkk/KhkkListPage.tsx` · `pages/khkk/KhkkDetailPage.tsx` · `types/khkk.ts` · `hooks/usePipelineStages.ts` · `components/pipeline/PipelineTreePanel.tsx`

## 2. Mitigation cưỡng-hành (M-1 · M-2 · B1 · R-11)

**4/4 CÓ MẶT, đo được, cả 2 app** — xem bảng C-5..C-8 ở §5.
- **M-1** `canPe` fallback → **7 hit**/app
- **M-2** `onOpen` callback → **7 hit**/app
- **B1** `stage2Count` → **6 hit**/app
- **R-11** cảnh-báo *"200 phiếu mới nhất"* CÒN GIỮ ✅

### 🟢 §2-bis — Hồi-quy chéo B1 ĐƯỢC CHẶN ĐÚNG CÁCH (phép đo quan-trọng nhất của lane này)

Lane feasibility cảnh-báo: `PipelineStageFolders.tsx:285-287` đặt `content` **TRƯỚC** `groups` ⇒ truyền `stage2Content` sẽ **nuốt 8 ngăn N1-N8 im lặng**, và **A-10 gốc không canh được** vì 2 path này ngoài danh-sách.

Đo thật `usePipelineStages.ts:292-297`:
```ts
...(opts.stage2Content !== undefined
      ? { content: opts.stage2Content(khkkItems), count: … }
      : { groups: khkkGroups }),
```
**Conditional spread** ⇒ ai KHÔNG truyền `stage2Content` thì stage2 vẫn nhận `groups: khkkGroups`.
- Trang PE `PurchaseEvaluationsListPage.tsx:524` truyền **chỉ** `{ stage1Content: … }` ⇒ **giữ nguyên 8 ngăn N1-N8** ✅
- `PipelineTreePanel.tsx:314` truyền `stage2Options ?? {}` ⇒ mọi call-site khác (cây PE, GĐ3) rơi vào `{}` ⇒ **groups nguyên vẹn** ✅
- `:173-175` có comment đánh dấu `stage2Count` là **"BẮT BUỘC đi kèm `stage2Content`"** kèm trích `PipelineStageFolders.tsx:95-97`

⇒ **Additive thật, không phá.** Lane hiểu đúng bẫy và xử đúng — dù không kịp ghi lại rằng nó đã hiểu.

## 3. Bẫy hồi-quy (M-4 URL · R-5 set-state-in-render)

- **M-4** — `grep -oE "set(SearchParams|Sp)\([^)]*'(filter|view|group)'"` → **0 hit × 2 app** ✅ ⇒ **không hồi-sinh bug S155** (sidebar sáng nhầm leaf).
- **R-5** — `KhkkListPage.tsx:97-101` set-state-trong-render **CÓ CHỦ ĐÍCH**: 3 build PASS và không có cảnh-báo React ⇒ không bị refactor thành `useEffect`. ⚠️ *Chưa xác-nhận bằng đọc diff từng dòng — mới suy từ build sạch.*

## 4. Bảng E-7 — "cột nào chết, chuyển đi đâu"

🔴 **TRỐNG — KHÔNG DỰNG LẠI ĐƯỢC.**
Đây là thứ **chỉ lane biết**: nó là người quyết **bỏ cột nào** khi ép 8 cột bảng cũ (Mã · Gói thầu · **Dự án** · Trạng thái · **Tổng đề xuất** · **Tổng duyệt** · **Người soạn** · **Ngày tạo**) + **6 `KpiCard`** + phân-trang vào **card 400px**. Lead đọc được *kết-quả* trên đĩa nhưng **không đọc được lý-do bỏ**.
⇒ **Phải soi diff `KhkkListPanel.tsx` bằng mắt và dựng bảng này TRƯỚC UAT.** Bỏ qua = đúng ca **M-7 mất-chức-năng-im-lặng** mà lane regression cảnh-báo.

## 5. Tự-kiểm acceptance (C-1..C-8 · D-1..D-11 · E-1 · E-3)

### Nhóm 2 — **10/10 OK × 2 app**

| # | Đo | fe-admin | fe-user |
|---|---|---|---|
| C-1a | `components/khkk/` ≥3 file | 3 ✅ | 3 ✅ |
| C-1b | 0 hit import `pages/khkk/KhkkWorkflowPanel` | 0 ✅ | 0 ✅ |
| C-2a | `lg:grid-cols-[400px_1fr_360px]` = 1 | ✅ | ✅ |
| C-2b | `h-[calc(100vh-4rem)]` ≥1 | ✅ | ✅ |
| C-3 | route `/khkk/:id` CÒN SỐNG = 1 | ✅ | ✅ |
| **C-4** | 0 hit `filter`/`view`/`group` trong `setSearchParams` | 0 ✅ | 0 ✅ |
| **C-5** | fallback `canPe` (M-1) | 7 ✅ | 7 ✅ |
| **C-6** | `stage2Count` (B1) | 6 ✅ | 6 ✅ |
| **C-7** | `onOpen` callback (M-2) | 7 ✅ | 7 ✅ |
| **C-8** | cảnh-báo trần-200 CÒN | ✅ | ✅ |

### Nhóm 3 — **10/10 OK × 2 app**

D-1 `PePipelineStrip current={2}` ✅ · **D-2 `<Section` = 5** ✅ *(xem §5-bis)* · **D-3 ý-kiến ĐÃ DỜI panel 3→2**: `opinions` ở `KhkkWorkflowPanel` = **0**, ở `KhkkDetailContent` = **4** ✅ · D-4 `md:grid-cols-2` + `cấp đã duyệt` + `NV tham gia` ✅ · **D-5 (F-1) empty-state "Chọn phiếu ở danh sách"** ✅ · **D-6 (F-2) chip `Kết thúc`** 2 hit ✅ · D-8 `không qua CEO` + `departmentName` + `Duyệt thay CEO` + `opacity-50` ✅ · **D-9 khối 📎 "File đính kèm khi duyệt"** ✅ · D-10 `Lịch sử duyệt` + `Lịch sử thay đổi` + `/changelogs` ✅ · **QĐ-2 hộp `dashed` panel 3** ✅

### Nhóm 4

| # | Kết quả |
|---|---|
| **E-1 mirror SHA256** | 🟢 **8/8 cặp BYTE-IDENTICAL** (`KhkkListPanel` · `KhkkDetailContent` · `KhkkWorkflowPanel` · `KhkkListPage` · `KhkkDetailPage` · `types/khkk.ts` · `usePipelineStages.ts` · `PipelineTreePanel.tsx`) |
| **E-2** | 🟢 `git diff --name-only` → **0 path** khớp `components/pe/` `pages/pe/` `components/contracts/` `pages/contracts/` |
| **E-2b** | ⚠️ `hooks/usePipelineStages.ts` + `components/pipeline/PipelineTreePanel.tsx` sửa ở **CẢ 2 app** ⇒ kích luật *"chạm ⇒ kiểm lại folder GĐ2 trang PE + GĐ3"*. **ĐÃ KIỂM — §2-bis, PASS** |
| **E-3** | 🟢 build ×2 app exit 0 (§0) |
| **E-4** | 🟡 Domain **45/45 PASS** · Infra **598/599** — 1 FAIL **flaky**, §5-ter |
| E-5/E-6 | ⏸ chưa chạy (cần deploy) |
| **E-7** | 🔴 **THIẾU** (§4) |

### §5-bis — LEAD tự dẫm bẫy A-7b, ghi lại làm chứng

Lượt đo đầu lead chạy `grep -c '<Section title=' KhkkDetailContent.tsx` → **0**, suýt kết luận *"D-2 FAIL, thiếu 5 Section"*.
Đo lại đúng: **`<Section` = 5**, tiêu-đề nằm **khác dòng**. Section 2 là:
```
356:  title={`2. Giá đề xuất theo NCC trúng thầu (${plan.lines.length} dòng)`}
```
⇒ **template-literal có đếm động** — **đúng y nguyên** ca lane fidelity dự-báo ở F-4: *"Section 2 của KHKK gần như chắc-chắn cũng cần đếm động ⇒ implementer viết ĐÚNG sẽ FAIL A-7b; cách duy nhất để PASS là bỏ số đếm động cho vừa regex"*.
🔴 **Lane fidelity dự-báo trúng, và người dẫm vào bẫy là chính LEAD** — dù lead chính là người viết bản sửa D-2. Bài: **có thước đúng trong tay ≠ sẽ dùng thước đúng**; phải đọc lại bản sửa của mình TRƯỚC khi đo.

### §5-ter 🟡 1 test FAIL — FLAKY, CHƯA quy được trách-nhiệm

`SolutionErp.Infrastructure.Tests.Application.CreatePeWorkflowDefinitionCommandHandlerTests.Create_DifferentEvaluationType_DoesNotAffect_Other`

**Bằng-chứng bất-định (cùng một cây, không sửa gì giữa các lần):**

| Lần chạy | Failed |
|---|---|
| full `SolutionErp.slnx` | **2** |
| `Infrastructure.Tests` lượt 1 | **1** |
| `Infrastructure.Tests` lượt 2 | **1** |
| **chạy RIÊNG test đó ×3** | **0 / 0 / 0** |

Số fail **đổi giữa các lần trên cùng cây** ⇒ **không tất-định**. Hồi-quy thật thì fail đúng một con số mỗi lần.
**Nguyên-nhân khả dĩ (đo):** `tests/.../Application/PeWorkflowAdminTests.cs` tạo **`new SqliteDbFixture()` RIÊNG trong TỪNG test** (6 chỗ: `:44 :61 :80 :103 :124 :148`) ⇒ xUnit chạy song song = nhiều fixture SQLite đồng thời. Flake ở **tầng hạ-tầng test**, thuộc **PE workflow definition**, **0 giao với KHKK** (3 file BE đã sửa đều là `ContractSigningPlan*`).

🔴 **KHAI THẲNG cái CHƯA đo:** **baseline trên HEAD sạch KHÔNG chạy được** — `git worktree add` thất bại giữa chừng (`fatal: Could not reset index file to revision 'HEAD'`, đường-dẫn Dropbox; đã `git worktree prune` dọn sạch).
⇒ Mệnh-đề *"flake này có TRƯỚC S171"* là **SUY-LUẬN, KHÔNG phải phép đo**. Bằng-chứng hiện có đủ kết luận **"bất-định"**, **CHƯA** đủ kết luận **"không do ta"**. Ai chạy CI lần tới **phải đo lại** trước khi bỏ qua.

## 6. Chưa làm + lý do (khai thật)

| # | Mục | Trạng-thái |
|---|---|---|
| **E-7** | Bảng "cột nào chết, chuyển đi đâu" | 🔴 **KHÔNG CÓ** — lý-do bỏ cột đã mất cùng return (§4) |
| **B-3** | RUNTIME `approvals[]` có ≥1 `Decision=Reject` + `approvedByFullName ≠ null` | ⏸ BE khai: DB dev `SolutionErp_Dev` có `ContractSigningPlans` = **0 hàng** ⇒ dời UAT |
| **E-5 / E-6** | UAT `fe-user:8080` (3 màn khớp Ảnh 1/2/3, cửa-sổ ẩn-danh) + fe-admin **deep-link** `:8082/khkk/list?group=1` (menu ẩn) | ⏸ chưa deploy |
| **C-5 negative-control** | Gỡ tạm fallback ⇒ acceptance PHẢI TRƯỢT | ⏸ chưa chạy — `canPe`=7 hit mới là **điều-kiện CẦN**, chưa chứng thước có RĂNG |
| **D-9 vế tự-ẩn** | Phiếu duyệt KHÔNG kèm file ⇒ khối 📎 **không hiện** | ⏸ cần runtime |
| **R-5 xác-nhận diff** | Đọc từng dòng `:97-101` xác nhận không bị refactor | ⏸ mới suy từ build sạch |

## 7. Kết

Lane FE **làm đúng và đủ phần CODE** — 20/20 acceptance tĩnh PASS × 2 app · mirror **8/8 byte-identical** · **0** hồi-quy path cấm · hồi-quy chéo B1 chặn bằng conditional-spread · 3 build sạch. Cái mất là **phần NGHĨ**: 0 bản ghi trade-off, và **E-7 phải làm lại bằng tay**.

**Nợ mang sang, không giấu:** (a) **E-7** bảng cột-chết · (b) baseline test trên HEAD sạch **chưa đo** ⇒ **chưa được nói "flake có sẵn"** · (c) 5 acceptance runtime (B-3 · E-5 · E-6 · C-5-negative · D-9-tự-ẩn) chờ UAT.

END implementer-frontend-s171 (LEAD-ON-BEHALF) — 20/20 acceptance tĩnh × 2 app · mirror 8/8 · build 3/3 PASS · test 643/644 (1 flaky, CHƯA quy trách) · **E-7 THIẾU**
