import { useEffect, useState } from 'react' import { Loading, ErrorState } from '../../../components/PageState.jsx' import { api } from '../../../api/client.js' // Admin → Engagement → Triggers (ENGAGEMENT.md §4.3, Phase 5b). // // Read-only, and structurally so: **there is no table behind this screen.** A // trigger is DECLARED in code by core or by an installed module, so this is // whatever registered on the current boot. Uninstall a module and its triggers // stop appearing here; nothing was deleted and nothing needs to be. // // It exists because the two things it shows are otherwise invisible and both are // load-bearing elsewhere: // // • **The variables** are the contract a template may reference. When a rule // mails nothing sensible, "which variables does this event actually carry" // is the first question, and the answer used to live only in a module's source. // • **The ceiling** is the security boundary from G24 — the widest audience a // rule may ever give this trigger. A rule editor that offers a narrower set // than an operator expects is obeying a number declared here. const CEILING_NOTE = { owner: 'only the person the event is about', members: 'only members of the thing it is about', subscribers: 'only people who opted in', staff: 'only staff', admin: 'only administrators', authenticated: 'any signed-in account', everyone: 'anyone', } export default function EngagementTriggers() { const [triggers, setTriggers] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { let alive = true ;(async () => { try { const { triggers: list } = await api.admin.engagementTriggers() if (alive) setTriggers(list || []) } catch (err) { if (alive) setError(err.message) } finally { if (alive) setLoading(false) } })() return () => { alive = false } }, []) if (loading) return if (error) return return (

The events a rule can be built on, declared in code by core and by installed modules. This list is whatever is registered right now — it is not stored anywhere, so a module that is uninstalled simply stops appearing.

{triggers.length === 0 && (

Nothing is registered.

)} {triggers.map((t) => (

{t.label}

{t.id} · from {t.owner} · v{t.version}

Can reach at most
{t.ceiling} — {CEILING_NOTE[t.ceiling] || 'see the design document'}
{t.description && (

{t.description}

)} {(t.variables || []).length > 0 && ( {t.variables.map((v) => ( {/* `nowrap`: without it the "always set" pill wraps between its two words on a longer variable name, orphaning "set" on a line of its own and making the row read as two facts. */} ))}
Variable Type Example What it is
{`{{${v.name}}}`} {v.required && always set} {v.type} {/* A list variable's example is an array of objects; showing it as JSON is honest and short, and it is the shape an item list repeats over. */} {typeof v.example === 'string' ? v.example : JSON.stringify(v.example)} {v.description || ''}
)}
))}
) }