Coding Guidelines
Core conventions enforced across the Renewa One codebase. These are non-negotiable standards for all contributors.
KISS Principle
- Simplest solution always — no over-engineering for hypothetical needs
- Fixed conventions over configuration — same pattern everywhere
- Follow industry standards — flag deviations and get explicit confirmation
Naming Rules
| Rule | Details |
|---|---|
| Language | English-only identifiers (variables, functions, DB columns, JSON keys, enums, API paths, seed keys) — enforced by a pre-commit check (scripts/check-english-identifiers.sh) |
| Exceptions | Translation fields (nameDe, nameEn), i18n display text, German proper nouns in data values |
| Engineering docs | English-only: specs, plans, KB content, PR titles/descriptions, commit messages. Renewa proper nouns (EB, Eigenbedarf, Förderung, Gewerk, Übergabe) keep German form. i18n locale files, UI labels, and customer-facing copy stay German |
| Environments | Full names: development, staging, production — never dev/prod |
| Secrets | Environment-scoped, no suffixes: DATABASE_URL, JWT_SECRET |
| Secret exception | Semantic suffix for purpose: DATABASE_URL_MIGRATION |
Names track current domain scope
When a concept’s scope grows beyond what its original name covered, the name must be updated. Coined RENEWA-internal terms are fine when they still match the current scope; they become an anti-pattern when the scope has drifted past the name’s original meaning. The rule is no scope drift, not no abbreviations.
Canonical drift example — KOP
- Originally:
KOP= “Kooperationspartner” (RENEWA’s coop partners only). - Now: same model covers Kooperationspartner + external craftspeople + 1-person Einzelunternehmer (legally individual contacts, not companies).
- Accurate current-scope term:
Contractor. Continuing to useKOPfor the broader concept silently overloads the term.
Coined terms still in scope (stay): Auftragsübergabe, Umsetzung (phase), Eigenbedarf, EB, Förderung, Gewerk, Übergabe. Don’t strip jargon for jargon’s sake.
Test: ask “does this name still accurately describe everything currently in scope?” If the answer needs an “…except for…” caveat, the name has drifted — rename to fit the full scope. /audit flags scope drift, not abbreviation.
XSS Protection (MANDATORY)
All user-facing text fields must use sanitization helpers from backend/src/lib/sanitization-helpers.ts:
import { strictTextField, sanitizedLocalizedText, optionalSanitizedLocalizedText } from '@/lib/sanitization-helpers';
const schema = z.object({
label: sanitizedLocalizedText(255),
description: optionalSanitizedLocalizedText(1000),
internalNote: strictTextField(500),
});See Validation Pattern for full sanitization levels.
LocalizedText Pattern
All bilingual database fields use the LocalizedText type from Shared Layer:
// DB column: jsonb('name').$type<LocalizedText>().notNull()
// Zod schema: name: sanitizedLocalizedText(100)NEVER: Flat columns (nameDe/nameEn), JSONB without .$type<LocalizedText>(), separate Zod fields per language.
i18n — User-Facing Strings (MANDATORY)
Every user-readable string goes through useTranslation() / t('ns.key') with keys in frontend/src/locales/{de,en}/<ns>.json. NEVER hardcode user-visible text in .tsx/.ts.
- Applies to: JSX text, button labels,
aria-label/title/alt/placeholder, toasts, Zod errors, table headers, dialog titles, empty states, breadcrumbs. - Backend user-facing text (errors to client, email/SMS/push) keyed via
getFixedT(locale, '<ns>')at the boundary. - Exceptions: log messages, test fixtures, Storybook demos, error codes (
'NOT_FOUND'), URL slugs, brand names, unit symbols. If you have to think about it, it isn’t exempt. - Add keys to BOTH locales in the same commit (i18n parity hook enforces); fallback defaults passed to
t()must be English.
Changelog Fragments (MANDATORY for app code PRs)
Every PR touching frontend/src/, backend/, or shared/ includes bilingual changelog fragments in renewa-one/frontend/changelog/{de,en}/<branch-name>.md (exempt prefixes: chore-, docs-, test-, refactor-). Written for operators and end users (Teams “Updates” digest), not engineers — user-visible behaviour only. Enforced by the pre-push hook + CI changelog-check.
Security
- Never write secrets to files or pass via CLI arguments
- App secrets live in Infisical EU (project
renewa-one) and sync to Fly.io; GitHub Actions secrets hold CI-infra credentials only — see Secret Management Operations and CI-CD Workflows - Never use
--no-verifyto skip git hooks — see Git Workflow - Passwords: 12+ characters minimum
- Auth hashing:
Bun.password.hash()(argon2id) — no separate argon2 packages
Code Quality Gates
CI is the gate; git hooks are a fast local safety net (as of 2026-06).
| Gate | Checks |
|---|---|
| Pre-commit (~5s) | Format + lint staged files, i18n parity, LocalizedText validation, English-only identifiers, migration consistency, block commits to main |
| Pre-push (<30s) | Conflict check vs origin/main, changelog fragment, atlas.sum integrity. Deliberately does NOT run typecheck/tests — CI’s parallel shards are faster |
| Claude Code PostToolUse | scripts/quick-lint.sh (<100ms) flags codified anti-patterns on every Edit/Write (deprecated guards, sql.raw, setInterval, Number() on money, …) |
| CI | Full typecheck, lint, sharded test suites, schema drift, security scan — see Security Scanning and CI-CD Workflows |
Related Patterns
- Service Layer Pattern — backend architecture
- Component Decomposition — frontend file structure
- State Management — choosing the right state tool
- React Query Pattern — server state management
- URL State Management — URL-synced UI state
- API Layer Pattern — frontend-backend communication
- Dependency Injection — service wiring
- Error Handling Pattern — error propagation
- Validation Pattern — input validation and sanitization
- Background Jobs — async processing
- Middleware Stack — request pipeline
- Shared Layer — cross-boundary types
- Git Workflow — branching and commit rules