Files
Ekklesia/CLAUDE.md
T
ekklesiaandClaude Opus 5.5 405e509bcb
CI / verify (pull_request) Successful in 1m13s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 48s
CI / migration-smoke (pull_request) Successful in 2m22s
CI / image-build (pull_request) Successful in 1m8s
CI / deploy-smoke (same-origin) (pull_request) Successful in 49s
CI / package-publication (pull_request) Successful in 57s
CI / deploy-smoke (split) (pull_request) Successful in 3m38s
Fix the Stage 1 Dashboard UX defects found in assembled review
The assembled Stage 1 Dashboard was reviewed by hand on a desktop, with
the sidebar collapsed, and on an iPhone. This fixes what that review
found, and records the decisions in ADR 0064:

- Discover is its own page at /settings/discover, with Installed and
  Updates beneath it as children. There is no Browse item any more.
  /settings/discover/browse is still read, as an alias, and is respelled
  in place. Parents and children now read as a hierarchy: a guide line,
  text-only children, a disclosure chevron, and parent and current
  states that look different.
- The sidebar is navigation only. Display pairing moves from the sidebar
  and drawer to Settings -> Displays: the paired displays, a deliberate
  Add display dialog, and revocation. Nothing takes focus on arrival;
  the code field is focused only once Add display is chosen.
- Collapsed icons are centred. Nav-link layout moves from inline styles
  to the shell stylesheet, and every collapsed item is one square
  centred on the rail.
- /settings is an overview of the installation: displays paired,
  modules installed and running, and a line for each other area. It
  reads only existing endpoints.
- Phones get a sticky, safe-area-aware header holding the menu trigger
  and the page name. New pages start at the top, and focus no longer
  scrolls content under the header.
- @ekklesia/ui draws checkbox and radio indicators from tokens rather
  than accent-color, so the selected state is the strong accent in
  Light and Dark alike.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 20:37:32 -05:00

10 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/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 platform-owned. Display pairing lives in Settings → Displays (ADR 0064); the sidebar is navigation only. Signage's own display roster is still a transitional implementation that Stage 1 replaces with the platform's displays. Keep existing paired displays working across any change, and do not build a module installer, theme editor, or display features beyond what a task asks for early.

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://[email protected]: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.