# frontend-designer — MEMORY (L1 HOT) > 8th sub-agent (S47, 2026-06-02). Role: FE **design/UX/aesthetic** cho 2 app SOLUTION_ERP. Floor FD1–FD10 (AI_INFRA canonical, KHÔNG hạ). Forked `frontend-designer.agent.template.md`. ## Role + boundary - **MINE:** FE design/UX/redesign — `fe-admin/src/**` + `fe-user/src/**` styling/component/page/design-system/a11y/responsive/micro-interaction. Design-by-code (React/Tailwind/shadcn), KHÔNG Figma. - **NOT MINE:** BE/DB/business-logic (implementer-backend) · cookie-cutter mechanical mirror theo spec (implementer-frontend — **KHÔNG double-touch cùng file UI**) · test (test-specialist). - **store_memory GỠ** (broadcast 2026-06-02) → ghi finding/token/component vào FILE NÀY; em main + re-index đưa vào RAG. ## SE design-system (FD1 — DÙNG, KHÔNG reinvent) — VERIFIED S47 - Brand primary **`#1F7DC1`** · font **Be Vietnam Pro** (Vietnamese diacritics) · Tailwind tokens · ERP shell (TopBar + Bell + UserMenu). - ⚠️ **Token source = Tailwind v4 CSS-first** (NO `tailwind.config.js` file — template path stale). Tokens live in `fe-user/src/index.css` `@theme{}` block (mirror `fe-admin/src/index.css`). Read TRƯỚC khi build. - Brand scale `--color-brand-50..900`; **`--color-brand-600 = #1f7dc1`** (exact logo). Accent red `--color-accent-500/600` (from ® mark). Be Vietnam Pro + JetBrains Mono via Google Fonts `@import` in index.css. body 14px / lh 1.55 / letter-spacing -0.003em. - UI primitives are **hand-rolled cva** (NOT vanilla shadcn copy): `fe-user/src/components/ui/{Button,Input,Label}.tsx`. Button variants primary/secondary/outline/ghost/danger × sm/md/lg; focus-visible ring brand-500 + disabled:opacity-50 already wired. Input has focus-visible border-brand-500 + ring. REUSE these, don't reinvent. - Stack: React 19 + Vite 8 + TS 6 + TanStack Query + lucide-react + sonner. Node v22 local (engines `>=20`). - UI 100% tiếng Việt · Named export (trừ App) · TS6 `const X = {...} as const` thay enum · PageHeader chỉ {title, description, actions} · Duplicate 2 app CÓ CHỦ ĐÍCH (§3.9). ## FD2 visual-verification rig (SE-specific) — ✅ VERIFIED RAN end-to-end S47 - Dev: `cd fe-admin && npm run dev` → :8082 (proxy /api→:5443) · `cd fe-user && npm run dev` → :8080. - Auth: ERP behind login — token localStorage `solution-erp-admin-token` / `solution-erp-user-token`. Authed page screenshot cần API+SQL chạy + login fixture (seed JWT). Public `/login` chụp trực tiếp. - **PROVEN rig** (`webapp-testing` skill = Python Playwright, NOT npm @playwright/test): - Bash tool is **POSIX bash** despite env "PowerShell" note → use `cd "abs/path"` (NO `cd /d`). Forward-slash Windows paths work. - Chromium for Testing already installed (`C:\Users\pqhuy\AppData\Local\ms-playwright\chromium-1223`). Python 3.11 + `playwright` binding present & drives headless OK. NO install needed. - Run pattern: `python /scripts/with_server.py --server "npm run dev" --port 8080 --timeout 90 -- python my_shot.py` (helper starts/stops dev). Write my_shot.py in fe-user dir, **delete after** (throwaway, not app code). - Screenshot: `browser.new_page(viewport={w,h}, device_scale_factor=2)` → `page.screenshot(full_page=True)` → **Read PNG** to NHÌN. - 🪲 **2 Vite-dev gotchas (cost me 2 failed runs):** (1) `wait_until="networkidle"` NEVER fires — Vite HMR websocket stays open → use `domcontentloaded` + `wait_for_selector("form")`. (2) FIRST goto after cold server triggers Vite dep-optimize compile (>15s) → add a **warm-up goto with 60s timeout** before the viewport loop, else first viewport times out. - 🪲 (3) **Authed-page (Dashboard…) S55 BLOCKER:** `dotnet run` API binds **HTTPS :5443** + HTTP :5444 (Program.cs overrides `ASPNETCORE_URLS`), nhưng `vite.config.ts` proxy target = `http://localhost:5443` → protocol mismatch → /api login fail → kẹt /login. ĐỂ chụp authed: temp-set proxy `https://localhost:5443` (`secure:false` sẵn) + restart Vite + **REVERT sau**; HOẶC run API HTTP-only. + Dashboard = ProtectedRoute (cần JWT) → Playwright login THẬT (fill `admin@solutions.com.vn`/`Admin@123456` + submit + wait url≠/login). S55 rig này chặn cả designer + em main → **đáng tin nhất = deploy prod rồi login thật xem authed pages** (đừng vật lộn dev-rig cho authed screenshot). - Fallback khi stack chưa chạy: static component preview / screenshot `/login` — **KHÔNG bỏ soi** (FD2 cấm ship-unseen). ## Component inventory (built/verified — chống reinvent) - _(**3 shared UI E-Office (S69): `ui/PageHeader` giàu (eyebrow/icon/accent) + `ui/KpiCard` filter-chip + `ui/WidgetCard`** — accent map, a11y Enter/Space, gotcha-66 `text-white!` trên nền gradient. verbatim → `archive/2026-06.md` §"S69 … 3 shared UI")_ - _(**`pages/office/OfficeDashboardPage.tsx` (S69)** — E-Office landing PURO, compose 3 widget, reuse hook verbatim 0 API mới. verbatim → `archive/2026-06.md` §"S69 … OfficeDashboardPage")_ - `fe-user/src/pages/LoginPage.tsx` — login (public, no auth). Layout: gradient bg + 2 blur blobs + centered `max-w-md` card (bg-white/90 backdrop-blur) → logo / brand eyebrow / subtitle / Email+Mật khẩu / full-width Đăng nhập. Uses ui/{Button,Input,Label}. Solid baseline; nearly identical in fe-admin (mirror candidate). - `fe-user/src/pages/hrm/EmployeesListPage.tsx` — **2-panel master-detail HRM (S66 refine, was 3-panel S65)**: shell `lg:grid-cols-[22rem_1fr] xl:grid-cols-[24rem_1fr]`. **CỘT TRÁI** = `
` chứa Org-tree (TRÊN, `lg:max-h-[44%] lg:shrink-0`, cuộn riêng) + List+filter (DƯỚI, `flex-1`, cuộn riêng). **CỘT PHẢI** = Detail 5-tab (flex-1, rộng). 1000 LOC) = ONE atomic `Write` cả-file (lands-fully-or-errors, no half-break) + **EMIT change-list + build-verdict TRƯỚC khi vào FD2 screenshot loop** (2× truncate liên-tiếp CÙNG điểm rig-entry — S55/S58, em-main proxy recover). Truncate giữa-run → build ×2 verify. - **Tailwind v4 accent-stop:** teal/violet/amberx/greenx CHỈ có stop 50/100/500/600/700 — **KHÔNG -800** → head/value dùng -700 (brand -800 OK). Dùng -800 = silent no-class (build PASS, phải soi dist CSS). Rail pseudo cần `before:content-['']` else ::before không render box. - **Authed-rig gotcha #3:** dev-rig chặn authed ProtectedRoute (API HTTPS :5443 ⟂ vite proxy HTTP → login fail kẹt /login) → **verify authed pages qua DEPLOY prod login thật**, đừng vật-lộn dev-rig. Public /login chụp trực-tiếp OK. - **Lucide alias-dodge:** icon từ export-aggregation-line (`SendHorizonal`) / mojibake token (`网络Placeholder` autocomplete) → dùng proven-safe export (`Send`), node-check icons trước build. ## Activity log - **S162 (2026-07-30) Cây 4 folder GIAI ĐOẠN dưới từng gói thầu — 3 file MỚI + 3 trang, MIRROR đủ 2 app, build PASS ×2.** Owner (screenshot khoanh đỏ dải 4 GĐ → cây trái trang Duyệt NCC) + AskUser chốt: folder đặt DƯỚI TỪNG GÓI THẦU · áp cả trang GĐ khác. Nhãn VERBATIM "Duyệt NCC · Kế hoạch Hợp đồng · Duyệt Hợp đồng · Hợp đồng cứng" (GĐ2 CỐ Ý khác nhãn strip `PePipelineStrip` = lời-mới-nhất-thắng; KHÔNG sửa strip/menu). **3 file mới:** `components/pipeline/PipelineStageFolders.tsx` (thuần trình bày; `
` controlled `useState(n===currentStage)`; prop `content` cho GĐ1 giữ leaf phiếu cũ; `locked`/`failed`/`loading`/`hideEmpty`) · `hooks/usePipelineStages.ts` (2 query index key MỚI `pipeline-khkk-index`/`pipeline-contract-index` pageSize 200 — `PagedResult.cs:20` KẸP ≤200 không 400; `buildStages(peItems)` nối GĐ2 qua `khkk.purchaseEvaluationId`, GĐ3 qua `pe.contractId`, GĐ4 = HĐ đó có `hasSealedCopy`) · `components/pipeline/PipelineTreePanel.tsx` (panel cây trái cho trang GĐ2/GĐ4; `buildTree` RIÊNG, KHÔNG đụng memo `yearGroups` trang PE). **Wire:** PE list (`currentStage=1`, khối `
  • ` phiếu giữ NGUYÊN TỪNG BYTE kể cả thụt lề ⇒ diff chỉ 2 mẩu bọc = bằng chứng không đụng behavior) · KhkkListPage (=2) · HardCopiesPage (=4), 2 trang sau bọc lưới `lg:grid-cols-[19rem_minmax(0,1fr)]` + `min-w-0` + `` bọc `overflow-x-auto` + KPI `lg:grid-cols-6`→`xl:`. **Permission 2 tầng (S118/#82):** gate TRƯỚC fetch, `enabled:false` ⇒ 0 rác 403 **VỚI ĐIỀU KIỆN gate = ĐÚNG key policy của endpoint** *(reviewer F-1 @S162 bắt, lead sửa cùng lượt: GĐ2 `/contract-signing-plans` = `KeHoachKyKet.Read`, `MenuPermissionHandler.cs:40` so khớp CHÍNH XÁC 1 key — KHÔNG kế thừa/OR-con ⇒ OR key con sẽ fetch rồi ăn 403 khi role thiếu key root; gate đã đổi về `['KeHoachKyKet']`. GĐ3/4 `/contracts` `[Authorize]` trần nên OR key hiển-thị vô hại)*. ⚠️ menu-tree fallback quyền cha CHỈ khi menu con KHÔNG có row riêng (`GetMyMenuTreeQuery.cs:66`) — seeder TẠO row `Ct_*` (`DbInitializer.cs:2168-2176`) ⇒ `Ct_*` thực tế KHÔNG kế thừa `Contracts` *(câu "Ct_* kế thừa Contracts" bản đầu entry này SAI — reviewer F-8, đã sửa)*. **+5 fix hậu-review cùng lượt (lead áp):** F-1 gate-policy-key · F-3 leaf ngoài-index nhãn "(ngoài chỉ mục 200 — mở để xem)" tách nghĩa "(chưa cấp mã)" · F-4 index giữ `total` ⇒ cờ `truncated` khai cửa-sổ-200 ngay trên cây (footer folder + notice panel) · F-6 `hideEmpty` KHÔNG nuốt folder failed/loading · F-7 upload bản cứng invalidate thêm `pipeline-contract-index`. Giới hạn khai thẳng: F-2 phiếu liên-danh N-HĐ chỉ nối được HĐ[0] qua `pe.contractId` (chờ W5 link KHKK→HĐ) · F-5 lưới 19rem 2 trang chưa render thật (harness chỉ import 2 component) — nghiệm thu prod. **Evidence:** `.claude/workflows/runs/2026-07-30-S162-cay-4-folder-gd/sub-frontend-designer-0.md` + `ship-synthesis.md` · SHIP `bfc7b79` · cicd PASS 5/5 Run #432. 🔸 *H2 F-03 @S162: `ship-synthesis` trích `VERDICT: SHIP-READY` như verbatim của tao nhưng sub-file **0 hit** chuỗi đó (kết bằng "TỰ ĐÁNH GIÁ CÒN HỞ", không END-line) — nội dung khớp đĩa nên KHÔNG phải bịa, nhưng từ nay **mọi `sub-*.md` PHẢI kết bằng `END — VERDICT=<…>`*. Tag [s162, pipeline-stage-folders, 4-folder-gd, permission-policy-key-gate, index-query-key-moi, leaf-byte-verbatim, sha-pair-6-file, review-f1-f8]. - **S159 (2026-07-29) ARC-2 "Tổng quan quy trình" ×2 lượt — `UserDashboardPage.tsx` fe-user (đợt-1 hero pipeline + đợt-4 redesign list toàn-trình).** *(Entry on-behalf bởi lead @S160 closeout M9 — VERIFY từ 2 sub-file + commit; cả 2 lượt vai ghi-đĩa-trong-lúc-làm nên #53 ×2 first-return mất 0 B.)* **Đợt-1** (`b5799fc`, `sub-frontend-designer-tongquan.md` 15.803 B): file 309 dòng → THÊM hero pipeline 4 GĐ trên cùng, GIỮ trọn 3 query (`my-dashboard`/`my-contracts-recent`/`my-pe-recent`) + 5 StatCard + 2 list (owner: *"đừng vứt"*); đổi PageHeader title "Tổng quan tất cả luồng duyệt". **Đợt-4** (`a2bbcb9`, `sub-frontend-designer-dot4.md` 13.956 B): file lúc đó 766 dòng — đọc đĩa KHÔNG dùng bản nhớ (đã qua 3 đợt sửa nhãn giữa 2 lượt); bỏ khối owner khoanh (5 StatCard + 2 list "gần đây") → **list chi tiết toàn-trình**; GIỮ hero (owner không khoanh — *đợt-5 vai khác mới bỏ hero theo lệnh anh*); nhãn 4 GĐ echo bản MỚI NHẤT từ comment đầu file (GĐ2 "Kế hoạch ký kết HĐ", KHÔNG lấy tên đợt-1 "Đề xuất ký kết hợp đồng" đã superseded); +2 query `['pipeline-pe-approved']` (phase=7 pageSize=1 lấy `.total`) + `['pe-inbox']` **dùng `select: d => d.length` để KHÔNG vỡ cache dùng chung InboxPage**; verify param BE bằng đọc controller thật. 🔴 **Môi trường spawn KHÔNG có dev-server/Playwright ⇒ FD2 visual-loop bất-khả — thay bằng `npm run build` PASS + tự-review tĩnh FD4, KHAI THẲNG không bịa screenshot** (khuôn NO-SCREENSHOT này tái dùng cho mọi spawn thiếu rig). Bài giữ lại: (i) giữa 2 lượt cùng file, dòng-đếm/nhãn đổi ⇒ **khảo sát lại từ đĩa mỗi lượt**; (ii) đổi consumer của endpoint dùng-chung → `select` transform thay đổi shape cache. Tag [s159, tong-quan-quy-trinh, hero-pipeline-4gd, dot4-list-toan-trinh, no-screenshot-khai-that, select-transform-cache-safe, doc-dia-moi-luot, on-behalf-m9]. - _(**S79 (2026-06-19) PE list fe-user — DECOUPLE chọn/mở-rộng: `?expand=1` cạnh `?id`, nút Maximize2 mỗi dòng, ALL logic verbatim, harness-FD2 6 shot.** verbatim → `archive/2026-06.md` §"S79 (2026-06-19)")_ - _(**S69 (2026-06-17) OfficeDashboardPage.tsx fe-user — E-Office landing PURO, COMPOSES 3 shared widgets, reuse hooks verbatim 0 new API, build PASS 0 TS.** verbatim → `archive/2026-06.md` substring:"S69 (2026-06-17) OfficeDashboardPage.tsx")_ - _(**S69 (2026-06-17) Văn phòng số / Đơn từ RE-SKIN 2 file (PURO+HRM visual, client-side filter view, logic verbatim, lucide alias-dodge Send).** verbatim → `archive/2026-06.md` substring:"S69 (2026-06-17) Văn phòng số / Đơn từ")_ > _Older Activity (S69×2/S66/S65/S58/S55/S47 · 2026-06-02→17) → `archive/2026-06.md` verbatim · map `archive/_INDEX.md` · distill `archive/2026-06.gist.md`. Curated @S96 first-overflow (L1 26129B > autoinject-cap 25600B, H17-loop strike-1)._