30 KiB
sub-frontend-designer-L3 — LĂNG-KÍNH: HOST / BỐ CỤC
Run: 2026-08-05-S174-khkk-style-audit · Lane L3 (ensemble /fable-clone frontend-designer, 5 lane cùng vai) Mục tiêu: quét style/UX host+bố-cục của KHKK (GĐ2) đối chiếu Duyệt NCC (GĐ1, golive = CHUẨN). Đầu ra: spec thi-hành-được cho Opus 5 MAX.
0. KHUNG RỖNG (ghi @lượt 1, TRƯỚC khi đọc file) — append-as-I-read
0.1 File trong lăng-kính (3 chính)
- A.
fe-admin/src/pages/pe/PurchaseEvaluationWorkspacePage.tsx— CHUẨN (host 2-panel "Thao tác" GĐ1) — đọc HẾT 147 dòng - B.
fe-admin/src/pages/khkk/KhkkCreatePage.tsx— màn "Thao tác" GĐ2, vừa dựng lại — đọc HẾT 394 dòng - C.
fe-admin/src/pages/khkk/KhkkListPage.tsx— màn "Danh sách" GĐ2 (3-panel) — đọc HẾT 206 dòng - (đối chiếu)
fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx— KHÔNG mở toàn file (807 dòng): chỉ grep có mục tiêu + đọc đúng 24 dòng header/grid (:379-402) - (phụ, grep-only — không mở toàn file)
components/pe/PeListPanel.tsx·components/khkk/KhkkWorkListPanel.tsx·components/Layout.tsx·components/TopBar.tsx·src/index.css
0.2 Trục so sánh (6 trục bắt buộc)
- T1. Header trang: icon · cỡ chữ title · badge · dòng hint bên phải → §1/§2/§3, lệch: badge (H-3)
- T2. Chiều cao host
h-[calc(100vh-4rem)]→ KHỚP 4/4 file; sai ~3px là lỗi toàn hệ (§3bis-d) — KHÔNG sửa - T3. Grid cột + breakpoint → LỆCH 300/340 vs 260/320 (H-1), số học ở §3bis-b
- T4. EmptyState → KHỚP khuôn (icon+title+description), KHKK còn thêm nhánh not-found mà PE thiếu
- T5. Hành vi
<lg→ LỆCH NẶNG: KHKK-Create không có gì ⇒ ngõ cụt (H-2), 3 khuôn khác nhau ở §3bis-e - T6. Cách truyền
readOnly→ LỆCH: hằng số (Create) vs công thức host (List) (H-4)
0.3 Bảng kết
| # | Trục | PE (chuẩn) | KHKK Create | KHKK List | Verdict | Sửa |
|---|---|---|---|---|---|---|
| T1a | shell + header class | flex h-[calc(100vh-4rem)] flex-col + header 1 khuôn |
y hệt (:168-169) | y hệt (:123-124) | ✅ KHỚP BYTE | — |
| T1b | icon | ClipboardCheck h-5 w-5 text-slate-500 |
FileCheck cùng size/màu |
FileCheck |
✅ (đổi icon = đúng, theo module) | — |
| T1c | title | text-base font-semibold tracking-tight text-slate-900, không truncate |
+ min-w-0+truncate |
+ min-w-0+truncate |
⚠️ KHKK tốt hơn | H-7 giữ |
| T1d | badge | Workspace: KHÔNG · List: pill số (:386) | KHÔNG có | pill "Giai đoạn 2 · nhãn" (:128) | 🔴 lệch nội-bộ GĐ2 | H-3 |
| T1e | phải header | hint text-[12px] text-slate-500 · List: TRỐNG |
hint ✅ | Button "Lập kế hoạch" | ⚪ có lý do | H-9 giữ |
| T2 | chiều cao | 100vh-4rem |
y hệt | y hệt | ✅ | — |
| T3 | grid | [260px_1fr] / xl [320px_1fr] |
[300px_1fr] / xl [340px_1fr] |
[400px_1fr_360px] = PE list |
🔴 Create lệch | H-1 |
| T4 | EmptyState | icon+title+desc (:102-108) | icon+title+desc (:217-221) | + rẽ deletedView (:163-171) |
✅ | — |
| T5 | <lg |
dead-code (:135-144) | không có gì ⇒ không submit được | matchMedia → navigate (:102-109) | 🔴 hỏng chức năng | H-2 |
| T6 | readOnly | không dùng — mode="workspace" |
readOnly hằng số (:210) |
công thức host (:112-117) | 🔴 2 luật cho 1 component | H-4 |
| — | mirror 2 app | — | SHA256 == fe-user | SHA256 == fe-user | ✅ 4/4 SAME | giữ khi sửa |
0.4 Spec thi-hành-được → §4 (H-1…H-9, mỗi mục: file:line · hiện-trạng · đích · vì · rủi-ro)
- Đã viết. Thứ tự thi hành + danh sách KHÔNG SỬA ở cuối §4.
0.5 Nhật ký đọc (theo thứ tự tool-call)
wc -l4 file + Read BKhkkCreatePage(394) — song song- Read A
PurchaseEvaluationWorkspacePage(147) + Read CKhkkListPage(206) — song song → APPEND §1/§2/§3 - Grep header/grid
PurchaseEvaluationsListPage+ Grep root-className 3 panel - Grep
KhkkWorkListPanel(sticky/tier) + GrepPeListPanel(sticky create) - Grep
PurchaseEvaluationsListPagecơ-chế<lg(overlayexpand=1) + Bash sha256 mirror +git status - Bash
grep -c Button(đối chứng) + GrepLayout.tsx(main/topbar) - Grep
TopBar.tsx(h-14) + Bash khảo sát toàn bộlg:grid-cols-[→ APPEND §3bis + §4 - Bash
index.csstokens + probe cổng + ReadPurchaseEvaluationsListPage:379-402 - Bash đếm stop
violet-*toàn repo → sửa lại H-8 (bác giả-thuyết từ MEMORY) + APPEND §5
1. FILE A — PurchaseEvaluationWorkspacePage.tsx (CHUẨN) — 147 dòng, ĐÃ ĐỌC HẾT
Khuôn host "Thao tác" GĐ1 (đã golive, owner tuyên "đã tốt rồi", byte revert 1b3bcaa):
| Chi tiết | Giá trị | Dòng |
|---|---|---|
| Shell | <div className="flex h-[calc(100vh-4rem)] flex-col"> |
: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 | <main className="hidden overflow-y-auto bg-slate-50 p-6 lg:block"> |
: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 |
<lg |
Panel 2 ẩn; có khối lg:hidden (:136-144) — DEAD CODE: <button className="hidden"> 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 && <div className="text-sm text-slate-500">Đang tải…</div>} |
:120-122 |
| Not-found | KHÔNG có nhánh "Không tìm thấy phiếu" | — |
⚠️ 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 <h1> |
: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 · <nhãn nhóm>), khớp KhkkListPage:125-127 |
| Badge "Giai đoạn 2" | THIẾU — KhkkListPage:128-130 CÓ pill Giai đoạn 2 · <nhãn nhóm>, 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 | <KhkkDetailContent plan={…} readOnly onChanged={…} /> — 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 đủ" |
<lg |
Panel 2 hidden … lg:block; KHÔNG có fallback navigate (không selectPlan-style matchMedia) |
:187 | ⚠️ mobile = panel trái nằm 1 mình, bấm PE ⇒ set state nhưng panel 2 vẫn ẩn ⇒ bấm không thấy gì. KhkkListPage:102-109 CÓ matchMedia('(min-width:1024px)') → navigate /khkk/:id. PE cũng hở (dead-code) nhưng ở PE panel 1 có nút "+ Thêm mới" nên ít chết hơn |
| Banner "đang xem" | border-violet-200 bg-violet-50 + 2 nút (Mở trang đầy đủ outline · Về form tạo ghost) |
:191-204 | ➕ KHÔNG có ở PE — hợp lý (PE không có chế-độ xem-phiếu-người-khác trong workspace). Màu violet = token ngoài dải brand/slate của host (host GĐ1 chỉ dùng slate/brand) |
| Form card | rounded-lg border border-slate-200 bg-white shadow-sm + divide-y divide-slate-200 + Section px-5 py-4 |
:226-236,387-393 | ✅ khớp PeWorkspaceCreateView (helper Section khai rõ là duplicate cố ý, :385-386) |
| Footer form | border-t border-slate-200 bg-slate-50 px-5 py-3, hint trái + nút phải |
:353-376 | ✅ khuôn PE-create |
| Ký hiệu cảnh báo | dùng ký tự ⚠ / ✓ trong chuỗi text (:324, :357, :360) |
:324,357,360 | 🔴 ANTI-SLOP FD3: glyph thay icon. PE-family dùng lucide (X, Plus, ClipboardCheck). GĐ2 List cũng dùng lucide X (:189) |
3. FILE C — KhkkListPage.tsx (màn Danh-sách GĐ2, host 3-panel) — 206 dòng, ĐÃ ĐỌC HẾT
| Chi tiết | Giá trị | Dòng |
|---|---|---|
| Shell | flex h-[calc(100vh-4rem)] flex-col |
:123 |
| Header | class y hệt PE/Create | :124 |
| Icon | FileCheck h-5 w-5 text-slate-500 |
:126 |
| Title | truncate text-base font-semibold tracking-tight text-slate-900, wrapper flex min-w-0 items-center gap-2 |
:125-127 |
| Badge | ml-2 truncate rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600 — "Giai đoạn 2 · <nhãn nhóm>" |
: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 |
<lg |
selectPlan :102-109 dùng window.matchMedia('(min-width: 1024px)') → <lg navigate /khkk/:id (giữ ?group=) |
:102-109 |
| readOnly | tính ở HOST: `!plan | |
| Hộp rỗng panel 3 | rounded-lg border border-dashed border-slate-200 p-6 text-center text-sm text-slate-400 + lucide X |
:188-193 |
3bis. BẰNG CHỨNG PHỤ (đọc ngoài 3 file chính — chỉ grep có mục tiêu, KHÔNG mở toàn file)
(a) Khảo sát TOÀN BỘ host-grid fe-admin/src/pages (grep -rn "lg:grid-cols-\[")
contracts/ContractCreatePage.tsx:129 lg:grid-cols-[320px_1fr] ← "thao tác" GĐ3
contracts/ContractsListPage.tsx:125 lg:grid-cols-[340px_1fr_360px]
khkk/KhkkCreatePage.tsx:179 lg:grid-cols-[300px_1fr] xl:[340px_1fr] ← GĐ2 thao tác
khkk/KhkkListPage.tsx:140 lg:grid-cols-[400px_1fr_360px] ← = PE list ✅
pe/PurchaseEvaluationsListPage.tsx:397 lg:grid-cols-[400px_1fr_360px] ← CHUẨN list
pe/PurchaseEvaluationWorkspacePage.tsx:80 lg:grid-cols-[260px_1fr] xl:[320px_1fr] ← CHUẨN thao tác
⇒ KhkkCreatePage là giá trị THỨ BA trong họ "thao tác" (260/320 · 320 · 300/340) — không khớp PE, cũng không khớp Contract.
⇒ Đối chứng DƯƠNG: KhkkListPage:140 khớp PurchaseEvaluationsListPage:397 byte-for-byte, và KhkkListPage:9 ghi thẳng luật "Số đo panel lấy của PE (400px) vì hiến-pháp owner là 'giống nhất với Duyệt NCC'" ⇒ luật đã tồn tại, file Create không áp.
(b) Số đo thật (Layout fe-admin/src/components/Layout.tsx:372 sidebar w-72 = 288px, xl:w-80 = 320px)
| Viewport | Vùng nội dung | Panel-1 PE | Panel-2 PE | Panel-1 KHKK-Create | Panel-2 KHKK-Create | Δ panel-2 |
|---|---|---|---|---|---|---|
| 1024 (lg) | 1024−288 = 736 | 260 | 476 | 300 | 436 | −40px |
| 1280 (xl) | 1280−320 = 960 | 320 | 640 | 340 | 620 | −20px |
| 1366 (laptop SE) | 1366−320 = 1046 | 320 | 726 | 340 | 706 | −20px |
Panel-2 KHKK-Create trừ p-6 (48px) ⇒ 388px ở lg. Trong đó Section 2 dùng md:grid-cols-2 (:284) — md: là viewport-based (768px), KHÔNG container-based ⇒ ở 1024 nó ĐÃ chia đôi: 2 ô ~186px cho "Nhóm duyệt" + "Quy trình duyệt" (ô quy trình in mã · tên có truncate, :313-317) ⇒ tên quy trình gần như chắc chắn bị cắt. PE cho 428px ⇒ ~210px/ô. |
||||||
| ⇒ Lệch đi NGƯỢC hướng nhu cầu: cột danh sách rộng hơn chuẩn 40px, cột FORM (thứ cần chỗ) hẹp hơn chuẩn 40px. |
(c) Panel-1: hợp đồng bố-cục
PeListPanel.tsx:92root<aside className="flex flex-col overflow-hidden border-r border-slate-200 bg-white">+ sticky bottom action:238-241<div className="border-t border-slate-200 bg-white p-2"><Button …(grep -c Button= 8).KhkkWorkListPanel.tsx:142root<aside className="flex min-h-0 flex-col overflow-hidden border-r border-slate-200 bg-white">— khớp +min-h-0(cần cho 2 tầng). Nhưnggrep -c Button= 0 ⇒ KHÔNG có nút hành-động dính đáy. 🔴 ⇒ commentKhkkCreatePage.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 <div className="shrink-0 border-t-2 border-slate-200" />= 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-1cả 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 <header className="flex h-14 …"> (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) <lg — 3 khuôn KHÁC NHAU
| Màn | Cơ chế | Bằng chứng |
|---|---|---|
| PE list (chuẩn 3-panel) | selectRow set expand=1 khi window.innerWidth < 1024 ⇒ overlay full-bleed |
PurchaseEvaluationsListPage.tsx:147-158 |
| KHKK list | matchMedia('(min-width: 1024px)') sai ⇒ navigate('/khkk/:id') |
KhkkListPage.tsx:102-109 |
| PE workspace (chuẩn 2-panel) | dead-code lg:hidden + <button className="hidden"> 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 ở <lg: grid về grid-cols-1 ⇒ chỉ KhkkWorkListPanel hiện (aside không có lg: gate). Bấm PE ⇒ pickPe set state (:153-156) nhưng <main> vẫn hidden ⇒ màn không đổi gì. Form + nút "Tạo kế hoạch" (:372) nằm TRONG <main> ⇒ 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 <main> ẩ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-identicalPurchaseEvaluationWorkspacePage.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
Tiercó 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 — <lg là ngõ cụt, không tạo được phiếu (KhkkCreatePage.tsx:187)
- Hiện:
<main className="hidden … lg:block">, không có nhánh nào cho<lg. - Đích : theo khuôn
KhkkListPage.tsx:102-109(cùng module, đã chạy): trongpickPe, nếu!window.matchMedia('(min-width: 1024px)').matches⇒navigate('/khkk/create-mobile'…)KHÔNG có ⇒ cách rẻ + đúng nhất: bỏhidden … lg:blockcho<main>, để nó xếp DƯỚI panel-1 ở<lg(grid-cols-1sẵn có ⇒ 2 hàng), và thêmlg:cho panel-1 nếu cần giới hạn chiều cao. - ⚠️ KHÔNG mirror dead-code
PurchaseEvaluationWorkspacePage.tsx:135-144— nó là nợ, không phải chuẩn. - Vì : nút submit nằm trong
<main>⇒ 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:blockNHƯNG thêm khốilg:hiddennó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
<h1>đúng span củaKhkkListPage.tsx:128-130:<span className="ml-2 truncate rounded-full bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-600">Giai đoạn 2{presetGroup !== null ?· ${khkkGroupMenuLabel(presetGroup)}: ''}</span> - 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· <nhãn nhóm>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:
<KhkkDetailContent plan={viewPlan.data} readOnly … />— 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 importuseAuthnhư 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(<div className="border-t border-slate-200 bg-white p-2">) — 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 ở<lg. - Vì : comment là hợp-đồng cho lượt sau; sai comment ⇒ lượt sau tin có nút, không kiểm.
H-6 🟢 MINOR — Glyph thay icon (KhkkCreatePage.tsx:324, 357, 360)
- Hiện:
⚠ Chưa có quy trình mã…(:324) ·⚠ Chưa đủ để tạo kế hoạch…(:357) ·✓ Đủ thông tin bắt buộc…(:360). - Đích :
lucide-react—<AlertTriangle className="mr-1 inline h-3.5 w-3.5" />/<Check … />, 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:30FileCheck, Plus, X;PurchaseEvaluationsListPage:105 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-127có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+truncatecho 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ómborder-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,@themechỉ ghi đè 5 stop (:41-45violet 50/100/500/600/700).- ⇒
violet-200/violet-800VẪ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" trongPeListPanel: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:hiddencủ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
5.1 🔴 KHÔNG CÓ SCREENSHOT — khai thẳng, không bịa (khuôn NO-SCREENSHOT S159)
Vòng FD2 (run → shoot → critique) không chạy được trong lượt này:
- Probe cổng:
netstat⇒ chỉ0.0.0.0:8080LISTENING (pid 5912).5443/5444(API) KHÔNG có ⇒ đăng nhập fail. /khkk/createvà/khkk/listđều sauProtectedRoute⇒ không token thì kẹt/login(gotcha #3 đã ghi trong MEMORY: dev-rig chặn authed page).- Dựng harness render-isolated cố ý KHÔNG làm: mandate ISOLATION của run này chỉ cho ghi
sub-frontend-designer-L3.md; harness = file mới trongfe-user//fe-admin/⇒ vi phạm git-diff audit. ⇒ Mọi kết luận dưới đây là đo tĩnh trên mã nguồn + số học layout, KHÔNG phải quan sát bằng mắt. Ba mục cần nghiệm thu bằng mắt trên prod sau khi sửa: H-1 (panel-1 260px có wrap dòng PE không), H-2 (<lgsau khi mở<main>), H-3 (badge + title không tràn ở 1366).
5.2 Độ chắc của từng kết luận
| Mục | Loại bằng chứng | Chắc |
|---|---|---|
| H-1 grid | so chuỗi class + khảo sát 6 host + số học px | CAO — đọc được từ mã, không cần chạy |
H-2 <lg |
hidden … lg:block + vị trí nút submit trong DOM |
CAO về mặt suy luận DOM · CHƯA nhìn bằng mắt |
| H-3 badge | so 2 file cùng module | CAO |
| H-4 readOnly | hằng số vs công thức host | CAO (còn phụ thuộc ý owner, đã nêu 2 nhánh) |
| H-5 comment sai | grep -c Button = 0 (đối chứng dương PeListPanel = 8) |
CAO |
| H-6 glyph | 3 vị trí literal | CAO |
| H-7 truncate | so 4 file | CAO |
| H-8 violet | đo index.css + đếm toàn repo — bác giả-thuyết ban đầu của chính tao |
CAO |
5.3 Ngoài lăng-kính này (lane khác lo)
Nội dung bên trong panel (KhkkWorkListPanel 2 tầng, KhkkDetailContent, KhkkListPanel cây toàn-trình), micro-copy, trạng thái loading/empty của từng tầng, a11y focus-order, typography scale — KHÔNG chấm ở lane HOST/BỐ-CỤC. Tao chỉ chạm chúng ở mức "root className có khớp hợp-đồng grid của host không".
5.4 Đã KHÔNG làm (giữ lane)
Không sửa 1 dòng code nào (lane audit). Không đụng agent-memory/*, không đụng MD canonical, không đụng sub-MD lane khác. git status --porcelain -- fe-admin/src fe-user/src đo được là rỗng trước khi tao bắt đầu và tao không ghi gì vào đó.
END sub-frontend-designer-L3 — VERDICT=KHỚP-MỘT-PHẦN: 4 trục khớp chuẩn (shell/header-class/panel-2/EmptyState), 2 lệch nặng (grid 300→260 · <lg ngõ cụt không tạo được phiếu), 3 lệch vừa (badge GĐ2 · readOnly ép cứng · comment sai sự thật), 2 minor (glyph thay icon · truncate — giữ vì tốt hơn chuẩn), 3 mục CHỐT-KHÔNG-SỬA (h-calc toàn hệ · violet 2-hệ toàn hệ · nút tạo ở header List). KHÔNG có screenshot — đã khai lý do đo được.