feat(events): the public calendar, event pages and participation history (Phase 14a)
The anonymous surface an event was always for: GET /public/events, /public/events/:slug and /public/events/series/:slug, plus GET /player/events/history, and the four screens over them. Four org-lead decisions taken up front: split Phase 14 into 14a (website) and 14b (the app); add a `listed` flag rather than letting `state` mean both schedulable and announced; put the `events` capability string in the version block rather than publishing core as a pseudo-module; and drop "venue" from the spec rather than adding a field nothing had ever built. `listed` is announcement, not permission. Publishing is what makes a definition runnable, so without a separate flag a surprise event would have to be advertised in order to be allowed to happen. It is a column, a switch in Phase 13's editor, and three SQL predicates -- never a filter applied after a read, which works exactly as well until the first caller that forgets. The public shapes are a projection, and the projection is the security boundary: nothing is spread, so a column added to event_runs next year does not ride out through it. The spec, health, cleanup, claims, errors and member_key are all absent by construction. The six public event triggers gained `eventUrl` (version 1 -> 2), carrying ?run= because the page lives at the definition's slug while every trigger is about one occurrence. notify.event-started gained the button, at seedVersion 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
@@ -283,21 +283,40 @@ test('six are ceilinged authenticated; run.failed is admin on both halves', () =
|
||||
}
|
||||
})
|
||||
|
||||
test('no public event trigger declares a url — there is no page for one to point at yet', () => {
|
||||
// `news.post` shipped an example naming `/news/<slug>`, a path that does not
|
||||
// exist, and the template editor previewed a link that was dead in every mail
|
||||
// it sent. Phase 14 adds the variable alongside the page.
|
||||
test('every public event trigger points at the page Phase 14a built, and run.failed at the console', () => {
|
||||
// The inverse of what this asserted from Phase 10 until Phase 14a, and the
|
||||
// inversion is the point: `news.post` shipped an example naming `/news/<slug>`,
|
||||
// a path that did not exist, so the template editor previewed a link that was
|
||||
// dead in every mail it sent. The variable was withheld until there was a page,
|
||||
// and it arrived with it.
|
||||
for (const t of eventTriggers()) {
|
||||
const urls = t.variables.filter((v) => v.type === 'url')
|
||||
if (t.id === 'event.run.failed') {
|
||||
// No `eventUrl` here, deliberately: an admin reading that the machinery
|
||||
// broke wants the steps and the errors, not the storyline.
|
||||
assert.deepEqual(urls.map((v) => v.name), ['runUrl'])
|
||||
assert.match(urls[0].example, /^\/admin\/events\/runs\//)
|
||||
} else {
|
||||
assert.deepEqual(urls, [], `${t.id} must declare no url until Phase 14`)
|
||||
assert.deepEqual(urls.map((v) => v.name), ['eventUrl'], `${t.id}`)
|
||||
// The example has to carry `?run=`, because that is what makes a link in a
|
||||
// mail about last Friday open last Friday rather than next Friday.
|
||||
assert.match(urls[0].example, /^\/site\/events\/[^?]+\?run=/, `${t.id}`)
|
||||
// Optional, so `email.button` drops itself rather than rendering an inert
|
||||
// grey label when the event is not public and there is no page.
|
||||
assert.equal(urls[0].required, false, `${t.id}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('the six public triggers are at version 2 — the url variable is a declaration change', () => {
|
||||
// A variable added to a declaration is a version bump, not a correction: a rule
|
||||
// written against version 1 was written against a payload with no link in it.
|
||||
// `run.failed` gained nothing and stays where it was.
|
||||
for (const t of eventTriggers()) {
|
||||
assert.equal(t.version, t.id === 'event.run.failed' ? 1 : 2, `${t.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('every declared variable carries an example, which is what the editor previews with', () => {
|
||||
for (const t of eventTriggers()) {
|
||||
for (const v of t.variables) {
|
||||
|
||||
Reference in New Issue
Block a user