# CLAUDE.md Guidance for Claude Code (and other coding agents) working in this repository. This file is intentionally short and durable — it points at authoritative sources instead of restating their contents, because restated details drift. ## What this repository is Ekklesia is a modular, self-hosted church platform. **This repository is the platform** — Core, the API, the Dashboard and display shell, and the public `@ekklesia/*` module-development packages — built as a strictly layered pnpm/TypeScript monorepo: - **Core** (`core/`) owns platform infrastructure: identity, database connectivity, migration execution, module discovery/lifecycle, devices, and other feature-agnostic services. Core never imports a feature module, a surface, or an app. - **Modules** are independently versioned product packages. First-party modules live in **their own repositories** (Signage is `Zayin/Signage`), build against the published `@ekklesia/*` packages (ADR 0053), and reach an installation as a Module Package (ADR 0049) installed into module storage outside the image (ADR 0048). No module source lives in this repository; `templates/module` is the example, and `modules/` is only an optional, ignored local installed-modules directory. A module owns its data, migrations, and **its own domain UI** (ADR 0021), declares capabilities/permissions through `@ekklesia/contracts` and `@ekklesia/module-sdk`, and never imports Core, another module, or a surface. Manifest v8 permits independently optional primary and full-viewport display artifacts, authored in React against the role-neutral `@ekklesia/module-ui-sdk` ABI; database access is the Module Database ABI v1 (ADR 0050). - **Surfaces** (`surfaces/*`) own the containing application shell, navigation, and every presentation concern a module does not. The shell **hosts** module UI through the generic loader; it never rebuilds a module's domain screens. Surfaces consume public API/client contracts only. These boundaries are enforced by `pnpm boundaries` (`scripts/check-boundaries.ts`), not just documented. Read `docs/architecture/repository-boundaries.md` and `docs/architecture/platform-architecture.md` before proposing any change that crosses a layer. ## Product direction lives beside this repository The current roadmap and product/interface design contract are `ROADMAP.md` and `DESIGN.md` in the parent development directory (`../ROADMAP.md` and `../DESIGN.md` from a checkout in the standard `~/development/ekklesia/` layout). They are deliberately **not** committed here; do not copy them into this repository to make them available. They are authoritative for product direction: read the relevant stages and sections before designing a change. If this repository's documentation disagrees with them, flag the conflict, correct the repository-local documentation where appropriate, and never silently follow the stale text. Architectural decisions still change only through ADRs (below). Rules from those documents that every change here must respect: - **Ownership.** Modules own their domain data, behaviour, and UI. Shared resources — displays and devices, accounts and identity, roles and authorization, branding, mail, push, storage providers, and module installation/lifecycle — belong to the platform, and modules consume them. - **Branding.** Normal user-facing UI never hard-codes "Ekklesia"; product identity comes from installation branding (ADR 0046). Package names, diagnostics, logs, and developer tooling may. Existing hard-coded shell strings are known debt for Stage 1, not a pattern to copy. - **Visual direction.** Simple, minimal, and calm; responsive for phone, tablet, and desktop; light, dark, and system modes. Normal UI colours come only from the shared design tokens (`@ekklesia/design-tokens`, ADR 0039) — never a hard-coded colour. - **No browser-native dialogs.** `window.alert`, `window.confirm`, and `window.prompt` are prohibited in product UI. Destructive actions use in-product confirmation proportional to their impact. - **Displays are transitional.** Displays are platform-owned. Today's device pairing (the Dashboard sidebar's *Display devices* panel) and Signage's own display roster are a transitional implementation that Stage 1 migrates to **Settings → Displays**. Fix defects in them, but do not build the Stage 1 Settings, global Displays, theme editor, module installer, or responsive shell redesign early, and keep existing paired displays working across any change. ## ADRs are authoritative `docs/architecture/decisions/` records durable architectural decisions. **Accepted ADRs are immutable except for status and cross-reference corrections** — do not edit their content to fit a new change. A changed decision gets a new ADR that supersedes the old one. Read the full ADR index (`docs/architecture/decisions/README.md`) and every ADR relevant to the area you are touching before designing a change; do not assume this file's summary of any decision is complete or current. ## Read before you design At minimum, read the current versions of: - `docs/architecture/decisions/README.md` and every ADR relevant to your change - `docs/architecture/platform-architecture.md` and `docs/architecture/repository-boundaries.md` - `README.md` (current platform status — what is and is not implemented) - `../ROADMAP.md` and `../DESIGN.md` (current product direction; see above) - `docs/development/repository-workflow.md` and `docs/development/testing.md` - `CONTRIBUTING.md` These files change as the platform evolves. **Inspect the actual current repository state (source, tests, package.json scripts, CI workflow) before relying on anything in this file or in your own memory of a prior session** — treat stale assumptions as a bug to catch, not a shortcut. ## Working conventions - The canonical repositories are on the church Gitea under the `Zayin` organization: `ssh://git@192.168.68.76:2222/Zayin/Ekklesia.git` (this one), `Zayin/Signage.git`, and `Zayin/Classroom_Desktop.git`. A local clone may be behind the server: `git fetch --prune --tags` and branch from `origin/main`, never from whatever is checked out. - One branch per PR, off an up-to-date `origin/main`. Never commit or push directly to `main`, never merge your own PR, and never change branch-protection settings. - Never create or push a release tag. Release tags belong to the human maintainer. - A change that needs both a platform contract change and a module change is two PRs: Core first, on its own branch, with the compatibility boundary stated explicitly; then the module. Do not copy platform logic into a module to avoid making the correct Core change. - Keep PRs focused on one bounded slice. Record major architectural changes in an ADR rather than burying them in an implementation. - Add or update tests when you change a contract or architectural rule. New behavior needs new test coverage, not just a passing existing suite. - Errors that reach a user, log, CLI, or `pnpm doctor` output must be sanitized: never expose secrets, connection strings, raw SQL, raw driver/exception messages, stack traces, causes, or machine-specific paths. Match the sanitization pattern already used by `core/src/migrations/runner.ts` and `core/src/diagnostics/doctor.ts` rather than inventing a new one. - Never run a database migration automatically (on import, on startup, in a test that isn't explicitly a migration test, or as a side effect of an unrelated command). Migrations are always an explicit, operator-invoked action. - Report what you find. A defect, flaky test, documentation/ADR conflict, or CI problem outside the task at hand gets an issue in the repository it belongs to, as soon as you find it. Search for an existing issue first; something fixed in the same PR goes in that PR's description instead. Be descriptive: what you saw, the evidence (run and job links, commit, file and line), how to reproduce it, the impact, and options, and never a secret. Label it `found-by-claude` (an agent that cannot apply labels ends the body with a line saying so), leave it unassigned, and list what you filed when you report back. ## Canonical verification commands Run from the repository root; `package.json` is the source of truth if this list and the file ever disagree: - `pnpm verify` — the full code-only gate (format, lint, boundaries, typecheck, tests, module and migration validation, identity schema drift check, build). Docker-free; never touches a live database. - `pnpm test:migrations:integration` — live, disposable-PostgreSQL evidence: migration discovery, planning, and execution, plus the `apps/api` HTTP transport (signup, sign-in, principal resolution, sign-out). Requires Docker. Run this for any change touching either area. - `pnpm test:package-release` — builds the public `@ekklesia/*` package train, proves it reproducible, and proves a standalone module consumes exactly those tarballs. Publishes nothing. Run it for any change to the public packages or their release tooling. - `pnpm test:package-publication` — publishes a release train to a disposable Gitea (Docker) and proves the publication tool. Never the real registry. The real publication is the manual `Publish packages` workflow only; never run `scripts/publish-package-train.ts` against the real registry without the user's explicit intent. - `pnpm doctor` — read-only local readiness report. Never treat it as a substitute for `pnpm verify` or the integration suite. - `pnpm migrations:plan` / `pnpm migrations:apply` — read-only planning vs. mutating execution against `DATABASE_URL`. Never run `migrations:apply` without the user's explicit intent. - `pnpm api:start` — starts the identity HTTP transport against an already-migrated `DATABASE_URL`. It never applies migrations itself. Never run it without the user's explicit intent. ## When you're unsure If a task would require weakening an existing ADR's guarantee, introducing a second mechanism where one already exists (a second migration runner, a second discovery path, a second config system), or exposing something the boundary checker currently blocks, stop and surface the conflict instead of working around it.