# Contributing Ekklesia is being built in verified, reviewable phases. Changes should preserve the dependency boundaries described in [repository-boundaries.md](docs/architecture/repository-boundaries.md). Before opening a change, run: ```bash pnpm verify ``` Migration-runner changes and changes to identity/authentication or the API's session transport also require the disposable live PostgreSQL suite: ```bash pnpm test:migrations:integration ``` See the [testing guide](docs/development/testing.md) for the distinction between unit and live evidence and for credential-safe troubleshooting. When adding or changing a module manifest, also run `pnpm modules:validate` directly for focused, path-aware discovery errors. It is included in `pnpm verify`. If Docker is available, also validate the development infrastructure: ```bash pnpm infrastructure:config ``` ## Package rules - Use strict TypeScript for production packages. - Expose supported entry points through package `exports`; do not import another package's private source paths. - Use `workspace:` ranges for internal package dependencies. - Modules own their own UI and author it in React and TypeScript (ADR 0022). A module package may contain `.tsx` and depend on `react`, `react-dom`, and `@ekklesia/module-ui-sdk`, and may author plain `.css` inside a browser-artifact source root (ADR 0040). Every other UI framework, `.jsx`, other stylesheet dialects, and other presentation formats remain rejected by `pnpm boundaries`. Primary and display sources live at the exact `src/ui/index.tsx` and `src/display/index.tsx` conventions; one `pnpm build` invocation atomically compiles every role the v6 manifest declares via `@ekklesia/module-ui-build` (ADR 0023/0033). Module authors write no bundler configuration. A module's stylesheets are compiled into its declared artifact as text and installed by the host into that artifact's own Shadow DOM root, so module styles cannot leak into the Dashboard or another module (ADR 0040). Both roles use the same UI SDK context with no presentation-mode flag, and the same versioned mount ABI. - A module may also consume exactly two shared design packages — `@ekklesia/design-tokens` and `@ekklesia/ui` (ADR 0039) — so two modules' screens look like one product. That is a named allowlist: every other shared role (`api-client`, `auth-client`, `surface-sdk`, `module-ui-host`) stays rejected inside a module. Reference registered semantic tokens; never define an `--ekklesia-*` custom property of your own. - Module UI reaches its host only through `@ekklesia/module-ui-sdk` — never `@ekklesia/module-ui-host`, which is the host-side loader — and asks the host for its own API by relative path — never by constructing `/api/v1/modules//...`, which is Ekklesia's private deployment detail and would break standalone hosting. - The containing application shell, navigation, global theme, and platform policy belong to Core and the host application, never to a module. Ekklesia's own shell is `surfaces/shell`; it discovers primary module UIs through `GET /api/v1/dashboard/navigation`, resolves a dedicated display through `GET /api/v1/module-displays/`, mounts both with `@ekklesia/module-ui-host`, and statically imports no module (ADR 0026/0033). - Dedicated displays are module-owned renderers mounted by a separate full-viewport surface at `/display/`, never Dashboard UI with hidden chrome. Its page and descriptor require the module-owned, non-public permission declared by `entrypoints.display`; artifact bytes remain anonymous trusted code. Public/device identity, display inventory, and variants are deferred. - Dashboard groups are presentation configuration, never module structure. Do not add a parent module, child module, owning module, or group-derived dependency: real module relationships are the dependency contract's job alone, and grouping must never influence discovery, dependency ordering, startup, or authorization (ADR 0027). A module declares its display name (`manifest.name`); the Dashboard owns where it appears. - A surface is browser code. It may depend on browser-safe shared roles only — never `@ekklesia/contracts` or `@ekklesia/module-sdk`, whose type graphs pull Node's globals into the browser — and it reaches the server over HTTP. - Add or update tests when changing a contract or architectural rule. - Record major architectural changes in an ADR rather than burying them in an implementation. Runtime registries and aggregate schemas will be produced by later phases. Do not add a manually maintained feature-module registry; discovery starts from validated `ekklesia.module.json` files.