feat(engagement): the template editor, the trigger catalog and the send log (engagement Phase 5b)
Phase 5a gave templates a table, a renderer and nine seeded rows; nothing could
change one. This is the screen that lets an operator change one without being able
to break the mail the system depends on — plus the two screens Q4 promised Phase 5:
Triggers (read-only, from the registries) and the Send Log, which closes G15.
The shape follows from one fact: a mail body is rendered by the SERVER, so the
preview is too, and framed rather than redrawn in React. A client-side renderer
would be a second implementation of the one artifact that matters, agreeing with
the send path on the day it was written and drifting from the first Outlook fix on.
Settled with the org lead before any code: a shipped default is edited IN PLACE
(`protected` blocks deletion and nothing else, `customized = 1` keeps the edit);
duplicate is the only way to a new template; `renderByKey` now requires
`published`; a test send is logged under a synthetic `core.admin.test-send`; and a
template a rule points at refuses deletion with a 409 naming the rules.
Three things the plan did not know, found by building it:
- The undeclared-variable check cannot be a token scan. `email.itemList.variable`
holds a BARE name, so a digest pointed at `itmes` would have saved clean and
arrived empty. Blocks now declare `variables(props)`; the editor makes that
field a select over the trigger's list variables so the typo is unavailable.
- A duplicate that drops `seed_key` loses its variable palette, so duplicating
`notify.event` would have been refused for the tokens it was copied with — the
one action §4.6.2 offers, refusing itself. The copy inherits it; `customized`
is what the seeder actually reads.
- `validateEmailBlocks` returns `{ valid, errors }`, not an array, and the first
version tested it with `.length` — so block validation never ran at all.
Also fixes a Phase 4a defect the live walk found, with the org lead's approval: a
rule's template key was checked against a pattern with no dot in it, so no rule
could name any template that exists — §4.6.2's whole duplicate-and-point-a-rule-at-it
workflow was unreachable. Both models now read one pattern.
Verified against the running stack: real multipart mail into a mailpit catcher
including an unsaved draft, the draft/published arms both ways through the real
mailer path, every refusal, and the end-to-end duplicate → rule → 409 walk.
Server 1428 tests green, client 324.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,9 @@ import InvitesAdmin from './routes/admin/views/InvitesAdmin.jsx'
|
||||
import ModulesAdmin from './routes/admin/views/ModulesAdmin.jsx'
|
||||
import EngagementRules from './routes/admin/views/EngagementRules.jsx'
|
||||
import EngagementAudiences from './routes/admin/views/EngagementAudiences.jsx'
|
||||
import EngagementTemplates from './routes/admin/views/EngagementTemplates.jsx'
|
||||
import EngagementTriggers from './routes/admin/views/EngagementTriggers.jsx'
|
||||
import EngagementSendLog from './routes/admin/views/EngagementSendLog.jsx'
|
||||
import TeamsAdmin from './routes/admin/views/TeamsAdmin.jsx'
|
||||
import AccountAdmin from './routes/admin/views/AccountAdmin.jsx'
|
||||
import Moderation from './routes/admin/views/Moderation.jsx'
|
||||
@@ -186,7 +189,7 @@ export default function App() {
|
||||
actions that publish a game-written name is applied per request
|
||||
on the server, from the caller's live role (TEAMS.md 2.9). */}
|
||||
<Route path="teams" element={<TeamsAdmin />} />
|
||||
{/* Engagement (ENGAGEMENT.md Phase 4b). Admin-only, matching the
|
||||
{/* Engagement (ENGAGEMENT.md Phases 4b and 5b). Admin-only, matching the
|
||||
server: every route under /admin/engagement re-gates to `admin`
|
||||
on top of the group's staff gate, because this is the group that
|
||||
decides who receives mail. */}
|
||||
@@ -201,6 +204,9 @@ export default function App() {
|
||||
<Route index element={<Navigate to="rules" replace />} />
|
||||
<Route path="rules" element={<EngagementRules />} />
|
||||
<Route path="audiences" element={<EngagementAudiences />} />
|
||||
<Route path="templates" element={<EngagementTemplates />} />
|
||||
<Route path="triggers" element={<EngagementTriggers />} />
|
||||
<Route path="sends" element={<EngagementSendLog />} />
|
||||
</Route>
|
||||
<Route path="account" element={<AccountAdmin />} />
|
||||
{/* Installed modules' admin pages, at /admin/<id>/…, already inside
|
||||
|
||||
Reference in New Issue
Block a user