14 KiB
Implementer-Frontend Agent — Persistent Memory
- S133 (07-17) PE budget-freeze FE types+badge [wf_d9639faa-97a INV-A, return sạch]: 4 file — types 2-app +
budgetFrozen: boolean(fe-user:333· fe-admin:331— 2 file KHÔNG mirror nhưng block PeBudgetSummary nội-dung identical → 1 old_string dùng chung cả 2); PeDetailTabs 2-app byte-identical: badge amber🔒 Ngân sách chốt tại thời điểm duyệt@ headerPeBudgetSummaryTable:1350-1359(divbg-[#1F7DC1]→ +flex justify-between;normal-case tracking-normalOVERRIDEuppercase tracking-widecủa header; non-frozen = 1 span trong flex → appearance giữ nguyên). Gotcha mới đáng nhớ: className header LẶP 2 chỗ (:1350budget +:1931winners) → Edit anchor PHẢI kèm text visible "Tổng hợp ngân sách trình ký" mới unique. Hash sau sửa5534addb×2 = mirror giữ; npm build ×2 PASS. Tag[s133, budgetfrozen-badge, anchor-by-visible-text]
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.
🆕 S117 (2026-07-14) — PE "duyệt theo KHOẢNG" radio-5 ProMinMax ×2 app (anh Kiệt FDC; BE landed contract). Build PASS ×2, SHA-pair Panel+DetailTabs
- Feature: ô "CHỌN GIÁ CHỐT" cấp cuối +radio-5 "PRO — cả Min và Max (duyệt theo khoảng)" (source=ProMinMax) CHỈ khi
proSuggestedMinPrice!=null&&proSuggestedMaxPrice!=null. Chọn=chốt dải [Min,Max]. GIỮ radio single-select. - types (DIVERGE, edit RIÊNG KHÔNG cp):
PeDetailBundle(L433) +approvedPriceMaxAmount:number|nullSAU approvedPriceSource. Anchor 3-dòng approvedPriceAmount/Source/canEditPro same-string 2 file, khác line (admin488/user490). - PeWorkflowPanel
011ee8cf: priceCandidates +amountMax?; ProMinMax entry qua...(hasBothProPrices?[{amount:min,amountMax:max}]:[])spread TRƯỚCas {...;amountMax?:number|null}[]cast; filter type-guardamountMax?:number(subtype OK).selectedCandidate1-find → selectedPriceAmount+selectedPriceAmountMax. Submit +approvedPriceMaxAmount:sendPrice?(selectedPriceAmountMax??undefined):undefined(undefined→axios bỏ key; nguồn đơn selectedMax=null→undefined; BE reject source-đơn-kèm-Max). Render span ternarymin đ – max đ. Gate priceMissing GIỮ (ProMinMax có amount=min). NO new state (max derived) → onSuccess reset KHÔNG đổi. - PeDetailTabs
617b0aa0: label-map +ProMinMax:'PRO Min–Max'; "Giá chốt duyệt" value ternaryapprovedPriceMaxAmount!=null? fmtVnd(amt) – fmtVnd(max) : fmtVnd(amt). - LESSON: conditional array entry = spread
...(cond?[obj]:[])TRƯỚCas T[]cast (né inference lỗi);??undefined=omit-key axios chuẩn cho "source đơn không kèm Max"; Max KHÔNG cần state riêng (derive từ selectedCandidate) → 0 reset-site. Build ×2 PASS (user1939modindex-DuAp5TWx17.8s / admin1950modindex-D6YhgGH-7.2s, 0 TS err). Warn pre-existing @import+>500KB+realtime. NO BE/test touch. 6 file (3/app) diff-stat symmetric. Full precedent, NO ambiguity. Tag[s117, pe-approve-by-range, prominmax-5th-radio, spread-conditional-before-as-cast, undefined-omit-axios-key, derived-max-no-state, sha256-2pair, type-diverge-edit-same]
🆕 S116 (2026-07-13) — PE cho nhập SỐ ÂM báo giá "Số tiền báo giá cho hạng mục" ×2 app (owner anh Kiệt; BE+DB nhận âm sẵn 0 validator). Build PASS ×2, SHA-pair
PeDetailTabs.tsx BYTE-IDENTICAL, cp fe-user→fe-admin. Âm = phát-sinh-giảm/hoàn tiền NCC = giá THẬT (KHÔNG lỗi). 5 change.
- Sign-toggle 2 dialog (AddSupplier :2379 + QuoteDialog :2948): nút +/− pattern VndInlineEdit:1003.
neg=useState RIÊNG (QuoteDialogsetForm({thanhTien})thay CẢ object → nuốt neg nếu nhét vào form). 🔴 2 dialog POST commit form.thanhTien THẲNG (:2247/:2906, no nút "Lưu") → toggle PHẢI ghi giá-CÓ-DẤU NGAYsetForm({...,thanhTien:-form.thanhTien}), chỉ flip cờ = MẤT DẤU thầm lặng. DisplayformatVndInput(Math.abs(tt)); onChangemag=parseVnd();tt=neg?-mag:mag. QuoteDialog initneg=(existing?.thanhTien??0)<0; AddSupplierfalse. - GLOBAL parseVnd/formatVndInput :51-52 KHÔNG sửa (share ngân sách) → Math.abs wrap thay vì cho helper handle âm. formatVndInput(0)='' → placeholder OK.
- Gate
!==0(KHÔNG >0): AddSupplier submit :2244 + toast :2257.-0!==0=false → 0/rỗng vẫn skip="chưa báo giá". - 2 FE gate ĐỔI (KHÔNG 1): missingForApproval :212 + submitChecklist :243 → helper mới
hasRealSelectedQuote(ev)=ev.details.some(d=>d.quotes.some(q=>q.isSelected&&q.thanhTien!==0)). GOTCHA quote ởdetails[].quotesKHÔNGsuppliers[].quotes(PeSupplier no quotes field). Cũ SUM winnerQuoteTotal: net-zero +100/-100=0 chặn oan. Mirror BE selectedThanhTien WHERE IsSelected. Label→"Đã nhập giá chào thầu". computeGiaChaoThau GIỮ (Section3 display :1921 — no unused). - Ngân sách :2861 GIỮ ≥0 (change 4 no toggle — không trong diff). SHA
3e16dba5…×2 (git-diff --no-index exit0). Build ×2 PASS (user 1939modindex-shUGyhuw19.0s / admin 1950modindex-Yk1qEFOg7.6s, 0 TS err). Warn pre-existing @import+>500KB+realtime. NO BE/test touch. Full precedent. Tag[s116, pe-negative-quote, sign-toggle-immediate-commit, math-abs-no-global-mod, hasRealSelectedQuote, details-not-suppliers-quotes, gate-not-equal-zero, sha256-2pair]
🆕 S113 (2026-07-12) — Supplier import v2 FE ×2 app (anh Kiệt owner; BE done ∥ impl-backend). Build PASS ×2
3 file×2app: 2 SHA-pair (dialog+page byte-identical) + types DIVERGED (Department.parentId only S65 → edit-identical KHÔNG cp).
- types/master: Supplier +
isPublic:boolean(anchor// Tình trạng+status; git-diff = CHỈparentId, isPublic ∉ diff = mirror khớp). - SupplierImportDialog
78403bd8…: +"Tải file mẫu" (Download icon, blobGET /suppliers/import/template→a.download='Mau-Database-NCC.xlsx', pattern PeDetailTabs:3273 useMutation) dưới drop-zone · cột "Mã"<td>{r.code}→<Input value={r.code??''} onChange updateRow(rowIndex,{code})>(updateRow=setPreview map-patch) · type +mstMissing:boolean→ amber "⚠️ Chưa có MST, có thể bị trùng" cột Thông báo · helper blue draft. 3.d disabled ĐÃ đúng SẴN (errorCount>0||pending, btn hidden khi !layoutValid — KHÔNG chặn blank-code) → NO change. - SuppliersPage
9947b121…: +cột "Trạng thái" badge emerald "Đã công bố"/slate "Nháp (ẩn)" full-class-literal(P14) · Công bố(Globe emerald)/Ẩn(EyeOff) action-btn guard Update → publish mutPOST /suppliers/{id}/publish {maNcc||undefined, publish}(KHÔNG qua UpdateSupplier #73) · publish Dialog sm (Mã NCC Input, gate!maNcc.trim()) · filter Selectpublished(''=omit/'true'/'false' → axios undef/true/false) queryKey+ · actions w-32→w-40 (3 icon). - NO 4-place mirror (dialog+page trên page ĐÃ routed → no route/menuKey/Layout). NO BE/test touch (src/Backend/** = impl-backend ∥, tôi Read-only DTO). Build: admin 1950mod
index-CWBEeKi5.js8.48s / user 1939modindex-JpoDnukv.js1.54s, 0 TS err; pre-existing warn @import+>500KB+realtime dynamic. - DEVIATION flagged: "Tải file mẫu" đặt CHỈ trong dialog (② canonical + ③ acceptance "ImportDialog nút mẫu"; ③ SuppliersPage= badge+Công bố+filter+guard, KHÔNG list nút mẫu) — parent 2.d loosely list page-btn nữa → 1-line ask nếu muốn thêm page-level template btn. Tag
[s113, supplier-publish-draft, isPublic, template-blob-download, ma-editable-grid, mst-missing-warn, publish-not-update, sha256-2pair, type-diverge-edit-identical].
📦 Archived detail → archive/2026-H1.md (read on demand)
- 2026-07-12 Supplier import Excel FE Phase B ×2 app byte-identical — NEW SupplierImportDialog + wire SuppliersPage ("Import Excel NCC"); multipart preview→confirm (echo rows verbatim); enum serialize INT; NO 4-place (dialog trên page routed).
[supplier-import-excel, enum-int-wire, byte-identical-2app] - S97-badge (07-01) PE chip "✅ Đã được CEO duyệt" khi
!endsBeforeCeo && phase=DaDuyet— PeFinalizeChip +propphase?:number(KHÔNG narrow-union → TS2322) wire 6 call-site. SHA-pairda9c3ed9.[s97-badge, prop-number-not-union] - S97-Part2 (07-01) PE gate endsBeforeCeo runtime hook-only PeWorkflowPanel (làm-mờ Bước CEO, Mig 60). SHA-pair
b7abbc2f.[s97-part2, endsbeforeceo-gate] - S97 (07-01) PE finalize wording + đảo default checkbox opt-out→opt-in — sửa CẢ init
useState(false)LẪN reset-onSuccess; bare-string 2 chỗ (comment+JSX) anchor-context. SHA-pairbcf812fd.[s97, invert-default-checkbox] - S88 D2 (06-25) PE multi-winner OUTWARD — CreateContractDialog
{contractIds:string[]}; PeListPanelwinnerSupplierNamesprefer-fallback-single. SHA-pair + type-diverge-edit-same.[s88, multi-winner-outward, cardinality-grep-consumers] - S87+ (06-25) FROZEN PE cleanup — HoSoLink clear
.trim()(""=CLEAR, BE null-safe); #70 stale-echo||peFetching2 ô PRO; type wording DIVERGE edit-same. SHA-pair36292924.[s87plus, hosolink-clear-empty, gotcha70-guard] - S79 (2026-06-19) — PE live thousand-sep (
formatVndInput(parseVnd())on-keystroke 2 comp) + ghi chú giá PRO/CCM (note3-field absolute-set) + typo "Bảng so sánh giá". SHA-pair.[s79, type-diverge-2app] - S78 (2026-06-19) — PE "focus mode" overlay redesign mirror fe-user→admin CONVERGE. PeWorkflowPanel +
onApprovedprop; ListPagefixed inset-0 z-50slide overlay a11y. SHA-pair.[s78, mirror-step, converge] - S76 (2026-06-19) — PE budget ma trận 3 cột (DỰ ÁN/PRO/CCM) →
<table border-collapse>grid + badge✎ NS PRO/CCM. LESSON: flex+gap ≠ bảng, phải<table>. cwd-mislandfe-*/.claude/gitignore-guard.[s76]
🎯 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 (FIFO — one-line hooks; full detail → archive/2026-H1.md)
- 2026-06-17 Văn phòng số "Bảng điều khiển" — mirror fe-user→admin 4 file (PageHeader/KpiCard/WidgetCard/OfficeDashboardPage) + 4-place wiring
/office/prefix route. SHA-5file.[office-dashboard, 4-place] - 2026-06-16 (S65) PE mục "e. Link hồ sơ" NAS hyperlink + rename "Dự trù PRO"→"Ngân sách PRO". SHA-mirror 4 file. #53 empty-return (render landed OK); em-main bắt sót badge rename.
[s65, hosolink] - 2026-06-16 Department parentId cây tổ chức, fe-admin ONLY (no SHA). Select "Phòng cha" + load-all pageSize:200 + exclude-self khi Edit + cột "Thuộc"; BE cycle-guard 409.
[dept-parent, admin-only] - 2026-06-11 (S57bis) PE WorkItem FE ×2 app (select "c. Hạng mục" + PeHeaderForm/PeDetailTabs +3 field). Truncated #53 GIỮA mirror → em-main solo. LESSON: per-app stage khi slice lớn.
[s57bis, truncated-53] - 2026-06-09 (S55) Project +4 optional master fields (year/investor/location/package). String-form+convert-on-submit (S42 pattern).
package=valid TS key. SHA-pair.[s55, master-enrich] - 2026-06-08 (S54) ItTicket reassign → CONVERGE 2 app (reverse S53). Pattern: BE capability-flag gate (
GET /it-tickets/assignable-staff {canReassign,staff}) thay FE đoán role. SHA4bcaf2f.[s54, capability-flag] - 2026-06-08 (S52) ItTicket admin reassign + AttendanceReport menu, fe-admin ONLY. Menu = 2/4-place (menuKeys+Layout staticMap; types/menu.ts key:string no-union).
[s52, admin-only, menu-2place]
⚠️ 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.