ADR-0004: Code Quality and Formatting Standards
Last Updated: 2026-03-15 Status: Active Context: Decksmith
Context
A monorepo with multiple applications and packages requires consistent code style and quality standards. Without enforcement, code style drifts across packages, making the codebase harder to read and maintain.
Key questions:
- How do we enforce consistent formatting?
- How do we catch logical errors that TypeScript can't?
- Should formatting and linting be automatic or manual?
- How do we share configuration across packages?
Current Decision
We will use:
- Prettier for code formatting (automatic, zero-config)
- ESLint for linting (catch logical errors, enforce best practices)
- Shared configuration in
packages/config(future) for reuse across all packages - Enforcement via CI (future) and editor integration (immediate)
Prettier configuration:
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSpacing": true,
"proseWrap": "always"
}ESLint configuration (to be implemented in packages/config):
- Extend
@typescript-eslint/recommended - Enable
eslint-plugin-reactforapps/webandpackages/web-ui - Enable
eslint-plugin-importfor import ordering - Disable rules that conflict with Prettier (via
eslint-config-prettier)
Rationale
Why Prettier
"Clarity over cleverness" — Prettier eliminates formatting debates entirely.
- Opinionated, zero-config: No bikeshedding over brace style, indentation, or line length
- Automatic consistency: Format on save, format in CI, never think about it again
- Diff clarity: Consistent formatting makes git diffs focus on logic, not whitespace
- Editor support: Works in VSCode, Vim, IntelliJ out of the box
Configuration choices:
semi: true: Explicit statement terminators; avoids ASI (Automatic Semicolon Insertion) edge casessingleQuote: true: Consistent with most modern codebasesprintWidth: 100: Balance between readability and horizontal spacetrailingComma: "es5": Safer git diffs (adding a line doesn't modify previous line)arrowParens: "always": Explicit syntax, easier to add types laterendOfLine: "lf": Unix line endings (macOS/Linux standard, Git normalization)
Why ESLint
TypeScript catches type errors, but not logical errors:
- Unused variables (
@typescript-eslint/no-unused-vars) - Missing
awaiton promises (@typescript-eslint/no-floating-promises) - Accidental
==instead of===(TypeScript allows both) - React-specific issues (missing
keyprops, hooks rules)
ESLint complements TypeScript by catching patterns TypeScript can't.
Plugin strategy:
@typescript-eslint/eslint-plugin: TypeScript-specific ruleseslint-plugin-react: React best practices (for web/mobile packages)eslint-plugin-import: Import ordering, no circular dependencieseslint-config-prettier: Disables ESLint formatting rules that conflict with Prettier
Why Shared Config Package
Without shared config:
- Every package duplicates
.prettierrc,.eslintrc - Updates require changing 15+ files
- Packages drift out of sync over time
With packages/config:
// apps/web/.eslintrc.js
module.exports = {
extends: ['@decksmith/config/eslint-react'],
};Benefits:
- Single source of truth
- Update once, applies everywhere
- Packages can extend with package-specific rules
Note: packages/config implementation is deferred to a future PR. This ADR documents the strategy.
Why Not Alternatives
Biome: New tool, combines Prettier + ESLint. Very fast, but:
- Smaller ecosystem (fewer plugins)
- Less mature (fewer rules implemented)
- Risk: If Biome development stalls, we're locked in
Verdict: Stick with Prettier + ESLint (industry standard, stable, large ecosystem).
StandardJS: Opinionated linter with built-in formatting. But:
- Can't customize rules (not suitable for enterprise)
- Less flexible than ESLint
Trade-offs
Benefits:
- Consistent code style: No debates, no manual formatting
- Fewer PR comments: No more "add a space here" comments
- Catches common mistakes: ESLint catches logic errors TypeScript can't
- Better diffs: Automatic formatting makes diffs focus on logic
- Faster onboarding: New contributors don't need to learn style guide
Costs:
- Build complexity: Adds tools to the stack (Prettier, ESLint, plugins)
- Learning curve: Contributors must understand ESLint rules
- Slower feedback: Running linters adds time to CI (mitigated by Turborepo caching)
- Rule fatigue: Too many ESLint rules can slow development
Risks:
- False positives: ESLint may flag valid code (e.g.,
anytypes during prototyping)- Mitigation: Use
eslint-disable-next-linewith comments explaining why
- Mitigation: Use
- Configuration drift: Without discipline, packages may override shared config inconsistently
- Mitigation: ADR-0002 (Monorepo) enforces shared configs via
packages/config
- Mitigation: ADR-0002 (Monorepo) enforces shared configs via
- Prettier reformatting noise: Initial Prettier run touches many files
- Mitigation: Run once in initial infrastructure PR (this PR)
Evolution History
2026-03-15: Corrected semi setting — documentation matched to actual config
- ADR originally documented
semi: falsebut.prettierrc.jsonwas created withsemi: true - Ground truth is the config file; this entry corrects the documentation
- Additionally, the actual config includes
useTabs,bracketSpacing, andproseWrapfields that were not listed in the original ADR — those have been added to the documented config - No behaviour change: the codebase has always used semicolons
2026-01-08: Initial decision
- Chose Prettier for formatting (semicolons enabled, single quotes, 100 char width)
- Chose ESLint for linting (TypeScript + React plugins)
- Defined strategy for shared config in
packages/config(implementation deferred) - Implementation note: Configuration files placed at repository root initially (
.prettierrc.json,eslint.config.js,tsconfig.json) for immediate functionality. Migration topackages/configwill occur during package scaffolding phase when duplication becomes apparent and shared configs provide clear value. This follows the principle of avoiding premature abstraction.
References
- Prettier Documentation
- ESLint Documentation
- typescript-eslint
- eslint-config-prettier
- Related ADR: ADR-0002 (Monorepo structure explains shared config strategy)