Files
solution-erp/.claude/agent-memory/implementer-frontend/MEMORY.md
pqhuy1987 8f780b6237 [CLAUDE] Docs: S76 closeout — PE ngan sach ma tran 3 cot + bang luoi + badge quyen-NS
STATUS/HANDOFF (Mig 55->56, test 339->344, gotcha 69->70, bundle jOqxW4-p/DbsznVvR
Run #319, Phase +S76, In Progress->Recently Done) + gotcha #70 (FE absolute-set echo
stale-echo data-loss -> useIsFetching gate) + ef-core skill Mig 56 row + session log
2026-06-19-S76 + agent-memory harvest (impl-FE stray->canonical + 4 sub diary).
Curate-debt carry: reviewer 45KB + inv-codebase 35KB keep-floor-hit manual-condense.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:44:34 +07:00

14 KiB
Raw Blame History

Implementer-Frontend Agent — Persistent Memory

Persistent diary cross-session. Auto-injected first ~200 lines at spawn (L1 HOT). Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 archive/ on-demand · L3 RAG search_memory just-in-time. Keep entry ≤ 1.5K chars (gotcha #53). NEW agent S39 (2026-05-29) — split từ implementer (FE 2 app half). Backend scaffold history ở implementer-backend.


🆕 S76 (2026-06-19) — PE budget MA TRẬN 3 cột + bảng lưới <table> + badge quyền NS (anh Kiệt FDC)

  • Part 1 mirror (fe-user→fe-admin byte-identical, Python difflib slice-region): PeBudgetSummaryTable Block A → ma trận 3 cột (DỰ ÁN hiển-thị-only / PRO canEditPro / CCM canEditCcm). Type PeBudgetSummary +proInitialAmount+proAdjustmentAmount cạnh proEstimateAmount(LEGACY). proMut body {proInitialAmount,proAdjustmentAmount,proNote}. proFull=proInit+proAdj.
  • Part 3 mirror: PeWorkflowPanel approver .join(' / ') → map từng approver + badge ✎ NS PRO(amber)/✎ NS CCM(sky) gate a.canEditProBudget/canEditCcmBudget. Type PeCurrentApprovalLevelApprover +2 cờ CẢ 2 app (purchaseEvaluation.ts types DIFFER giữa 2 app → sửa cả 2; PeDetailTabs/PeWorkflowPanel byte-identical).
  • Bảng lưới (em-main, anh phản hồi "chưa chia cột giống Excel"): Block A flex→<table border-collapse> viền ô. BudgetCell xếp dọc (input full ô + Lưu dưới). BudgetNoteRowBudgetNoteCell (td colSpan=3). LESSON: flex+gap KHÔNG ra "bảng" — phải <table> viền ô mới giống spreadsheet.
  • cwd-misland: Part-1 cd fe-admin npm build → MEMORY rơi fe-user/.claude stray → em-main harvest về + .gitignore guard fe-*/.claude/ (S76).

🆕 S73b (2026-06-18) — PE budget "Ghi chú từ CCM" mirror ×2 app (anh Kiệt FDC)

Thêm dòng "Ghi chú từ CCM" trong panel TỔNG HỢP NGÂN SÁCH TRÌNH KÝ (PeBudgetSummaryTable), mirror y hệt "Ghi chú từ PRO" nhưng gate canEditCcm + ccmNoteText/setCcmNoteText + save qua ccmMut. 2 file ×2 app: types/purchaseEvaluation.ts (PeBudgetSummary +ccmNote:string|null sau adjustmentAmount) + PeDetailTabs.tsx (state mirror proNoteText + ccmMut body type +ccmNote + 2 call-site echo ccmNote:bs.ccmNote Ban hành+Hiệu chỉnh — endpoint /budget/ccm ABSOLUTE-SET cả 3 field, thiếu=null=CLEAR + block mới chèn SAU dòng V0/hiệu chỉnh TRƯỚC Ngân sách PRO, group CCM rows). LESSON xác nhận S73: budget-table region 2 app byte-IDENTICAL → block mới mirror identical (diff awk-PeBudgetSummary + diff sed-region đều IDENTICAL). PeBudgetSummary type block 2 app identical (KHÁC PeDetailBundle Color-records divergence) → an toàn edit cùng content. Build PASS ×2 (admin 1945mod index-CCPIU9Wr.js / user 1934mod index-j5Zh9w96.js, 0 TS err). NO ambiguity, full precedent. KHÔNG đụng BE (DTO ccmNote + endpoint body = em-main song song).


🎯 Role baseline

WRITE specialist FE 2 app (fe-admin + fe-user). Cookie-cutter mirror SHA256 IDENTICAL + Pattern 16-bis 4-place + declarative KIND_CONFIG + npm build × 2. Case 1+2 only. Tools: Read, Edit, Write, Bash, Skill, Grep, Glob + 5 RAG. Skill: permission-matrix.

🚫 Split boundary

  • MINE: fe-admin/src/** + fe-user/src/**
  • NOT: src/Backend/**implementer-backend (chỉ Read DTO shape) · tests/ → test-specialist
  • NOT: UX flow decision (drawer/tab/modal) → em main solo

📋 Patterns proven (apply confidently)

Pattern 16-bis 4-place mirror (9× cumulative — BLESSED FOUNDATION)

Add/move page cross-app MUST mirror 4 places:

  1. Page/types file · 2. App.tsx Route · 3. lib/menuKeys.ts const · 4. ⚠️ components/Layout.tsx resolvePath staticMap (DỄ MISS → silent sidebar drop gotcha #50) Verified clean S33/S34/S35/S36/S37/S38 = 9× cumulative. Spec MUST list 4 places explicit.

SHA256 IDENTICAL × 2 app

Viết fe-admin → cp fe-user → sha256sum verify. Khác UX (admin full sidebar vs user filter) → mirror tay + diff. Proven S36 (3 pair) + S37 (4 pair) + S38 (5 pair).

Declarative KIND_CONFIG Record (S35, 3× proven)

Single-page multi-kind CRUD URL :kind param + Record<Kind, {fields, columns, icon, label}> + renderField switch FieldType. Reuse: HrmConfigs (S35) + WorkflowApps (S38) + HrmConfigs +2 kind (S51). ADD-KIND playbook: union type + KIND_CONFIG entry + KINDS array + renderCells branch (before fallback) + import icon — :kind-driven page = NO new App.tsx route.

Pattern 14 Tailwind JIT palette

Dynamic class purged. PALETTE array full literal as const cycle index % length.

TS6 / convention

  • erasableSyntaxOnly cấm enum → const X = {...} as const + type X = typeof X[keyof typeof X]
  • Named export only (trừ App). UI 100% tiếng Việt.
  • PageHeader signature (S37): title/description/actions only — KHÔNG icon / children prop (build fail TS2322)
  • fe-user thiếu Card/Badge shadcn → fallback inline <div className="rounded-lg border bg-card">

Verify protocol

cd fe-admin && npm run build + cd fe-user && npm run build BOTH 0 TS error + sha256sum mirror proof. Bundle >500KB warning OK pre-existing.

📅 Recent activity (last 10 FIFO)

  • 2026-06-17 (Văn phòng số "Bảng điều khiển" — mirror fe-user→fe-admin + 4-place wiring ×2 app): Mechanical mirror+wire step (frontend-designer built in fe-user, em main chốt UX). (A) index.css SYNC prereq: fe-admin STALE pre-S66 → cp fe-user/src/index.css fe-admin/ (no admin-only class to merge — utility sets identical, chỉ S66 gotcha-66 diffs: h1-h4 #0b1220/wt700 + .label-eyebrow brand-600 + accent palette comments) → SHA256 e8631471… identical. (B) Mirror 4 file cp user→admin SHA256 IDENTICAL: ui/PageHeader 6ff5303f…(Văn-phòng-số richer header — eyebrow/icon/accent/breadcrumb, NOT @/components/PageHeader constrained one), ui/KpiCard f8042ade…(clickable filter chip a11y role=button), ui/WidgetCard 9221cbed…(gotcha66 gradient header text-white!), pages/office/OfficeDashboardPage c6d9dc08…(composes 3 ui over EXISTING hooks, NO new API). All @/ imports + Button/api/cn + types/{proposal,workflowApps,meeting} đã có ở fe-admin → 0 import fix. (C) 4-place ×2 app: page(mirror) + App.tsx import+<Route path="/office/dashboard"> (NEW /office/ prefix convention — page file ở pages/office/, không xung đột; flat office routes giữ nguyên) + menuKeys OffDashboard:'Off_Dashboard' (mirror BE MenuKeys.OffDashboard, đặt sau Off trước OffDanhBa đúng thứ tự BE) + Layout staticMap Off_Dashboard:'/office/dashboard' (4th place gotcha#50, trước Off_DanhBa). Leaf admin auto via BE All[]. Build PASS ×2 (user 1934mod index-BYj_ew5Q.js, admin 1945mod index-Cn1flmn6.js, 0 TS err). CSS @import-order warning + >500KB chunk = pre-existing. NO ambiguity, full precedent (Pattern 16-bis 4-place + SHA256 mirror). Tag [office-dashboard, mirror-step, 4-place, sha256-5file, office-route-prefix].
  • 2026-06-16 (S65 PE mục E "Link hồ sơ" FE ×2 app — em-main PROXY, PE-Workflow FE-stage died-empty #53): Thêm mục "e. Link hồ sơ" hyperlink NAS dưới mục "d. Bản so sánh" + rename "Dự trù PRO"→"Ngân sách PRO". 4-file ×{user,admin} SHA256-mirror: PeDetailTabs.tsx (HoSoLinkRow :1353/1386 — useState(ev.hoSoLink), PUT echo required+hoSoLink, readOnly→<a target=_blank rel=noopener noreferrer> null-safe) + PeWorkspaceCreateView.tsx + types/purchaseEvaluation.ts (+hoSoLink). Ship Run #293 PASS, bundle both-rotate, GET phiếu thật "hoSoLink":null backward-compat ✓. SURPRISE: render landed on disk despite empty-return — work COMPLETE, chỉ MEMORY-update bị cắt (#53, lần này trong Workflow fan-out); em main self-gate bắt badge "DỰ TRÙ PRO" sót rename (agent chỉ đổi row label 1120/1126, sót badge 1078) → vá nốt ×2 app. Tag [s65, pe-section-e-link-FE, hosolink-row, em-main-proxy-truncated-53, workflow-fanout].
  • 2026-06-16 (Department parentId — cây tổ chức, fe-admin ONLY): Case 1 master-data enrich (NO 4-place, NO menu/route/Layout — chỉ Place-1 page+type). BE đã sẵn (local 0f44d97): DepartmentDto.parentId:Guid? + POST/PUT nhận parentId? + cycle-guard 409 ConflictException. fe-admin ONLY (intentional — KHÔNG fe-user, KHÔNG SHA256). 2 file: (1) types/master.ts Department +parentId:string|null (sau name, trước managerUserId) — DepartmentInput auto-inherit via Omit · (2) DepartmentsPage.tsx: import Select + FormState/emptyForm +parentId:string ('' khi rỗng) + load-all query ['departments-all'] pageSize:200 (reuse pattern proven UsersPage/Workflows/AttendanceReport) + deptNameById Map từ allDepts cho cột "Thuộc" + mutate payload parentId:d.parentId||null + openEdit parentId:d.parentId??'' + Dialog <Select> "Phòng cha (Thuộc khối/phòng)" sau Tên trước Ghi chú: option đầu value="" "— Không có (cấp gốc) —" + .filter(d=>d.id!==form.id) exclude-self khi Edit (chống tự-làm-cha; cycle sâu BE guard 409) + table column "Thuộc" giữa name↔note (d.parentId?deptNameById.get()??'—':'—'). Select = native <select> passthrough, value='' ↔ null sentinel (proven UsersPage departmentId). Build PASS (1941 mod, 0 TS err — tsc -b clean trước vite). NO ambiguity, full precedent (S55 enrich + UsersPage Select).
  • 2026-06-11 (S57bis PE WorkItem FE ×2 app — PARTIAL, on-behalf em main ghi hộ, H2-proposed): Task = PeWorkspaceCreateView select "c. Hạng mục công việc *" sau Dự án + PeHeaderForm (select + load existing + PUT/POST workItemId) + PeDetailTabs (subtitle "Dự án Hạng mục" + FormRow + inline-edit khóa) + types +3 field. Return-truncated #53 GIỮA mirror fe-admin → em main solo mirror 7 edits PeHeaderForm + 3 edits PeDetailTabs ×2 app; PeHeaderForm SHA256 IDENTICAL. LEARNED: mirror đo bằng SHA256 (không diff mắt); option label [Category] Code — Name + canSubmit require; route reuse /catalogs/work-items (KHÔNG endpoint mới). SURPRISE: điểm gãy lặp tại mirror-2-app-trong-1-spawn → cân nhắc per-app stage khi slice lớn. Tag [s57bis, truncated-53, sha256-mirror, on-behalf].
  • 2026-06-09 (S55 HMW P2 — Project +4 optional master fields): Case 1 master-data enrich (NO 4-place, NO menu/route/Layout — chỉ Place-1 page+type). BE adds 4 nullable Project fields parallel (implementer-backend). 2 file × 2 app: (1) types/master.ts Project +year:number|null+investor/location/package:string|null (sau note) +ProjectInput auto-inherit via Omit · (2) ProjectsPage.tsx (single-Dialog CRUD, NO separate pages): FormState +4 string (form dùng string, convert on submit) + emptyForm +4 '' + mutate payload year:d.year?Number():null, investor/location/package:d.x||null + openEdit year:p.year?.toString()??'', x:p.x??'' + Dialog 4 Input sau "Ngày kết thúc" trước "Ghi chú" (Năm type=number, Chủ đầu tư, Địa điểm col-span-2, Gói thầu col-span-2) + table column "Chủ đầu tư" (p.investor??'—') giữa name↔startDate. package = valid TS object KEY (reserved chỉ khi binding-identifier) → form.package/{...form,package:x} build sạch, KHÔNG cần rename. cp admin→user SHA256 IDENTICAL: master.ts 93ac1b0f…, ProjectsPage b002061…. Build PASS ×2 (admin 1945mod, user 1934mod, 0 TS err — tsc -b clean trước vite). Reuse S42 enrich-pattern (string-form + convert-on-submit). NO ambiguity, full precedent.
  • 2026-06-08 (S54 ItTicket reassign → CONVERGE 2 app, REVERSE S53 divergence) [harvested by em main — agent MEMORY write mis-landed, B2/B3]: S53 đã tách fe-admin-only (admin reassign). S54 cho tổ IT tự reassign → cả 2 app cần nút. Pattern mới: BE capability-flag gate thay vì FE đoán role. Dropdown đổi GET /usersGET /it-tickets/assignable-staff trả {canReassign:bool, staff:[{id,fullName}]} ([Authorize] any-auth, KHÔNG 403 → chống gotcha #44). canReassign = staffQ.data?.canReassign ?? false (fetch on mount, KHÔNG enabled) → nút Pencil bọc {canReassign && …}. types/workflowApps.ts +AssignableStaff+AssignableStaffResult (mirror cả 2). fe-admin rewrite (bỏ UserOption/Paged) + fe-user full-add → SHA256 IDENTICAL 4bcaf2f… (viết admin canonical → cp → verify; cùng @/... import + shadcn Dialog/Select/Button identical 2 app). Build PASS ×2 (0 TS err). Gotcha (pre-existing, NOT từ change này): types/workflowApps.ts 2 app NOT SHA-identical — fe-admin có AttendanceReportDto (P11-E) mà fe-user thiếu; 2 type S54 mirror đúng cả 2. Lesson: BE-computed capability flag = single-source → 2 app converge lại sau intentional divergence.
  • 2026-06-08 (S52 Task C+D-FE — ItTicket admin reassign + AttendanceReport menu, fe-admin ONLY): Both intentional mirror-break (admin-only, NO fe-user touch, NO SHA256). Task D-FE menu wiring: Page+App.tsx route /attendance/report ALREADY exist (S52 prior). Only 2 of 4-place needed: (1) menuKeys.ts +OffAttendanceReport='Off_AttendanceReport' (mirror BE string exact, after OffChamCong) · (2) Layout.tsx staticMap +Off_AttendanceReport:'/attendance/report' (4th place gotcha #50). types/menu.ts = MenuNode tree type, key:string NOT typed-union → NO mirror there (resolvePath(key:string)). Leaf perm-gated via BE All[]→admin auto. Task C reassign: ItTicketsPage.tsx top-comment updated DIVERGES fe-user. Per-card Pencil button (cạnh 👤 assignee) → Dialog (size sm) + Select user. Users source = GET /users {params:{page:1,pageSize:200}}→{items:UserOption{id,fullName,email}} (reuse, proven PeWorkflows/Workflows/MeetingCalendar — enabled:target!==null lazy fetch). useMutation api.put(/it-tickets/${id}/assign,{assignedToUserId})→204 (NO json read)→invalidate['it-tickets']+toast.success+close. preselect t.assignedToUserId. UI deps: Dialog(open/onClose/title/children/footer?/size) + Select(native passthrough) + Button(variant=outline) + toast(sonner) + getErrorMessage(@/lib/apiError). Build PASS (0 err, 1945 mod). git: only 3 fe-admin file, fe-user untouched.

⚠️ Anti-patterns (DO NOT)

  1. Touch BE files · 2. Miss 4th Layout staticMap · 3. Skip npm build × 2 · 4. git add -A · 5. Push remote · 6. UX decision autonomous → REFUSE

🔄 Curate trigger

Size > ~30KB → archive to L2 (tiered v1). Commit scope (em main commits): FE-Admin · FE-User.