feat: add display kiosk host
CI / verify (pull_request) Successful in 38s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 59s

This commit is contained in:
ekklesia
2026-08-23 20:50:56 -05:00
parent be5ed7853d
commit 2e0fe059c3
80 changed files with 4868 additions and 397 deletions
+24 -13
View File
@@ -1,6 +1,6 @@
# Ekklesia module template
This is the smallest supported backend-only Ekklesia module. It proves the static manifest,
This is the smallest supported Ekklesia module product. It proves the static manifest,
compiled runtime and schema entrypoints, capability registration, a module-owned Drizzle table, a
generated module-owned migration, logical storage-bucket declarations, and one permissioned HTTP
route (`GET /records`, declared in `register`) that reads that table through
@@ -8,18 +8,25 @@ route (`GET /records`, declared in `register`) that reads that table through
module-constructed Drizzle instance or a raw `pg` client. Migration execution and storage
provisioning are intentionally not included.
It also ships an **optional** React UI (`src/ui/`), compiled to the `entrypoints.ui` artifact its
manifest declares by `pnpm build`
([ADR 0023](../../docs/architecture/decisions/0023-module-ui-build-pipeline.md)). UI is a choice, not
a requirement: to keep a module backend-only, delete `src/ui/`, `tsconfig.ui.json`,
`tsconfig.ui-test.json`, `tests/module-ui.test.ts`, the `ui` entrypoint from the manifest, and the
React and module-UI dependencies. Everything else keeps working, and `ekklesia-build-module-ui`
simply reports that there is nothing to build.
It also ships two independently optional React roles: a primary/management UI at `src/ui/index.tsx`
and a full-viewport display at `src/display/index.tsx`. One invocation of `pnpm build` compiles each
declared role to the exact artifact in `entrypoints.ui` and `entrypoints.display.artifact`
([ADR 0023](../../docs/architecture/decisions/0023-module-ui-build-pipeline.md)). Both default-export
the same role-neutral `defineModuleUi({ id, version, mount })` ABI and receive the same
`ModuleUiHostContext`; there is deliberately no mode flag. The manifest entrypoint and the host route
choose the role.
No host mounts a module UI yet — the Dashboard shell does not exist. `tests/module-ui.test.ts` builds
the artifact from source, validates it with `parseModuleUiExport`, mounts it into a real DOM, and
unmounts it, which is what proves the pipeline today. See
[Declaring a UI entrypoint](../../docs/module-development/README.md#declaring-a-ui-entrypoint).
Either role can be omitted independently. To remove only the display example, delete `src/display/`
and `entrypoints.display` (and remove `example.display.view` if nothing else uses it). To keep the
module backend-only, also delete `src/ui/`, `tsconfig.ui.json`, `tsconfig.ui-test.json`,
`tests/module-ui.test.ts`, the `ui` entrypoint, and the React/module-UI dependencies. The builder
succeeds with an empty artifact list when neither role is declared.
`tests/module-ui.test.ts` builds both artifacts from source, validates them with
`parseModuleUiExport`, mounts each into a real DOM, and unmounts it. Ekklesia mounts the primary UI
inside its Dashboard shell and serves the display without Dashboard chrome at the stable
`/display/example` page. See
[Declaring UI entrypoints](../../docs/module-development/README.md#declaring-ui-entrypoints).
To create a module inside this repository:
@@ -46,7 +53,11 @@ To create a module inside this repository:
root. The declared production entrypoint is the compiled `dist/server/index.js`, not source
TypeScript; the schema entrypoint is likewise compiled to `dist/schema/index.js`.
Do not add UI components, styles, renderers, navigation, or layouts. Those belong to surfaces.
The module owns its feature screens and display renderer. Do not add platform navigation, the
containing shell, global theme, authentication screens, or top-level routing; those belong to
surfaces and Core. A module UI asks its host for relative API paths and realtime state, and never
hardcodes Ekklesia routes. The display permission is enforced by the host before it serves the page
or descriptor; `context.permissions` remains a presentation-only hint.
Keep imports side-effect-free, register capabilities in `register`, acquire resources in `start`,
and release them in `stop`. Never construct a Drizzle instance, read `DATABASE_URL`, or reach for a
raw `pg` client — `context.services.database` is the only supported way to query this module's own