Skip to content

feat(calendar): show campus events inside the academic calendar - #803

Draft
smmariquit wants to merge 1 commit into
stagingfrom
feat/calendar-events
Draft

feat(calendar): show campus events inside the academic calendar#803
smmariquit wants to merge 1 commit into
stagingfrom
feat/calendar-events

Conversation

@smmariquit

@smmariquit smmariquit commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Important

Scope note — this PR is not the registrar-milestone feature.

The maintainer's request ("acad calendar should have a view of events within that calendar") means the official OUR academic calendar milestones — registration, start of classes, dropping deadline, LOA deadline, exam periods, holidays, grade submission, graduation. This PR shows campus events (the events table: Feb Fair, testimonials, Lantern Parade). That is a different feature.

Left open as a draft because the pieces are reusable and campus events on the calendar may still be wanted: buildEventTimeline() is generic over { startsOn, endsOn } day keys, so the marker grid, cluster-with-count collision handling, boundary pinning, and the month-grouped list all work for milestones with no lib change. Registrar milestones are being planned separately.

Verification below is stale — do not trust the "live screen is empty" claim. The local .env DATABASE_URL used for the browser pass points at a stale dataset (7 rows / 4 active); production (/api/events) serves 11 rows, 8 active. Against real prod data the strip (AY 2026-2027, padded to 2026-08-01 – 2026-12-31) has 2 events in range — Gear UP 2026 (Jul 31 → Aug 1, kept by the overlap rule and pinned to the first day shown) and 🎉 The Kickoff Start (Aug 1, exactly on rangeStart, included, marker at the first grid cell) — both in one cluster dot, and 6 out of range. That was computed from the live prod payload, not re-screenshotted; screenshot work was stopped on instruction.

Unrelated data cleanup for someone: two active prod events are titled "Untitled campus event Jun 29, 2:27 AM" and "Untitled campus event Jul 22, 11:18 PM" — draft rows that will render as-is in any events list.


Why

"Acad calendar should have a view of events within that calendar." /calendar showed term windows only; campus events existed on the map and in the events list but not on the one screen a student opens to ask "what is happening this semester".

What

  • Markers on the year strip. Each event occurrence gets a dot on the same month-padded strip the term bars use (no second coordinate system — buildEventTimeline() takes the rangeStart/rangeEnd that buildYearTimeline() already returns).
  • A month-grouped list under the strip: title, campus-time range, category, recurrence label. Each row opens the existing event detail panel (queryStore + sidePanelStore, same call EventsList makes) and closes the calendar screen, so nothing about event display is duplicated.
  • No new data path. Events come from the app context (getAppData().events) that the events list and map already use.

Collisions

Markers snap to a fixed 5% grid (EVENT_MARKER_STEP_PCT). Everything inside one cell becomes one dot with a count, so a dense week can never stack. Because the step is fixed, two markers are always at least 5% of the strip apart — 5% of the narrowest strip we support (294px at a 320px viewport) is ~15px against a 12px dot. Measured with 60 synthetic events (route-stubbed /api/events, nothing committed):

viewport strip width markers min gap between dot edges dots outside strip doc overflow
1280 830px 17 29.50px 0 0
768 742px 17 25.09px 0 0
320 294px 17 2.69px 0 0

Positions are quantised to the cell center, so a marker can sit up to half a cell from the exact day; the tooltip and the list carry the real dates. Noted with a ponytail: comment plus the upgrade path (derive the step from a measured strip width) if day-level precision is ever wanted.

Recurrence and timezone

EventData.occurrenceStartsAt/EndsAt is already the projected occurrence for annual / every_1st_sem / every_2nd_sem (event-time.ts), so a yearly event lands on the year it next runs; the list labels it ("Repeats yearly", "Every 1st sem"). Day keys go through toManilaDateKey, display through formatCampusRange — Asia/Manila throughout, as before.

Edge cases

  • An event that started before the strip but runs into it is kept and pinned to the first day shown (overlap, not containment).
  • Events outside the academic year are not silently dropped: "N events fall outside AY X - Y." with a link to the full campus events list.
  • Past events stay in the calendar, just quieter (grey dot, muted row).
  • Empty state says so plainly instead of rendering an empty strip; it waits for appBootstrapStore.phase rather than appData.loaded (which flips true on mount with an empty dataset and would flash "no events" on every cold load).

Verify

  • bun run build:e2e, served dist/server/entry.mjs with .env, checked /calendar at 320 / 768 / 1280. No horizontal overflow at any width (documentElement.scrollWidth - clientWidth === 0, .acal-body likewise), no page errors (only the local _vercel/insights 404).
  • Clicking a row navigates to /event/<slug>/ with the existing event panel open and the calendar screen closed.
  • With today's production data the AY 2026-2027 strip is legitimately empty: the 4 active events run Jun 29 – Jul 4 2026 (previous AY), and 1st sem starts Aug 3 — so the screen shows the empty state plus "4 events fall outside AY 2026 - 2027". The Dec 15 lantern parade would land in December if it were flipped back to is_active.
  • bun run test (445 pass), bunx vitest run (167 pass), bunx biome check on touched files, bun run build.

Unit tests cover the placement maths and clustering in src/lib/academic-calendar.test.ts (grid centers, same-cell clustering, 60-event spacing invariant, overlap pinning, out-of-range counting, month grouping); component tests cover markers, counted clusters, the list, both empty/loading states and a 320px overflow guard.

Draft, and no run/e2e label — the serial CI queue is running and concurrent E2E runs exhaust the shared pooler.

The calendar screen only showed term windows, so students had no way to
see what is happening during the semester without leaving for the events
list. Place event occurrences on the existing year strip and list them by
month underneath, reusing the app-context events (no second fetch) and the
existing event detail panel.

Markers snap to a 5% grid on the strip, so events in the same week collapse
into one dot with a count instead of overlapping; adjacent markers stay a
full step (>= 15px at a 320px viewport) apart at any width. Recurring events
use the occurrence already projected by event-time.ts, events that overlap
the strip from before it are pinned to its first day, and events outside the
academic year are counted with a link to the full events list rather than
silently dropped.
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
saan-ang-room Ready Ready Preview, Comment Jul 26, 2026 6:52pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant