Signage no longer needs an Ekklesia checkout. A clean clone installs with `pnpm install --frozen-lockfile` and runs lint, typecheck, test, build, and validate by itself, taking Ekklesia's module-development packages from Ekklesia's public registry (ADRs 0053 and 0054). - Dependencies: every `workspace:*` becomes `^0.2.0`. @ekklesia/module-pack is added as a devDependency pinned exactly to 0.2.0, as ADR 0053 requires; it is not invoked yet. No @ekklesia/contracts dependency. drizzle-orm stays a direct dependency, satisfying the adapter's optional peer. - .npmrc points the @ekklesia scope at https://git.riverofglorymn.org/api/packages/River_of_Glory_Church/npm/ with no credential, because reading needs none. - pnpm-workspace.yaml holds this repository's pnpm settings only: - `allowBuilds: esbuild: true`, as the platform's standalone fixture does; - exact-version minimumReleaseAgeExclude entries for the 0.2.0 train, which pnpm 11 otherwise holds back on its release day; - the platform's narrowly scoped override for GHSA-67mh-4wv8-2f99 (esbuild via drizzle-kit), which Signage inherited while it built inside Ekklesia's workspace. - engines.node `>=24.11.0 <25` and packageManager `[email protected].0`, both from Ekklesia 0.2.0. Node 24.18.0 goes in .node-version for CI, as in Ekklesia. - biome.json carries the platform's Biome configuration, which `biome lint` used to pick up from the workspace root, so lint rules are unchanged. - pnpm-lock.yaml is Signage's own: registry tarballs with integrity hashes, and no workspace or file links. - `prepack` runs this package's own build instead of the workspace root's turbo. - `pnpm validate` (scripts/validate-module.mjs) checks the built package and its migrations with the published SDK parsers only: - the manifest, its identity, and its entrypoints; - that the built runtime's id, version, and database ABI match the manifest; - that the built schema metadata is plain, namespaced, and has module-internal foreign keys; - the SDK compatibility range; - that the migration journal and policy agree. Core's own `modules:validate` and `migrations:validate` are not published. What only an Ekklesia installation can check is named in the script. Removed: ci/assemble.sh, ci/platform.env, ci/pnpm-lock.yaml, ci/update-lock.sh. The lint, typecheck, test, and build jobs check out only this repository; build also runs `pnpm validate`. The integration job stays disabled. Its first step is now an explicit placeholder rather than the deleted assembly, and its explanation says it returns when Signage is a canonical linux-x64-glibc Module Package installed into Ekklesia's generic image. No source, behaviour, schema, or version change: Signage stays 0.1.2. Co-Authored-By: Claude Opus 5.5 <[email protected]>
7.9 KiB
Signage
Ekklesia's visual programming, timeline, synchronization, and display-delivery module.
Signage decides what is on a screen, when, and on which screens together. It natively supports static images; every other kind of content reaches a screen because another Ekklesia module registered a content provider and contributed a live React renderer for it. Signage never learns what an announcement, a Bible passage, or a trivia question is.
This repository is a standalone, independently versioned Ekklesia module package. It is not a fork of Ekklesia and vendors none of its SDKs.
- Module id:
signage - Package:
@ekklesia/signage - Manifest version: 8, with the Module Database ABI v1 (
database: { abi: 1 })
Status
Version 0.1.2 is feature-complete for its declared scope, and Signage has run in production since 0.1.1. What exists today:
- The module package, manifest (v8), four capabilities, six permissions, ten operator settings, and one logical storage bucket.
- Eleven tables and a single initial migration, declared non-destructive.
- Native image ingest: one image per streamed request, 25 MiB, content-addressed, with display and thumbnail renditions and temporary object keys removed after any unsuccessful upload.
- The content provider contract and registry, a bounded and sanitized call path, and Signage's own built-in image provider registered through that same capability.
- Timelines, provider source slots, pinned slots, channel groups, synchronized audience channels, authoritative playback with absolute instants, activation and scheduling, and viewer registration, assignment, heartbeat, and health.
- A management-UI artifact and a full-viewport display artifact, both built through the canonical Ekklesia module UI pipeline and mounted through the platform's browser ABI.
- Two permanent scene layers with a crossfade that never fades the outgoing slide, provider renderers mounted as browser contributions, the built-in "Content unavailable" slate, deliberate blackout, and offline continuation from this display's own persisted state.
No cutover from the legacy Announcements container has happened or is planned by this repository. Video, audio, MQTT, Home Assistant, Companion, and a notification outbox are out of scope; so is any content module — Signage ships with no announcements, scripture, or trivia, and cannot render any until one is installed.
Offline cold start is not yet possible
A display that is already mounted keeps playing with no network: its schedule, playback state, provider payloads, and media are Signage's own, persisted by this module in IndexedDB and Cache Storage. Core's service worker caches module code, not Signage domain data.
A display that is restarted while Ekklesia is completely unreachable cannot mount Signage at all. Mounting requires an authenticated module descriptor, and Core does not yet cache one. True offline cold start depends on a Core-provided authorized offline display descriptor or lease; until that exists, this module does not claim it. See ADR 0006.
Developing Signage
Signage builds and tests on its own. You need no Ekklesia checkout and no Ekklesia account:
- Node.js in the range
engines.nodedeclares (>=24.11.0 <25);.node-versionnames the version CI uses. - pnpm at the version
packageManagerpins, through Corepack (corepack enable).
git clone <this repository> signage
cd signage
corepack enable
pnpm install --frozen-lockfile
Ekklesia's module-development packages (@ekklesia/module-sdk, @ekklesia/module-database,
@ekklesia/module-ui-sdk, @ekklesia/ui, @ekklesia/design-tokens, @ekklesia/module-ui-build,
@ekklesia/module-pack, @ekklesia/typescript-config) come from Ekklesia's public package registry.
The committed .npmrc points the @ekklesia scope at it, and reading it needs no token and no
login (platform ADRs 0053 and 0054); never add a credential to that file. Everything else comes from
the public npm registry. package.json takes the Ekklesia packages as ^0.2.0 ranges, except
@ekklesia/module-pack, which is pinned exactly; pnpm-lock.yaml pins every package's exact version
and integrity, so always install with --frozen-lockfile unless you mean to change a dependency.
pnpm-workspace.yaml holds this repository's own pnpm settings: it allows esbuild's install script,
exempts exactly the Ekklesia package train in the lockfile from pnpm's minimum release age, and keeps
one narrowly scoped advisory override. It does not make Signage part of any other workspace.
Signage requires Ekklesia 0.2: manifest version 8 and the Module Database ABI v1 (platform ADR 0050).
It reaches PostgreSQL only through the ABI v1 executor the platform hands it, with its own Drizzle
tables and the module-side adapter from @ekklesia/module-database/drizzle. Core receives SQL text,
bound values, and plain schema metadata, never a Drizzle object.
Commands
pnpm lint # Biome, with this repository's biome.json
pnpm typecheck # node, browser, and browser-test projects
pnpm test # unit, browser-artifact, and display contract tests (vitest.config.ts)
pnpm build # tsc, then the canonical module UI build of the primary and display artifacts
pnpm validate # after build: the built package and its migrations, as the platform reads them
CI runs exactly these, from a clean checkout.
pnpm validate uses only the published SDK parsers. It cannot check what only an Ekklesia
installation knows: compatibility with that installation, other installed modules' capabilities and
dependencies, or a migration's checksum against a database's applied history. Ekklesia checks those
when Signage is installed.
Never run a migration against a persistent database while developing this module. Signage reads no environment variable, constructs no database client, configures no object storage, hardcodes no Ekklesia URL, opens no socket of its own, and applies no migration: every one of those reaches it through the host.
Changing the schema
pnpm migration:generate # drizzle-kit, into drizzle/
tests/schema.test.ts pins the checksum of the generated SQL, so a regenerated migration fails the
suite until the pin is updated deliberately. drizzle/meta/_ekklesia.json declares the migration
non-destructive: tests/schema.test.ts asserts it, and Ekklesia's own migration validation enforces
it when Signage is packaged and installed.
Verifying what only a browser can show
Automated DOM assertions cannot prove a crossfade, a letterboxed image, or a slate. Those are verified by driving the built artifacts in a real Chromium against a stub host and reading the frames: native fill, both letterboxed fits, a contributed renderer, a frozen mid-crossfade composite, the slate, a blackout, the offline indicator, every management tab, and the narrow-screen gate. The harness is deliberately not committed — it stubs a host and would rot into a second, wrong description of the platform ABI.
What Signage owns, and what providers own
| Signage owns | A content provider owns |
|---|---|
| Native image assets and their renditions | Its own domain content and tables |
| Saved timelines, repeating patterns, and slots | Which of its content is available now |
| Channel groups and synchronized audience channels | Selecting the next item from one of its sources |
| Slot timing and authoritative playback | Repeat and no-repeat policy |
| Timeline activation and scheduling | Audience-specific variants and fallback |
| Viewer registration, assignment, heartbeat, health | Its own live React presentation |
| Transitions, offline continuation, playback controls | Dynamic change within one slide |
| The management and display UI | Its own internal multi-stage behaviour |
Licence and scope
Deployment, the module store, a theme editor, a mobile management UI, video and audio, MQTT, Home Assistant, Companion, and any migration of legacy Announcements data are explicitly out of scope for this repository.