Skip to content

Latest commit

 

History

History
92 lines (79 loc) · 6.51 KB

File metadata and controls

92 lines (79 loc) · 6.51 KB
name project-mtp-stack
description Confirmed stack for Multi-Tenant Platform — ASP.NET Core Web API backend + React 19 + Radix Primitives + CSS Modules + native CSS Grid/container queries + TanStack Table/Virtual + Zod/RHF/TanStack Query/Zustand frontend. Replaces the MUI-based plan from ADR-0003.
metadata
type
project

For the multi-tenant-platform repo at D:\Projects\mtp\mtp-docs:

Authoritative ADR: adr/0007-ui-stack-radix-cssmodules.html (supersedes ADR-0003).

Why this stack: User wants Tally-like desktop feel in the browser — sub-50ms screen switches, keystroke-driven navigation, dense data over thousands of rows. Two non-negotiables locked 2026-05-20:

  1. No runtime CSS-in-JS.
  2. Minimal framework intrusion — primitives are headless; the team owns its DOM and CSS.

How to apply:

Backend (unchanged)

  • ASP.NET Core Web API (latest stable — .NET 10 at time of writing)
  • MVC controllers (ControllerBase + [ApiController] + [HttpGet]/[HttpPost]). No Minimal APIs (per [[feedback-no-minimal-apis]]).
  • Traditional layered / service-oriented (per [[feedback-no-cqrs-no-mediatr]]).
  • ErrorOr for service results. FluentValidation for shape validation.
  • EF Core 9 (Npgsql) + Dapper for reads.
  • PostgreSQL 17+ with ltree (per [[project-mtp-postgres-ltree]]).
  • Per-request connection-string resolution (per [[project-mtp-tenancy-model]]).

Frontend

  • React 19+ + TypeScript 5.9+ — strict mode on.
  • Radix UI Primitives (@radix-ui/react-*) — headless behaviour, ARIA, keyboard, focus, portal. Opt-in per primitive (Dialog, Popover, DropdownMenu, Tooltip, Select, Tabs, Toast, ScrollArea, AlertDialog, ContextMenu). Use the primitives package only — not the styled @radix-ui/themes package.
  • CSS Modules + PostCSS + CSS custom properties — compile-time CSS, scoped class names, zero runtime serialization. One .module.css next to each component file.
  • Native CSS Grid + Container Queries — no grid library. In-house Grid / Stack / Cluster / SplitPane primitives (~30 lines CSS each). @container for component-level responsiveness; media queries only for app-shell breakpoints.
  • TanStack Table + TanStack Virtual — headless table state + virtualization. Wrap in own DataGrid component that owns sticky headers, column resize, keyboard nav.
  • lucide-react — icon set; matches the inline SVG style already used in mockups/*.html (24×24, stroke-based, currentColor).
  • Zod — schema validation; single source of truth for client types + runtime validation; pairs with RHF via @hookform/resolvers/zod.
  • React Hook Form — form state, low re-render; resolver = Zod schemas.
  • TanStack Query (v5+) — server state, caching, revalidation, ETag-aware.
  • Zustand — small client/UI stores (auth, current client code, current locale, ephemeral UI flags).
  • react-hotkeys-hook — F-key / Alt-key shortcuts for Tally-style navigation.
  • i18next + react-i18next (per page 08).
  • Vite (latest) — build tool.
  • React Router (latest, data mode) — routing.
  • HTTP: native fetch + thin wrapper that attaches JWT + client-code header. No axios.

Theming

  • Tokens defined as CSS custom properties in tokens.css (--color-primary, --space-2, --font-sm, etc.).
  • Tenant skin = toggle data-theme attribute on <html>. CSS variables resolve down the cascade. No JS re-render path.
  • Dark mode = same mechanism, different variable values.

Boundaries

  • Server-side validation = FluentValidation. Authoritative.
  • Client-side validation = Zod. Optimistic; mirrors server rules for UX. Never sole gatekeeper.
  • Shape contract flows: OpenAPI schema → generated TypeScript types → Zod schemas authored alongside types.
  • Result envelope server-side: Result<T> (per page 06) → JSON. Client: TanStack Query mutations/queries unwrap into Result<T>-typed responses; UI dispatches by IssueKind (Advisory/Confirmable/Blocking) per page 05.
  • Form binding: RHF on plain <input> / <select>. For overlays/menus/dialogs, RHF state + Radix primitives wrapped in own components.
  • Forms render off platform UI schema (per page 07): generic <FieldRenderer> switches on FieldKey → in-house input components.

What NOT to use

  • No runtime CSS-in-JS of any kind (rules out any styling library that serializes styles at render time).
  • No compile-time CSS-in-JS either — CSS Modules is sufficient.
  • No utility-class CSS framework — style stays in .css Module files, not in JSX.
  • No styled component libraries — primitives must be headless.
  • No Redux / Redux Toolkit — Zustand covers it.
  • No Formik — RHF.
  • No Yup — Zod.
  • No SWR — TanStack Query.
  • No axiosfetch + thin wrapper.
  • No global-CSS framework (Bootstrap-style) — class-name leaks.
  • No CRA / Next.js / Remix — Vite SPA; no SSR/edge needs for the admin app.

Hard rules

  • TypeScript strict mode on: noUncheckedIndexedAccess, noImplicitOverride, noFallthroughCasesInSwitch.
  • Every component: Component.tsx + at most one Component.module.css.
  • All colour/spacing/font tokens via CSS variables in tokens.css. No hardcoded hex/rem in component CSS.
  • Tenant theme switch = data-theme attribute on <html>. No JS re-render.
  • Radix primitives wrapped in own components (Dialog, Popover, etc.); never imported raw into feature code.
  • Zod schemas authored next to types; types via z.infer — never duplicated.
  • One QueryClient per app. TanStack Query keys: [entity, ...filters] tuple.
  • All forms wired through RHF + Zod; no raw onSubmit handlers in feature code.
  • Cross-component state in Zustand or TanStack Query cache. No prop drilling beyond 2 levels.
  • Container queries preferred over media queries for component responsiveness.
  • No new dep adds without an ADR if > 50 KB gz or duplicates an existing concern.

Folder structure (high-level only — see ADR-0006)

Documented today: feature-folder pattern in mtp-websrc/features/<feature>/manifest.ts + pages/, shell at @/app/feature-registry. Not yet locked: location of primitives/, components/, lib/, hooks/, styles/tokens.css. Will be settled in a follow-up ADR when mtp-web repo is initialized.

Related

  • [[project-mtp-postgres-ltree]] (database)
  • [[project-mtp-tenancy-model]] (isolation)
  • [[project-mtp-repo-topology]] (repo split + feature-folder pattern)
  • [[feedback-no-cqrs-no-mediatr]] + [[feedback-no-minimal-apis]] (backend style)