Skip to content
BoringStack
Star

Architecture rules

2 min read

The apps/ui enforces its architecture through ESLint. The UI-specific plugin is @boring-stack-pkg/eslint-plugin-react-component-architecture, which composes with the shared plugins (resource-architecture, module-boundaries, structured-logging, env-access, test-conventions, code-flow) that both apps use.

Architecture is not vibes; it’s compile-time enforcement across 7 rule categories.

Component anatomy. No hooks in .tsx; logic belongs in .hooks.ts.

Single semantic module. Constants, utils, and types each get their own file (.constants.ts, .utils.ts, .types.ts).

className discipline. Use cn(...); pull long class strings out for readable conditionals.

File naming. PascalCase components; kebab-case otherwise; suffix matches role (.hooks.ts, .queries.ts, .store.ts).

Prop ordering. Required props, optional props, then handlers.

View object boundary. Components read the hook’s view object; no direct TanStack Query, Zustand, or import.meta.env.

Queries layer. useQuery only in *.queries.ts; component hooks call those query hooks.

Each rule has a fix-it suggestion where mechanical; the rest fail the lint gate and need a real edit.

Pure .tsx files are snapshot-friendly: one render assertion, no hook setup.

Hooks return a typed view object, so you test with renderHook and no DOM. Module boundaries keep logic out of .tsx, so deadline edits don’t collapse into one file.

Consistent file suffixes (.hooks.ts, .queries.ts, .store.ts) create the same layout in every feature folder, making the codebase navigable at scale.

// eslint-disable-next-line <rule> works, but every suppression is reviewed. Common valid cases:

  • Third-party render-prop APIs that force a hook-like pattern in .tsx.
  • Sub-components that have no state but are too small to split into their own folder. Inline them; the lint rule has a size threshold.

If you find yourself suppressing the same rule across many files, that’s a signal to update the rule, not to keep suppressing.

Plugin source + per-rule docs: @boring-stack-pkg/eslint-plugin-react-component-architecture. apps/ui ESLint config: eslint.config.mjs.