import { defineModuleUi, type ModuleUiHostContext } from "@ekklesia/module-ui-sdk"; import { type ReactElement, useEffect, useRef } from "react"; import { createRoot } from "react-dom/client"; import { SignageSlate } from "../browser/slate.js"; import { createDisplayController } from "./controller.js"; import displayStyles from "./styles.css"; /** * Signage's full-viewport display. * * React renders the fixed furniture — the stage's root, the blackout cover, and the slate — and the * controller owns everything that moves. That split is deliberate: the two scene layers are created * once and never rebuilt, and a React tree that re-created them on every state change would be the * `v1` renderer again. * * The artifact performs no permission check of its own. The host resolved an authorization-bearing * descriptor before these bytes were ever fetched, and a television has nobody standing at it to act * on a denial — telling a lobby wall "you may not see this" would only report something about the * installation's authorization to whoever walks past. */ function SignageDisplay({ context }: { readonly context: ModuleUiHostContext }): ReactElement { const stageRef = useRef(null); useEffect(() => { const root = stageRef.current; if (!root) { return; } const controller = createDisplayController({ context, root }); void controller.start(); return () => { void controller.stop(); }; }, [context]); return (
{/* Shown only when the stage says there is nothing renderable *and* no good frame. */}
{/* A deliberate blackout: pure black over everything, while the hidden timeline runs on. */}
); } /** * The compiled full-viewport display artifact. It uses the same role-neutral browser ABI as the * management UI; its separate manifest entrypoint — not a mode flag — is what tells a host which * artifact it is mounting. */ export default defineModuleUi({ id: "signage", version: "0.1.3", styles: displayStyles, mount(container, context) { const root = createRoot(container); root.render(); return () => root.unmount(); }, });