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

RuleDetails
LanguageEnglish-only identifiers (variables, functions, DB columns, JSON keys, enums, API paths, seed keys) — enforced by a pre-commit check (scripts/check-english-identifiers.sh)
ExceptionsTranslation fields (nameDe, nameEn), i18n display text, German proper nouns in data values
Engineering docsEnglish-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
EnvironmentsFull names: development, staging, production — never dev/prod
SecretsEnvironment-scoped, no suffixes: DATABASE_URL, JWT_SECRET
Secret exceptionSemantic 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 use KOP for 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-verify to 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).

GateChecks
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 PostToolUsescripts/quick-lint.sh (<100ms) flags codified anti-patterns on every Edit/Write (deprecated guards, sql.raw, setInterval, Number() on money, …)
CIFull typecheck, lint, sharded test suites, schema drift, security scan — see Security Scanning and CI-CD Workflows