Skip to content

Reduce notification polling#436

Merged
Producdevity merged 6 commits into
stagingfrom
reduce-notification-polling
Jun 5, 2026
Merged

Reduce notification polling#436
Producdevity merged 6 commits into
stagingfrom
reduce-notification-polling

Conversation

@Producdevity

@Producdevity Producdevity commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Description

Reduces notification server load by removing the unused realtime/SSE notification path and making the navbar notification center less chatty.

The bell now keeps only the unread-count query active for signed-in users, with a 10-minute refresh interval while closed and no background list polling. The notification list query only runs while the dropdown or mobile sheet is open, then refreshes once per minute while visible. Opening the menu also refreshes the unread count so the badge stays reasonably current when a user interacts with it.

Notification writes no longer do extra unread-count lookups for realtime delivery that had no active client integration. Unused weekly and maintenance batching wrappers were removed as dead code, and immediate BOTH delivery now sends email when email delivery is enabled.

No linked issue.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactor
  • Other (please describe): Server load reduction

How Has This Been Tested?

  • Local build
  • Lint
  • Typecheck
  • Unit tests
  • Manual testing

Ran:

./node_modules/.bin/eslint src/data/constants.ts src/components/notifications/NotificationCenter.tsx src/server/notifications/service.ts src/server/notifications/batchingService.ts src/server/notifications/types.ts src/server/notifications/service.test.ts src/types/static-images.d.ts
./node_modules/.bin/vitest run src/server/notifications/service.test.ts
./node_modules/.bin/tsc --noEmit --pretty false
git diff --check

Screenshots (if applicable)

N/A

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have made corresponding changes to the documentation
  • I have checked that all checks (lint, typecheck, test) pass

Notes for reviewers

The removed realtime server path used an in-memory connection map and was not connected to the notification UI. Keeping polling, but reducing it to a single cheap badge refresh plus on-demand list loading, keeps the behavior predictable without adding always-open client connections.

@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
emuready Ready Ready Preview, Comment Jun 5, 2026 6:29pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d68e3487-7bae-400b-9dd0-efa321d3ccec

📥 Commits

Reviewing files that changed from the base of the PR and between fa7e7af and 050f8a0.

📒 Files selected for processing (1)
  • src/server/notifications/batchingService.ts

Walkthrough

This PR removes SSE realtime notification support and related hooks/routes, scopes frontend notification queries to the open UI, converts backend delivery to batched processing that treats IN_APP deliveries as immediate successes, and updates types, constants, and tests accordingly.

Changes

Notification System Architecture Migration

Layer / File(s) Summary
Configuration and Type Updates
src/server/notifications/types.ts, src/data/constants.ts, src/types/static-images.d.ts
Replace enableRealTimeDelivery with enableEmailDelivery; remove POLLING_INTERVALS.NOTIFICATIONS; add *.png StaticImageData declaration.
Frontend Polling Implementation
src/components/notifications/NotificationCenter.tsx
Notification queries are enabled only when UI is open, polling/refetch/stale settings tied to open state, handleToggleNotifications() centralizes open/invalidate logic, click handling uses explicit actionUrl navigation, and mobile AnimatePresence updated.
Batching Service Changes
src/server/notifications/batchingService.ts
NotificationBatchingService computes ready items, treats IN_APP/BOTH as immediately successful (Promise.resolve(true)), preserves email/DB status updates, starts internal timer in constructor, and no longer exports BatchConfig/class or a destroy() method.
Test Mock Cleanup and Additions
src/server/notifications/service.test.ts
Remove realtimeService mock, refactor emailService mock to use hoisted helper, reset mocks accordingly, and add a createNotification test asserting immediate email send and SENT status for BOTH with email enabled.
Service Routing & Creation Flow
src/server/notifications/service.ts
Record rate-limit entries before routing; immediate creations call deliverNotification(...); event-created notifications are staged, deduplicated, enriched, forced to IN_APP, and scheduled via batching when applicable.
Delivery Behavior Refactoring
src/server/notifications/service.ts
deliverInApp replaced by a parameterless success-returning call; email delivery is conditional on config; final deliveryStatus persisted from aggregated results; realtime unread-count updates removed.
Recipient Selection & Helpers
src/server/notifications/service.ts
Refactor recipient selection per event, apply actor-exclusion and banned/blocked filters, rewrite filterBannedUsers, preserve deduplication, and overhaul enrichContextWithData with Prisma lookups.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and accurately describes the main objective of the PR: reducing notification polling to lower server load.
Description check ✅ Passed The description comprehensively covers the PR's purpose, changes, testing performed, and rationale. All major template sections are addressed with appropriate detail.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 reduce-notification-polling
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch reduce-notification-polling

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.

@Producdevity Producdevity marked this pull request as ready for review June 5, 2026 13:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
src/server/notifications/batchingService.ts (2)

91-119: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't hide scheduling failures behind a void fire-and-forget call.

findMany() runs after this method returns, and .catch(console.error) drops the failure on the floor. If the user lookup fails, callers still see success even though no maintenance notices were queued. Make this method async, await the query, and let the error propagate so the caller can handle or surface it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/notifications/batchingService.ts` around lines 91 - 119, The
scheduleMaintenanceNotification method currently fires prisma.user.findMany()
without awaiting and swallows errors with .catch(console.error); change the
signature to async scheduleMaintenanceNotification(...): Promise<void>, await
the prisma.user.findMany(...) call, iterate users and call
this.scheduleNotification as before, and remove the .catch so any thrown error
propagates to the caller (or rethrow the caught error) so failures in
prisma.user.findMany or scheduling are not hidden.

95-113: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Match maintenance recipients to the channels you actually send.

This lookup only filters inAppEnabled, but every queued notification is sent as DeliveryChannel.BOTH. That will email users who only opted into in-app delivery, and it will miss users who enabled email but disabled in-app. Build the recipient list from both channel preferences and derive the channel per user before scheduling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/notifications/batchingService.ts` around lines 95 - 113, The
current query filters only notificationPreferences.some.inAppEnabled and then
always schedules with DeliveryChannel.BOTH, which mismatches user prefs; update
the query that builds the recipients (the block that selects
notificationPreferences for NotificationType.MAINTENANCE_NOTICE) to fetch both
inApp/email preference fields, compute per-user deliveryChannel (EMAIL, IN_APP,
or BOTH) from their notificationPreferences, and call this.scheduleNotification
with the derived deliveryChannel instead of always DeliveryChannel.BOTH; ensure
you reference NotificationType.MAINTENANCE_NOTICE and the scheduleNotification
call when making the change.
src/server/notifications/service.ts (1)

167-181: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix email delivery for immediate: true with DeliveryChannel.BOTH (or enforce it’s unsupported)
deliverNotification() only sends email when data.deliveryChannel === DeliveryChannel.EMAIL, while the batching path treats DeliveryChannel.BOTH as including email. There are currently no repo call sites using { immediate: true } with DeliveryChannel.BOTH, but the logic would silently drop email if that combination becomes possible.

Suggested alignment
     if (
       this.config.enableEmailDelivery &&
       this.emailService &&
-      data.deliveryChannel === DeliveryChannel.EMAIL
+      (data.deliveryChannel === DeliveryChannel.EMAIL ||
+        data.deliveryChannel === DeliveryChannel.BOTH)
     ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/notifications/service.ts` around lines 167 - 181,
deliverNotification() currently only sends email when data.deliveryChannel ===
DeliveryChannel.EMAIL which drops email for immediate deliveries when
deliveryChannel === DeliveryChannel.BOTH; update the logic in the
deliverNotification method of the notifications service to treat
DeliveryChannel.BOTH the same as EMAIL for immediate:true (i.e., include BOTH in
the condition that triggers sending email), or alternatively add an explicit
guard that throws/returns an unsupported error when immediate:true is passed
with DeliveryChannel.BOTH so the behavior is explicit; reference
DeliveryChannel, deliverNotification (or the method in the notifications service
where the shown diff lives) when making the change.
🧹 Nitpick comments (1)
src/server/notifications/service.ts (1)

99-101: 💤 Low value

Duplicate log statement.

notificationBatchingService.scheduleNotification already logs the same "Notification scheduled for batch processing" message (see batchingService.ts:66). Remove to avoid log noise.

Suggested fix
     const batchId = notificationBatchingService.scheduleNotification(data, scheduledFor)
-
-    console.log(`Notification scheduled for batch processing: ${batchId}`)
     return batchId
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/notifications/service.ts` around lines 99 - 101, Remove the
duplicate console.log in service.ts: after calling
notificationBatchingService.scheduleNotification(data, scheduledFor) drop the
line that logs "Notification scheduled for batch processing: ${batchId}" because
scheduleNotification already emits the same message; keep the call and the
returned batchId if needed, but avoid emitting the redundant log to reduce noise
(refer to notificationBatchingService.scheduleNotification for the existing
logging).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/server/notifications/batchingService.ts`:
- Around line 91-119: The scheduleMaintenanceNotification method currently fires
prisma.user.findMany() without awaiting and swallows errors with
.catch(console.error); change the signature to async
scheduleMaintenanceNotification(...): Promise<void>, await the
prisma.user.findMany(...) call, iterate users and call this.scheduleNotification
as before, and remove the .catch so any thrown error propagates to the caller
(or rethrow the caught error) so failures in prisma.user.findMany or scheduling
are not hidden.
- Around line 95-113: The current query filters only
notificationPreferences.some.inAppEnabled and then always schedules with
DeliveryChannel.BOTH, which mismatches user prefs; update the query that builds
the recipients (the block that selects notificationPreferences for
NotificationType.MAINTENANCE_NOTICE) to fetch both inApp/email preference
fields, compute per-user deliveryChannel (EMAIL, IN_APP, or BOTH) from their
notificationPreferences, and call this.scheduleNotification with the derived
deliveryChannel instead of always DeliveryChannel.BOTH; ensure you reference
NotificationType.MAINTENANCE_NOTICE and the scheduleNotification call when
making the change.

In `@src/server/notifications/service.ts`:
- Around line 167-181: deliverNotification() currently only sends email when
data.deliveryChannel === DeliveryChannel.EMAIL which drops email for immediate
deliveries when deliveryChannel === DeliveryChannel.BOTH; update the logic in
the deliverNotification method of the notifications service to treat
DeliveryChannel.BOTH the same as EMAIL for immediate:true (i.e., include BOTH in
the condition that triggers sending email), or alternatively add an explicit
guard that throws/returns an unsupported error when immediate:true is passed
with DeliveryChannel.BOTH so the behavior is explicit; reference
DeliveryChannel, deliverNotification (or the method in the notifications service
where the shown diff lives) when making the change.

---

Nitpick comments:
In `@src/server/notifications/service.ts`:
- Around line 99-101: Remove the duplicate console.log in service.ts: after
calling notificationBatchingService.scheduleNotification(data, scheduledFor)
drop the line that logs "Notification scheduled for batch processing:
${batchId}" because scheduleNotification already emits the same message; keep
the call and the returned batchId if needed, but avoid emitting the redundant
log to reduce noise (refer to notificationBatchingService.scheduleNotification
for the existing logging).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 527f31d3-6922-4626-be90-042bae7ab0b6

📥 Commits

Reviewing files that changed from the base of the PR and between 5b70a20 and b945858.

📒 Files selected for processing (9)
  • src/app/api/notifications/stream/route.ts
  • src/components/notifications/NotificationCenter.tsx
  • src/data/constants.ts
  • src/hooks/useRealtimeNotifications.ts
  • src/server/notifications/batchingService.ts
  • src/server/notifications/realtimeService.ts
  • src/server/notifications/service.test.ts
  • src/server/notifications/service.ts
  • src/server/notifications/types.ts
💤 Files with no reviewable changes (6)
  • src/app/api/notifications/stream/route.ts
  • src/hooks/useRealtimeNotifications.ts
  • src/data/constants.ts
  • src/server/notifications/service.test.ts
  • src/server/notifications/types.ts
  • src/server/notifications/realtimeService.ts

}

// Schedule weekly digest notifications
scheduleWeeklyDigest(userId: string): void {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

is this even used?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/server/notifications/batchingService.ts (1)

144-149: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use enum values instead of string literals for deliveryChannel comparison.

Lines 137-140 correctly use DeliveryChannel.IN_APP and DeliveryChannel.BOTH, but this block uses string literals 'EMAIL' and 'BOTH'. This is inconsistent and violates the coding guidelines.

Proposed fix
     if (
-      (data.deliveryChannel === 'EMAIL' || data.deliveryChannel === 'BOTH') &&
+      (data.deliveryChannel === DeliveryChannel.EMAIL || data.deliveryChannel === DeliveryChannel.BOTH) &&
       this.emailService
     ) {

As per coding guidelines: "Import enum values from @orm; do not use string literals for enum values in application code".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/notifications/batchingService.ts` around lines 144 - 149, Replace
the string-literal checks for deliveryChannel with the DeliveryChannel enum:
update the condition in the block that calls this.deliverEmail(data) to compare
data.deliveryChannel against DeliveryChannel.EMAIL and DeliveryChannel.BOTH
(imported from `@orm` if not already), ensuring consistency with other checks that
use DeliveryChannel.IN_APP and DeliveryChannel.BOTH; keep the call to
this.deliverEmail(data) and push to deliveryPromises unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/server/notifications/batchingService.ts`:
- Around line 144-149: Replace the string-literal checks for deliveryChannel
with the DeliveryChannel enum: update the condition in the block that calls
this.deliverEmail(data) to compare data.deliveryChannel against
DeliveryChannel.EMAIL and DeliveryChannel.BOTH (imported from `@orm` if not
already), ensuring consistency with other checks that use DeliveryChannel.IN_APP
and DeliveryChannel.BOTH; keep the call to this.deliverEmail(data) and push to
deliveryPromises unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e5248335-7c59-40dc-b1af-4269ecd4df4f

📥 Commits

Reviewing files that changed from the base of the PR and between f40a287 and 8bbfcee.

📒 Files selected for processing (3)
  • src/server/notifications/batchingService.ts
  • src/server/notifications/service.test.ts
  • src/server/notifications/service.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/server/notifications/service.ts

@Producdevity Producdevity merged commit a105781 into staging Jun 5, 2026
8 checks passed
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