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:
@@ -1164,11 +1164,24 @@ export default function EventEditor() {
|
||||
<span className="field-label">Storyline</span>
|
||||
<textarea className="input" rows={4} value={form.body} onChange={(e) => set({ body: e.target.value })} />
|
||||
</label>
|
||||
{/* Announcement, not permission. Publishing is what makes an event
|
||||
RUNNABLE, so without this switch a surprise invasion would have to be
|
||||
advertised a fortnight in advance in order to be allowed to happen. */}
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 14 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(form.listed)}
|
||||
onChange={(e) => set({ listed: e.target.checked })}
|
||||
/>
|
||||
<span className="field-label" style={{ margin: 0 }}>Show on the public calendar</span>
|
||||
</label>
|
||||
<p className="sans dim" style={{ fontSize: '0.78rem', margin: '10px 0 0' }}>
|
||||
The grace window is how late this event may still start: past it an occurrence becomes
|
||||
<em> missed</em> rather than beginning hours after it was announced. Two runs sharing a
|
||||
concurrency key never overlap — <code>{'{placeholders}'}</code> are filled from the run’s own
|
||||
params.
|
||||
params. An event that is <em>not</em> shown publicly still schedules, still runs and is
|
||||
still on this calendar — it is simply not announced, and its lifecycle mails carry no
|
||||
link because there is no page to link to.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user