IMPLFE-GD3: BLOCKED

# Lane FE GĐ3 — áp cây 4-folder GĐ lên cụm "Duyệt hợp đồng"

Spawn: implementer-frontend @S167 (2026-08-01). Owner slot 55 "Nên đồng nhất".
Rào: chỉ `fe-admin/src` + `fe-user/src`. KHÔNG BE, KHÔNG commit.

**Kết quả: BLOCKED tại §0 — 0 file chạm.** Không phải "không làm được", mà là: khuôn 3 trang
kia KHÔNG PORT sang trang GĐ3 nếu không có 1 quyết định bố cục của em-main. Mirror y-hệt
(prepend cột 19rem) là hỏng ĐO ĐƯỢC ở 1366 (số bên dưới), và hỏng CÂM — build vẫn xanh.

---

## §0 — ĐO ĐĨA (làm trước khi gõ; bài học #1)

### §0.1 Khuôn S162 `bfc7b79` áp lên trang HÌNH DẠNG NÀO

| Trang khuôn | Vỏ ngoài | Cách áp |
|---|---|---|
| `pe/PurchaseEvaluationsListPage.tsx` (GĐ1) | trang giấy-cuộn, đã CÓ cây trái riêng | nhét `PipelineStageFolders` vào TRONG cây sẵn có, `stage1Content` giữ nguyên khối leaf cũ |
| `khkk/KhkkListPage.tsx` (GĐ2) | trang giấy-cuộn (`space-y`, thẻ card) | bọc `grid gap-5 lg:grid-cols-[19rem_minmax(0,1fr)] xl:grid-cols-[21rem_…]` + `<PipelineTreePanel currentStage={2}/>` |
| `contracts/HardCopiesPage.tsx` (GĐ4) | trang giấy-cuộn | y hệt trên, `currentStage={4}` |

Neo đo: `fe-admin/src/pages/khkk/KhkkListPage.tsx:197` · `…/HardCopiesPage.tsx:116` — CẢ HAI
là `grid gap-5 lg:grid-cols-[19rem_minmax(0,1fr)]`, tức **trang trôi theo chiều dọc, cột trái
chỉ ăn bề ngang**.

### §0.2 Trang GĐ3 là trang nào (lead hỏi: 1 trang chung hay 7 file?)

**Trả lời: 1 COMPONENT TRANG dùng chung cho cả 7 loại HĐ, lọc bằng `?type=N` — KHÔNG phải 7 file.**
Bằng chứng: `fe-user/src/components/Layout.tsx:143-153` regex `^Ct_([^_]+)_(List|Create|Pending|WfView|Approved|Deleted)$`
→ mọi loại đều trỏ CÙNG 1 route + đổi query.

| App | File trang | Route | Leaf `Ct_*` trỏ tới | Vỏ ngoài |
|---|---|---|---|---|
| fe-admin | `pages/contracts/ContractsListPage.tsx` (254 dòng) | `/contracts` | `List` `Create` `Pending` (`Layout.tsx:117-125`) | 3-panel cao-cố-định |
| fe-user | `pages/contracts/MyContractsPage.tsx` (231 dòng) | `/my-contracts` | `List` `Approved` `Deleted` | 3-panel cao-cố-định |
| fe-user | `pages/InboxPage.tsx` (352 dòng) | `/inbox` | `Pending` (= đúng nghĩa "đang duyệt") | 3-panel + dải stats |
| fe-user | `pages/pe/WorkflowMatrixViewPage.tsx` | `/contracts/workflow-matrix?type=3&ct=N` | `WfView` | dùng chung PE/KHKK |

fe-admin KHÔNG có `MyContractsPage`, KHÔNG có `InboxPage`, KHÔNG có `WorkflowMatrixViewPage`
(cố ý — `Layout.tsx:138-142`).

### §0.3 🔴 Ba lệch so với tiền-đề đề bài

**A-1 (CHẶN — bố cục):** trang GĐ3 ×2 app KHÔNG phải trang giấy-cuộn. Cả 3 đều là
`flex h-[calc(100vh-4rem)] flex-col` + `grid flex-1 overflow-hidden lg:grid-cols-[320px|340px_1fr_360px]`
(`ContractsListPage.tsx:105,124` · `MyContractsPage.tsx:104,135` · `InboxPage.tsx:165,188`).
Khuôn `19rem_minmax(0,1fr)` **không có chỗ cắm**: bọc grid ngoài thì cột trái cao 100vh cạnh
một khối đã tự cuộn trong; prepend cột vào chính grid 3-panel thì:

> Sidebar app = `w-72` / `xl:w-80` (`Layout.tsx:499` user, `:372` admin) = 288/320px.
> Grid 3-panel **không có `gap`** ⇒ cộng thẳng.
>
> | Bề ngang màn | Vùng nội dung | Panel 2 (chi tiết HĐ) HÔM NAY | Sau khi thêm cột cây 19rem (304px) |
> |---|---|---|---|
> | 1366 | 1046 | **346px** | **42px** ⛔ |
> | 1536 | 1216 | 516px | **212px** ⛔ |
> | 1920 | 1600 | 900px | 596px ✅ |
>
> Panel 2 render `ContractDetailContent` (form chi tiết HĐ đầy đủ). 42px = chết trang.
> Chính khuôn S162 đã lo đúng bề ngang này: comment `KhkkListPage.tsx:198-200`
> *"bảng 7 cột không thổi vỡ lưới ở 1366"*.

Cơ chế thu gọn có sẵn của `PipelineTreePanel` KHÔNG cứu được: nút "Hiện cây / Ẩn cây" là
`lg:hidden` (`PipelineTreePanel.tsx:197`) — chỉ sống DƯỚI lg, tức đúng dải mà panel 2/3 đã bị
`hidden lg:block`. Ở desktop panel luôn mở, không có chế độ đóng.
⇒ **Đây là "thiếu prop cho GĐ3"** theo đúng ngoại lệ lead nêu, nhưng nới prop nào (`collapsible`?
`defaultCollapsed`? mở ra thì ĐẨY hay PHỦ?) là quyết định UX ⇒ trả về em-main (refuse-criteria #1
"page layout", anti-pattern #7).

**A-2 (CHẶN NHẸ — phạm vi):** "trang GĐ3" ×2 app không đối xứng. fe-admin có ĐÚNG 1 ứng viên
(`ContractsListPage`); fe-user có 3 (`MyContractsPage` = danh sách · `InboxPage` = *Pending*,
đúng nghĩa "đang duyệt" nhất · `WorkflowMatrixViewPage`). Khuôn cho mỗi GĐ đúng 1 trang ⇒ phải
chốt trang nào. (Tôi nghiêng `MyContractsPage` — nó là cặp đối xứng của `ContractsListPage`.)

**A-3 (KHÔNG chặn — tiền-đề "SHA-pair identical" bất khả thi):** yêu cầu *"mirror ×2 app SHA-pair
identical cho MỌI file chạm"* KHÔNG áp được cho cặp trang GĐ3: hai app dùng **file KHÁC TÊN,
KHÁC RUỘT**, lệch sẵn từ trước, không phải do lượt này.

```
792cc373…  fe-admin/src/pages/contracts/ContractsListPage.tsx   (254 dòng, có Select phase + phân trang)
768728e3…  fe-user/src/pages/contracts/MyContractsPage.tsx      (231 dòng, có view "Đã xóa" + nút Tạo HĐ)
```

Đối chứng DƯƠNG cho phép đo hash (cùng lệnh, cùng lượt, ra BẰNG NHAU — chứng phép đo không mù):

```
d70ac315… = d70ac315…  components/pipeline/PipelineTreePanel.tsx      (admin = user)
bed52f39… = bed52f39…  components/pipeline/PipelineStageFolders.tsx   (admin = user)
76e18233… = 76e18233…  hooks/usePipelineStages.ts                     (admin = user, ĐÃ gồm K7 aaed699)
e4cb0bbb… = e4cb0bbb…  pages/contracts/HardCopiesPage.tsx             (admin = user, trang khuôn GĐ4)
```

⇒ SHA-pair chỉ khả thi cho component/hook (vốn đã bằng). Với trang GĐ3 phải đổi sang bằng chứng
`diff` khối chèn + khai lệch tại chỗ (bài học #10).

### §0.4 Bối cảnh mới hôm nay — fe-admin GĐ3 đã BỊ ẨN khỏi sidebar

`fe-admin/src/components/Layout.tsx:227-240` (K6 — S166, land HÔM NAY 2026-08-01):
`ADMIN_HIDDEN_KEYS` chứa `'Contracts'` (GĐ3 root) + `'HopDongCung'` (GĐ4 root) + luật prefix
`key.startsWith('Ct_')` (`:248`). `filterForAdmin` lọc ĐỆ QUY ⇒ **toàn bộ lối vào GĐ3 trên
sidebar fe-admin đã chết**; `/contracts` chỉ còn vào được bằng deep-link.

Hệ quả cho lượt này: nửa fe-admin của việc là **trang không ai thấy trên menu**. Không sai (route
còn sống, và tiền lệ `HardCopiesPage` fe-admin cũng đang ở đúng tình trạng đó từ K6), nhưng lead
nên biết TRƯỚC khi trả công: "đồng nhất ×2 app" hôm nay ≠ "2 app cùng dùng được".

---

## §1 — 4 đường đi, để em-main chốt 1 chữ

| # | Cách | Panel 2 @1366 | Đụng file khuôn? | Ghi chú |
|---|---|---|---|---|
| **A** | prepend cột `19rem` vào grid 3-panel | **42px ⛔** | không | mirror chữ-nghĩa đúng nhất, hỏng thật |
| **B** | cột cây chỉ hiện `≥2xl`, dưới đó ẩn hẳn | 346px (không có cây) | không | 1366/1536 = KHÔNG có cây ⇒ "đồng nhất" chỉ đúng ở màn to |
| **C** | nới `PipelineTreePanel` +chế độ thu gọn desktop (nút "Cây toàn trình" ở đầu cột, GĐ3 mặc định ĐÓNG) | 346px khi đóng | **CÓ** (2 file SHA-pair) | đúng ngoại lệ "thiếu prop cho GĐ3"; còn 1 câu hỏi: mở ra thì ĐẨY panel hay PHỦ lên |
| **D** | cây thay chỗ Panel 3 khi chưa chọn HĐ | 346px | không | Panel 3 đang là ô trống "Quy trình duyệt sẽ hiện khi chọn HĐ" ⇒ tận dụng chỗ đã trống, nhưng cây nhảy chỗ khi chọn HĐ |

**Tôi nghiêng C** (giữ được "đồng nhất" ở MỌI bề ngang, không cướp chỗ của chi tiết HĐ, và là
lần nới prop DUY NHẤT dùng lại được cho `InboxPage` nếu sau này áp tiếp). Nhưng C sửa file
SHA-pair dùng chung 4 trang khác ⇒ không tự quyết.

---

## §2 — UAT-NÓI-TRƯỚC (ghi sẵn cho lượt ship, chưa dùng tới)

- Prod hiện **0 HĐ** (lead khẳng định — tôi KHÔNG tự đo prod lượt này) ⇒ khi cây GĐ3 lên, mọi
  gói thầu sẽ hiện folder "Duyệt Hợp đồng" rỗng + chip **SẮP**. Đó là ĐÚNG SPEC, cùng lớp K5,
  không phải lỗi nối dữ liệu.
- Phân biệt 2 cái im khác nhau: folder rỗng vì gói CHƯA có HĐ (chip SẮP) ⟂ folder **khoá** vì
  tài khoản không có quyền `Contracts`/`Ct_*_List` (`usePipelineStages.ts:58,328` — khoá thì
  KHÔNG gọi API). Đọc nhầm ổ khoá thành "chưa có dữ liệu" là bẫy quen.
- Nguồn dữ liệu GĐ3 đã là TẬP `contractIds` (union `pe.contractId` legacy + `khkkPlanContractIds`)
  sau K7 — `usePipelineStages.ts:293-299`. Không cần sửa hook cho lượt GĐ3 này.

---

## §3 — Bằng chứng "0 chạm"

```
$ git status --porcelain fe-admin/src fe-user/src
(rỗng — xem §4 dòng chạy cuối)
```
Không chạy `npx tsc -b` / `npm run build`: 0 file thay đổi ⇒ build chỉ đo lại baseline của
`aaed699`, không chứng minh được gì về lượt này. Đổi lại, `git status` rỗng là bằng chứng MẠNH
HƠN cho "không để lại việc dở".

## §4 — Đã chạm / đã đo

- Đọc: `bfc7b79` (diff 3 trang khuôn) · `aaed699` (diff `usePipelineStages`) ·
  `PipelineTreePanel.tsx` · `PipelineStageFolders.tsx` · `usePipelineStages.ts` (bản đĩa, đã gồm K7) ·
  `ContractsListPage.tsx` · `MyContractsPage.tsx` · `InboxPage.tsx` (đầu file + grid) ·
  `Layout.tsx` ×2 app (khối `resolvePath` + `ADMIN_HIDDEN_KEYS`) · `App.tsx` ×2 app (routes).
- Ghi: DUY NHẤT file artifact này.

END implfe-gd3 — VERDICT=BLOCKED (§0 chặn: A-1 bố cục 3-panel không nhận khuôn cột-19rem, số đo 1366 → Panel 2 còn 42px; A-2 fe-user có 3 ứng viên trang; A-3 cặp trang GĐ3 khác file nên SHA-pair identical bất khả thi). 0 file chạm. Cần em-main chốt 1 trong 4 đường ở §1 (tôi nghiêng C) + chốt trang đích ở A-2, rồi tôi scaffold trong 1 lượt.
