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]>
147 lines
7.9 KiB
Markdown
147 lines
7.9 KiB
Markdown
# 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.node` declares (`>=24.11.0 <25`); `.node-version` names the
|
|
version CI uses.
|
|
- **pnpm** at the version `packageManager` pins, through Corepack (`corepack enable`).
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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.
|