feat(events): the minimal admin surface (Phase 3)
Three screens, an Events nav group and the six live run controls Phase 1 left
absent on purpose because nothing was in flight. An admin can now author,
publish, start and watch an event that announces things and cues a human; a
moderator can stop one that is going wrong.
Six controls, not eight. `advance` is absent because a phase today advances when
its steps go terminal — the per-step skip already does that — and Phase 5 is what
gives a phase an advance condition. Cancel takes `{ reason }`, not `{ cleanup }`,
until Phase 8's ledger exists. Every control is a compare-and-set on the status it
may act from, so a console rendered thirty seconds ago cannot act on a run that
has moved.
Fixes a defect in the Phase 2 runner: `advanceRun` drained up to
EVENT_STEPS_PER_TICK steps while only checking the run's status at the top of the
tick, so a pause pressed mid-batch did nothing for up to 24 more steps.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6t8mrAWhZU5vnyYgZTMtL
This commit is contained in:
@@ -472,6 +472,45 @@ export const api = {
|
||||
setEngagementRetention: (body) =>
|
||||
req('/admin/engagement/retention', { method: 'PUT', body }),
|
||||
|
||||
// Events (docs/website/EVENTS.md, Phase 3). Reads are staff-wide; authoring
|
||||
// is admin+editor, publish and start are admin ONLY, and the six live
|
||||
// controls are admin+moderator — the one gate in this feature wider than
|
||||
// admin, because stopping a run at 2am is incident response and starting
|
||||
// one is not (§N2). The buttons follow the same split, and the server
|
||||
// re-checks every one of them.
|
||||
listEvents: (state) => req(`/admin/events${state ? `?state=${encodeURIComponent(state)}` : ''}`),
|
||||
getEvent: (id) => req(`/admin/events/${id}`),
|
||||
createEvent: (body) => req('/admin/events', { method: 'POST', body }),
|
||||
updateEvent: (id, body) => req(`/admin/events/${id}`, { method: 'PUT', body }),
|
||||
publishEvent: (id) => req(`/admin/events/${id}/publish`, { method: 'POST' }),
|
||||
archiveEvent: (id) => req(`/admin/events/${id}`, { method: 'DELETE' }),
|
||||
listEventVersions: (id) => req(`/admin/events/${id}/versions`),
|
||||
eventCatalog: () => req('/admin/events/catalog'),
|
||||
eventSeries: () => req('/admin/events/series'),
|
||||
startEventRun: (id, body) => req(`/admin/events/${id}/runs`, { method: 'POST', body }),
|
||||
listEventRuns: ({ definitionId, status, limit } = {}) => {
|
||||
const qs = new URLSearchParams()
|
||||
if (definitionId) qs.set('definitionId', String(definitionId))
|
||||
if (status) qs.set('status', status)
|
||||
if (limit) qs.set('limit', String(limit))
|
||||
const suffix = qs.toString()
|
||||
return req(`/admin/events/runs${suffix ? `?${suffix}` : ''}`)
|
||||
},
|
||||
getEventRun: (runId) => req(`/admin/events/runs/${runId}`),
|
||||
getEventRunLog: (runId, limit) =>
|
||||
req(`/admin/events/runs/${runId}/log${limit ? `?limit=${Number(limit)}` : ''}`),
|
||||
pauseEventRun: (runId, reason) =>
|
||||
req(`/admin/events/runs/${runId}/pause`, { method: 'POST', body: { reason } }),
|
||||
resumeEventRun: (runId) => req(`/admin/events/runs/${runId}/resume`, { method: 'POST' }),
|
||||
cancelEventRun: (runId, reason) =>
|
||||
req(`/admin/events/runs/${runId}/cancel`, { method: 'POST', body: { reason } }),
|
||||
confirmEventStep: (runId, stepId, note) =>
|
||||
req(`/admin/events/runs/${runId}/steps/${stepId}/confirm`, { method: 'POST', body: { note } }),
|
||||
skipEventStep: (runId, stepId, reason) =>
|
||||
req(`/admin/events/runs/${runId}/steps/${stepId}/skip`, { method: 'POST', body: { reason } }),
|
||||
retryEventStep: (runId, stepId) =>
|
||||
req(`/admin/events/runs/${runId}/steps/${stepId}/retry`, { method: 'POST' }),
|
||||
|
||||
// Teams (docs/website/TEAMS.md §2.11). Three of these mean something
|
||||
// different depending on who calls them: for a moderator, unhide and
|
||||
// setTeamDisplayName file a request and the response says `pending: true`.
|
||||
|
||||
Reference in New Issue
Block a user