P-004 – Dynamic Event Scheduling
P-004 – Dynamic Event Scheduling
Purpose / Big Picture
The Events page listed events under a static “Upcoming” heading. On a static Jekyll site the build only happens at deploy, so an event’s date would go stale between deploys. This plan makes the split dynamic on the client: an event moves from Upcoming to Past the day after its scheduled date, Past events remain listed for a 30-day retention window, and older events are hidden — all without a rebuild, and without breaking the no-JavaScript baseline.
Context and Orientation
- Events are data-driven from
_data/events.yml, rendered bypages/events.md. assets/js/main.jsis loaded globally (_layouts/default.html,defer) and is reserved for progressive enhancement. It is a set of self-guarding IIFEs; the existing one early-returns unless[data-catalog-root]is present.- The site must remain fully usable with JavaScript disabled (AGENTS.md).
_config.ymlsetstimezone: Etc/UTC, so build-time date math is in UTC.
Scope
- Factor the event card markup into
_includes/event-card.html(used by both the Upcoming and Past loops; DRY). - Rewrite
pages/events.mdto render two sections — Upcoming and Past — split at BUILD time bysite.time, with a 30-day retention cutoff, plus data hooks (data-events,data-events-list,data-events-section,data-event-date,data-events-retention-days) and empty-state handling. - Add a second, self-guarded IIFE to
assets/js/main.jsthat, on each visit, re-buckets events by the visitor’s LOCAL current date, hides events older than the retention window, sorts each list, and toggles section/empty-state visibility. - Add minimal CSS in
_sass/_components.scss: spacing between the two sections and a[hidden]safety rule so hidden items leave the grid flow.
Design decisions
- Progressive enhancement. Jekyll renders a correct split at build time (no-JS users get a sensible, deploy-fresh view); JS refines it to the current date at view time. The categorization logic is intentionally duplicated in Liquid (build) and JS (runtime); keep the two in sync.
- Day-granularity, local time. Event dates are parsed as LOCAL midnight
(
new Date(y, m-1, d)) to avoid the UTC off-by-one thatnew Date("YYYY-MM-DD")causes. “Past” begins whenfloor((todayMidnight - eventDate)/day) >= 1. - Retention. Shown in Past while
daysSince <= 30; hidden at 31+. The window lives in one place per surface:data-events-retention-dayson the wrapper (JS) andretention_daysinpages/events.md(build); keep them equal. - No framework, no new file. The behavior is one small IIFE appended to the existing global script, guarded by its own container so it no-ops elsewhere.
Validation and Acceptance
jekyll buildcompletes without warnings;node --check assets/js/main.jspasses.- Build-time render places the seeded 2026-07-17 event under Upcoming, with the
Past section and the upcoming empty-state
hidden. - A headless-browser test with events at today, +7, −1, −15, −30, and −31 days confirms: today/+7 → Upcoming (soonest first); −1/−15/−30 → Past (most recent first); −31 → hidden; Past section revealed; upcoming empty-state hidden.
- With JS disabled, the build-time split remains correct and usable.
Outcomes & Retrospective
Implemented and validated as planned. The event card was extracted to
_includes/event-card.html; pages/events.md now renders build-time Upcoming
and Past sections; assets/js/main.js gained a self-guarded events IIFE; and
_sass/_components.scss got section spacing plus a [hidden] safety rule. The
headless-Chromium test passed every boundary case (day 0 upcoming, day 1 past,
day 30 shown, day 31 hidden), and the live single-event page rendered correctly
with JS active and no console errors.
Change Log
- 2026-07-10: Added client-side dynamic Upcoming/Past scheduling with a 30-day retention window as progressive enhancement over a build-time split.