Skip to content

fix: remove misspelled selector default and notify on monitor downgrade#259

Closed
bd73-com wants to merge 2 commits intomainfrom
claude/fix-github-issues-cz4fc
Closed

fix: remove misspelled selector default and notify on monitor downgrade#259
bd73-com wants to merge 2 commits intomainfrom
claude/fix-github-issues-cz4fc

Conversation

@bd73-com
Copy link
Owner

@bd73-com bd73-com commented Mar 23, 2026

Summary

Closes #254
Closes #253

Test plan

  • npm run check passes (TypeScript)
  • npm run test passes (1727 tests, 63 suites)
  • Open CreateMonitorDialog and verify the selector field is empty with placeholder text
  • Cancel a Pro subscription and verify a downgrade notification email is sent listing affected monitors

https://claude.ai/code/session_01EaiPVsbd2waiYqem5rzKSY

Summary by CodeRabbit

  • New Features

    • Users now receive email notifications when their monitoring tier is downgraded, listing the affected monitors.
  • Bug Fixes

    • Fixed the Create Monitor form's CSS selector field to initialize as empty instead of showing placeholder text.

…owngrade (#254, #253)

Clear the bogus "name of the selctor" default value from the selector
field so users see the placeholder instead. Add a tier-downgrade email
that lists which monitors were switched from hourly to daily when a
subscription is cancelled or downgraded.

Closes #254
Closes #253

https://claude.ai/code/session_01EaiPVsbd2waiYqem5rzKSY
@github-actions github-actions bot added the fix label Mar 23, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 23, 2026

Warning

Rate limit exceeded

@bd73-com has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 51 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7433f4e1-3bfc-44fd-b3b7-c9d30fc8d953

📥 Commits

Reviewing files that changed from the base of the PR and between 342de80 and c2c36ae.

📒 Files selected for processing (3)
  • server/services/email.test.ts
  • server/services/email.ts
  • server/webhookHandlers.test.ts
📝 Walkthrough

Walkthrough

This PR fixes a misspelled CSS selector placeholder in CreateMonitorDialog and adds email notifications when users' hourly monitors are downgraded during subscription tier changes. The storage layer return type is updated to include monitor names alongside downgrade counts, enabling the email service to notify users of affected monitors.

Changes

Cohort / File(s) Summary
CSS Selector Form Fix
client/src/components/CreateMonitorDialog.tsx
Changed defaultValues.selector from misspelled placeholder "name of the selctor" to empty string "", ensuring the field starts empty.
Tier Downgrade Email Service
server/services/email.ts
Added sendTierDowngradeEmail(userId, monitorNames) function (~60 lines) with email rate-limit checks, user lookup, sanitized HTML/text formatting, error handling, and logging via Resend API.
Storage Layer Signature Update
server/storage.ts, server/storage.test.ts
Changed downgradeHourlyMonitors() return type from Promise<number> to Promise<{ count: number; monitorNames: string[] }>. Updated query to return monitor names; tests updated to assert both count and name list.
Webhook Integration
server/webhookHandlers.ts, server/webhookHandlers.test.ts
Updated three subscription downgrade paths to destructure new return type, call sendTierDowngradeEmail(user.id, monitorNames) when monitors are downgraded, and log email failures without affecting downgrade flow. Mocks updated to match new signatures.

Sequence Diagram

sequenceDiagram
    participant Stripe as Stripe Webhook
    participant Handler as webhookHandlers
    participant Storage as DatabaseStorage
    participant Email as emailService
    participant Resend as Resend API

    Stripe->>Handler: Subscription downgrade event
    Handler->>Storage: downgradeHourlyMonitors(userId)
    Storage->>Storage: Query hourly monitors
    Storage-->>Handler: { count, monitorNames }
    
    alt count > 0
        Handler->>Email: sendTierDowngradeEmail(userId, monitorNames)
        Email->>Email: Check canSendEmail()
        Email->>Email: Lookup user & email
        Email->>Email: Build HTML/text content
        Email->>Resend: Send email
        Resend-->>Email: Response
        Email-->>Handler: { success, id } or { success: false, error }
        Handler->>Handler: Log email result
    end
    
    Handler-->>Stripe: Webhook processed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #255: Both PRs touch CreateMonitorDialog and the downgrade flow (storage signature and webhook integration), with this PR introducing email notification as part of the downgrade process.

Suggested labels

fix, feature

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes both main changes: fixing the misspelled selector default and adding tier downgrade notification.
Linked Issues check ✅ Passed The PR fully addresses both linked issues: removes the misspelled selector default (#254) and implements email notification on monitor downgrade (#253).
Out of Scope Changes check ✅ Passed All changes are directly aligned with the two linked issues; no out-of-scope modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-github-issues-cz4fc

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.

Copy link

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/services/email.ts`:
- Around line 437-442: The current check rejects when user.email is missing even
though user.notificationEmail may be present; change the validation to compute
recipientEmail = (user.notificationEmail || user.email) first and then validate
that recipientEmail exists and is non-empty (e.g., non-null/trimmed) before
proceeding (update the early return in server/services/email.ts accordingly),
and update the error message to say "User has no recipient email" or similar;
keep the monitorList creation using sanitizePlainText as-is.

In `@server/webhookHandlers.test.ts`:
- Around line 29-35: Add two Vitest test cases in webhookHandlers.test.ts that
exercise the mocked downgrade flow: (1) set the mock for downgradeHourlyMonitors
to resolve { count: N, monitorNames: [...] } with N>0 and assert that
sendTierDowngradeEmail was called once with the expected monitorNames (and any
other expected args); (2) set downgradeHourlyMonitors to resolve { count: 0,
monitorNames: [] } and assert that sendTierDowngradeEmail was not called. Use
the existing vi.mock hooks for downgradeHourlyMonitors and
sendTierDowngradeEmail, reset mocks between tests (vi.clearAllMocks or similar),
and call the webhook handler function that triggers these services so the
assertions verify the behavior.

In `@server/webhookHandlers.ts`:
- Around line 107-108: The call sites that await sendTierDowngradeEmail (e.g.,
the call using user.id and monitorNames around the Stripe handling and the two
other sites) only handle rejections but not a returned { success: false };
update each site to await the result into a variable (e.g., const res = await
sendTierDowngradeEmail(...)) and explicitly check res.success — if false, log an
error including the user id, monitorNames and any res.error/message; keep the
existing .catch for thrown errors or convert to try/catch and log thrown errors
similarly so both failure modes are recorded.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 4004753c-db4b-4927-bb50-0267597e4f5e

📥 Commits

Reviewing files that changed from the base of the PR and between e639b71 and 342de80.

📒 Files selected for processing (6)
  • client/src/components/CreateMonitorDialog.tsx
  • server/services/email.ts
  • server/storage.test.ts
  • server/storage.ts
  • server/webhookHandlers.test.ts
  • server/webhookHandlers.ts

Comment on lines +437 to +442
if (!user || !user.email) {
return { success: false, error: "User has no email address" };
}

const recipientEmail = user.notificationEmail || user.email;
const monitorList = monitorNames.map(n => ` • ${sanitizePlainText(n)}`).join("\n");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Recipient validation is too strict and can drop valid notification emails.

This branch rejects users without user.email even when user.notificationEmail is set. Validate the resolved recipient instead.

Proposed fix
-    const user = await authStorage.getUser(userId);
-    if (!user || !user.email) {
-      return { success: false, error: "User has no email address" };
-    }
-
-    const recipientEmail = user.notificationEmail || user.email;
+    const user = await authStorage.getUser(userId);
+    if (!user) {
+      return { success: false, error: "User not found" };
+    }
+
+    const recipientEmail = user.notificationEmail || user.email;
+    if (!recipientEmail) {
+      return { success: false, error: "User has no email address" };
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!user || !user.email) {
return { success: false, error: "User has no email address" };
}
const recipientEmail = user.notificationEmail || user.email;
const monitorList = monitorNames.map(n => ` • ${sanitizePlainText(n)}`).join("\n");
const user = await authStorage.getUser(userId);
if (!user) {
return { success: false, error: "User not found" };
}
const recipientEmail = user.notificationEmail || user.email;
if (!recipientEmail) {
return { success: false, error: "User has no email address" };
}
const monitorList = monitorNames.map(n => ` • ${sanitizePlainText(n)}`).join("\n");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/services/email.ts` around lines 437 - 442, The current check rejects
when user.email is missing even though user.notificationEmail may be present;
change the validation to compute recipientEmail = (user.notificationEmail ||
user.email) first and then validate that recipientEmail exists and is non-empty
(e.g., non-null/trimmed) before proceeding (update the early return in
server/services/email.ts accordingly), and update the error message to say "User
has no recipient email" or similar; keep the monitorList creation using
sanitizePlainText as-is.

Comment on lines +107 to +108
await sendTierDowngradeEmail(user.id, monitorNames).catch(err =>
console.error(`[Stripe] Failed to send downgrade email for user ${user.id}:`, err));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unsuccessful downgrade-email results are currently silent.

sendTierDowngradeEmail can return { success: false } without throwing, but these call sites only handle rejected promises. Log/handle unsuccessful results explicitly.

Proposed fix pattern (apply to all three call sites)
-          await sendTierDowngradeEmail(user.id, monitorNames).catch(err =>
-            console.error(`[Stripe] Failed to send downgrade email for user ${user.id}:`, err));
+          try {
+            const emailResult = await sendTierDowngradeEmail(user.id, monitorNames);
+            if (!emailResult.success) {
+              console.error(
+                `[Stripe] Failed to send downgrade email for user ${user.id}: ${emailResult.error ?? "unknown error"}`
+              );
+            }
+          } catch (err) {
+            console.error(`[Stripe] Failed to send downgrade email for user ${user.id}:`, err);
+          }

Also applies to: 153-154, 181-182

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

In `@server/webhookHandlers.ts` around lines 107 - 108, The call sites that await
sendTierDowngradeEmail (e.g., the call using user.id and monitorNames around the
Stripe handling and the two other sites) only handle rejections but not a
returned { success: false }; update each site to await the result into a
variable (e.g., const res = await sendTierDowngradeEmail(...)) and explicitly
check res.success — if false, log an error including the user id, monitorNames
and any res.error/message; keep the existing .catch for thrown errors or convert
to try/catch and log thrown errors similarly so both failure modes are recorded.

… error handling

- Add recordUsage() calls to sendTierDowngradeEmail for proper Resend cap tracking
- Add early return for empty monitorNames array
- Use fallback "(unnamed monitor)" for empty monitor names in email body
- Use String(error?.message ?? error) for safer error extraction
- Add test for empty monitorNames, downgrade email send, and no-send scenarios

https://claude.ai/code/session_01EaiPVsbd2waiYqem5rzKSY
@bd73-com bd73-com closed this Mar 23, 2026
@bd73-com bd73-com deleted the claude/fix-github-issues-cz4fc branch March 23, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Selector field pre-filled with misspelled placeholder text Bug: No user notification when monitors are downgraded from hourly to daily

2 participants