` (:48-75): header tầng `border-b bg-slate-50/80 px-3 py-2` + icon 5×5 + title `text-[11px] font-bold uppercase tracking-wide text-slate-600` + pill đếm `rounded-full bg-slate-100 px-2 py-0.5 text-[10px]`
+- **KHÔNG có ô tìm, KHÔNG có ô lọc, KHÔNG phân trang, KHÔNG nút đáy** (cố ý, :298-302)
+- Card tầng-trên (PE): `px-3 py-2` · mã `font-mono text-[10px] text-brand-700` · tên `text-[12px] font-medium text-slate-800` · selected `bg-amber-50 ring-1 ring-inset ring-amber-300` (:189-211)
+- Card tầng-dưới (KHKK): `px-3 py-2` · mã `font-mono text-[10px] text-violet-700` · badge `text-[9px]` · tên `text-[12px] font-medium text-slate-800` · selected `bg-violet-50 ring-1 ring-inset ring-violet-300` (:252-282)
+- loading = **chữ "Đang tải…"** `p-3 text-[11px] text-slate-500` (:160, :230) — KHÁC skeleton của A/B
+- error = "Không tải được danh sách." + "Thử lại" `text-[10px]` (:161-172, :231-242)
+- empty = `` (:173-177, :243-248) — KHÁC A (`EmptyState`) và KHÁC B (icon-chip)
+- gate quyền: `!canKhkk` → khối `Lock` giữa cột (:143-151)
---
## 2. Bảng đối chiếu 10 trục
-Trục: card phiếu (bố cục hàng · cỡ chữ · badge trạng thái · chip nhóm · số tiền) · ô tìm · ô lọc · khoảng cách · empty-state · phân trang · nút đáy · loading · error.
-_(pending)_
+> 🔴 **Đính chính phạm vi CHUẨN (đo được, quan trọng cho §4):** `PeListPanel.tsx` **KHÔNG phải** panel đang chạy ở màn Duyệt NCC golive. Màn golive là `pages/pe/PurchaseEvaluationsListPage.tsx` — nó **tự vẽ `` panel-1 inline** (`:399-455`), có CÂY 4 tầng inline (`:472-…`). `PeListPanel.tsx` chỉ được dùng ở màn **Thao tác** (`PurchaseEvaluationWorkspacePage`). Vậy CHUẨN có **2 mặt**: (A1) panel màn-danh-sách = `PurchaseEvaluationsListPage:399-455` · (A2) panel màn-thao-tác = `PeListPanel.tsx`. Đối chiếu đúng cặp: **B ⟂ A1**, **C ⟂ A2**.
+
+| Trục | A1 `PurchaseEvaluationsListPage` (golive) | A2 `PeListPanel` | B `KhkkListPanel` | C `KhkkWorkListPanel` | Verdict |
+|---|---|---|---|---|---|
+| **Vỏ panel** | `aside flex flex-col overflow-hidden border-r border-slate-200 bg-white` (:399) | y hệt (:92) | y hệt (:254) | + `min-h-0` (:142) | ✅ KHỚP |
+| **Hàng nhãn + pill đếm trong panel** | KHÔNG (đếm nằm ở header trang `:386-388`) | CÓ "Danh sách phiếu" + pill (:96-101) | **KHÔNG** — và header trang B để pill = "Giai đoạn 2" (`KhkkListPage:128-130`) **CHỨ KHÔNG PHẢI SỐ ĐẾM** | pill đếm mỗi tầng (:66-70) | 🔴 **DELTA-1**: KHKK là màn DUY NHẤT không cho biết "đang có bao nhiêu phiếu" ở chế-độ cây |
+| **Ô tìm** | 1 ô, `Search` `left-2.5 top-2.5 h-4 w-4`, `Input pl-8`, ph. "Tìm mã / tên gói thầu / dự án…" (:401-409) | y hệt (:103-111) | **2 Ô CHỒNG NHAU ở chế-độ cây** — ô host (`:258-266`, `Input` h-8 rounded-lg) + ô ruột cây (`PipelineTreePanel:236-244`, `input` thô h-8 rounded-**md** ph. "Lọc dự án / hạng mục / mã phiếu…") | KHÔNG có (cố ý) | 🔴 **DELTA-2 (nặng nhất)** |
+| **Ô lọc trạng thái** | `Select` (:437-453) + 2 banner thay-thế: đỏ "Phiếu đã xóa" (:427-431), hổ phách "Lọc cố định: Đã gửi duyệt" (:432-435) | `Select` (:117-122) + banner xám lọc-cố-định (:112-115) | `Select` + `aria-label` (:267-276). **KHÔNG có banner cho `deletedView`** — vào leaf "Đã xóa" thì `Select` vẫn hiện dù server bỏ qua `phase` (`:209`) | KHÔNG | 🔴 **DELTA-3**: mất banner ngữ-cảnh + `Select` chết-lặng ở view Đã xóa |
+| **Nhãn trạng thái (từ vựng)** | "Nháp · Đã gửi duyệt · **Cần chỉnh sửa lại** · Đã duyệt · Từ chối" (`purchaseEvaluation.ts:83-89`) | ↑ | "**Đang soạn thảo** · **Chờ duyệt** · Đã duyệt · **Trả lại** · Từ chối" (`khkk.ts:49-55`) | ↑ | 🟡 **DELTA-4**: 3/5 nhãn khác chữ cho cùng ý |
+| **Badge trạng thái (hình)** | `rounded px-1.5 py-0.5 text-[10px] font-medium` + `bg-X-100 text-X-700`, **KHÔNG viền** (`PeListPanel:183-190` + `purchaseEvaluation.ts:91-97`) | ↑ | `rounded-**md** **border** px-1.5 py-0.5 text-[10px] font-medium` + `bg-X-100 text-X-**800** border-X-300` (`:136-143` + `khkk.ts:58-64`) | như B nhưng `text-[9px]` (:270-277) | 🔴 **DELTA-5**: 3 biến-thể badge cho cùng 1 hệ trạng-thái |
+| **Card — bố cục hàng** | 4 hàng: tiêu-đề+badge · mã·dự-án · chip-loại+SLA · "✓ Đã tạo HĐ" | ↑ (:158-200) | 3 hàng: tiêu-đề+badge · mã+N?+ngày · người-soạn+tiền (:121-160) | 2 hàng, không badge tầng-trên (:200-210) | 🟡 hợp lý (khác dữ-liệu) |
+| **Card — cỡ chữ** | tiêu-đề `text-[13px] font-medium text-slate-900`; phụ `text-[11px] text-slate-500`; mã `font-mono` | ↑ | tiêu-đề `text-[13px]` ✅; phụ `text-[11px]` ✅; mã `font-mono` ✅ | tiêu-đề **`text-[12px] text-slate-800`**; phụ **`text-[10px]`**; mã `font-mono text-[10px]` | 🔴 **DELTA-6**: C nhỏ hơn A2 nguyên 1 nấc (12/10 vs 13/11) |
+| **Card — padding + selected** | `px-3 py-2.5` · `bg-brand-50 ring-1 ring-inset ring-brand-200` | ↑ (:154-156) | `px-3 py-2.5` ✅ + `bg-brand-50 ring…brand-200` ✅ + **`focus-visible:ring-2 …brand-500/60`** (A KHÔNG có) (:117-118) | **`px-3 py-2`** + ring **amber-300 / violet-300** (:196-198, :258-261) | 🟡 B tốt hơn A (giữ) · 🔴 **DELTA-7**: C dùng 2 màu chọn khác brand |
+| **Chip nhóm N1-N8** | — | — | `rounded border border-violet-200 bg-violet-50 px-1 **text-[9px]** font-semibold text-violet-700`, **không `py`** (:147-151) | y hệt (:265-269) | 🟡 **DELTA-8**: 9px + thiếu `py-0.5` ⇒ lệch baseline với mã `font-mono` bên cạnh; là chữ NHỎ NHẤT toàn màn |
+| **Số tiền** | không có trên card | không có | `tabular-nums`, `text-emerald-700` khi `frozen` / `text-brand-800` (:157-159); `formatVnd` = `toLocaleString('vi-VN') + ' đ'` (:44-47) | không có | 🟡 **DELTA-9**: nghĩa của màu (đã chốt/tạm tính) **không có chú giải** ở panel |
+| **Khoảng cách khối lọc** | `space-y-2 … p-3` (:400) | y hệt (:94) | y hệt (:257) | không có khối lọc | ✅ KHỚP |
+| **Empty-state** | `` bọc `p-6` (:465-469) | y hệt (:135-147) | **tự vẽ**: `p-6 text-center text-sm` + `icon-chip` inline style (:319-330) | `` (:173-177, :243-248) | 🔴 **DELTA-10**: 3 hình empty khác nhau; chỉ A dùng component chung `EmptyState` |
+| **Loading** | 6 skeleton `h-16 animate-pulse rounded-md bg-slate-100`, **thiếu** `motion-reduce` (:458-463) | y hệt, thiếu `motion-reduce` (:128-134) | 8 skeleton **có** `motion-reduce:animate-none` (:300-306) | **chữ "Đang tải…"** `p-3 text-[11px]` (:160, :230) | 🔴 **DELTA-11**: C tụt hạng (chữ thay skeleton) · 🟢 B hơn A về a11y |
+| **Error** | **KHÔNG có nhánh `isError`** (0 hit) | **KHÔNG** (0 hit) | CÓ + nút "Thử lại" (:307-318) | CÓ ×2 + "Thử lại" `text-[10px]` (:161-172, :231-242) | 🟢 KHKK hơn — **đừng hạ theo A**; ngược lại nên nâng A |
+| **Phân trang** | KHÔNG (pageSize 50 cứng, `:84/:102`) | KHÔNG (pageSize 50, `:76`) | CÓ, chỉ ở `flatMode`: "N kế hoạch — Trang x / y" + `←`/`→` (:347-372), `PAGE_SIZE=20` (:42) | KHÔNG (30 phiếu mới nhất, `:40`) | 🔴 **DELTA-12**: nút `←`/`→` là **ký tự trần**, không phải `lucide` icon (FD3) + không `aria-label` |
+| **Nút đáy** | KHÔNG (nút "Lập kế hoạch"/"Tạo" nằm ở header trang) | CÓ sticky `Button w-full` + `Plus` (:239-248) | KHÔNG — nút "Lập kế hoạch" ở header trang (`KhkkListPage:133-136`) | KHÔNG (cố ý, có lý-lẽ ghi tại :298-302) | ✅ hợp lý |
+| **Trạng thái thiếu-quyền** | — | — | dòng chữ `text-[10px] italic slate-400` (:282-287) | khối `Lock` giữa cột (:143-151) | 🟡 **DELTA-13**: 2 hình cho cùng 1 việc, bản B mờ hơn ghi-chú thường |
---
-## 3. Delta chi tiết (mỗi delta = evidence file:line + fix cụ thể)
-_(pending)_
+## 3. Delta chi tiết — xếp theo mức đau (mỗi mục: bằng-chứng → sửa gì)
+
+**MIRROR đã kiểm:** `sha256sum` 2 app → `KhkkListPanel` `ad9073db…314d` KHỚP · `KhkkWorkListPanel` `fcb97650…0e26` KHỚP ⇒ **mọi sửa phải áp 2 app** (`fe-admin` + `fe-user`), giữ SHA cặp.
+
+### 🔴 DELTA-2 — HAI Ô TÌM CHỒNG NHAU (nặng nhất, thấy ngay bằng mắt)
+Ở chế-độ cây (`flatMode === false`), panel render:
+- ô 1 = host `KhkkListPanel.tsx:260-265` — `Input` (h-8 **rounded-lg** border-slate-**300** text-**sm**), ph. "Tìm mã kế hoạch / mã phiếu / gói thầu…"
+- ô 2 = ruột cây `PipelineTreePanel.tsx:236-243` — ` ` thô (h-8 **rounded-md** border-slate-**200** text-**[12px]**), ph. "Lọc dự án / hạng mục / mã phiếu…", cách ô 1 đúng 1 `p-2`+`p-3` ≈ 20px.
+Hai ô **trái nghĩa nhau**: gõ ô 1 ⇒ `flatMode` bật (`:201`) ⇒ **cây + ô 2 biến mất giữa lúc đang gõ**; gõ ô 2 ⇒ lọc trong cây (`PipelineTreePanel:145-153`), ép mở hết node (`:180`).
+→ **Sửa:** 1 ô DUY NHẤT. Nâng `PipelineTreePanel` nhận `searchTerm`/`onSearchTermChange` (controlled) + ẩn ô ruột khi `hideHeader` (cùng lý-lẽ đã dùng để ẩn header ở `:225-232`). Host giữ 1 `Input`.
+
+### 🔴 DELTA-5 — 3 biến-thể badge cho cùng 1 hệ trạng-thái
+`purchaseEvaluation.ts:91-97` (`rounded`, không viền, `text-X-700`) ⟂ `khkk.ts:58-64` (`rounded-md`+`border`+`text-X-800`) ⟂ `KhkkWorkListPanel.tsx:270-277` (`text-[9px]`).
+→ **Sửa:** chọn 1 khuôn. Khuyến nghị **giữ khuôn KHKK có viền** (viền giúp badge không chìm khi nền card đổi sang `bg-brand-50` lúc chọn — PE đang chìm thật) và **nâng PE lên khuôn đó**, KHÔNG hạ KHKK. Nếu owner muốn "khác gì cũng phải giống golive" thì đổi ngược, nhưng phải đổi ở `khkk.ts` (1 chỗ) chứ đừng vá tại card.
+→ Bắt buộc: `KhkkWorkListPanel:272` `text-[9px]` → `text-[10px]` (bằng B) — không có lý do để cùng badge nhỏ hơn ở màn khác.
+
+### 🔴 DELTA-6 — card màn Thao tác nhỏ hơn 1 nấc so với chuẩn A2
+`KhkkWorkListPanel:205` tiêu-đề `text-[12px] text-slate-800` + `:201-203` phụ `text-[10px]` ⟂ chuẩn `PeListPanel:160` `text-[13px] text-slate-900` + `:165` `text-[11px]`. Padding `py-2` (`:196`) ⟂ `py-2.5` (`PeListPanel:154`).
+→ **Sửa:** đưa C về `text-[13px]/text-slate-900` + `text-[11px]` + `py-2.5`. Cột C rộng 300-340px (`KhkkCreatePage:180`) — thừa chỗ, không phải ép nhỏ vì hẹp.
+
+### 🔴 DELTA-10 — 3 hình empty-state
+A dùng `` (`EmptyState.tsx` — icon trong `rounded-xl bg-slate-100 p-2.5`, title `text-[13px] font-semibold slate-700`, desc `text-xs slate-500`) · B tự vẽ `icon-chip` + `text-sm` (`KhkkListPanel:319-330`) · C chỉ 1 dòng `italic slate-400` (`:173-177`, `:243-248`).
+→ **Sửa:** cả B và C dùng ``. B: `icon={Inbox}` + title "Không có kế hoạch khớp bộ lọc" + description nói **cách thoát** ("Xoá từ khoá hoặc chọn Tất cả trạng thái"). C: title ngắn + description giữ nguyên câu giải-thích đang có (câu đó tốt, chỉ sai vỏ).
+
+### 🔴 DELTA-11 — loading màn Thao tác tụt hạng
+`KhkkWorkListPanel:160`/`:230` = chữ "Đang tải…" ⟂ chuẩn skeleton `h-16 animate-pulse` (`PeListPanel:128-134`).
+→ **Sửa:** skeleton 4-6 dòng, **kèm `motion-reduce:animate-none`** (KHKK list đã làm đúng ở `:303`, PE golive đang THIẾU — xem DELTA-14).
+
+### 🔴 DELTA-12 — nút phân trang là ký tự trần
+`KhkkListPanel:360`/`:368` in `←` `→` bằng text, không `aria-label`, không icon set. Trái FD3 (icon set nhất quán = lucide) + FD5 (nút không có tên đọc được).
+→ **Sửa:** ` ` / `` + `aria-label="Trang trước"` / `"Trang sau"`; giữ `disabled:opacity-40` (đã có).
+
+### 🔴 DELTA-15 (MỚI, FD5 — ĐO ĐƯỢC) — chữ `slate-400` cho thông tin BẮT BUỘC ĐỌC
+Tương-phản `#94a3b8` trên nền trắng = **2.56:1** (tự tính theo WCAG relative-luminance) ⇒ **trượt AA 4.5:1, trượt cả ngưỡng 3:1**. Đang dùng cho đúng những câu người dùng CẦN đọc nhất:
+- `KhkkListPanel:278` "Đang lọc — danh sách lấy thẳng từ máy chủ…" (`text-[10px] italic text-slate-400`)
+- `KhkkListPanel:283` "Bạn chưa có quyền xem phiếu Duyệt NCC…"
+- `KhkkListPanel:230` "Gói này chưa có kế hoạch ký kết."
+- `KhkkWorkListPanel:174`, `:244`, `:290`
+- (systemic, KHÔNG phải lỗi riêng KHKK) `PipelineTreePanel:278` "Cây tải 200 phiếu mới nhất…"
+→ **Sửa:** `text-slate-400` → `text-slate-500` (`#64748b` = **4.76:1**, PASS AA) cho MỌI dòng chữ mang thông tin; giữ `slate-400` chỉ cho icon trang trí. Và nâng `text-[10px]` → `text-[11px]` cho 2 dòng ghi-chú của B (chúng đang là chữ nhỏ nhất + mờ nhất trong khi giải thích hành-vi lạ nhất của màn).
+
+### 🟡 DELTA-1 — mất số đếm
+`KhkkListPage:128-130` pill header = "Giai đoạn 2 · " ⟂ golive `PurchaseEvaluationsListPage:386-388` pill = **số phiếu**. Ở chế-độ cây, B **không nơi nào** cho biết đang có bao nhiêu kế hoạch (pill của cây bị `hideHeader` ẩn, `PipelineTreePanel:210-219`).
+→ **Sửa:** thêm pill số vào header trang B, đứng CẠNH pill "Giai đoạn 2" (2 pill khác vai, đừng gộp): flat = `list.data.total`; tree = tổng `stage2Count`.
+
+### 🟡 DELTA-3 — mất banner ngữ-cảnh ở view "Đã xóa"
+Golive có 2 banner thay chỗ `Select` (`:427-435`: đỏ cho Đã-xóa, hổ-phách cho Chờ-tôi). B vào `?view=deleted` vẫn hiện `Select` trạng-thái dù server **bỏ qua** `phase` (`KhkkListPanel:209` `phase: deletedView ? undefined : …`) ⇒ ô lọc **chết câm**: đổi giá trị, danh sách không đổi.
+→ **Sửa:** `deletedView` ⇒ thay `Select` bằng banner đỏ đúng khuôn golive: `rounded border border-red-200 bg-red-50 px-2 py-1.5 text-[11px] text-red-700` + câu "🗑 Phiếu đã xóa — chỉ xem."
+
+### 🟡 DELTA-4 — từ vựng trạng thái lệch
+`khkk.ts:49-55` "Đang soạn thảo/Chờ duyệt/Trả lại" ⟂ `purchaseEvaluation.ts:83-89` "Nháp/Đã gửi duyệt/Cần chỉnh sửa lại".
+→ **Sửa (đề-xuất, cần owner gật vì là chữ hiển thị):** thống nhất theo golive — `1:'Nháp'`, `2:'Đã gửi duyệt'`, `98:'Cần chỉnh sửa lại'`. 1 chỗ sửa, 0 rủi ro máy (label thuần hiển thị). Nếu owner muốn giữ chữ KHKK thì đảo lại — nhưng ĐỪNG để 2 bộ chữ song song, cùng 1 người dùng đi qua GĐ1→GĐ2 trong 1 phút.
+
+### 🟡 DELTA-7 — màu "đang chọn" của C
+`KhkkWorkListPanel:197` `ring-amber-300` + `:260` `ring-violet-300` ⟂ chuẩn `ring-brand-200`+`bg-brand-50`.
+→ **Sửa:** giữ màu accent cho **đầu tầng** (`Tier` icon `:60`, đang đúng) nhưng trạng-thái CHỌN của mọi card về `bg-brand-50 ring-brand-200` — "đang chọn" là 1 khái niệm, không nên đổi màu theo tầng, người dùng phải học 2 lần.
+
+### 🟡 DELTA-8 — chip nhóm N1-N8
+`KhkkListPanel:147-151` `text-[9px] font-semibold px-1` **không có `py`** ⇒ hộp chip cao hơn chữ, lệch baseline với `font-mono` bên cạnh; 9px là cỡ chữ nhỏ nhất toàn bộ 3 file.
+→ **Sửa:** `text-[10px] px-1.5 py-0.5 leading-none` + `title={khkkGroupMenuLabel(n)}` (hàm đã tồn tại, `KhkkListPage:129` đang dùng) — "N5" một mình không nói được gì.
+
+### 🟡 DELTA-9 — số tiền đổi màu không chú giải
+`KhkkListPanel:157-159`: `frozen` (phase = DaDuyet) → `text-emerald-700`, còn lại `text-brand-800`. Người dùng không có cách nào biết 2 màu = "đã chốt" ⟂ "tạm tính".
+→ **Sửa:** `title={frozen ? 'Giá trị đã duyệt (đã chốt)' : 'Giá trị đề xuất (tạm tính)'}` + thêm 1 ký-tự phân biệt (vd `≈` trước số khi chưa chốt). Rẻ, không tốn chỗ.
+
+### 🟡 DELTA-13 — 2 hình cho "thiếu quyền"
+B = dòng chữ mờ (`:282-287`) · C = khối `Lock` căn giữa (`:143-151`). C đúng hơn (cột trắng phải nói ra lý do).
+→ **Sửa:** B dùng lại đúng khuôn C (icon `Lock` + `text-[11px] text-slate-500`), đặt ngay trên chỗ đáng lẽ là cây.
+
+### 🟢 DELTA-14 (nâng CHUẨN, không hạ KHKK) — 2 chỗ KHKK làm ĐÚNG hơn golive
+1. **Nhánh `isError` + "Thử lại"**: B `:307-318`, C `:161-172`/`:231-242` — golive `PurchaseEvaluationsListPage` và `PeListPanel` **0 hit `isError`** ⇒ mất mạng = màn trắng im lặng, không phân biệt được với "chưa có phiếu".
+2. **`motion-reduce:animate-none`** trên skeleton: B `:303` có; A1 `:461` + A2 `:131` **không** (FD5).
+→ **Sửa:** port 2 thứ này **NGƯỢC** sang PE. Đây là chỗ duy nhất trong bài mà "đồng bộ" nghĩa là sửa bên golive.
+
+### ✅ Đã KHỚP — không đụng
+Vỏ `` · lưới host 3-panel `lg:grid-cols-[400px_1fr_360px]` (`KhkkListPage:140` = `PurchaseEvaluationsListPage:397`) · khối lọc `space-y-2 border-b p-3` · vị trí + kích thước icon `Search` · `Input`/`Select` cùng primitive (`h-8 rounded-lg`) · card `px-3 py-2.5` + selected `bg-brand-50 ring-brand-200` (B) · `divide-y divide-slate-100` · header trang (icon 5×5 + `h1 text-base font-semibold` + pill).
+🟢 B còn **hơn** A ở `focus-visible:ring-2 ring-inset ring-brand-500/60` trên card (`:117`) — A **không có** focus ring ⇒ **giữ B, port sang A** (FD5).
---
diff --git a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L3.md b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L3.md
index d0f7b28..0c32436 100644
--- a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L3.md
+++ b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L3.md
@@ -33,17 +33,185 @@
---
-## 1. FILE A — PurchaseEvaluationWorkspacePage.tsx (CHUẨN)
-_(chưa đọc)_
+## 1. FILE A — PurchaseEvaluationWorkspacePage.tsx (CHUẨN) — 147 dòng, ĐÃ ĐỌC HẾT
-## 2. FILE B — KhkkCreatePage.tsx
-_(chưa đọc)_
+Khuôn host "Thao tác" GĐ1 (đã golive, owner tuyên "đã tốt rồi", byte revert `1b3bcaa`):
-## 3. FILE C — KhkkListPage.tsx
-_(chưa đọc)_
+| Chi tiết | Giá trị | Dòng |
+|---|---|---|
+| Shell | `` | :69 |
+| Header | `flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-slate-200 bg-white px-6 py-3` | :70 |
+| Icon | `ClipboardCheck` `h-5 w-5 text-slate-500` | :72 |
+| Title | `text-base font-semibold tracking-tight text-slate-900` — KHÔNG `truncate`, KHÔNG `min-w-0` ở wrapper (:71 `flex items-center gap-2`) | :71-73 |
+| Badge | **KHÔNG CÓ** badge ở host Thao-tác | — |
+| Hint phải | `text-[12px] text-slate-500` — "Workspace 2-panel — Workflow + Duyệt ở menu “Duyệt”." | :75-77 |
+| Grid | `grid flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[260px_1fr] xl:grid-cols-[320px_1fr]` | :80 |
+| Panel 2 | `
` | :100 |
+| EmptyState | `icon={ClipboardCheck}` + title "Chọn phiếu hoặc tạo mới" + description có nhắc nút `"+ Thêm mới"` | :102-108 |
+| readOnly | **KHÔNG có prop `readOnly`** — thay bằng `mode="workspace"` truyền xuống `PeDetailTabs` (:127) + `editableOnly` cho panel list (:95) | :95,127 |
+| `` không bao giờ bấm được, comment nói "tap row khi mobile sẽ navigate fullpage" nhưng KHÔNG ai gọi | :135-144 |
+| Loading | `{selectedId && detail.isLoading && Đang tải…
}` | :120-122 |
+| Not-found | **KHÔNG có** nhánh "Không tìm thấy phiếu" | — |
-## 4. SPEC THI-HÀNH-ĐƯỢC
-_(chưa viết)_
+⚠️ Ghi nhận NGƯỢC: chuẩn GĐ1 CÓ 2 chỗ yếu (dead-code `lg:hidden` :136-144 · thiếu nhánh not-found) — KHÔNG được "mirror cho giống" 2 cái này.
+
+## 2. FILE B — KhkkCreatePage.tsx (màn Thao-tác GĐ2, vừa dựng lại) — 394 dòng, ĐÃ ĐỌC HẾT
+
+| Chi tiết | Giá trị | Dòng | vs PE |
+|---|---|---|---|
+| Shell | `flex h-[calc(100vh-4rem)] flex-col` | :168 | ✅ KHỚP byte |
+| Header class | y hệt PE | :169 | ✅ KHỚP byte |
+| Icon | `FileCheck h-5 w-5 text-slate-500` | :171 | ✅ khớp khuôn (icon khác = đúng, GĐ2 dùng FileCheck xuyên suốt) |
+| Title wrapper | `flex min-w-0 items-center gap-2` + `truncate` trên `` | :170-172 | ⚠️ LỆCH nhẹ vs PE (PE không truncate) — nhưng đây là **cải tiến ĐÚNG** vì title GĐ2 dài (`… — Thao tác · `), khớp `KhkkListPage:125-127` |
+| Badge "Giai đoạn 2" | **THIẾU** — `KhkkListPage:128-130` CÓ pill `Giai đoạn 2 · `, màn Thao-tác KHÔNG | :169-177 | 🔴 LỆCH NỘI-BỘ GĐ2 (2 màn cùng module, 1 có 1 không) |
+| Hint phải | `text-[12px] text-slate-500` "Bấm phiếu ở cột trái — tầng trên lập kế hoạch, tầng dưới chỉ xem." | :174-176 | ✅ KHỚP khuôn PE |
+| Grid | `lg:grid-cols-[300px_1fr] xl:grid-cols-[340px_1fr]` | :179 | 🔴 LỆCH SỐ ĐO: PE = `260px`/`320px`. Comment đầu file (:6) tự khai "bê khuôn `lg:grid-cols-[300px_1fr]`" — tức là **comment mô tả đúng code nhưng code KHÔNG đúng khuôn PE** |
+| Panel 2 | `hidden overflow-y-auto bg-slate-50 p-6 lg:block` | :187 | ✅ KHỚP byte |
+| EmptyState | `icon={FileCheck}` + title "Chọn phiếu Duyệt NCC để lập kế hoạch" + desc chỉ rõ 2 tầng | :217-221 | ✅ khớp khuôn |
+| Loading | `Đang tải…` `text-sm text-slate-500` (:205) | :205 | ✅ |
+| Not-found | CÓ `Không tìm thấy phiếu.` `text-sm text-red-600` (:207) | :207 | ✅ TỐT HƠN PE |
+| readOnly | ` ` — **hard-code `readOnly` không điều kiện** | :210 | 🔴 LỆCH TRỤC: `KhkkListPage:113-117` tính `readOnly` theo phase+drafter (P2-7 "host quyết"); màn Thao-tác ép cứng ⇒ **người vừa tạo phiếu KHÔNG sửa được ngay tại chỗ**, phải bấm "Mở trang đầy đủ" |
+| `" | :128-130 |
+| Phải | **Nút** `Lập kế hoạch` (`Plus` + navigate `/khkk/create?group=`) — KHÔNG phải dòng hint | :132-137 |
+| Grid | `lg:grid-cols-[400px_1fr_360px]` (3 panel) | :140 |
+| Panel 2 | `hidden overflow-y-auto bg-slate-50 p-6 lg:block` | :162 |
+| Panel 3 | `hidden overflow-y-auto border-l border-slate-200 bg-white p-4 lg:block` | :186 |
+| EmptyState | `icon={FileCheck}`, title/desc rẽ theo `deletedView` | :163-171 |
+| `` + **sticky bottom action** `:238-241` `` — khớp + `min-h-0` (cần cho 2 tầng). Nhưng `grep -c Button` = **0** ⇒ **KHÔNG có nút hành-động dính đáy**.
+ 🔴 ⇒ comment `KhkkCreatePage.tsx:7` *"Panel 1 = danh sách (`KhkkWorkListPanel`) + nút hành-động dính đáy"* **MÔ TẢ THỨ KHÔNG TỒN TẠI** (claim > việc đã làm). Thứ ở đáy tầng-1 là `:219
` = vạch ngăn 2 tầng, không phải nút.
+- 2 tầng: `Tier()` :48-73, dùng ở :154 và :221; `min-h-0 flex-1` cả hai ⇒ tỉ lệ 50/50 (comment :46-47).
+
+### (d) Chiều cao `h-[calc(100vh-4rem)]` — đo lại chrome thật
+`Layout.tsx:370` dải gradient `h-1` (4px) + `TopBar.tsx:84` `` (56px) + `border-b` (1px) = **61px ≈ 3.81rem**, trong khi mọi host trừ **4rem = 64px** ⇒ hụt ~3px.
+⇒ **KHÔNG phải lỗi KHKK** — 4/4 host (PE ×2, KHKK ×2) đều `h-[calc(100vh-4rem)]`, sai lệch là **toàn hệ thống**. Ghi nhận, KHÔNG đưa vào spec sửa lần này (đổi 1 file = lệch khỏi 3 file kia; phải sửa cả họ hoặc không sửa).
+
+### (e) `` không ai bấm | `PurchaseEvaluationWorkspacePage.tsx:135-144` |
+| **KHKK Create** | **KHÔNG CÓ GÌ** | `KhkkCreatePage.tsx:187` `hidden … lg:block` |
+🔴 Hệ quả KHKK-Create ở `` vẫn `hidden` ⇒ **màn không đổi gì**. Form + nút "Tạo kế hoạch" (:372) nằm TRONG `` ⇒ **không thể tạo kế hoạch trên màn <1024px**. Đây là **hỏng chức năng**, không phải lệch thẩm mỹ — nặng hơn dead-code của PE (PE vẫn còn nút "+ Thêm mới" ở panel-1 nhưng cũng dẫn vào `` ẩn ⇒ PE cũng hở, chỉ là hở SẴN của chuẩn).
+
+### (f) Mirror 2 app — ✅ SẠCH
+`sha256sum` fe-admin vs fe-user (4 file KHKK): `KhkkCreatePage` `bac8e0050711` · `KhkkListPage` `ee0dc8afc5fd` · `KhkkWorkListPanel` `fcb97650f51c` · `KhkkListPanel` `ad9073db2c90` → **SAME 4/4**. `git status --porcelain -- fe-admin/src fe-user/src` = **rỗng** ⇒ bản S174 đã commit, không có drift treo. ⚠️ Mọi sửa dưới đây PHẢI áp CẢ 2 app rồi so lại SHA256.
+
+---
+
+## 4. SPEC THI-HÀNH-ĐƯỢC (cho Opus 5 MAX) — lăng-kính HOST/BỐ-CỤC
+
+> Nguyên tắc chấm: **chuẩn = `PurchaseEvaluationWorkspacePage.tsx` (GĐ1 golive)**. Chỉ lệch khi có lý do viết ra được; lệch không lý do ⇒ kéo về chuẩn. Mọi patch áp **CẢ `fe-admin/` và `fe-user/`** rồi verify SHA256 == (§3bis-f).
+
+### H-1 🔴 MAJOR — Grid cột sai chuẩn (`KhkkCreatePage.tsx:179`)
+- Hiện: `lg:grid-cols-[300px_1fr] xl:grid-cols-[340px_1fr]`
+- Đích : `lg:grid-cols-[260px_1fr] xl:grid-cols-[320px_1fr]` (byte-identical `PurchaseEvaluationWorkspacePage.tsx:80`)
+- Vì : (i) hiến-pháp owner "giống Duyệt NCC" — file anh em `KhkkListPage.tsx:9` đã áp đúng luật này cho 400px; (ii) §3bis-b: đang lấy 40px của **cột form** đưa cho **cột danh sách**, ngược nhu cầu.
+- Kèm : sửa luôn comment `:6` (`lg:grid-cols-[300px_1fr]`) — comment đang mô tả số cũ, để lại là bẫy cho lượt sau.
+- Rủi ro: panel-1 hẹp đi 40px ⇒ dòng PE trong `Tier` có thể wrap. Kiểm bằng mắt tầng-1 ở 1024 & 1366; nếu wrap thì **KHÔNG nới grid** mà rút gọn nội dung dòng (nguyên tắc: giữ khuôn host, đổi nội dung panel).
+
+### H-2 🔴 MAJOR — ``, không có nhánh nào cho ``, để nó xếp DƯỚI panel-1 ở `` ⇒ hiện tại `<1024px` = không dùng được màn. Đây là lỗi **chức năng**.
+- Cách rẻ hơn nếu owner không muốn đụng layout mobile: giữ `hidden lg:block` NHƯNG thêm khối `lg:hidden` nói thẳng *"Màn Thao tác cần màn hình ≥1024px — mở mục Danh sách để xem/tạo"* + nút navigate `/khkk/list`. Thà nói thật còn hơn im lặng.
+
+### H-3 🟡 MEDIUM — Thiếu badge "Giai đoạn 2" ở header màn Thao-tác (`KhkkCreatePage.tsx:169-177`)
+- Hiện: chỉ icon + title.
+- Đích : chèn sau `` đúng span của `KhkkListPage.tsx:128-130`:
+ `Giai đoạn 2{presetGroup !== null ? ` · ${khkkGroupMenuLabel(presetGroup)}` : ''} `
+- Vì : 2 màn CÙNG module hiện KHÁC nhau. Chuẩn PE cũng có badge ở header list (`PurchaseEvaluationsListPage.tsx:386`, nội dung là **số lượng**) ⇒ badge-ở-header là ngôn ngữ chung của họ; GĐ2 đã chọn nghĩa "giai đoạn/nhóm" thì phải nhất quán 2 màn.
+- Kèm : nếu chèn badge thì title đã có `khkkGroupMenuLabel` (:164) ⇒ **bỏ phần `· ` khỏi title** để không lặp 2 lần cùng một chữ trên 1 dòng (title về `Kế hoạch ký kết HĐ — Thao tác`, nhãn nhóm sống trong badge — đúng như màn List).
+
+### H-4 🟡 MEDIUM — `readOnly` ép cứng, không do host tính (`KhkkCreatePage.tsx:210`)
+- Hiện: ` ` — hằng số.
+- Đích : dùng CÙNG công thức host của `KhkkListPage.tsx:112-117`
+ (`!plan || !(phase ∈ {DangSoanThao, TraLai}) || plan.drafterUserId !== user.id`), tức import `useAuth` như file anh em.
+- Vì : (i) trục "cách truyền readOnly" đang có **2 luật khác nhau cho cùng 1 component** ⇒ người tạo xong phiếu ở màn Thao-tác không sửa được ngay, phải bấm "Mở trang đầy đủ" (:196) — thao tác thừa do host, không do quyền; (ii) `[P2-7]` đã chốt "readOnly do HOST quyết".
+- ⚠️ NẾU owner CỐ Ý muốn màn Thao-tác luôn chỉ-xem (banner :193 nói vậy) ⇒ **giữ nguyên code, nhưng ghi lý do vào comment ngay tại :210** — hiện chỗ này không có dòng nào giải thích, nên đọc như bug.
+
+### H-5 🟡 MEDIUM — Comment sai sự thật (`KhkkCreatePage.tsx:7`)
+- Hiện: *"Panel 1 = danh sách (`KhkkWorkListPanel`) + nút hành-động dính đáy"*. Thực tế `grep -c Button KhkkWorkListPanel.tsx` = **0** (đối chứng dương: `PeListPanel.tsx` = 8, có sticky bottom :238-241).
+- Đích : hoặc (a) xoá vế "+ nút hành-động dính đáy", hoặc (b) **thêm thật** nút dính đáy panel-1 theo khuôn `PeListPanel.tsx:238-241` (``) — hợp lý nếu muốn có lối "Làm mới danh sách" / "Về form tạo" khi đang xem phiếu ở `
` / `
`, hoặc bỏ hẳn glyph và để màu chữ (`text-red-600` / `text-emerald-600`) mang nghĩa.
+- Vì : FD3 anti-slop (glyph/emoji thay icon-set); cả họ file dùng lucide (`KhkkListPage:30` `FileCheck, Plus, X`; `PurchaseEvaluationsListPage:10` 5 icon). Glyph `⚠` còn render lệch baseline giữa font Be Vietnam Pro và fallback emoji-font.
+
+### H-7 🟢 MINOR — Title `truncate` + `min-w-0`: GIỮ, và **nâng ngược lên PE**
+- `KhkkCreatePage:170-172` + `KhkkListPage:125-127` có `min-w-0` + `truncate`; PE (`:71,73`, `PurchaseEvaluationsListPage:383,385`) KHÔNG.
+- Verdict: **KHKK đúng hơn chuẩn** (title GĐ2 dài do nhãn nhóm). ⇒ **KHÔNG hạ KHKK xuống**; đề xuất RIÊNG (ngoài lượt này, cần owner đồng ý vì chạm file golive): thêm `min-w-0`+`truncate` cho 2 file PE.
+
+### H-8 🟢 MINOR — Màu violet của banner "đang xem" (`KhkkCreatePage.tsx:191-204, 288`) — **ĐÃ ĐO, KHÔNG SỬA**
+- Hiện: `border-violet-200 bg-violet-50 text-violet-800` (:191-192) + pill nhóm `border-violet-200 bg-violet-50 text-violet-700` (:288).
+- Verdict bố-cục: **giữ** — host GĐ1 không có trạng-thái "đang xem phiếu" nên không có chuẩn để đối chiếu; violet tách "chế-độ xem" khỏi brand (hành-động) và amber (cảnh báo). Chốt 1 luật cho GĐ2: **violet = chế-độ chỉ-xem**.
+- 🔬 **Tự bác bỏ giả-thuyết của chính tao (đo trước khi khuyên):** tao vào lượt này mang sẵn luật trong MEMORY *"dải accent chỉ có stop 50/100/500/600/700, dùng -800 = silent no-class"*. **ĐO LẠI: SAI cho `violet`.**
+ - `fe-admin/src/index.css:5` `@theme {` — **KHÔNG có** directive reset (`grep -n "initial" index.css` ⇒ **0 hit**) ⇒ dải Tailwind mặc định **vẫn còn**, `@theme` chỉ **ghi đè 5 stop** (`:41-45` violet 50/100/500/600/700).
+ - ⇒ `violet-200`/`violet-800` **VẪN RA MÀU** — nhưng là màu của dải **mặc định Tailwind** (tím `#ddd6fe`/`#5b21b6`), khác hệ với dải tự-định-nghĩa (xanh-tím `#f1effe`/`#5042c4`). Đây là **trộn 2 hệ màu trong 1 khối**, KHÔNG phải mất-class.
+ - Quy mô: `grep -rno "violet-[0-9]+" fe-admin/src` ⇒ `violet-700`×45 · `violet-50`×38 · **`violet-200`×13** · `violet-100`×12 · **`violet-800`×11** · `violet-500`×11 · `violet-600`×7 · `violet-300`×4 · `violet-400`×1, nằm ở **12 file** — trong đó có **file GĐ1 golive** (`components/pe/PeDetailTabs.tsx`, `PeWorkflowPanel.tsx`, `PeFinalizeChip.tsx`).
+ - ⇒ **TOÀN HỆ THỐNG, không phải lệch của KHKK.** Sửa riêng `KhkkCreatePage` = tạo lệch mới với chính GĐ1. **KHÔNG đưa vào lượt sửa này**; nếu owner muốn dọn thì mở việc riêng "chuẩn hoá dải violet 2-hệ" quét cả 12 file × 2 app.
+ - 🔴 Luật ĐÚNG (thay luật cũ trong MEMORY): tên màu **CÓ trong Tailwind mặc định** (`teal`, `violet`) mà chỉ định-nghĩa-lại vài stop ⇒ stop ngoài dải **im lặng rơi về hệ màu mặc định** (nguy hơn: trông vẫn "có màu"). Tên **BỊA** (`amberx`, `greenx`) ⇒ stop ngoài dải **không tồn tại class** (mất style hẳn). Hai ca KHÁC NHAU, trước nay tao gộp làm một.
+
+### H-9 ⚪ INFO — Nút "Lập kế hoạch" ở header màn List (`KhkkListPage.tsx:132-137`) — **KHÔNG SỬA**
+- Chuẩn `PurchaseEvaluationsListPage.tsx:382-390`: header **chỉ có nhóm trái** (icon + title + badge-số), **bên phải TRỐNG** — GĐ1 tạo phiếu bằng sticky "+ Thêm mới" trong `PeListPanel:238-241` ở màn Thao-tác.
+- GĐ2 đảo lại: nút tạo nằm ở header màn **List**, còn panel-1 màn **Thao-tác** KHÔNG có nút nào (§3bis-c).
+- Verdict: **giữ nút ở List** (gỡ đi thì GĐ2 mất lối vào duy nhất nhìn thấy được), nhưng nó chính là lý do **H-5 phải chọn nhánh (a) sửa comment** — đừng thêm nút vào panel-1 chỉ để "giống PE", vì lối tạo của GĐ2 đã có chỗ đứng khác và đang hoạt động.
+
+### Thứ tự thi hành đề nghị
+`H-2` (hỏng chức năng) → `H-1` (số đo, 1 dòng) → `H-3`+`H-7`-giữ (header) → `H-4` (quyết định owner: sửa hay ghi lý do) → `H-5`, `H-6` (dọn).
+Sau mỗi nhóm: `cd fe-admin && npm run build` + `cd fe-user && npm run build` + `sha256sum` 2 bản == nhau.
+
+### KHÔNG SỬA (chốt để lượt sau khỏi "sửa cho giống")
+- `h-[calc(100vh-4rem)]` hụt ~3px — lỗi **toàn hệ thống**, sửa lẻ 1 file = tạo lệch mới (§3bis-d).
+- Dead-code `lg:hidden` của PE (:135-144) — **đừng mirror**.
+- Thiếu nhánh "Không tìm thấy phiếu" của PE — KHKK đã tốt hơn (`KhkkCreatePage:207`, `KhkkListPage:174`), giữ.
+- Grid 3-panel `KhkkListPage:140` — **đã khớp PE byte-for-byte**, không đụng.
## 5. TỰ ĐÁNH GIÁ + GIỚI HẠN
_(chưa viết)_
diff --git a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L4.md b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L4.md
index 46dafc0..27924ea 100644
--- a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L4.md
+++ b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L4.md
@@ -21,25 +21,56 @@
---
-## A. Inventory 3 file
+## A. Inventory 3 file ✅
-_(chưa đọc — append khi đọc)_
+| File (fe-admin) | LOC | Vai | Router? |
+|---|---|---|---|
+| `components/pipeline/PipelineTreePanel.tsx` | 352 | Panel cây trái: query `pipeline-pe-index` (200 phiếu) → `buildTree` Năm>Dự án>Hạng mục → mỗi hạng mục render `
` (`:330-335`) | **KHÔNG** (0 `useNavigate`) |
+| `components/pipeline/PipelineStageFolders.tsx` | 334 | THUẦN TRÌNH BÀY: vẽ 4 folder GĐ + ngăn nhóm + leaf. Không fetch, không route | **KHÔNG** |
+| `hooks/usePipelineStages.ts` | 404 | Nguồn dữ liệu + **TOÀN BỘ điều-hướng** (`useNavigate` khai `:200`) | **CÓ — nhà duy nhất của `navigate(`** |
-## B. Host-map (6 host)
+- fe-user MIRROR: LOC 352/334/404 y hệt; 3 header đều khai *"File MIRROR SHA256 identical giữa 2 app — sửa 1 bên PHẢI copy sang bên kia"* (`PipelineStageFolders.tsx:10`, `usePipelineStages.ts:3`, `PipelineTreePanel.tsx:6`) ⇒ **mọi patch = ×2 app**, nghiệm thu bằng SHA256 từng cặp.
+- Kiến trúc đã sẵn sàng cho O6: `navigate` KHÔNG rải trong 2 file trình-bày ⇒ callback-hoá chỉ đụng 1 file logic + prop-surface.
-_(chưa đọc)_
+## B. Host-map — ĐỦ 6 host của `PipelineTreePanel` ✅
-## C. (O2) Truy chữ "SẮP"
+| # | Host (file:line render) | `currentStage` | Prop đang truyền | Ghi chú |
+|---|---|---|---|---|
+| H1 | `fe-admin/src/components/khkk/KhkkListPanel.tsx:292` | **2** | `stage2Options` (content+count+`onOpenKhkk`), `hideHeader`, `bodyMaxHClass` | Màn KHKK 3-panel — **host của cả O2 lẫn O6** |
+| H2 | `fe-user/src/components/khkk/KhkkListPanel.tsx:292` | **2** | (mirror H1) | mirror |
+| H3 | `fe-admin/src/pages/contracts/ContractsListPage.tsx:249` | 3 | `bodyMaxHClass="max-h-[28rem] lg:max-h-none"`, render CÓ ĐIỀU KIỆN `{!selectedId && …}` | HĐ (admin) |
+| H4 | `fe-user/src/pages/contracts/MyContractsPage.tsx:225` | 3 | như H3 | HĐ (user) |
+| H5 | `fe-admin/src/pages/contracts/HardCopiesPage.tsx:117` | 4 | chỉ `currentStage` | Bản cứng (admin) |
+| H6 | `fe-user/src/pages/contracts/HardCopiesPage.tsx:117` | 4 | như H5 | Bản cứng (user) |
-_(chưa đọc)_
+**+2 call-site KHÔNG qua Panel** (đếm cho O2 vì badge sống trong `PipelineStageFolders`):
+- H7 `fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx:524` — dùng THẲNG `` (cây riêng trang Duyệt NCC; `PipelineTreePanel.tsx:8-10` khai rõ KHÔNG đụng cây đó).
+- H8 `fe-user/src/pages/pe/PurchaseEvaluationsListPage.tsx:524` — mirror.
+
+**Host đã GỠ (đừng đếm nhầm):** `fe-user/src/pages/InboxPage.tsx:341` — `[S174 owner] GỠ PipelineTreePanel khỏi Hộp thư`. Không còn render.
+
+⇒ **O2 chạm 8 điểm render (H1-H8)**; **O6 chạm 8 điểm nhưng chỉ H1/H2 (+H7/H8 nếu nhận nhánh đối-xứng) ĐỔI HÀNH VI** — chi tiết §D/§F.
## D. (O2) Đề xuất "0"
_(chưa đọc)_
-## E. (O6) Truy điều-hướng
+## E. (O6) Truy điều-hướng — ĐÚNG đường đang đi ✅
-_(chưa đọc)_
+`useNavigate` chỉ khai ở `usePipelineStages.ts:200`; 2 file component **0 hit** `navigate(`. 6 call-site:
+
+| # | file:line | Leaf/nút | Đích hiện tại | Callback-hoá? |
+|---|---|---|---|---|
+| **N1** | `usePipelineStages.ts:219` | **CARD PHIẾU PE (GĐ1)** | **`navigate(\`/purchase-evaluations?id=${p.id}&expand=1\`)`** | ❌ **CHƯA — ĐÚNG lỗi O6 owner báo** |
+| N2 | `:233` | Card/leaf KHKK (GĐ2) | `opts.onOpenKhkk ? onOpenKhkk(k.id) : navigate(\`/khkk/${k.id}\`)` | ✅ ĐÃ (M-2 S171) — **khuôn mẫu để nhân bản** |
+| N3 | `:263` | Tên ngăn nhóm N1-N8 | `navigate(\`/khkk/list?group=${n}\`)` | ❌ (rời trang CÓ CHỦ ĐÍCH: "mở danh sách đã lọc") |
+| N4 | `:272` | Tên ngăn "(chưa phân nhóm)" | `navigate('/khkk/list')` | ❌ (như N3) |
+| N5 | `:366` | Leaf HĐ (GĐ3) | `navigate(\`/contracts/${contractId}\`)` | ❌ |
+| N6 | `:378` | Leaf bản cứng (GĐ4) | `navigate(\`/hard-copies?type=${c.type}\`)` | ❌ |
+
+**Chuỗi sự-kiện lỗi owner gặp (H1/H2, màn KHKK):** `KhkkListPanel` **KHÔNG** truyền `stage1Content` ⇒ `usePipelineStages.ts:204-206` rơi xuống nhánh `else` `:207-222` ⇒ mỗi phiếu PE thành leaf `onOpen` = N1 ⇒ `PipelineStageFolders.tsx:119` `onClick={leaf.onOpen}` ⇒ **rời màn KHKK sang `/purchase-evaluations?id=…&expand=1`**. Khớp nguyên văn owner.
+
+⚠️ **Đối xứng bị bỏ quên** — owner nói *"tương tự với duyệt NCC"*: ở H7/H8 (trang Duyệt NCC) GĐ1 dùng `stage1Content` nên PE ở-tại-chỗ ✔, **nhưng leaf GĐ2 chạy N2 nhánh-mặc-định ⇒ `navigate('/khkk/:id')` ⇒ VĂNG khỏi màn Duyệt NCC**. Lỗi TỒN TẠI ĐỐI XỨNG 2 chiều: KHKK→PE (N1) và PE→KHKK (N2 khi host không truyền callback).
## F. (O6) Đề xuất callback-hoá
diff --git a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L5.md b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L5.md
index 1e819ac..3b77cc2 100644
--- a/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L5.md
+++ b/.claude/workflows/runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L5.md
@@ -31,3 +31,57 @@
## Nhật ký đọc (append tới đâu ghi tới đó)
+### §1+§2+§3 — Diff KHUNG Section (PE chuẩn ⟂ KHKK) [đọc @lượt 2-5]
+
+**PE `Section` (`fe-admin/src/components/pe/PeDetailTabs.tsx:514-523`) — TRẦN, 2 prop:**
+```
+function Section({ title, children }: { title: string; children: React.ReactNode })
+
+ {title}
+```
+**KHKK `Section` (`fe-admin/src/components/khkk/KhkkDetailContent.tsx:60-96`) — GIÀU, 8 prop:**
+`title · icon · accent · head · chipBg · chipFg · count? · actions?` →
+`` + `icon-chip` 1.75rem + `h3 text-sm font-semibold tracking-tight {head}` + count-pill + `actions` ml-auto.
+
+| Trục | PE (chuẩn golive) | KHKK | Verdict |
+|---|---|---|---|
+| Đánh số trong title | ✅ "1./2./3./5." nhúng chuỗi (`PeDetailTabs.tsx:402,405,418,426`) | ✅ y hệt (`:287,357,449,529`) | KHỚP |
+| Count trong title | ✅ nhúng CHUỖI `(${details.length} hạng mục · ${suppliers.length} NCC)` (`:405`) | ⚠️ **HAI KIỂU LẪN**: S2 nhúng chuỗi `(${plan.lines.length} dòng)` (`:357`) · S3/S4 dùng `count` pill (`:455,535`) | **LỆCH NỘI-BỘ** |
+| icon-chip | ❌ PE **không có icon** ở section-head | ✅ có | KHKK GIÀU HƠN chuẩn |
+| accent/head color | ❌ đồng loạt `text-slate-500` | ✅ brand/teal/violet per-section | KHKK GIÀU HƠN chuẩn |
+| Kiểu chữ head | `text-xs uppercase tracking-wide` (eyebrow) | `text-sm tracking-tight` (title thường) | **LỆCH** |
+| actions slot | ❌ không (PE nhét nút vào thân, vd `ItemsTab`) | ✅ `ml-auto` | KHKK GIÀU HƠN |
+| Padding | `px-3 py-3 sm:px-5 sm:py-4` — **có bậc responsive**, comment `:515-516` ghi rõ "laptop màn nhỏ tiết kiệm ~16px" | `px-5 py-4` **CỨNG** | 🔴 **KHKK REGRESS** — mất bài học laptop 1366/mobile 375 |
+| divide-y giữa section | `divide-y divide-slate-200` (`:397`) | y hệt (`:284`) | KHỚP |
+| Vỏ ngoài | KHÔNG `card-accent` (bọc bởi host) | `card-accent` + `--accent: brand-500` (`:264`) | KHKK GIÀU HƠN |
+| Khối "nổi bật" | ✅ PE có: section 4 **KHÔNG bọc `Section`** mà là `` trần chứa khối viền + **thanh header `bg-[#1F7DC1] text-white uppercase`** (`:423-425` + `:1995-1998`) | ❌ KHKK không có khối nổi bật nào | 🔴 **THIẾU** — đây chính là khuôn cho O3 (xem §7.6) |
+
+> ⚖️ **Kết luận khung (quan trọng, không đảo chiều mù quáng):** KHKK **không phải "xấu hơn"** — nó giàu hơn PE ở 4 trục (icon/accent/actions/card). Việc cần làm KHÔNG phải hạ KHKK xuống mức PE, mà là (a) **vá đúng 2 chỗ KHKK regress thật** (padding responsive + count 2-kiểu), (b) **port ngược** khuôn "khối nổi bật header xanh" của PE sang KHKK cho O3. Nếu muốn đồng bộ 1 chiều thì hướng đúng là **nâng PE lên khuôn Section giàu của KHKK** (việc lớn, ngoài lượt này) — ghi vào backlog §9-P2.
+
+### §4 — Diff BẢNG (table)
+
+| Trục | PE | KHKK | Verdict |
+|---|---|---|---|
+| Layout | `table-fixed` + **width từng cột** (`w-[24%] w-[9%]…`, `:2719-2728`) + `min-w-[860px]` | `w-full text-sm` **không table-fixed, không set width cột** (`:375,468`) | 🔴 LỆCH — cột KHKK co giãn theo nội dung ⇒ nhảy layout khi tên NCC dài |
+| Viền | lưới ĐẦY ĐỦ kiểu Excel: `border border-slate-300` mọi ô (`:1402-1407`) hoặc `border border-slate-200` + `border-r` (`:2719-2727`) | chỉ `border-b` hàng (`:388,481`) | LỆCH (chấp nhận được — nhưng phải chọn 1) |
+| thead | `bg-slate-50 text-slate-600` (`:2720`) **hoặc** `bg-slate-100 text-[11px] font-semibold uppercase tracking-wide text-slate-600` (`:1404,1509`) — **PE không dùng `.label-eyebrow` lần nào trong file 3613 dòng** | `bg-slate-50/70` + `th class="label-eyebrow"` (`:376-383,469-476`) | LỆCH: `.label-eyebrow` = `11px/700/ls .07em/**brand-600**` (`fe-admin/src/index.css:89-95`) ⇒ th KHKK **màu brand**, th PE **slate**. Head-xanh trên nền `bg-slate-50/70` = nhạt, kém tương phản header/thân |
+| Body font-size | `text-xs` / `text-[13px]` | `text-sm` bảng + `text-xs` từng ô lẫn lộn (`:397,431-434`) | LỆCH nội-bộ: cột NCC `text-sm`, cột Hạng mục/Ghi chú `text-xs` cùng hàng ⇒ baseline gãy |
+| Số | `text-right` + `tabular-nums` | y hệt (`:431-433`) | KHỚP ✅ |
+| overflow | `overflow-x-auto` + min-w | `overflow-x-auto` **không min-w** (`:374,467`) | LỆCH nhẹ |
+| Hàng rỗng | `— ` | dùng `'—'` chuỗi trần trong `text-slate-600` (`:434,493`) | LỆCH — em-dash KHKK **cùng màu chữ thật** ⇒ đọc nhầm là dữ liệu |
+
+### §5 — Diff FIELD / label / value / rỗng
+
+- KHKK `Field` (`KhkkDetailContent.tsx:98-116`): label `text-[11px] font-semibold uppercase tracking-wide **text-brand-600**` · value `font-medium **text-brand-800**` · rỗng `text-slate-300` + `—`.
+- PE tương đương `FormRow` (`PeDetailTabs.tsx:2166`) + label rời `text-[11px] font-semibold uppercase tracking-wide **text-slate-500**` (`:1774`), value mặc định slate.
+- 🔴 **Lệch nặng nhất về màu:** KHKK tô **brand-600/brand-800 cho MỌI value** (`:110-111`) ⇒ toàn phiếu xanh, mất hierarchy (FD4 "1 focal point"). PE giữ value slate, chỉ tô màu chỗ CÓ NGHĨA (winner emerald `:2008`, cảnh báo amber `:1336`).
+- Rỗng: PE có 3 biến thể — `text-sm text-slate-500` (`:2572,3194,3285`), `italic text-slate-400` (`:603,736,3380,3533`), `text-slate-300 —` (in-cell). KHKK dùng `text-sm text-muted-foreground` (`:372,465,547`) — **token thứ 4, không có ở PE**. → chọn 1: `text-sm italic text-slate-400` cho empty-block, `text-slate-300 —` cho in-cell.
+
+### §6 — Diff KHOẢNG CÁCH
+
+- Section padding: xem bảng §3 (KHKK mất bậc `px-3 py-3 sm:`).
+- Header→body: PE `mb-3` (`:519`) · KHKK `mb-3` (`:75`) → KHỚP ✅.
+- Grid field: KHKK `gap-x-6 gap-y-4 md:grid-cols-2` (`:301,344`) — trên scale ✅.
+- Ô bảng: PE `px-2 py-1.5` (dày đặc, ERP) · KHKK `px-3 py-2` (thoáng hơn) → LỆCH, nhưng KHKK **dễ đọc hơn**; giữ KHKK, ghi rõ là chủ đích.
+- 🔴 Bảng KHKK **không có `hover:bg-*` hàng** (PE cũng không ở table, nhưng có ở nút) — thiếu affordance khi bảng có hành động inline (S2 select, S3 sửa/xóa). FD4 "states".
+