Skip to content

Support multiple workouts in submission window open emails#385

Open
zacjones93 wants to merge 2 commits intomainfrom
claude/multiple-workouts-submission-email-XDluL
Open

Support multiple workouts in submission window open emails#385
zacjones93 wants to merge 2 commits intomainfrom
claude/multiple-workouts-submission-email-XDluL

Conversation

@zacjones93
Copy link
Copy Markdown
Contributor

@zacjones93 zacjones93 commented Apr 11, 2026

Summary

This PR enables sending a single "submission window opens" email when multiple workouts in the same competition share the same submission window, improving the user experience by reducing email volume for events with multiple concurrent workouts.

Key Changes

  • Updated sendWindowOpensNotification function to accept multiple competition event IDs and workout information instead of a single event/workout

    • Changed competitionEventId parameter to competitionEventIds: string[]
    • Changed workoutName/workoutDescription parameters to workouts: WorkoutInfo[]
    • Updated notification reservation logic to handle multiple events and only send email if at least one reservation succeeds
  • Enhanced email template (SubmissionWindowOpensEmail) to display multiple workouts

    • Added WorkoutInfo interface for structured workout data
    • Maintained backward compatibility with deprecated workoutName/workoutDescription props
    • Updated UI to show "X workouts" vs single workout with appropriate singular/plural text
    • Added visual separator (horizontal rule) between multiple workouts in the email
  • Refactored notification processing in processSubmissionWindowNotifications

    • Introduced WindowOpenGroup interface to group events by competition + submission open time
    • Events sharing the same submission window are now collected and sent as a single grouped email
    • Per-event notifications (closing/closed) remain unchanged and are processed separately
    • Moved time window calculations outside the event loop for efficiency
  • Updated tests to reflect new multi-event API

    • Added test case for sending single email with multiple workouts
    • Updated existing tests to use new parameter names and structures

Implementation Details

  • The grouping key for window-opens notifications is ${competitionId}::${submissionOpensAt}, ensuring events in the same competition with identical submission times are grouped together
  • Notification reservations are attempted for each event in a group; the email is sent if at least one succeeds
  • On email failure, all successful reservations are cleaned up to allow retry on the next cron run
  • The email subject line now lists all workout names joined by commas (e.g., "Submission Window Open: Workout 1, Workout 2 - Competition Name")

https://claude.ai/code/session_01NPjNVihbNaLUbBNU4ZETri


Summary by cubic

Send a single “submission window opens” email when multiple workouts in the same competition share the same window, listing all workouts in one message. Adds atomic reservation to prevent duplicate sends and groups by competition + open time + close time.

  • New Features

    • Group events by competition + submission open time + close time; send one email with all workouts. Closing/closed reminders stay per-event.
    • Updated SubmissionWindowOpensEmail to render multiple workouts with pluralized copy, dividers, and a pluralized CTA; subject lists all workout names.
    • Reserve notifications for all events in a group in one transaction; skip if any event was already reserved and clean up all reservations on send failure.
  • Migration

    • sendWindowOpensNotification now takes competitionEventIds: string[] and workouts: WorkoutInfo[] (replaces competitionEventId, workoutName, and workoutDescription).
    • SubmissionWindowOpensEmail supports workouts; single-workout props are still accepted but deprecated.

Written for commit 7c439ce. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Submission window opening emails now support multiple workouts in a single notification.
    • Email messaging automatically adjusts between singular and plural phrasing based on the number of events included.
  • Chores

    • Improved grouping of multiple events sharing the same submission window opening time.

When multiple workouts share the same submission window, send a single
email listing all workouts instead of separate emails per workout.
Groups events by (competitionId, submissionOpensAt) and consolidates
them into one notification with all workout names and descriptions.

https://claude.ai/code/session_01NPjNVihbNaLUbBNU4ZETri
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

Walkthrough

The pull request extends the submission window notification system to support multiple workouts per notification email. The email component now accepts an array of workouts with fallback to legacy single-workout properties. The server notification handler groups "window opens" events by competition and submission time, then sends one email per group listing all associated workouts.

Changes

Cohort / File(s) Summary
Email Component
apps/wodsmith-start/src/react-email/submission-window-opens.tsx
Added WorkoutInfo type; extended SubmissionWindowOpensProps with workouts?: WorkoutInfo[] array (legacy workoutName/workoutDescription marked deprecated); updated email rendering to be plural-aware with conditional singular/plural text, multi-event dividers, and per-workout sections.
Server Notification Handler
apps/wodsmith-start/src/server/notifications/submission-window.ts
Refactored sendWindowOpensNotification to accept competitionEventIds: string[] and workouts: WorkoutInfo[]; introduced grouping of "window opens" events by (competitionId, submissionOpensAt) in processSubmissionWindowNotifications; changed reservation strategy to attempt multiple reservations per group with transactional cleanup on email failure.
Tests
apps/wodsmith-start/test/server/notifications/submission-window.test.ts
Updated test cases to use pluralized inputs (competitionEventIds, workouts array); added new test validating multi-event behavior sends single email with multiple workout names; adjusted existing "already sent" scenario assertions.

Sequence Diagram(s)

sequenceDiagram
    participant Service as Notification Service
    participant DB as Database
    participant Grouper as Event Grouper
    participant Email as Email Renderer
    participant Mailer as Email Sender
    
    Service->>Grouper: processSubmissionWindowNotifications(events)
    Grouper->>Grouper: Group by (competitionId, submissionOpensAt)
    
    loop For each event group
        Grouper->>DB: Attempt reservation for each eventId
        DB-->>Grouper: Reservation results
        alt At least one succeeded
            Grouper->>Email: render with workouts[]
            Email-->>Grouper: HTML email
            Grouper->>Mailer: send email
            alt Email sent successfully
                Grouper->>Grouper: Continue
            else Email failed
                Grouper->>DB: Delete all reservations for group
                DB-->>Grouper: Deleted
            end
        else All failed
            Grouper->>Grouper: Skip (already sent)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #214: Directly related—introduces or modifies the same submission window notification and email features being extended in this PR to support multiple workouts per notification group.

Poem

🐰 Hoppy changes, bundled tight—
Multiple workouts in one email flight,
Grouped by time with plural might,
Reservations dance left and right,
Notifications now shine bright! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Support multiple workouts in submission window open emails' directly and concisely describes the main change: enabling the system to handle multiple workouts in a single submission window open email instead of separate emails.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/multiple-workouts-submission-email-XDluL

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 11, 2026

🚀 Preview Deployed

URL: https://wodsmith-app-pr-385.zacjones93.workers.dev

Detail Value
Commit 5fddc17
Stage pr-385
Deployed 2026-04-11T20:18:44.904Z

This comment is automatically updated on each push to this PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/wodsmith-start/test/server/notifications/submission-window.test.ts (1)

68-105: ⚠️ Potential issue | 🟡 Minor

Add // @lat: references to the updated specs.

These updated tests are missing the required spec-trace comments. Please add exactly one // @lat: [[section-id]] beside each relevant it(...) block.

As per coding guidelines, "Add // @lat: [[section-id]] comments in test code to tie source code to test specifications in lat.md" and "Place exactly one @lat: code reference comment next to the relevant test, not at the top of the file".

Also applies to: 107-147, 149-180, 182-221, 223-249

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/wodsmith-start/test/server/notifications/submission-window.test.ts`
around lines 68 - 105, The test block for "sends notification when reservation
succeeds (first time)" and the other listed it(...) blocks are missing the
required spec-trace comment; add exactly one comment line like // `@lat`:
[[section-id]] immediately beside each relevant it(...) declaration (e.g.,
directly after the it("sends notification when reservation succeeds (first
time)", ...) line) for the tests that reference sendWindowOpensNotification and
the other it blocks mentioned so each test has a single `@lat` reference tying it
to the spec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/wodsmith-start/src/server/notifications/submission-window.ts`:
- Around line 662-678: The grouping currently builds groupKey using
event.submissionOpensAt which collapses events with identical open times but
different close times; update the logic around windowOpenGroups and groupKey to
include event.submissionClosesAt (e.g., use
`${competition.id}::${event.submissionOpensAt}::${event.submissionClosesAt}`) or
alternatively assert that any event added to an existing group has the same
event.submissionClosesAt and reject/branch if it differs, and ensure the stored
group.submissionClosesAt comes from the grouping key (not the first-seen event)
so the correct deadline is used; apply the same change to the similar grouping
code near the second occurrence (the block referenced at lines ~804–805).
- Around line 251-270: The loop that calls reserveNotification over
competitionEventIds can result in race conditions; change it to perform a single
atomic reservation for the whole group using Drizzle ORM transactions so either
the entire set is reserved or none are. Wrap the reservations in
db.transaction() (or create a single group-lock row) and perform the per-event
reserve operations inside that transaction, using reserveNotification (or a new
groupReservation routine) and checking a single outcome (e.g., reservedEventIds
populated inside the transaction) before returning; reference
competitionEventIds, reservedEventIds, reserveNotification, and
SUBMISSION_WINDOW_NOTIFICATION_TYPES.WINDOW_OPENS when making the change. Ensure
the transaction returns a definitive boolean/owned list so only one process
proceeds to send the grouped email.

---

Outside diff comments:
In `@apps/wodsmith-start/test/server/notifications/submission-window.test.ts`:
- Around line 68-105: The test block for "sends notification when reservation
succeeds (first time)" and the other listed it(...) blocks are missing the
required spec-trace comment; add exactly one comment line like // `@lat`:
[[section-id]] immediately beside each relevant it(...) declaration (e.g.,
directly after the it("sends notification when reservation succeeds (first
time)", ...) line) for the tests that reference sendWindowOpensNotification and
the other it blocks mentioned so each test has a single `@lat` reference tying it
to the spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 28227d69-9db1-4640-8b59-e8d6f3f15c39

📥 Commits

Reviewing files that changed from the base of the PR and between 142c844 and 0413103.

📒 Files selected for processing (3)
  • apps/wodsmith-start/src/react-email/submission-window-opens.tsx
  • apps/wodsmith-start/src/server/notifications/submission-window.ts
  • apps/wodsmith-start/test/server/notifications/submission-window.test.ts

Comment thread apps/wodsmith-start/src/server/notifications/submission-window.ts Outdated
Comment thread apps/wodsmith-start/src/server/notifications/submission-window.ts
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/wodsmith-start/src/server/notifications/submission-window.ts">

<violation number="1" location="apps/wodsmith-start/src/server/notifications/submission-window.ts:268">
P1: Grouped window-open emails can be sent twice in concurrent runs because sending is allowed when only a subset of event reservations succeeds.</violation>

<violation number="2" location="apps/wodsmith-start/src/server/notifications/submission-window.ts:664">
P2: The grouping key omits `submissionClosesAt`, so events with different close times can be merged and emailed with the wrong close deadline.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/wodsmith-start/src/server/notifications/submission-window.ts
Comment thread apps/wodsmith-start/src/server/notifications/submission-window.ts Outdated
…osesAt in group key

Wrap grouped notification reservations in a db.transaction() so either all
events are reserved or none are, preventing duplicate emails from concurrent
cron runs. Also include submissionClosesAt in the grouping key so events with
different close times aren't merged into one email with the wrong deadline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/wodsmith-start/src/server/notifications/submission-window.ts">

<violation number="1" location="apps/wodsmith-start/src/server/notifications/submission-window.ts:279">
P1: Early-returning `[]` inside the transaction can commit partial reservations, causing some events to be marked as notified even though no grouped email was sent.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


if (existing) {
// This event was already reserved — another process owns this group
return []
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Early-returning [] inside the transaction can commit partial reservations, causing some events to be marked as notified even though no grouped email was sent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/wodsmith-start/src/server/notifications/submission-window.ts, line 279:

<comment>Early-returning `[]` inside the transaction can commit partial reservations, causing some events to be marked as notified even though no grouped email was sent.</comment>

<file context>
@@ -248,22 +248,49 @@ export async function sendWindowOpensNotification(params: {
+
+      if (existing) {
+        // This event was already reserved — another process owns this group
+        return []
+      }
+
</file context>
Fix with Cubic

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.

3 participants