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_…]` + `` | | `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.