Core's agent guidance still described modules living in this monorepo and manifest v7, and said nothing about the product rules that now govern every change. Record that first-party modules live in their own Zayin repositories and install as Module Packages, that the parent ROADMAP.md and DESIGN.md are authoritative for product direction, the ownership, branding, token, dark-mode and no-native-dialog rules, that display pairing is transitional until Stage 1, and the branch-per-PR, no-push-to-main, and no-release-tag workflow. Co-Authored-By: Claude Opus 5.5 <[email protected]>
9.5 KiB
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/moduleis the example, andmodules/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/contractsand@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-sdkABI; 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, andwindow.promptare 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.mdand every ADR relevant to your changedocs/architecture/platform-architecture.mdanddocs/architecture/repository-boundaries.mdREADME.md(current platform status — what is and is not implemented)../ROADMAP.mdand../DESIGN.md(current product direction; see above)docs/development/repository-workflow.mdanddocs/development/testing.mdCONTRIBUTING.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
Zayinorganization:ssh://[email protected]:2222/Zayin/Ekklesia.git(this one),Zayin/Signage.git, andZayin/Classroom_Desktop.git. A local clone may be behind the server:git fetch --prune --tagsand branch fromorigin/main, never from whatever is checked out. - One branch per PR, off an up-to-date
origin/main. Never commit or push directly tomain, 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 doctoroutput 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 bycore/src/migrations/runner.tsandcore/src/diagnostics/doctor.tsrather 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.
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 theapps/apiHTTP 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 manualPublish packagesworkflow only; never runscripts/publish-package-train.tsagainst the real registry without the user's explicit intent.pnpm doctor— read-only local readiness report. Never treat it as a substitute forpnpm verifyor the integration suite.pnpm migrations:plan/pnpm migrations:apply— read-only planning vs. mutating execution againstDATABASE_URL. Never runmigrations:applywithout the user's explicit intent.pnpm api:start— starts the identity HTTP transport against an already-migratedDATABASE_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.