feat(events): schedule, recurrence and the calendar (Phase 4)
The four closed recurrence shapes computed in the definition's own IANA zone, a fourteen-day materialisation horizon with projections beyond it, series as a managed thing, and the admin calendar that replaces the plugin this feature exists to replace. An event now happens on its own. No schema change: Phase 1 built every column this needed. - events/recurrence.js is the ONE place an occurrence is computed, so the runner's expansion and the calendar's forecast cannot disagree. No date library added — Node ships the tzdata one would vendor, behind Intl. - The runner's materialise leg is now two halves: expand, then sweep. The window starts at `now - grace`, so an occurrence nobody could have seen is never invented retroactively; the horizon is what makes the missed sweep mean anything for a recurrence. - Publishing is the schedule switch and archiving turns it off, and publishing re-pins every occurrence that has not started. - A projection is never drawn over an instant a run occupies, so a cancelled occurrence does not reappear as a forecast. 54 new tests, incl. the DST fixture set the plan asked for and three new statements proved against a real MariaDB. Suite 1768/1711/56 skipped/1 fail (pre-existing CRLF). Walked end to end on the local review stack. Docs: RunicGateway/docs#PENDING Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -20,9 +20,10 @@ import { runStatusWord, isTerminalRun } from '../../../lib/eventAuthoring.js'
|
||||
// comes from the run row itself (`waitingSteps`), so a run needs nobody to open
|
||||
// it before it can say so.
|
||||
//
|
||||
// What is NOT here: a calendar. Recurrence and the month view are Phase 4, and a
|
||||
// definition today can only carry `schedule: { kind: 'manual' }` — so the honest
|
||||
// list is a list, and the screen says as much rather than showing an empty grid.
|
||||
// **The calendar is a separate screen, not a third table here.** It answers
|
||||
// "when", this one answers "what" — and Phase 4, which built it, also made a
|
||||
// definition able to carry a recurrence, so the two questions stopped having the
|
||||
// same answer the moment an occurrence could exist before anybody pressed Start.
|
||||
|
||||
const STATE_WORD = { draft: 'Draft', ready: 'Ready', archived: 'Archived' }
|
||||
|
||||
@@ -108,8 +109,9 @@ export default function EventsAdmin() {
|
||||
<p className="sans" style={{ margin: 0, fontSize: '0.86rem', color: 'var(--muted)', maxWidth: 620 }}>
|
||||
Scheduled, bounded, audited changes to the live world. A definition is authored as a draft,
|
||||
published as an immutable version, and every occurrence of it runs against the version it
|
||||
pinned. Recurrence and the calendar arrive with the next phase — for now an occurrence is
|
||||
started by hand.
|
||||
pinned. A definition can repeat — once, weekly, or on the nth weekday of the month, in its
|
||||
own timezone — and the <Link to="/admin/events/calendar">calendar</Link> is where those
|
||||
occurrences are read.
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-end' }}>
|
||||
<label>
|
||||
@@ -121,6 +123,9 @@ export default function EventsAdmin() {
|
||||
<option value="archived">Archived</option>
|
||||
</select>
|
||||
</label>
|
||||
<Link className="pill" style={{ fontSize: '0.74rem' }} to="/admin/events/calendar">
|
||||
Calendar
|
||||
</Link>
|
||||
{mayAuthor && (
|
||||
<Link className="pill" style={{ fontSize: '0.74rem' }} to="/admin/events/new">
|
||||
New event
|
||||
|
||||
Reference in New Issue
Block a user