Project-scoped memory for D:\Projects\mtp\mtp-docs (GitHub: paramhq/mtp-docs).
- Check Radix first (standing rule) — Before building ANY interactive control, check Radix UI for a primitive; wrap it in @mtp/ui. Don't reinvent the wheel.
- Reuse-first / extract-when-repeated gate — Before hand-rolling: search Radix/@mtp/lib first; when hand-rolling something structural, extract a primitive that OWNS the invariant so per-screen bugs can't recur. Register it.
- Interactive fidelity — Full-fidelity = every tab switches, every button navigates/toasts, every toggle changes state. Rendering a control without wiring it is a stub. Verify each in the browser.
- URL-driven tabs (no duplicate strip) — Screen tabs in nav.ts are rendered once by AppShell; read
?tab=via useSearchParams, never render a second in-page strip. Match nav labels exactly. - Full-fidelity ports, no stubs — Port EVERY section + EVERY tab's real content + ALL forms. "In totality" = full cross-repo pipeline (mockup/ADR→data→page→wire→verify→docs→push both repos). DoD box in 12-build-plan.html.
- Verify data-grid layouts — After any custom CSS Grid table, read
gridTemplateColumns+ per-cell width in chrome-devtools. 0px tracks render no pixels and pass eyeball inspection. Lint + manual inspection together. - No native datepicker — Never
<input type="date|time|...">. Use @mtp/ui<DatePicker>(Radix Popover + react-day-picker, token-styled, controlled ISO string; Controller in RHF). - Drawer sticky footer — Drawer/dialog forms = fixed header + scrollable
<DialogBody>+ pinned<DialogFooter>(Cancel/Save always visible). Never let actions scroll away. - Keep docs in sync — At the END of a task, update ADRs/memory IF REQUIRED (new lib/pattern/rule → yes; trivial fix → no). Don't over-document.
- Dense grids stay bespoke — Don't convert dense Tally-style editable grids (Voucher Entry lines, TDS challan) to the padded field kit; preserve their compact density. Field kit = normal-sized controls only.
- Reports use native PDF/Excel libs — QuestPDF / ClosedXML directly. NEVER HTML→PDF, NEVER Puppeteer/headless browser.
- Diagrams use light palette — Mermaid nodes must use pastel fills + dark outlines/text. No dark fills.
- GNUMS data source default — Without a live connection string, study
D:\Extractions\GNUMS_PU_2019\(file dump). Live DB only when user supplies it or explicitly asks. - Scope is bigger than GNUMS — GNUMS archaic = study input/floor, NEVER ceiling. Design modern superset. Don't size aggregates from legacy. Finance = domain of ~8-10 modules, 25-35 aggregates.
- Mockups use shared shell — New mockups MUST use _shell.js + _mockup.css + _nav-manifest.js, never inline/copy-paste chrome. Authority ADR-0010. Prevents drift.
- No dropped-tech mentions in current docs — Skip "rejected libraries" / "alternatives considered" tables in current docs. State positive choices + principles only. Historical mentions OK inside superseded ADRs.
- Consult ADRs first, don't ask — For features in documented modules, read the relevant ADRs + plan.html BEFORE any AskUserQuestion. Only ask when ADRs are genuinely silent/ambiguous on that specific point.
- Detail views open in a drawer — Drill-through (voucher/bill/document/ledger-line/account detail) = drawer over the list, never a full-page route. Convert /finance/document/:id to a drawer; preserves worklist + filters.
- Aggregate-rooted master-detail nav — No entity-per-menu sprawl. Child entities / config sub-objects / pipeline stages live as a detail tab/drawer on their parent, never a separate top-level menu item. Signatory Master → Bank Accounts › Signatories (done). Principle app-wide.
-
MSW → real API breeze — mocks mirror routes/payloads/response shapes/error envelopes exactly; live swap touches only the network layer
-
React Query optimistic updates required — useMutation with onMutate/onError/onSettled, no local state overrides
-
No placeholder toasts — every button does what its label says: navigate, mutate, open drawer, download
-
Build status / resume point — read first when resuming build. mtp-web at D:\Projects\mtp-web: scaffold+tokens+AppShell+full component library done+Voucher Entry & Day Book pages live+CSS tokenized with stylelint guard. Pushed to github.com/paramhq/mtp-web (PRIVATE, main). Next: more Finance pages (COA/Ledger/Approvals).
-
Postgres 17+ and ltree — DB is PostgreSQL 17+ exclusively; ltree for hierarchical data.
-
Tenancy model (DB-per-client) — Each client owns its Postgres DB. Client code in JWT/header resolves connection string per request. Single API + single frontend deployment, subdomain per client.
-
Stack — ASP.NET Core Web API + React 19 + Radix Primitives (headless) + CSS Modules + native CSS Grid/container queries + TanStack Table/Virtual + Zod/RHF/TanStack Query/Zustand + Vite. Authoritative: ADR-0007.
-
Data layer (demo API + Query + Zustand) — Fetch via
apiGet(real fetch); MSW servesmocks/fixtures in dev (visible in Network), real API via one env flag; TanStack Query + query-key-factory; one Zustand store. Hard rules: filtering = POST (apiSearch) never GET params; route names always camelCase. Pin ADR-0011. -
UI decisions (consolidated) — stack, monorepo (pnpm, no npm publish, github.com/paramhq private), design-system (faithful port of shell, two-tier light-first tokens), state (TanStack+Zustand, no Redux), TS-only, responsive container-query-first. ADRs 0007/0008/0010/0019/0020/0021. Read before any frontend work.
-
Component authoring — Native HTML default; Radix only for non-trivial behaviour. JSX renames, uncontrolled RHF inputs, Radix
asChild, headless tables. Page layout MUST come from @mtp/ui primitives (<Sidebar>etc.); never hand-rolled grid in page CSS — enforced by stylelint. Authoritative: ADR-0008. -
List pagination (keyset + infinite + virtual) — Unbounded lists/ledgers use keyset cursor pagination +
useInfiniteQuery+ TanStack Virtual + bounded default dateRange (POST apiSearch). Ledger is the reference. Pins ADR-0025. -
PascalCase DB — Database, table, and column names ALWAYS in PascalCase. Raw SQL must double-quote identifiers in Postgres.
-
DB constraint naming — Predictable, industry-standard prefixes: PK_/FK_/UQ_/CK_/IX_/UX_/SQ_/TR_/TFN_/FN_. No Postgres auto-names.
-
Persistence model (hybrid) — Relational (EF Core + Dapper) AND event sourcing (Marten) coexist. Choice is per-entity, not global.
-
Excel/CSV stack — DocumentFormat.OpenXml (bulk) / ClosedXML (formatted) / NPOI (legacy .xls) / ExcelDataReader (ingest). CSV: CsvHelper default, Sep/Sylvan hot path. EPPlus banned.
-
Authz model (ADR-0004) — One Permission concept (no Menu/Operation split), single-column ltree+jsonb scope, JWT claims, IPermissionFilter in service layer. Replaces GNUMS SEC_ sprawl.
-
GNUMS legacy conventions — Table/SP/ASPX/App_Code four-namespace mirror, prefix→folder rollup, LOG_/LOJ_ audit shadows (1,320 tables!), code-gen Base+override pattern, the 5 cross-FK gravity wells (SEC_User/STU_Student/STF_Staff/INS_*/SUB_Subject).
-
GNUMS business logic in SPs — Hard rule: ~9,000 of 34,547 GNUMS procs contain custom business logic. C# code is mostly thin wrappers. Any module study that ignores SPs is incomplete.
-
Student Phase A decisions — Resolved 2026-05-18. Medium-shape Student (~50 cols, not slim 15) · Student module owns StudentExam projection (Exam-result state splits to Exam) · 3 aggregates (Identity + Enrolment + Profile), not 1.
-
Rule engine v1 — v1 cut of page 04: Blocking-only, field rules only, no cross-field/stepper/sections. Concepts borrowed from + improved over GNWebSoft.Forms.
-
Repo topology + no Module Federation — 3 repos at build start (mtp-docs · mtp-api · mtp-web). Contracts repo + mobile deferred. Single Vite bundle with feature-module pattern; no MF. Pins ADR-0005 + ADR-0006.
-
Module naming: HCM not HRMS — New people module = "HCM / Human Capital". Legacy study stays "HR-Payroll" (don't rename
gnums/hr-payroll/). "HRMS" deprecated. -
Navigation depth (4-level) — mtp-web shell = module(rail) → group(submenu header) → page(submenu item) → view(in-page tab). Collapsible groups, Ctrl-K palette, never 5-deep. 360-hubs add vtab rail.
-
Finance frontend CA-readiness (2026-05-29) — mtp-web Finance = design-complete demo shell, NOT usable by a CA; ~30% there. Write spine unwired (Voucher Entry submit = toast only), GST/TDS/BankRecon are demo-only. Punch list P0/P1/P2 in docs/audits/finance-frontend-readiness-audit.html. Build contract: mtp-web/docs/conventions/04-wiring-compliance-contract.md; golden slice = Voucher Entry useCreateVoucher.
-
Conventions precedence (mtp-web wins) — global ~/.claude/conventions target a different project; mtp-web/docs/conventions + CLAUDE.md authoritative. Mirror real neighbours, not global templates.
-
Display formatting in @/lib/format/* — money.ts + date.ts are the ONE home for currency/number/date formatting. Never inline Intl/toLocaleString/toISOString. Codemod retained at scripts/. Inline-style-with-literals is the next anti-pattern (~150 sites).
-
Accounting module study (2026-05-20) — 19 clusters + 4 cross-cuts complete. Non-obvious legacy gaps: NO period close, NO base-currency/locale, GST is rate-master only, no Trial Balance/BS/P&L in legacy, OPD is copy-paste of Fee Collection Bank. Source:
gnums/accounting/scope.html. -
Staff module study (2026-05-20) — 12 clusters + 4 cross-cuts complete. Non-obvious legacy quirks: 2 hardcoded backdoors (StaffID 1281/31782 + Absconding), 3 duplicate 'PO' status UDFs, citation DBs manual-entry only, all statutory submissions absent, 125+ RDLCs in reports. Source:
gnums/staff/scope.html. -
Design invariants (2026-05-22 review) — statements in GL not Tax; JournalEntry EntryType stamped not created; HR owns 24Q; masters relational; Punch=VO; OffCyclePayment; no HR payment rail; AR/AP factored. Honor in all module plans.
-
Mockup × Aggregate audit (2026-05-22) — 30 mockups vs aggregates + 4-level nav. Accounting needs amend (add Budgeting agg, 7 cross-write red flags, dedupe approvals/recon); HR-Payroll confirms ADR-0009. Doc: docs/audits/mockup-aggregate-audit.html.
-
HR-Payroll module study (2026-05-21) — 13 clusters + 4 cross-cuts complete. 5 sibling prefixes (HRP+HRA+HRL+HRT+HRC, HRA/HRL/HRC procedure-only). 314 custom SPs studied (no skipping). 51 KB UPSERTSalary + 57 KB SelectForReturnIncome are load-bearing. EPF.cs broken; Davda is an OEM not a tenant; 30+ legacy typos in domain names; no statutory e-filing wires. Source:
gnums/hr-payroll/scope.html.