Files
sass-mohfarawati/CLAUDE.md
T
moh f627118b2c CONFIGURED - Auto-activate git hooks via npm prepare script
Add a prepare script that runs 'git config core.hooksPath .githooks' on every
npm install, so the commit-msg style gate and pre-push test gate turn on
automatically per clone with nothing to remember. Guarded with '|| true' so
installs never fail where git is absent (e.g. tarball deploys). Document that
the deploy server never needs it since it only pulls.
2026-09-20 18:54:55 +02:00

8.5 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# Development
npm run dev          # Start Next.js dev server
npm run build        # Build for production (--webpack flag applied in package.json)
npm run lint         # Run ESLint
npm run test         # Run all tests with Vitest
npx vitest run tests/some-file.test.ts  # Run a single test file

# Database (Drizzle ORM + drizzle-kit)
npm run db:generate   # Generate a migration from schema changes
npm run db:migrate    # Apply migrations
npm run db:push       # Push schema to DB directly (dev shortcut, no migration)
npm run db:studio     # Open Drizzle Studio to browse the database

Docker / task runner (see Makefile)

make start       # Start DB container + dev server (http://localhost:3014)
make stop        # Stop containers
make db-up       # Start only the database container
make migrate     # Apply Drizzle migrations
make studio      # Open Drizzle Studio
make psql        # Open a psql shell on the database
make test        # Run the whole test suite + copy-paste summary
make deploy      # (on server) Pull + rebuild + restart
make health      # Hit /api/health via public URL

Architecture

This is a multilingual Next.js (App Router) portfolio site with an admin workspace. Stack: TypeScript, next-intl, Drizzle ORM + PostgreSQL, Tailwind CSS, Radix UI, framer-motion, nodemailer.

Routing overview

There are two applications sharing one Next.js instance:

Public siteapp/[locale]/(site)/ Localized routes for de, en, ar. Default locale is dynamic (stored in AppConfig), read at request time via /api/site/default-locale. Maintenance mode redirects visitors to /coming-soon.

Admin workspaceapp/_admin/ (canonical source) Accessed via a dedicated subdomain (root.mohfarawati.de) in production, or via the /root path prefix in development. The middleware rewrites both to app/admin-internal/. The app/root/ and app/admin-internal/ directories mirror app/_admin/ — treat app/_admin/ as the source of truth.

The full routing rewrite logic lives in lib/admin-routing.ts and proxy.ts (the Next.js 16 middleware entry point — this project has no middleware.ts).

i18n

  • Locales: de, en, ar — defined in i18n/routing.ts
  • Default locale is configurable at runtime via AppConfig (key: default_locale)
  • localePrefix: "as-needed" — default locale has no prefix in URLs
  • No locale cookie or browser detection; locale is set explicitly by user
  • Translation messages live in messages/{locale}.json

Persistence

The Drizzle client is in lib/db/index.ts (postgres.js driver); the schema is in lib/db/schema.ts and DB enums in lib/db/enums.ts. All DB access must go through server-side modules in lib/. Client components must never access the database.

AppConfig is a key-value table used for all runtime configuration: site settings, SMTP, marquee, maintenance mode, default locale. lib/app-config.ts is the aggregate entry point; individual settings are in lib/site-settings.ts, lib/mail-settings.ts, lib/marquee-settings.ts.

Module boundaries

  • lib/* — server-side application logic (queries, services, config)
  • components/ui/ — shared Radix UI primitives (design system base)
  • components/layout/, components/site/ — public site UI
  • components/admin/, components/dashboard/ — admin UI
  • Server actions (actions.ts files in page directories) are the entry points for form submissions; they call lib/* modules
  • Business logic must not live inside UI components

Key canonical files

Concern File
i18n routing i18n/routing.ts
Admin routing logic lib/admin-routing.ts
Middleware (routing + auth) proxy.ts
DB client (Drizzle) lib/db/index.ts
DB schema lib/db/schema.ts
AppConfig aggregate lib/app-config.ts
Portfolio queries lib/portfolio.ts
Media handling lib/media.ts
Contact flow lib/mail.ts

Documentation to read by task scope

  • Small UI/copy/style fixes: read only the relevant files
  • Feature changes: read specs/<feature>.md + docs/ARCHITECTURE.md if structure is affected
  • Cross-cutting/architecture changes: read docs/ARCHITECTURE.md, docs/DOMAIN_RULES.md, docs/FEATURES.md, and the relevant specs/ file

Update docs/ and specs/ only when the change affects feature scope, business rules, architecture, or public behavior.

Environment variables

Key variables (see .env.example for full list):

DATABASE_URL              PostgreSQL connection string
NEXT_PUBLIC_SITE_URL      Public site URL
NEXT_PUBLIC_ADMIN_URL     Admin subdomain URL
ADMIN_HOST                Admin hostname (used by middleware for host-based routing)
ADMIN_PASSWORD            In-app admin session password
ADMIN_AUTH_SECRET         JWT/cookie secret for admin session
ADMIN_BASIC_AUTH_USER     HTTP Basic Auth user (optional, adds middleware-level protection)
ADMIN_BASIC_AUTH_PASS     HTTP Basic Auth password
SITE_RUNTIME_ORIGIN       Internal origin for middleware to fetch runtime state (defaults to http://127.0.0.1:3000 in production)

Working rules

  • Tests are mandatory for every logic change, in the SAME change. New behaviour → new tests covering the intent (positive and negative cases), not one happy example. Deliberate change → update the affected tests and say which/why. A test that fails unexpectedly is a real bug → fix the code, not the test. Test the real thing — integration tests use a real (in-process PGlite) database, so do NOT mock our own lib//DB layer; only true external boundaries (the admin session, third-party APIs, SMTP) may be substituted. Keep the suite green (make test); the pre-push hook (.githooks/pre-push) enforces it. Frontend/UI is verified manually; add component tests only for components with real logic.
  • Before making any change, first explain the plan briefly and list the files that will be touched.
  • Make the smallest safe change that solves the task.
  • Do not modify unrelated files.
  • Preserve existing architecture, naming, and folder conventions.
  • Prefer server-side logic in lib/* and keep business logic out of UI components.
  • Never access the database (Drizzle) from client components.
  • For admin-related changes, treat app/_admin/ as the canonical source of truth unless explicitly told otherwise.
  • Do not add new dependencies unless absolutely necessary and explicitly justified.
  • After code changes, run only the minimum relevant checks (for example: targeted test, lint on changed files, or build if necessary).
  • If a task may affect routing, auth, i18n, or runtime config, inspect proxy.ts, lib/admin-routing.ts, i18n/routing.ts, and the relevant lib/app-config.ts modules first.
  • For schema or database changes, inspect the Drizzle schema (lib/db/schema.ts), the drizzle-kit migration flow, and migration impact before editing.
  • Ask before performing large refactors, file moves, destructive changes, or broad formatting changes.
  • When updating behavior, also update docs/specs if the change affects public behavior, business rules, or architecture.

Git commit messages (MANDATORY)

Every commit subject must follow this exact style, or the commit-msg hook (.githooks/commit-msg) will reject the commit:

VERB - Short description
  • VERB is an ALL-CAPS verb, at least 3 letters — e.g. ADDED, FIXED, STYLED, REMOVED, RENAMED, REFACTORED, IMPROVED, DOCUMENTED, CLEANED, REVERTED, CONFIGURED, POLISHED.
  • Then exactly " - " (space, hyphen, space).
  • Then a capitalized, imperative description with no trailing period.

Good: FIXED - Load .env in drizzle.config so drizzle-kit targets the right DB Bad (rejected): Flatten portfolio category routes — no VERB prefix. Bad (rejected): feat(hero): add backdrop — Conventional Commits is NOT used here.

Do not add any Co-Authored-By / "Generated with Claude Code" attribution lines to commits in this repo. Write the body (when useful) as wrapped prose or bullet points explaining the why, matching the existing history.

The hook (and the pre-push test gate) live in .githooks/ and are activated per clone via core.hooksPath. You don't need to remember this: the prepare npm script runs git config core.hooksPath .githooks automatically on every npm install. The deploy server does not need it — it only pulls, never commits or pushes, so no hook ever fires there. Emergency skip: git commit --no-verify.