feat(events): schedule, recurrence and the calendar (Phase 4) #186
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/events-phase-4"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Event System Phase 4 (
EVENTS_PLAN.md). Docs half: RunicGateway/docs#212.No schema change. Phase 1 built every column this needed —
timezone,series_id,series_order,grace_seconds,event_series.ordering, andUNIQUE (definition_id, scope, scheduled_for). Phase 4 fills them.Five decisions, settled 2026-09-02, all as recommended
detail.dstAdjusted, so nobody rediscovers daylight saving at 3am on the last Sunday in October. Neither rule ever drops an occurrence.nthis 1..4 plus-1for "last". There is deliberately no fifth: every month has a first through fourth of every weekday, so the closed set has no absent-occurrence case to define.-1is not a synonym for4— it is the shape a "last Friday" contest actually is, and a test walks three years of months to prove the claim rather than assert it in a comment.uq_evrun_occurrence, so the occurrence would not come back on the new version, it would vanish. A run that has begun keeps its pin for ever.Three things the build settled
now - graceis the window start, notnow. An occurrence nobody ever materialised is never invented retroactively — waking up after three days down must not manufacture three days ofmissedhistory no operator could have seen or cancelled. It does not need to: because rows exist a fortnight ahead of their instant, a real outage finds them already there and the sweep marks them honestly. The horizon is what makes the missed sweep mean anything for a recurring event — the two halves ofmaterialiseneed each other.readyas "a version has been published and the schedule is live", so a second enabled flag would be another answer to a questionstateanswers, and the two would eventually disagree. Expansion reads the published version's spec, never the working copy: a half-typedweeklyan author is midway through must not materialise anything.admin, editor, notadmin. Naming an arc is authoring; §N2's narrow gate is about committing the deployment to a run. The delete is a real delete and the only one in this feature — a series pins nothing,series_idisON DELETE SET NULL, and the response says how many definitions were detached, because that is the entire consequence of the act.No date library, and why
The server's dependency tree has none — no luxon, no date-fns, no tz package — and this does not add one. Node ships the full tzdata behind
Intl.DateTimeFormat, which is the same database a library would vendor a copy of and is already whatisTimezone()validates against.events/recurrence.jsformats an instant into a zone's wall clock and inverts that mapping by search; it is the one place an occurrence is computed, so the runner's expansion and the calendar's forecast cannot disagree about a date.Still no cron, for §E's reason: there is no parser in the tree, the only precedent is in the bot (another process), and a cron string is the one field an operator cannot proofread.
timeisHH:MM, weekdays are English names, and the schedule panel is a form with a preview line that reads the whole thing back in English.A defect this phase introduced into the test harness, and fixed
Putting the expansion leg in front of
tick()madeeventRunner.test.jsreach the dead-port pool on every tick — the file still passed and took minutes. StubbingfindSchedulablethere returned it to 0.45s. Worth naming because a suite that is merely slow reads as a suite that is fine.Verify
npm test— 1768 tests, 1711 pass, 56 skipped, 1 fail. The one failure isengagementManifest.test.js, pre-existing and environmental (CRLF undercore.autocrlf=true);edgebefore this branch is 1714/1662/51/1, and +54 is exactly the tests added here.eventRecurrence.test.js(17) is the DST fixture set this plan asked for — Berlin across both 2026 transitions, Lord Howe's thirty-minute gap, and Kolkata's half-hour no-DST offset.eventSchedule.test.js(16) covers expansion and the calendar;eventSeries.test.js(7); 7 ineventSpec.test.js; 5 ineventRunnerSql.test.jsprovingfindSchedulable,listInWindowandrepinScheduledagainst a real MariaDB (32/32 with a database, skipped without); 2 ineventsAdmin.test.js. Client: 354 pass, 7 new.npm run routes:manifestandnpm run swagger— four routes added, none moved.check:modulesandcheck:hostsclean. The client builds.The live walk, on the local review stack
Server + Vite against the
uomm-dbcontainer, asnavadminandnavmod:Europe/Berlindefinition published at 20:00 local materialised itself on the next tick:2026-09-04T18:00Zand2026-09-11T18:00Z— 20:00 CEST, both inside the horizon. Nobody pressed anything.Asia/Kolkata"last Friday" contest 23 days out appeared as a projection at14:00Z= 19:30 IST.18:00Zon 16 and 23 October and19:00Zfrom the 30th — the instant moves, the local clock stays 20:00.{"version": 2, "repinned": 2}, and both scheduled runs moved v1 → v2 while the screen showed "Berlin Friday Muster v2".navmodgot 200 on the calendar and the series read, 403 on series create, update and delete;navadmingot 201.navmod's screens correctly offered neither New event nor Series.kindand annth: 5were both refused with the message naming what is allowed. A 200-day calendar window was 400.{"ok": true, "detached": 1}and the definition survived without an arc.One defect the walk found, fixed here: in the List view the ←/→/Today stepper did nothing — the list always runs sixty days forward from now — so three controls were visibly present and inert. They are month-view only now.