19 KiB
19 KiB
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.jsfile — template path stale). Tokens live infe-user/src/index.css@theme{}block (mirrorfe-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@importin index.css. body 14px / lh 1.55 / letter-spacing -0.003em.
- Brand scale
- 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 constthay 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/loginchụp trực tiếp. - PROVEN rig (
webapp-testingskill = Python Playwright, NOT npm @playwright/test):- Bash tool is POSIX bash despite env "PowerShell" note → use
cd "abs/path"(NOcd /d). Forward-slash Windows paths work. - Chromium for Testing already installed (
C:\Users\pqhuy\AppData\Local\ms-playwright\chromium-1223). Python 3.11 +playwrightbinding present & drives headless OK. NO install needed. - Run pattern:
python <skill>/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.
- Bash tool is POSIX bash despite env "PowerShell" note → use
- 🪲 2 Vite-dev gotchas (cost me 2 failed runs): (1)
wait_until="networkidle"NEVER fires — Vite HMR websocket stays open → usedomcontentloaded+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 runAPI binds HTTPS :5443 + HTTP :5444 (Program.cs overridesASPNETCORE_URLS), nhưngvite.config.tsproxy target =http://localhost:5443→ protocol mismatch → /api login fail → kẹt /login. ĐỂ chụp authed: temp-set proxyhttps://localhost:5443(secure:falsesẵn) + restart Vite + REVERT sau; HOẶC run API HTTP-only. + Dashboard = ProtectedRoute (cần JWT) → Playwright login THẬT (filladmin@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).
- 🪲 (3) Authed-page (Dashboard…) S55 BLOCKER:
- 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 cho Văn phòng số / E-Office (S69, 2026-06-17) — PURO-style + HRM visual language. ALL build-PASS 0 TS:
fe-user/src/components/ui/PageHeader.tsx— richer page header (eyebrow/title/subtitle/icon/accent/actions/breadcrumb). ⚠️ KHÁC@/components/PageHeader(constrained {title,description,actions}) — module path@/components/ui/PageHeaderriêng, KHÔNG collision. icon-chip accent-tinted + titletext-xl font-boldaccent-head. LocalACCENTmap {chipBg,chipFg,head} (brand=text-brand-800, rest -700). Title trên nền SÁNG → KHÔNG cầntext-white!.fe-user/src/components/ui/KpiCard.tsx— clickable stat card = FILTER chip (PURO: row KpiCards thay tabs). icon-chip +.stat-value text-2xlaccent +.label-eyebrow. active =bg-{x}-50+border-{x}-300+ring-{x}-500. a11y FULL:onClick→role=button+tabIndex=0+Enter/Space (e.key===' ')+aria-pressed=active+focus-visible ring; no-onClick = inert div. hover-translate-y-0.5+motion-reduce:transform-none.fe-user/src/components/ui/WidgetCard.tsx— dashboard widget container (PURO HomePage). Wrap.card-accent(rail via inline--accent). Header: brand=.app-gradient-brand text-white· non-brand=tintedbg-{x}-50bar. gotcha 66 APPLIED: gradient<h3>title =text-white!(bang) — plain text-white thua unlayered h1-h4 rule. Props: title/icon/accent/stats[]/onExpand/onRefresh/children/empty/emptyText.stats[]= clickable StatChip row (mỗi chip a11y button khi có onClick). empty → muted icon-chip + emptyText. Header IconButton (RefreshCw/Maximize2) contrast-adapt gradient↔tinted, aria-label.- 3 đều: NAMED export ·
import type(verbatimModuleSyntax) ·cnfrom@/lib/cn· lucide-react · accent palettes stop 50/100/500/600/700 ONLY (no -800) → head/value -700 (brand -800 OK) · icon-chip recolor['--chip-bg' as string]/['--chip-fg' as string]inline (pattern từ HRM Card). NO new npm dep. Buildtsc -b && vitePASS 0 TS, 24.87s (warning @import-order + chunk-size = pre-existing). fe-admin NOT mirrored (separate pass nếu cần). FD2 authed-screenshot SKIP (components chưa wired vào page nào — pure library; visual verify khi page tiêu thụ chúng).
fe-user/src/pages/office/OfficeDashboardPage.tsx— E-Office landing dashboard (S69, 2026-06-17) — PURO HomePage, COMPOSES the 3 shared ui widgets. build-PASS 0 TS, 434ms. Layout:ui/PageHeader(eyebrow "Văn phòng số" / title "Bảng điều khiển" / icon LayoutDashboard / accent brand) on top →grid grid-cols-1 lg:grid-cols-3 gap-5: LEFTlg:col-span-2= stack of 4WidgetCard(Đề xuất brand / Đơn từ teal / Ticket CNTT violet / Phòng họp hôm nay amberx — each body = row of 3KpiCardfilter-chips except Phòng họp = 1 KpiCard + next-4 booking peek list) · RIGHTlg:col-span-1= "Công việc của tôi" WidgetCard (brand-50 hero countmyTodo+ 3 clickableMetricRow) + "Thao tác nhanh".card-accentpanel (3 Button primary/secondary/outline). Stacks 1-col <lg. Hooks REUSED (verbatim queryKey+endpoint = shared TanStack cache, NO new API, NO new BE): proposals['proposals',{…}]→GET /proposals(+ separateinboxOnly:truequery for "Cần duyệt" = needs-my-action signal) · đơn-từ['/leave-requests'|'/ot-requests'|'/travel-requests',{page:1}]→those 3 endpoints, merged+countByStatusclient-side · tickets['it-tickets']→GET /it-tickets· meetings['meeting-bookings',{…}]→GET /meeting-bookingswindowed to today (local-midnight→+1d ISO). Counts ALL client-side (countByStatusreducer; status enums from@/types/proposal+@/types/workflowApps). States graceful per-widget: isError→WidgetError(retry btn, accent-500 chip) · isLoading→WidgetSkeleton(3 pulse bars,motion-reduce:animate-none) · empty→WidgetCardemptyprop. NEVER blocks page. Routing question: only/proposals/newexists as real create route → quick-actions "Tạo đề xuất"→/proposals/new, "Tạo đơn"→/workflow-apps/leave(đơn-từ landing, NO standalone /new), "Tạo ticket"→/it-tickets(ticket landing) — every link hits an EXISTING route (no*fallback dead-link). onExpand per widget → its real route. a11y: KpiCard/MetricRow clickable = role=button+Enter/Space+focus-ring; reduced-motion honored. Routing/menu wiring NOT done (next agent's job — page NOT imported in App.tsx yet). fe-admin NOT mirrored. FD2 authed-screenshot SKIP (ProtectedRoute + gotcha #3 rig blocks authed; visual verify via deploy).fe-user/src/pages/LoginPage.tsx— login (public, no auth). Layout: gradient bg + 2 blur blobs + centeredmax-w-mdcard (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): shelllg:grid-cols-[22rem_1fr] xl:grid-cols-[24rem_1fr]. CỘT TRÁI =<div flex flex-col gap-4>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). <lg → 1-col (tree→list→detail) + mobile tree-toggletreeOpenMobile(treehidden→flex). Org tree = recursiveTreeNodeconsumeGET /api/departments/tree(DepartmentTreeNode {id,code,name,parentId,directEmployeeCount,totalEmployeeCount,children}); gốc "SOLUTION COMPANY" (companyOpen) →pickDept(null)=all;CountBadge(totalEmployeeCount, active=brand-600 fill) →deptIdURL param. Detail = avatar header (.app-gradient-brand+ initials-in-rounded-2xl) + 5-tab nav (Tổng quan/Thân nhân/Trình độ/Kinh nghiệm/Hợp đồng) count pills + brand underline. Accent system (S66 việc 2+3):type Accent='brand'|'teal'|'violet'|'amberx'|'greenx'+ACCENTmap (chipBg/chipFg/head/rail/labelText).Cardnhậnaccentprop →.icon-chiptinted (--chip-bg/--chip-fginline) + headingtext-{x}-700+ left rail pseudobefore:content-[''] before:w-1 before:bg-{x}-500(clip qua overflow-hidden).Fieldlabel =text-{x}-700uppercase semibold (was slate-400), value =font-medium text-slate-900(was slate-800). OverviewTab: 1 accent/card (Thông tin chung=brand, Sức khoẻ/Lương=greenx, Liên hệ/Ngân hàng=teal, Giấy tờ/Đoàn thể=violet, Công việc=amberx). Tab-body sections: family=violet, đào tạo=teal, kỹ năng=greenx, công tác=amberx, hợp đồng=brand. ⚠️ accent palettes chỉ có stop 50/100/500/600/700 — KHÔNG -800 → head dùng -700 (else Tailwind v4 silent no-class). Reusable:Avatar(hash 5 gradients + dim),CountBadge,Card(+accent),Field(+accent/mono/icon/full). ALL 5 satellite CRUD + 15 satellite api endpoint (+ top-level del + 3 reads) + 3 query keys preserved verbatim (grep+tsc verified, layout/style-only). fe-admin NOT mirrored (separate pass).
Anti-slop catches + rubric verdicts
- LoginPage (S47): rubric PASS. Anti-generic ✓ (brand #1F7DC1 NOT default-blue, no emoji, lucide-ready, purposeful palette). Fix applied: subtitle "Đăng nhập để tiếp tục"
text-slate-500→text-slate-600(borderline ~4.6:1 over translucent card → solid ~7.5:1, FD5 contrast floor). 1-line, no layout shift, on-scale (FD1). Screenshots:/tmp/fd2-login-shots/login-{before,after}-{mobile-375,desktop-1440}.png.- ⚠️ fe-admin parity follow-up: same subtitle likely
text-slate-500in fe-admin LoginPage — apply same bump next fe-admin touch (did NOT touch fe-admin this run; scope-disciplined). - Minor noted (NOT fixed, out of bounded scope): 2
blur-3xlblobs barely visible at 1440 = render cost ~0 payoff; eyebrowtracking-[0.2em]heavy. Candidates if login redesign requested.
- ⚠️ fe-admin parity follow-up: same subtitle likely
Lessons / anti-patterns (value_protect — GIỮ L1 bất-kể tuổi)
- Anti-truncation #53 (atomic-Write): file lớn (>1000 LOC) = ONE atomic
Writecả-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 (网络Placeholderautocomplete) → dùng proven-safe export (Send), node-check icons trước build.
Activity log
- S159 (2026-07-29) ARC-2 "Tổng quan quy trình" ×2 lượt —
UserDashboardPage.tsxfe-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.md15.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.md13.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ùngselect: 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ằngnpm run buildPASS + 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 →selecttransform 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 (anh chốt, annotated screenshot):
pages/pe/PurchaseEvaluationsListPage.tsx. S78 cũ: bấm dòng = mở overlay full-bleed, 2 panel giữa/phải LÀ placeholder vĩnh viễn. S79 đổi: bấm dòng = inline 3-panel detail "như cũ" (8e68ed1) + thêm nút "Xem mở rộng" mỗi dòng → overlay. Mechanism = param thứ-2?expand=1cạnh?id: overlay render CHỈ khiid && expand; inline detail (panel giữa<PeDetailTabs readOnly onBack=closeDetail>+ panel phải<PeWorkflowPanel readOnly={!pendingMe} onApproved>) render khiid && !expand. Handlers:selectRow=set id+clear expand (NHƯNG<lg/innerWidth<1024 → set expand=1 đi thẳng overlay vì 3-panel không vừa) ·expandRow=id+expand=1 ·collapseFocus(Thu gọn/Esc/backdrop)=setParam('expand',null)GIỮ id (về inline, KHÔNG về list) ·closeDetail(← Đóng/del)=clear id+expand → list ·onApproved=clear cả hai → list (phiếu rời inbox). Overlay gate đổihasSelection→overlayActive=id&&expand; Esc gọi collapseFocus (không closeFocus). Nút "Xem mở rộng" =Maximize2(lucide, import THÊM) icon-btn 7×7 góc phải-trên mỗi leaf row,opacity-0 group-hover/row:opacity-100+opacity-100khi selected,title+aria-label+brand focus-ring; leaf<button>cũ bọc trong<div className="group/row relative">+pr-9chừa chỗ nút. ALL logic VERBATIM (grep ✓): 3 queryKey (approval-workflows-v2-filter/pe-list/pe-detail) · 3 endpoint (/inbox+paged+detail) ·delmutation (onSuccess giờ clear id+expand thaysetParam) ·yearGroups4-tầng tree memo ·PeUrgentChipspill · search/filter/SLA/localStorage-expand.PurchaseEvaluationDetailPageroute GIỮ. slide+a11y S78 giữ NGUYÊN (mount-rAF×2, role=dialog/aria-modal, focus-trap Tab-vòng, body scroll-lock, motion-reduce).npm run build(tsc -b strict + vite v8) PASS 0 TS err, 570ms (3 warning pre-existing). FD2: static layout-shell harness (backend :5443 down → authed dev-rig blocked gotcha#3) — Playwright 6 shot × 3 state: (a) inline 3-panel desktop1920+laptop1280 = list+PeDetailTabs giữa+PeWorkflowPanel phải, dòng-chọn có nút Maximize2; (b) overlay 1920+1280 = full-bleed phiếu+panel cạnh; (c) mobile390 list (1-col, nút mở-rộng) + mobile390 overlay (stack). Read tất 6 PNG — đúng hết, no overflow, pill+tree intact. Harness/driver/shots ĐÃ XÓA. Rubric PASS. fe-admin NOT mirrored (implementer-frontend SHA-mirror riêng), no BE, no commit. Tag [s79, pe-decouple-select-expand, expand-param, maximize2-row-btn, inline-restore-8e68ed1, lg-straight-overlay, collapse-keeps-id, logic-verbatim, build-pass, harness-fd2]. - (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.mdsubstring:"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.mdsubstring:"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.mdverbatim · maparchive/_INDEX.md· distillarchive/2026-06.gist.md. Curated @S96 first-overflow (L1 26129B > autoinject-cap 25600B, H17-loop strike-1).