Files
solution-erp/.claude/agent-memory/frontend-designer/MEMORY.md
2026-08-05 17:06:33 +07:00

19 KiB
Raw Blame History

frontend-designer — MEMORY (L1 HOT)

8th sub-agent (S47, 2026-06-02). Role: FE design/UX/aesthetic cho 2 app SOLUTION_ERP. Floor FD1FD10 (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 <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.
  • 🪲 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 /loginKHÔ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.tsx2-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 = <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-toggle treeOpenMobile (tree hiddenflex). Org tree = recursive TreeNode consume GET /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) → deptId URL 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' + ACCENT map (chipBg/chipFg/head/rail/labelText). Card nhận accent prop → .icon-chip tinted (--chip-bg/--chip-fg inline) + heading text-{x}-700 + left rail pseudo before:content-[''] before:w-1 before:bg-{x}-500 (clip qua overflow-hidden). Field label = text-{x}-700 uppercase 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-500text-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-500 in 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-3xl blobs barely visible at 1440 = render cost ~0 payoff; eyebrow tracking-[0.2em] heavy. Candidates if login redesign requested.

Lessons / anti-patterns (value_protect — GIỮ L1 bất-kể tuổi)

  • Anti-truncation #53 (atomic-Write): file lớn (>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

  • S174 (2026-08-05) AUDIT style KHKK(GĐ2) ⟂ Duyệt NCC(GĐ1) — ensemble 5 lane CÙNG 1 VAI qua /fable-clone, 5/5 xong 0 chết, 755K tok/147 tool-use/~17. (Entry on-behalf bởi lead @pause cổng C2 — VERIFY từ 5 sub-file trên đĩa + tự re-đo 2 claim nặng nhất.) Run wf_016b1bed-b85, artifact runs/2026-08-05-S174-khkk-style-audit/sub-frontend-designer-L1..L5.md (34+41+30+33+32 KB) + spec-khkk-style-dong-bo-05-08-2026.md. Lăng-kính: L1 panel-duyệt · L2 panel-list · L3 host · L4 cây · L5 nội-dung. Verdict: L1 15 lệch (13 bê-thẳng) · L2 CHƯA-PASS 13 delta + 3 trục rubric đỏ · L3 KHỚP-MỘT-PHẦN · L4 SHIP-SPEC-READY · L5 6 P0 + 8 P1 + 3 P2. 🔴 3 phát hiện owner KHÔNG nêu mà nặng hơn phần lớn danh sách owner nêu: (a) LỖI THẬT KhkkWorkflowPanel:501 tải file bằng <a href> trần ⇒ JWT chỉ gắn qua interceptor axios (lib/api.ts:18) ⇒ endpoint [Authorize] ăn 401, nút không chạy — lead verify bằng đối-chứng responseType:'blob' ở PE :331; (b) vỏ card-accent bọc panel KHKK ⟂ PE panel TRẦN = thứ làm 2 bên "nhìn khác hệ" TRƯỚC mọi mục owner kê; (c) <lg màn Thao tác <main hidden lg:block>mobile không tạo được phiếu — regression do CHÍNH LEAD gây ra cùng phiên. 🔴 BÀI LỚN NHẤT — lane BÁC 2 giả-định của lead, lead tự verify thì lane ĐÚNG: O3 hạng-mục N-dòng KHÔNG cần migration (ContractSigningPlanLineConfiguration.cs:37-38 unique index 3 CỘT (PlanId,SupplierId,CatalogEntryId) ⇒ 1 NCC đã được phép N dòng × N hạng mục từ Mig 71) · O4 shopdrawing FE-only 0-BE (rail dossierItemId đủ 6 tầng từ Mig 69). Lead đã báo owner "O3 nặng nhất cần migration" — SAI, đã đính chính. ⇒ Trước khi tuyên một việc là "nặng/cần migration", ĐỌC EF configuration + index; đừng suy từ hình-dạng UI. Đây đúng khuôn máy rộng, UI hẹp (owner S164): schema đã tổng-quát sẵn, chỉ UI đang khoá 1-1. 🔸 Chỗ CỐ Ý không bê: chip "NS PRO"/"NS CCM" (PeWorkflowPanel:457-463) — 2 cờ canEditProBudget/canEditCcmBudget mở quyền sửa ngân sách per-gói-thầu (PeWorkItemBudgets, Mig 50), khái niệm KHKK không có ⇒ bê sang = vẽ nhãn quyền không tồn tại. Và chỗ KHKK TỐT HƠN PE (mốc chữ-ký + in đậm người đã ký) ⇒ GIỮ, đừng hạ xuống cho "đồng bộ". 🔴 NO-SCREENSHOT ×5 lane, khai thẳng: dev-rig chặn (chỉ :8080 LISTENING, API :5443 không có, trang sau ProtectedRoute — gotcha #3) ⇒ mọi kết luận là đọc mã, KHÔNG phải đã nhìn. Thay bằng: đo class trên đĩa + sha256sum mirror + tự tính tương-phản WCAG (bắt được slate-400 = 2.56:1 cho 6 dòng chữ mang thông tin). Người land BẮT BUỘC soi 375/1366/1440 trên prod sau deploy. 🧪 Luật C2 (ép ghi khung rỗng lượt 1-2 TRƯỚC khi đọc) có RĂNG THẬT: run trước cùng khuôn 3/5 lane chết cháy-lượt; run này 0/5 chết. Cùng model, cùng harness, khác thứ tự ghi. Tag [s174, ensemble-5-lane-1-vai, fable-clone, audit-khkk-vs-pe, 401-a-href-khong-mang-jwt, card-accent-ngoai-layer-gotcha66, lane-bac-gia-dinh-lead, may-rong-ui-hep, no-screenshot-khai-that, c2-khung-rong-0-lane-chet].
  • 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; <details> 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 <li> 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 + <table> bọc overflow-x-auto + KPI lg:grid-cols-6xl:. 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 <slug> — 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).