- BE: GetAssignableItStaffQuery {canReassign,staff} capability endpoint + AssignItTicketHandler authz Admin-OR-dept-IT (Forbidden) + assignee-must-IT (Conflict); controller /assign hạ [Authorize(Roles=Admin)]→[Authorize] (handler enforce fine-grained data-driven)
- FE: fe-admin + fe-user ItTicketsPage SHA256-identical (reverse S53 divergence), nút gate by canReassign, dropdown từ /assignable-staff (không /users)
- Test: +13 authz guard (203→216 PASS), reviewer PASS (role-string Admin chain-verified real)
- No migration (DepartmentId reuse), no menu change
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.9 KiB
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 RAGsearch_memoryjust-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.
🎯 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:
- Page/types file · 2.
App.tsxRoute · 3.lib/menuKeys.tsconst · 4. ⚠️components/Layout.tsxresolvePathstaticMap (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
erasableSyntaxOnlycấ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/actionsonly — KHÔNGicon/childrenprop (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-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 /users→GET /it-tickets/assignable-stafftrả{canReassign:bool, staff:[{id,fullName}]}([Authorize]any-auth, KHÔNG 403 → chống gotcha #44).canReassign = staffQ.data?.canReassign ?? false(fetch on mount, KHÔNGenabled) → 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 IDENTICAL4bcaf2f…(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/reportALREADY 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!==nulllazy 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. - 2026-06-08 (P11-E Wave 1 — AttendanceReportPage fe-admin ONLY): Report endpoint
[Authorize(Roles=Admin)]→ KHÔNG fe-user page → NO SHA256 mirror (intentional). 4 FE file: (1) types/workflowApps.ts +AttendanceReportRowDto{userId,fullName,departmentName?,daysPresent,totalWorkHours,otRaw,otWeekday,otWeekend,otHoliday,otWeighted}+AttendanceReportDto{year,month,rows,grandTotalWorkHours,grandTotalOtWeighted} (decimal→number) · (2) pages/office/AttendanceReportPage.tsx NEW: PageHeader+filter(Year Input number / Month Select 1-12 / Phòng ban Select fetch /departments) + TanStack key ['attendance-report',year,month,deptId] GET /attendances/report + Table 9 col STT/Họ tên/Phòng ban/Ngày công/Tổng giờ/OT thường/OT cuối tuần/OT lễ/OT quy đổi + tfoot Tổng(colSpan trick) + fmtNum vi-VN · (3) App.tsx import+route /attendance/report · (4) MyAttendancePage.tsx +button "Báo cáo" admin-only (user?.roles.includes('Admin')) navigate → DIVERGED fe-user (header comment cảnh báo). Download Excel:api.get(url,{params,responseType:'blob'})(api instance inject JWT interceptor + refresh-retry — CHUẨN HƠN raw fetch spec gợi ý; proven ReportsPage/FormsPage/PeDetailTabs) → blob → createObjectURL → anchor.download.click → revoke. Filename content-disposition regex, fallback BaoCao-ChamCong-{Y}-{MM}.xlsx. Build PASS (0 err, 1945 mod). KHÔNG menu key (button-reachable MVP). - 2026-06-08 (S51 P11-C — vehicles+drivers kind → HrmConfigsPage): Declarative KIND_CONFIG +2 entry (10th proof). 4-place adapt (
:kind-driven page → NO App.tsx route): types/hrm-config.ts (union +'vehicles'+'drivers' + VehicleDto/DriverDto + Create/Update inputs) · HrmConfigsPage.tsx (KIND_CONFIG +2, KINDS array +2, renderCells +2 branch before ot-policies fallback, import Car+IdCard) · menuKeys.ts (+Hrm_Config_Vehicles/Drivers — BE string exact) · Layout.tsx staticMap +2 BOTH app. Field keys: vehicles{code,name,licensePlate,seatCount,description} drivers{code,name,phoneNumber,licenseNumber,licenseClass,description}. cp admin→user 3 file SHA256 identical (page a3afd724, type 2c0775b3, menuKeys d650c086). Layout mirror tay (structural diff OK, 2 entry verified both). Build PASS ×2 (admin 1944mod, user 1934mod, 0 TS err). lucide IdCard EXISTS (no UserRound fallback). AMBIGUITY: BE catalog vehicles/drivers chưa tồn tại on-disk (Wave 1 parallel — implementer-backend đang/sẽ làm) → FE scaffold theo contract spec cấp; runtime cần BE/hrm-configs/vehicles+/driversendpoint + Hrm_Config_Vehicles/Drivers const trong BE MenuKeys.cs + seed. - 2026-05-30 (S42 P11-B Wave 2 — leave balance display): WorkflowAppDetailPage.tsx + workflowApps.ts (2 app SHA256 identical). +3 optional
leaveBalance{Entitled,Used,Remaining}?: number|nulltrong// leaveblock (BEdecimal?→ camelCase). Block "Số dư phép" sau Section 1 IIFEkind==='leave' && d.leaveBalanceRemaining != null: year từ StartDate, banner amber/red khiremaining<0 || (status!==DaDuyet && remaining<numDays). Case 1, KHÔNG 4-place (enrich existing page). cp fe-admin→fe-user. Build PASS ×2 (page 8ef83e4b, type 1c4f167a). Lesson reuse: IIFE inline(() => {...})()cho conditional block có derived vars — sạch hơn tách helper.
⚠️ Anti-patterns (DO NOT)
- ❌ 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.