Files
solution-erp/.claude/agent-memory/implementer-frontend/MEMORY.md
2026-08-01 09:21:16 +07:00

24 KiB
Raw Blame History

Implementer-Frontend Agent — Persistent Memory

  • S166 (08-01) K4a-FE regex 42 leaf KHKK + single-source nhãn nhóm [artifact runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k4a.md]: 🔴 Khuôn S164 "đo đĩa TRƯỚC khi gõ" ăn tiếp 2 lần trong CÙNG 1 đề bài — cả 2 tiền-đề lead đều sai theo hướng "tưởng chưa có / tưởng có": ① đề bài bảo export KHKK_GROUP_LABELS mới, đĩa đã có KHKK_APPROVAL_GROUP_LABELS (types/khkk.ts:129, K2 land, 5 call-site) ⇒ đẻ map-2 là phá đúng acceptance-5 (single-source) mà nó phục vụ; K2 còn để sẵn uỷ-quyền trong comment :125-127. Đo thêm: nhãn BE ≡ `${n}. ${short}` cho 8/8 ⇒ giải bằng DERIVE khkkGroupMenuLabel(n), KHÔNG hardcode lần 2, 0 call-site cũ bị đụng (hardcode lại = double-numbering "N2 - 2. …" ở select). ② comment vá-8 bảo "gỡ KeHoachKyKet khỏi ẩn-admin ⇒ phải nới regex trước", nhưng isAdminHidden:194 = key.startsWith('Ct_') — KHKK chưa bao giờ bị ẩn ⇒ ràng buộc ĐANG hiệu lực chứ không phải tương lai; viết comment theo ĐĨA + FLAG "7 nhóm rỗng" (MenuGroup chỉ xét children.length>0, không xét con resolve được ⇒ header hiện, thân rỗng). BÀI MỚI đáng giữ: chứng regex bằng new Function() chạy CHÍNH khối text đã ship (indexOf('const khkkMatch') → slice → eval), key sinh từ KhkkGroupNumbers/KhkkLeafActions đọc thẳng MenuKeys.cs ⇒ 42/42 resolve, 0 drop, +3 control-âm (G9→null · action bogus→null · Khkk_List nhóm-1-không-infix→null vì chặn ở staticMap trước). Nhãn cũng kèm control-âm (chèn 'x' vào N4 → phép so FLAG) — 0-hit trần vô nghĩa. Ghi chú khuôn: Khkk_G1 + 6 leaf KHÔNG-infix (S159, cấm đổi = mồ côi permission-row), 42 leaf infix chỉ là nhóm 2..8; giữ [1-8] theo spec = rộng hơn tập seed 1 nấc, cố ý. group/filter/viewTRANSIENT_QUERY_KEYS:311 ⇒ active-state phân biệt (bài view=approved S155). SHA-pair types eba649cc7a99b865 ×2 IDENTICAL; Layout 2 app cố ý KHÔNG mirror (user +34 regex · admin +13 comment-only, 0 dòng xoá). Build ×2 PASS (user 1950mod index-B2v0kn5F 20.8s · admin 1961mod index-84zDBHE- 8.2s). 0 .cs (MenuKeys/DbInitializer M = lane BE), 0 chạm KhkkListPage/CreatePage/WorkflowPanel/DetailPage. 3 FLAG treo (tên const · ?group= chưa có consumer → K4b quyết "vắng group = nhóm 1 hay tất cả" · 7 nhóm rỗng admin). Tag [s166, khkk-k4a-regex-42-leaf, tien-de-lead-sai-x2, derive-khong-hardcode-lan-2, new-function-chay-text-da-ship, control-am-bat-buoc, nhom1-khong-infix, layout-co-y-khong-mirror]

  • S164/S165 (07-31) K2-FE KHKK nhóm-duyệt N1N8 [relaunch p2 sau p1 chết #53 — chi tiết runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k2.md §1-§5]: 🔴 BÀI CHÍNH — tiền-đề lead SAI, đo git diff TRƯỚC khi gõ: lead khai CreatePage "mid-wire, 8 khai báo chưa vào JSX" (TS6133 sẽ đỏ) ⇒ đo đĩa thật thì 8/8 ĐÃ có call-site; Detail cũng land đủ. Việc thật ≈ 1/4 mô tả — chỉ ListPage dở (p1 kịp thêm mỗi dòng import). Khuôn S114 "WRITE-lane garble ≠ mất code" xác nhận lần nữa: lead đọc transcript dở-dang → kết luận theo Ý ĐỊNH đang chạy, không theo đĩa. Viết lại phần đã đúng = phá. ⇒ p2 chỉ sửa ListPage 3 điểm: badge N{g} NHÉT VÀO ô "Mã kế hoạch" (không đẻ cột — bảng đã cuộn ngang @1366 do cây GĐ 19rem, ghi chú S162 nằm sẵn trong file) · +cột "Tổng duyệt" ΣtotalApprovedAmount (vá-8 spec, BE trả 0 cho phiếu chưa duyệt ⇒ ép kẻo đọc nhầm "đã duyệt 0 đ") · colSpan 7→8 hai chỗ (loading + empty row — quên = lỗi CÂM tới lúc bảng rỗng). npm run build = tsc -b && vite build + noUnusedLocals:true ⇒ build LÀ phép thử TS6133 thật, không cần tin IDE (#68). cp 4 file → SHA-pair 5/5 IDENTICAL (Panel không sửa = đối chứng dương, hash 2B64144EF6E8 BẰNG baseline ⇒ chứng phép đo đúng thứ). diff-stat đối xứng 269+/36 ×2 app. 0 .cs (13 .cs M/?? là lane BE), 0 route/menu (K4a). Tag [s164, khkk-nhom-duyet, do-dia-truoc-khi-go, colspan-loi-cam, badge-trong-o-khong-de-cot, panel-doi-chung-duong]

  • S161 (07-29) ARC W1+W2+W3đ1 KHKK FE — 3 lượt, chi tiết = sub-md runs/2026-07-29-S161-khkk-*/ (mọi lượt #53, đĩa cứu trọn): W1 Designer type-10: containment 2-LỚP render+payload (levelFlagsPayload(e, lock) ép false 2/2 call-site — chỉ disabled = containment GIẢ vì copyFromDefinition tuồn state); 2 tiền-đề đề-bài SAI tự bẻ (FE không có bảng nhãn — nhãn từ BE applicableTypeLabel; khuôn type-9 không tồn tại → mirror Contract:3). W2 3 page + types ×2 SHA-pair; types-theo-ENTITY khi BE song song; header types = HỢP-ĐỒNG 12-endpoint (reviewer đối chiếu ra 6 đứt → lead vá; bài: hợp-đồng phải LEAD-định-nghĩa literal); WfView 2-app-2-đường (user matrix?type=10 whitelist+10 · admin Designer deep-link); place-3 menuKeys SKIP-có-khai (0 consumer). W3đ1 KhkkWorkflowPanel mirror PE-panel 1063-dòng theo bảng GIỮ/BỎ tường minh (BỎ đủ 2 đường cấm + price-picker/budget/4-radio); cây workflow THÔ — FE tự suy ✓/●/○ từ con-trỏ đôi (StepIndex 0-based vs Level.Order giá-trị); bổ approverUserId opinion (W2 khai thiếu). Tag [s161, khkk-fe-3-wave, containment-2-lop, types-theo-entity, giu-bo-tuong-minh, fe-suy-status-con-tro]

  • S153 (07-26) arc ctx-adopt T2 + T5 (2 lượt hmw) [lead seed on-behalf @closeout M9; T2 return mất schema-fail (#53 +1) → derive-from-body, template VẪN LAND 8.819B; T5 return CLEAN]: T2 template .claude/templates/session-mind-template.md — 3 marker (RULES-START/END · MIND-TOP) · 0 dòng key ts: (ts sống trong HEADING sau em-dash ⇒ không phạm rào-2) · 6 hit rào- (3 rào × tên+lý-do) · chiều chèn NGƯỢC _context khai TO trong khối luật · placeholder <k> trong schema-comment chống parser false-match (mirror trick template _context:32) · kiểm _SECRET_RES để ví-dụ rào-3 không tự khớp mẫu (secrets-sweep rglob TRỌN session-dir, KHÔNG biết enclosure). T5 hmw.js 3/3 acceptance: VALID_ROLES 20→21 (sau T10/T11 = 23) + 🔴 writeGuard là 2 ĐIỂM không phải 1:228 JS-comment doc :235 template-literal ĐI VÀO PROMPT sub; chỉ sửa :235 ⇒ doc lệch trong CÙNG file, chỉ sửa :228prompt runtime KHÔNG đổi ⇒ vai read-only bị hướng-dẫn như write-sub ⇒ Bash-write MD ⇒ mojibake G-009. :235 = điểm có RĂNG. Vùng CẤM đo bằng diff: WIDTH_CAP 0 hit · TIER2_EXPECTED_FULL_ID 0 hit. 2 kẽ báo lead:naming-collision cùng-role 2-invocation (index i reset mỗi invocation ⇒ đợt-2 suýt đè sub-implementer-frontend-1.md của đợt-1; tự né bằng -2, verify đợt-1 nguyên 2020B) — kẽ CÒN LẠI sau S134 (S134 chỉ vá ca 2-role-cùng-invocation) ② node --check file thô luôn SyntaxError (top-level await) ⇒ acceptance phải là wrapped-parse + đối-chứng HEAD, không phải check thô. Tag [s153, t2-template-0-ts-key, t5-writeguard-2-diem-235-co-rang, naming-collision-cung-role-2-invocation, node-check-tho-vo-nghia]

  • S134 (07-17) PE lũy kế TẠM TÍNH FE 2-app [wf_58302bd5-b9f INV-2.B · return sạch 0-garble] [engine: opus-worker]: types ×2 +5 field pending* (DIVERGE — edit riêng từng app cùng old_string sau budgetFrozen; grep 1/1) · PeDetailTabs 5534addbd28eaa16 ×2 IDENTICAL: BudgetSharedNumCell:1184+BudgetColValue:1197 +sub?: ReactNode prop → sub-dòng amber "Tạm tính" row1/2 (withDetail "+X từ N phiếu chưa duyệt", N8 count-TƯƠNG-ỨNG delta KHÔNG list.length) + row5/6 (gọn) + block liệt kê pendingPriorPes dưới matrix (plain+Badge PurchaseEvaluationPhaseLabel, N9 KHÔNG link — 403 S89) + gate showPending = !bs.budgetFrozen && (...) ?? -guard data cũ · PeWorkflowPanel a83a58e1bebc0ada ×2: banner M2-gate !readOnly && phase === PurchaseEvaluationPhase.ChoDuyet (enum-constant) + reminder trong Dialog isApproveAction TRÊN price-picker; disabled-condition 0-diff (B3 grep). npm ×2 PASS (user index-BVoJX7QA 20.9s · admin index-mcHk0pmo 9.3s). Learned: (1) sub?: ReactNode slot-prop trên cell-component dùng chung = chèn sub-dòng vào N render-site không nhân bản markup; (2) hmw đánh index sub-file GLOBAL theo taskList (backend=0, frontend=1 → sub-implementer-frontend-1.md) — hết collision S133 khi 2 role KHÁC nhau cùng invocation. Tag [s134, pe-luyke-tam-tinh, sub-slot-prop, n8-count-per-delta, global-tasklist-index]

  • 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 @ header PeBudgetSummaryTable :1350-1359 (div bg-[#1F7DC1] → +flex justify-between; normal-case tracking-normal OVERRIDE uppercase tracking-wide của header; non-frozen = 1 span trong flex → appearance giữ nguyên). Gotcha mới đáng nhớ: className header LẶP 2 chỗ (:1350 budget + :1931 winners) → 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ửa 5534addb ×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 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.


🆕 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|null SAU 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ƯỚC as {...;amountMax?:number|null}[] cast; filter type-guard amountMax?:number (subtype OK). selectedCandidate 1-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 ternary min đ max đ. Gate priceMissing GIỮ (ProMinMax có amount=min). NO new state (max derived) → onSuccess reset KHÔNG đổi.
  • PeDetailTabs 617b0aa0: label-map +ProMinMax:'PRO MinMax'; "Giá chốt duyệt" value ternary approvedPriceMaxAmount!=null? fmtVnd(amt) fmtVnd(max) : fmtVnd(amt).
  • LESSON: conditional array entry = spread ...(cond?[obj]:[]) TRƯỚC as 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 (user1939mod index-DuAp5TWx 17.8s / admin1950mod index-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 (QuoteDialog setForm({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 NGAY setForm({...,thanhTien:-form.thanhTien}), chỉ flip cờ = MẤT DẤU thầm lặng. Display formatVndInput(Math.abs(tt)); onChange mag=parseVnd();tt=neg?-mag:mag. QuoteDialog init neg=(existing?.thanhTien??0)<0; AddSupplier false.
  • 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[].quotes KHÔNG suppliers[].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 1939mod index-shUGyhuw 19.0s / admin 1950mod index-Yk1qEFOg 7.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, blob GET /suppliers/import/templatea.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 mut POST /suppliers/{id}/publish {maNcc||undefined, publish} (KHÔNG qua UpdateSupplier #73) · publish Dialog sm (Mã NCC Input, gate !maNcc.trim()) · filter Select published(''=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.js 8.48s / user 1939mod index-JpoDnukv.js 1.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 +prop phase?:number (KHÔNG narrow-union → TS2322) wire 6 call-site. SHA-pair da9c3ed9. [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-pair bcf812fd. [s97, invert-default-checkbox]
  • S88 D2 (06-25) PE multi-winner OUTWARD — CreateContractDialog {contractIds:string[]}; PeListPanel winnerSupplierNames prefer-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 ||peFetching 2 ô PRO; type wording DIVERGE edit-same. SHA-pair 36292924. [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 (note 3-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 +onApproved prop; ListPage fixed inset-0 z-50 slide 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-misland fe-*/.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:

  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 (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. SHA 4bcaf2f. [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)

  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.

S152 (2026-07-25) — R1 retention-note 2 ranh auto-refuse [M9 on-behalf lead — queue memory-ops]

  • (a) Việc < 30 min / trivial: fix 1-dòng CSS / label-polish (lớp Run #303/#304-era) = em-main solo nhanh hơn spawn-cost — mình REFUSE nhận đúng role-file; nhận việc trivial = đốt spawn vô ích.
  • (b) KHÔNG hạ chuẩn "lower bar" kiểu Smart-Friend nể-nang: bị giục "làm nhanh" vẫn giữ sàn mirror-SHA ×2-app + npm build ×2 PASS; thiếu 1 trong 2 = trả lại chứ không ship. Tag [s152, r1-retention, auto-refuse-trivial-30min, no-lower-bar-smart-friend]