141 lines
7.3 KiB
Markdown
141 lines
7.3 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: 7
|
||
|
||
## Status
|
||
|
||
Version 0.1.1 is feature-complete for its declared scope and unreleased. What exists today:
|
||
|
||
- The module package, manifest (v7), 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.
|
||
|
||
Nothing here is deployed, and 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 against Ekklesia
|
||
|
||
Signage is developed **inside the authoritative Ekklesia workspace**, checked out at the path it will
|
||
eventually be installed at:
|
||
|
||
```bash
|
||
git clone <ekklesia> ekklesia
|
||
git clone <this repository> ekklesia/modules/signage
|
||
cd ekklesia
|
||
pnpm install
|
||
```
|
||
|
||
`modules/*` is an Ekklesia workspace glob, so the checkout is picked up with no registry edit,
|
||
no second copy of Ekklesia, and no vendored SDK. `@ekklesia/module-sdk`, `@ekklesia/module-ui-sdk`,
|
||
`@ekklesia/design-tokens`, `@ekklesia/ui`, and `@ekklesia/module-ui-build` all resolve through the
|
||
workspace as `workspace:*` dependencies.
|
||
|
||
Two consequences worth knowing:
|
||
|
||
- Ekklesia uses a shared workspace lockfile, so installing here writes an importer entry for
|
||
`modules/signage` into Ekklesia's `pnpm-lock.yaml`. That entry belongs to whoever maintains the
|
||
Ekklesia checkout; it is not committed from this repository.
|
||
- Signage requires the Ekklesia platform prerequisites for shared design tokens, module stylesheets,
|
||
browser contributions, bounded streaming request bodies, and display offline code
|
||
(ADRs 0039–0043). An Ekklesia checkout without them cannot build or validate this module.
|
||
|
||
### Commands
|
||
|
||
Run from the **Ekklesia repository root**:
|
||
|
||
```bash
|
||
pnpm --filter @ekklesia/signage build # tsc + the canonical module UI build
|
||
pnpm --filter @ekklesia/signage test # unit and browser-artifact tests
|
||
pnpm --filter @ekklesia/signage typecheck # node, browser, and browser-test projects
|
||
pnpm --filter @ekklesia/signage lint
|
||
|
||
pnpm boundaries # four-layer boundary enforcement
|
||
pnpm modules:validate # static manifest, entrypoint, and schema validation
|
||
pnpm migrations:validate # migration journal, policy, ordering, and checksums
|
||
pnpm verify # the full Docker-free gate
|
||
```
|
||
|
||
Signage's tests are its own: `pnpm test` here runs `vitest` against this package's `vitest.config.ts`,
|
||
which includes `.test.tsx` as well as `.test.ts`. The workspace-root config includes only `.test.ts`,
|
||
so a browser test added here without that config would be silently skipped.
|
||
|
||
Never run `pnpm migrations:apply` 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 --filter @ekklesia/signage 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; `pnpm migrations:validate` enforces that.
|
||
|
||
### 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.
|