Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ Classifies an Event by its time shape, and determines the Check-in Post timing r

- **Hourly** — fixed start and end time on a single day (e.g. 10am–2pm).
- **All-Day** — a full-day calendar entry where start date = end date.
- **Multi-Day** — a full-day calendar entry spanning a start date and a later end date (e.g. a competition). Deferred — not handled by v1.
- **Multi-Day** — a full-day calendar entry spanning a start date and a later end date (e.g. a competition). Gets no Event Check-in Post or attendance of its own — instead it generates a Multi-Day Child Event per day, each carrying real Attendance Tracking. See Multi-Day Child Event.
_Avoid_: "event type"

**Multi-Day Child Event**:
A synthetic, ordinary All-Day Event that Hawk Bot generates automatically for each calendar day of a Multi-Day Event's span, once that Multi-Day Event's Calendar Role is `team_meeting` — a 3-day regional generates three. This is the entire mechanism by which a multi-day competition gets real Attendance Tracking: each Multi-Day Child Event gets its own Event Check-in Post, its own Reaction Cutoff, its own Event Attendance Report — every mechanic identical to any other All-Day Event, with no new attendance logic anywhere. The Multi-Day Event itself (the "parent") is untouched by this — it still syncs, still gets a Meeting Type, and still appears exactly once in the Weekly Summary Post with its full date range; a Multi-Day Child Event never appears there itself, so the same competition is never listed more than once. If the parent's date span changes on a later sync, Multi-Day Child Events are added or marked removed to match, the same way an Event is normally created or removed — a Multi-Day Child Event whose attendance is already finalized is never retroactively altered by this. See ADR-0012.
_Avoid_: "sub-event", "day instance" (suggests calendar recurrence, which this isn't)

**Event Check-in Post**:
The single Slack message posted before an Event that both reminds the team of it and serves as the surface people react to for attendance. Includes the Event's title, date/time, location, description, and a short reminder of what each reaction means. Posted to the announcements channel with an `@channel` mention. The bot pre-populates it with 👍, a Clock Reaction, and ❌ so a team member can just click an existing reaction rather than pick their own emoji from scratch; picking something else entirely still works and is read the same way. The bot deletes it at the Reaction Cutoff, which finalizes attendance for that Event.
_Avoid_: pre-event reminder post, check-in post (as if separate from the reminder)

**Check-in Post Timing**:
How long before an Event its Check-in Post goes out. Set per Meeting Type (not per individual Event) by Hawk Bot admins. Starting defaults: Hourly posts 4 hours before the scheduled start; All-Day posts at 4pm the day before.
How long before an Event its Check-in Post goes out. Set per Meeting Type (not per individual Event) by Hawk Bot admins. Starting defaults: Hourly posts 4 hours before the scheduled start; All-Day posts at 4pm the day before. A Multi-Day Event's Check-in Posts (one per Multi-Day Child Event) all post together — admin-configurable days before the Multi-Day Event's first day (default 2), at the same time-of-day as the All-Day default — rather than each child posting the night before its own day, so the team can mark a whole competition's attendance in one sitting before they're busy at the venue.

**Calendar Change Handling**:
What happens when the Team Meeting Calendar changes after an Event's Check-in Post has already gone out. The original post is never deleted or reposted for an edit — the Event record behind it is simply updated (including a duration change), and any reactions already collected carry forward unchanged. What the bot does depends on the kind of change:
Expand Down Expand Up @@ -74,7 +78,7 @@ No link out to another surface — the detail lives entirely inside the thread r
Replaces "Slack workspace Owner/Admin" as the authorization model for every admin-gated capability (`/hawkbot config`, `/hawkbot event create`, the season CSV export, and the Reaction Cutoff Verification failure DM) — full replacement, not an additional either/or check. Defined by membership in a dedicated Slack User Group — not Slack's built-in Admins group — so a team can grant bot-admin duties (mentors, team leads) without handing out full Slack workspace administration. An admin configures it by handle (e.g. `hawkbot-admins`); the bot resolves that to the group's Slack-internal id once, at set-time, and checks membership against the id from then on. Slack workspace Owners always retain HawkBot Admin rights regardless of the group's state, as a bootstrap/lockout safety net — otherwise an empty or unconfigured group would mean nobody could run the command needed to fix it. Group membership is cached (same 5-minute-TTL shape as the old per-user admin cache), rather than looked up per check.

**Weekly Summary Post**:
A single Slack message posted to the announcements channel at an admin-configurable day/time (default Sunday, noon), listing every Event scheduled for the 7 days starting the day after that post — a rolling window, not aligned to any fixed Monday–Sunday calendar week (see ADR-0011) — a full planning digest, including Hourly, All-Day, and Multi-Day Events alike, regardless of whether an Event already has (or will soon have) its own Event Check-in Post. Only one Weekly Summary Post exists at a time: the previous one is deleted before the new one is posted. This is the first place Multi-Day Events are tracked as Events at all — nothing about their attendance mechanics changes; they're still deferred — this just makes them visible for planning.
A single Slack message posted to the announcements channel at an admin-configurable day/time (default Sunday, noon), listing every Event scheduled for the 7 days starting the day after that post — a rolling window, not aligned to any fixed Monday–Sunday calendar week (see ADR-0011) — a full planning digest, including Hourly, All-Day, and Multi-Day Events alike, regardless of whether an Event already has (or will soon have) its own Event Check-in Post. Only one Weekly Summary Post exists at a time: the previous one is deleted before the new one is posted. A Multi-Day Event appears here exactly once, as its own date-range line — its Multi-Day Child Events are never listed individually, so the same competition is never shown more than once.
_Avoid_: "this week" or "calendar week" implying Monday–Sunday alignment — "This Week" is the Slack-facing label, but the underlying window floats with whatever day the post lands on (see ADR-0011).

**Weekly Summary Change Reflection**:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Multi-Day Event attendance is per-day Child Events, not one combined post

A Multi-Day Event gets Attendance Tracking by generating a Multi-Day Child Event — an ordinary, synthetic All-Day Event — for each calendar day of its span. Each child gets its own Event Check-in Post, Reaction Cutoff, and Event Attendance Report, running through 100% of the existing All-Day machinery unmodified. The Multi-Day Event itself is untouched: it still syncs and still appears once in the Weekly Summary Post with its full date range, exactly as it does today; it simply never gets a Check-in Post or attendance of its own. See `CONTEXT.md`, Multi-Day Child Event.

The alternative considered first — one combined Check-in Post per competition, with a numbered-emoji reaction (1️⃣, 2️⃣, …) per day — was designed in detail before being set aside. It would have required a new day dimension on the `attendance` table (today's schema is strictly one row per `(event_id, user_id)`, with no way to represent "attended day 1 and 3 but not 2"), a new emoji vocabulary with a hard cap on supported days, an extension to `reactionCutoff()` (which today only ever looks at an Event's `startsAt`, never `endsAt`) to handle a multi-day span correctly, and a carve-out from ADR-0003's "👍 is the sole attendance signal" rule, since a single combined post has no single boolean signal to key off of. The per-day decomposition needs none of that — every one of those problems either doesn't apply or is already solved by existing single-day code.

**The trade being made**: this gives up the "one interaction per person per competition" goal that motivated the feature in the first place. A 3-day competition now produces three separate Check-in Posts instead of one. What keeps that from reintroducing the original problem — competition days already being busy — is that Check-in Post _timing_ is decoupled from Reaction Cutoff _timing_: every child's Check-in Post is posted together, an admin-configurable number of days before the competition's first day (default 2, see `checkin_offset_multiday_days`), but each child's Reaction Cutoff still lands independently at the end of its own day. Posting is batched for convenience; attendance-locking isn't, so a schedule change mid-competition can still register on a later day.

**Consequence**: one new nullable column, `events.multiday_parent_id`, linking a Multi-Day Child Event back to its parent — used both to exclude children from the Weekly Summary listing and to reconcile the child set when a competition's date span changes on a later sync (adding or removing children to match, never retroactively altering an already-finalized child). One new setting, `checkin_offset_multiday_days`. No other change to the attendance domain model.

Worth revisiting if three (or four) near-simultaneous, separately-`@channel`-tagged Check-in Posts turn out to feel spammy in practice, or if mentors want one consolidated Event Attendance Report per competition rather than one per day — both were explicitly deferred, not ruled out, since either can likely be layered on without touching this decomposition.
Loading