Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughAdds Microsoft Graph types across Outlook/email utilities, tightens several function and prop typings, changes deleteDraft to ignore 404s (log warning), changes deleteFilter to return only status, refactors OutlookFolder to derive fields from MailFolder via a converter, adjusts several mutate/refetchPremium signatures, and enables a biome noExplicitAny warning. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as Caller
participant App as App Service
participant Graph as Microsoft Graph
Client->>App: deleteDraft(draftId)
App->>Graph: DELETE /me/messages/{draftId}
alt 204 No Content
Graph-->>App: 204
App-->>Client: resolves (logs success)
else 404 Not Found
Graph-->>App: 404
App-->>Client: resolves (logs warning)
else Other Error
Graph-->>App: 4xx/5xx Error
App-->>Client: throws (logs error)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (3)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
apps/web/utils/outlook/draft.ts (1)
26-36: Same 404 handling issue in deleteDraft.Check
statusCode === 404orcode === "ErrorItemNotFound"; otherwise we’ll rethrow on not-found.Apply this diff:
try { logger.info("Deleting draft", { draftId }); await client.getClient().api(`/me/messages/${draftId}`).delete(); logger.info("Successfully deleted draft", { draftId }); } catch (error) { - if (error instanceof Error && "code" in error && error.code === 404) { + const anyErr = error as { statusCode?: number; code?: string }; + if (anyErr?.statusCode === 404 || anyErr?.code === "ErrorItemNotFound") { logger.warn("Draft not found or already deleted, skipping deletion.", { draftId, }); } else { logger.error("Failed to delete draft", { draftId, error }); throw error; } }apps/web/utils/outlook/thread.ts (1)
10-15: Escape threadId in OData filter to avoid breaking queries.Quotes in conversationId will break the filter and can be abused. Use the existing escape helper.
Apply this diff:
const messages: { value: Message[] } = await client .getClient() .api("/me/messages") - .filter(`conversationId eq '${threadId}'`) + .filter(`conversationId eq '${escapeODataString(threadId)}'`) .orderby("receivedDateTime desc") .get();apps/web/utils/outlook/message.ts (2)
247-257: Pagination bug: hasMore computed from filtered messages, not raw page size.Because convertMessages filters out drafts, messages.length can be < maxResults even when more pages exist. Compute hasMore from response.value.length instead.
Apply this diff:
- // For non-search, calculate next page token based on message count - const hasMore = messages.length === maxResults; + // For non-search, calculate next page token based on raw page size + const hasMore = response.value.length === maxResults;
306-323: pageToken is ignored in getMessages.The function returns nextPageToken but never consumes options.pageToken, so callers can’t paginate. Support both raw skiptoken and a full @odata.nextLink URL.
Apply this diff:
let request = client .getClient() .api("/me/messages") .top(top) .select( "id,conversationId,conversationIndex,subject,bodyPreview,body,from,toRecipients,receivedDateTime,isRead,categories,parentFolderId,isDraft", ); if (options.query) { request = request.filter( `contains(subject, '${escapeODataString(options.query)}')`, ); } + // Respect pageToken: accept either a raw $skiptoken or a full @odata.nextLink + if (options.pageToken) { + let token = options.pageToken; + try { + const url = new URL(options.pageToken); + token = url.searchParams.get("$skiptoken") ?? options.pageToken; + } catch { + // Not a URL; treat as raw skiptoken + } + request = request.skipToken(token); + } + const response: { value: Message[]; "@odata.nextLink"?: string } = await request.get();apps/web/utils/outlook/filter.ts (3)
31-36: moveToFolder must be a folder ID, not the string "archive".Graph messageRules.actions.moveToFolder expects a folder ID. Using "archive" will fail at runtime. Fetch the Archive folder ID first.
Apply this diff (and helper):
+async function getArchiveFolderId(client: OutlookClient): Promise<string> { + const { id } = await client + .getClient() + .api("/me/mailFolders/archive") + .select("id") + .get(); + return id; +}export async function createFilter(options: { client: OutlookClient; from: string; addLabelIds?: string[]; removeLabelIds?: string[]; }) { const { client, from, removeLabelIds } = options; try { - // Create a mail rule that moves messages from specific sender + // Create a mail rule that moves messages from specific sender + const archiveFolderId = + removeLabelIds?.includes("INBOX") ? await getArchiveFolderId(client) : undefined; const rule: MessageRule = { displayName: `Filter for ${from}`, sequence: 1, isEnabled: true, conditions: { senderContains: [from], }, actions: { - moveToFolder: removeLabelIds?.includes("INBOX") ? "archive" : undefined, + moveToFolder: archiveFolderId, markAsRead: removeLabelIds?.includes("UNREAD") ? true : undefined, // Categories would need to be handled separately }, };
71-76: Same issue: use Archive folder ID for auto-archive rule.Apply this diff:
try { // For Outlook, we'll create a rule that moves messages to archive + const archiveFolderId = await getArchiveFolderId(client); const rule: MessageRule = { displayName: `Auto-archive filter for ${from}`, sequence: 1, isEnabled: true, conditions: { senderContains: [from], }, actions: { - moveToFolder: "archive", + moveToFolder: archiveFolderId, markAsRead: true, ...(labelName && { assignCategories: [labelName] }), }, };
211-222: Same moveToFolder ID issue in updateFilter.Apply this diff:
try { - const rule: MessageRule = { + const archiveFolderId = + removeLabelIds?.includes("INBOX") ? await getArchiveFolderId(client) : undefined; + const rule: MessageRule = { displayName: `Filter for ${from}`, sequence: 1, isEnabled: true, conditions: { senderContains: [from], }, actions: { - moveToFolder: removeLabelIds?.includes("INBOX") ? "archive" : undefined, + moveToFolder: archiveFolderId, markAsRead: removeLabelIds?.includes("UNREAD") ? true : undefined, }, };Also applies to: 224-227
🧹 Nitpick comments (5)
apps/web/utils/outlook/message.ts (1)
328-331: Standardize nextPageToken shape across APIs.queryBatchMessages returns the $skiptoken, but getMessages returns the full nextLink. Consider normalizing to always return the raw skiptoken for consistency.
apps/web/utils/outlook/filter.ts (2)
131-137: Harden duplicate detection using Graph error shape/status.String matching is brittle. Also check status 409 and common Graph error codes.
Apply this diff:
-function isFilterExistsError(error: unknown): boolean { - const errorMessage = (error as unknown as { message: string })?.message || ""; - return ( - errorMessage.includes("already exists") || - errorMessage.includes("duplicate") || - errorMessage.includes("conflict") - ); -} +function isFilterExistsError(error: unknown): boolean { + const anyErr = error as any; + const status = anyErr?.statusCode ?? anyErr?.status; + const code = anyErr?.code ?? anyErr?.error?.code; + const message: string = + anyErr?.message ?? anyErr?.error?.message ?? ""; + return ( + status === 409 || + code === "nameAlreadyExists" || + code === "ErrorItemAlreadyExists" || + /already exists|duplicate|conflict/i.test(message) + ); +}
153-171: Consider actually creating a rule that assigns the category.You already use actions.assignCategories elsewhere. Here you only create the category. Optionally add a rule to assign it for messages from the sender.
Example change:
const rule: MessageRule = { displayName: `Assign category ${categoryName} for ${from}`, sequence: 1, isEnabled: true, conditions: { senderContains: [from] }, actions: { assignCategories: [categoryName] }, }; await client.getClient().api("/me/mailFolders/inbox/messageRules").post(rule);Also applies to: 177-189
apps/web/utils/outlook/folders.ts (2)
70-75: Avoid non-null assertion when shifting from the queue.Comply with “Don’t use non-null assertions” guideline and keep types safe.
Apply this diff:
- const folderQueue = [...folders]; + const folderQueue: OutlookFolder[] = [...folders]; - while (folderQueue.length > 0) { - const folder = folderQueue.shift()!; + let folder: OutlookFolder | undefined; + while ((folder = folderQueue.shift())) {
108-113: Type the create-folder response.Keep typing consistent with Graph types.
Apply this diff:
- const response = await client.getClient().api("/me/mailFolders").post({ - displayName: folderName, - }); + const response: MailFolder = await client + .getClient() + .api("/me/mailFolders") + .post({ displayName: folderName });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
apps/web/utils/outlook/attachment.ts(1 hunks)apps/web/utils/outlook/draft.ts(3 hunks)apps/web/utils/outlook/filter.ts(9 hunks)apps/web/utils/outlook/folders.ts(6 hunks)apps/web/utils/outlook/label.ts(5 hunks)apps/web/utils/outlook/mail.ts(4 hunks)apps/web/utils/outlook/message.ts(4 hunks)apps/web/utils/outlook/thread.ts(6 hunks)biome.json(1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
!{.cursor/rules/*.mdc}
📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
biome.jsonapps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
biome.jsonapps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/outlook/attachment.tsapps/web/utils/outlook/mail.tsapps/web/utils/outlook/message.tsapps/web/utils/outlook/draft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/outlook/thread.tsapps/web/utils/outlook/filter.ts
🧬 Code graph analysis (4)
apps/web/utils/outlook/attachment.ts (1)
apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)
apps/web/utils/outlook/folders.ts (1)
apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)
apps/web/utils/outlook/label.ts (1)
apps/web/utils/email/microsoft.ts (1)
labelMessage(262-272)
apps/web/utils/outlook/thread.ts (1)
apps/web/utils/outlook/odata-escape.ts (1)
escapeODataString(12-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Secret Detection
- GitHub Check: Jit Security
- GitHub Check: Cursor Bugbot
🔇 Additional comments (15)
apps/web/utils/outlook/mail.ts (2)
1-1: LGTM on typed Message results.The added Message typings for create/forward/draft improve safety without changing behavior.
Also applies to: 50-53, 112-115, 209-212
46-48: conversationId is read-only on Message; verify this assignment is tolerated.Microsoft Graph treats conversationId as read-only; setting it on create may be ignored or rejected. Please confirm this doesn’t trigger 4xx responses and remove if unnecessary.
Would you like me to patch this by dropping the assignment and relying on Graph to thread the conversation automatically?
apps/web/utils/outlook/label.ts (2)
57-64: LGTM: typed category list shape.Explicit
{ value: OutlookCategory[] }typing and thenamederivation look good.
72-77: LGTM: direct OutlookCategory typing for get/create.Removing casts and relying on Graph types improves clarity.
Also applies to: 95-103
apps/web/utils/outlook/thread.ts (1)
77-78: LGTM on OData paging/types and narrowed message arrays.Typed OData shapes and consistent Message usage look good.
Also applies to: 102-109, 129-136, 159-159
biome.json (1)
27-27: LGTM: Appropriate linting policy changeChanging
noExplicitAnyfrom "off" to "warn" aligns well with the TypeScript improvements being made across the Outlook utilities. This will help catch future instances of untypedanyusage while not being overly strict by blocking builds.apps/web/utils/outlook/message.ts (2)
184-186: Typed nextLink handling is good.Explicitly typing the Graph page shape with optional "@odata.nextLink" is clear and safe.
208-215: Search branch response typing looks good.Consistent with the filter branch and facilitates token extraction.
apps/web/utils/outlook/filter.ts (5)
2-5: Stronger typing import is good.Using MessageRule and OutlookCategory from Graph types improves safety.
38-41: Response typing LGTM.Returning the created rule as MessageRule is appropriate.
78-81: Response typing LGTM.
114-117: Typed list response LGTM.{ value: MessageRule[] } matches Graph paging.
100-106: Verified no callers useresponse.data
AlldeleteFiltercall sites only inspectres.statusand never accessres.data, so omitting the data is safe.apps/web/utils/outlook/folders.ts (2)
29-36: LGTM on typed responses and mapping.Also applies to: 38-39
46-53: LGTM on typed responses and mapping for child folders.Also applies to: 55-56
| const response: Message = await client | ||
| .getClient() | ||
| .api(`/me/messages/${draftId}`) | ||
| .get(); |
There was a problem hiding this comment.
404 detection uses the wrong error property; use statusCode or Graph code string.
Graph errors expose statusCode (number) and code (string like "ErrorItemNotFound"). Comparing error.code === 404 will miss 404s and rethrow.
Apply this diff:
try {
- const response: Message = await client
+ const response: Message = await client
.getClient()
.api(`/me/messages/${draftId}`)
.get();
const message = convertMessage(response);
return message;
} catch (error) {
- if (error instanceof Error && "code" in error && error.code === 404)
- return null;
+ const anyErr = error as { statusCode?: number; code?: string };
+ if (anyErr?.statusCode === 404 || anyErr?.code === "ErrorItemNotFound") {
+ return null;
+ }
throw error;
}Also applies to: 17-19
🤖 Prompt for AI Agents
In apps/web/utils/outlook/draft.ts around lines 10-13 (and also update lines
17-19), the 404 detection compares error.code to numeric 404 which is incorrect;
Graph errors expose statusCode (number) and code (string like
"ErrorItemNotFound"). Change the error handling to detect 404s by checking
error.statusCode === 404 or by matching error.code === "ErrorItemNotFound" (or
both), then handle the not-found case instead of rethrowing; leave other errors
rethrown. Ensure both try/catch blocks use the same corrected detection.
| const escapedThreadId = threadId.replace(/'/g, "''"); | ||
| const messages = await client | ||
| const messages: { value: Message[] } = await client | ||
| .getClient() | ||
| .api("/me/messages") | ||
| .filter(`conversationId eq '${escapedThreadId}'`) | ||
| .get(); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove non-null assertion on message.id and select only needed fields.
Non-null assertions are disallowed in the guidelines and can mask data issues. Filter safely and request only id.
Apply this diff:
- const escapedThreadId = threadId.replace(/'/g, "''");
- const messages: { value: Message[] } = await client
+ const escapedThreadId = threadId.replace(/'/g, "''");
+ const messages: { value: Array<Pick<Message, "id">> } = await client
.getClient()
.api("/me/messages")
- .filter(`conversationId eq '${escapedThreadId}'`)
- .get();
+ .filter(`conversationId eq '${escapedThreadId}'`)
+ .select("id")
+ .get();
await Promise.all(
- messages.value.map((message) =>
- labelMessage({ client, messageId: message.id!, categories }),
- ),
+ messages.value
+ .filter((m): m is { id: string } => typeof m.id === "string")
+ .map((m) => labelMessage({ client, messageId: m.id, categories })),
);Optional: import and use escapeODataString instead of manual replace for consistency across modules.
Also applies to: 224-226
🤖 Prompt for AI Agents
In apps/web/utils/outlook/label.ts around lines 216-221 (and also apply same
change to 224-226), stop using non-null assertions on message.id and only
request the id field from Graph: use the client's OData select to request id
only (e.g., .select('id')) when fetching messages, remove any "!" assertions and
instead safely handle possible undefined ids (skip or filter out messages
without an id), and replace the manual threadId.replace(...) with
escapeODataString(threadId) for safer OData escaping (import escapeODataString
where needed).
There was a problem hiding this comment.
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 (3)
packages/resend/src/contacts.ts (2)
16-27: Same here: keep return type narrow and remove console useMirror the change for
deleteContactto avoidPromise<void | T>and adhere to logging guidelines.export async function deleteContact(options: { email: string; audienceId?: string; }) { - if (!resend) { - console.warn("Resend not configured"); - return; - } + if (!resend) { + throw new Error("Resend client not configured"); + } const audienceId = process.env.RESEND_AUDIENCE_ID || options.audienceId; if (!audienceId) throw new Error("Missing audienceId"); return resend.contacts.remove({ email: options.email, audienceId }); }
3-14: Throw when Resend client is not configured and remove console.warn
The earlyreturn;widens the return type toPromise<void | T>andconsole.warnviolates our no-console rule. Replace in bothcreateContactanddeleteContact:if (!resend) { - console.warn("Resend not configured"); - return; + throw new Error("Resend client not configured"); }apps/web/utils/email/microsoft.ts (1)
805-813: Escape single quotes in OData filter to prevent breakage/injection
options.fromcan contain'. Escape for OData.- .filter( - `from/emailAddress/address eq '${options.from}' and receivedDateTime lt ${options.date.toISOString()}`, - ) + .filter(() => { + const fromEsc = options.from.replace(/'/g, "''"); + return `from/emailAddress/address eq '${fromEsc}' and receivedDateTime lt ${options.date.toISOString()}`; + })
🧹 Nitpick comments (12)
apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx (2)
179-182: Remove any and biome-ignore by building params type-safelyAvoid
as anyand the lint suppression. Build URLSearchParams from typed entries.Apply within this block:
- url: `/api/threads?${ - // biome-ignore lint/suspicious/noExplicitAny: simplest - new URLSearchParams(query as any).toString() - }`, + url: `/api/threads?${toSearchParams(query)}`,Add this helper near the file bottom (or above
run()):function toSearchParams(q: ThreadsQuery): string { const params = new URLSearchParams(); for (const [k, v] of Object.entries(q)) { if (v !== undefined && v !== null && v !== "") params.set(k, String(v)); } return params.toString(); }
46-46: Type abortRef preciselyPrefer a nullable ref to avoid undefined initial value.
- const abortRef = useRef<() => void>(undefined); + const abortRef = useRef<(() => void) | null>(null);apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
256-265: Replace console.error with Sentry (project guideline: don’t use console)Capture errors instead of logging to console.
+import { captureException } from "@sentry/nextjs"; @@ - console.error(res); + captureException(new Error(res.serverError ?? "updateRuleAction failed")); @@ - console.error(res); + captureException(new Error(res.serverError ?? "createRuleAction failed"));Also applies to: 288-295
apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx (1)
3-3: Avoid namespace import for Sentry (house style)Switch to named import to comply with “no namespace imports”.
-import * as Sentry from "@sentry/nextjs"; +import { captureException } from "@sentry/nextjs"; @@ - useEffect(() => { - Sentry.captureException(error); - }, [error]); + useEffect(() => { + captureException(error); + }, [error]);Also applies to: 12-14
apps/web/utils/email/microsoft.ts (1)
462-468: Add explicit return types to avoid implicit any driftDocument the shapes returned by the Outlook filter helpers.
+import type { MessageRule } from "@microsoft/microsoft-graph-types"; @@ - async createFilter(options: { + async createFilter(options: { from: string; addLabelIds?: string[]; removeLabelIds?: string[]; - }) { + }): Promise<{ status: number; data?: MessageRule }> { return createFilter({ client: this.client, ...options }); } @@ - async createAutoArchiveFilter(options: { from: string; labelName?: string }) { + async createAutoArchiveFilter(options: { from: string; labelName?: string }): Promise<{ status: number; data?: MessageRule }> { return createAutoArchiveFilter({ client: this.client, from: options.from, labelName: options.labelName, }); } @@ - async deleteFilter(id: string) { + async deleteFilter(id: string): Promise<{ status: number }> { return deleteFilter({ client: this.client, id }); }Also applies to: 470-476, 478-480
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (1)
110-111: Remove any-cast; serialize params in a typed wayAvoid the biome-ignore and any by constructing URLSearchParams explicitly, including array keys.
- // biome-ignore lint/suspicious/noExplicitAny: simplest - const urlParams = new URLSearchParams(params as any); + const urlParams = toNewsletterStatsSearchParams(params);Add helper (outside this hunk), typed to NewsletterStatsQuery:
function toNewsletterStatsSearchParams(params: NewsletterStatsQuery) { const sp = new URLSearchParams(); params.types?.forEach((t) => sp.append("types", t)); params.filters?.forEach((f) => sp.append("filters", f)); if (params.orderBy) sp.set("orderBy", params.orderBy); if (params.limit != null) sp.set("limit", String(params.limit)); if (params.includeMissingUnsubscribe != null) { sp.set("includeMissingUnsubscribe", String(params.includeMissingUnsubscribe)); } if ((params as any).from) sp.set("from", String((params as any).from)); if ((params as any).to) sp.set("to", String((params as any).to)); return sp; }apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts (1)
4-4: Standardizemutatereturn type toPromise<void>across bulk-unsubscribe
Change all occurrences ofmutate: () => Promise<any>;(in types.ts:26, hooks.ts:607, BulkActions.tsx:26) to
mutate: () => Promise<void>;and remove the
// biome-ignore lint/suspicious/noExplicitAnysuppression.apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx (1)
25-26: Replace any with a safer return type; drop the ignoreIf callers pass SWR’s mutate directly, prefer Promise here to satisfy noExplicitAny without forcing wrappers.
- // biome-ignore lint/suspicious/noExplicitAny: lazy - mutate: () => Promise<any>; + mutate: () => Promise<unknown>;Alternative (preferred long-term): switch to Promise and wrap at call site:
// <BulkActions mutate={async () => { await mutate(); }} />apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx (1)
21-21: LGTM on refetchPremium typing; consider decoupling type importTightening to Promise<UserResponse | null | undefined> looks good. Optional: re-export UserResponse from a domain types module to avoid coupling UI to route types.
Also applies to: 73-74, 163-164, 227-228
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (3)
132-140: Reduce refetch storms: call mutate once per bulk actionYou’re calling mutate for each item in bulk loops. For SWR this triggers multiple revalidations. Prefer updating all items, then a single mutate at the end.
Example sketch:
for (const item of items) { // do work without mutate } await mutate();For archive/delete helpers that accept onFinish, collect completions and mutate once when all settle.
Also applies to: 302-311, 479-483, 578-583
604-607: Unify mutate type in shortcuts; remove any + ignoreMake mutate here match the rest of the file (Promise) and drop the ignore. If the caller passes SWR’s mutate, wrap it there to return void.
- refetchPremium: () => Promise<UserResponse | null | undefined>; + refetchPremium: () => Promise<UserResponse | null | undefined>; hasUnsubscribeAccess: boolean; - // biome-ignore lint/suspicious/noExplicitAny: simplest - mutate: () => Promise<any>; + mutate: () => Promise<void>;Supporting call site (in BulkUnsubscribeSection):
useBulkUnsubscribeShortcuts({ // ... mutate: async () => { await mutate(); }, });
336-343: Optional API ergonomics: make refetchPremium optional in useAutoArchiveApproveButton passes a no-op refetch. Consider making refetchPremium optional and guarding its call to simplify usage.
-export function useAutoArchive<T extends Row>({ ..., refetchPremium, ... }:{ +export function useAutoArchive<T extends Row>({ ..., refetchPremium, ... }:{ // ... - refetchPremium: () => Promise<UserResponse | null | undefined>; + refetchPremium?: () => Promise<UserResponse | null | undefined>;And guard:
await refetchPremium?.();
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(4 hunks)apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx(4 hunks)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts(12 hunks)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts(2 hunks)apps/web/scripts/addUsersToResend.ts(1 hunks)apps/web/utils/email/microsoft.ts(8 hunks)apps/web/utils/outlook/filter.ts(9 hunks)packages/resend/src/contacts.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/utils/outlook/filter.ts
🧰 Additional context used
📓 Path-based instructions (17)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/scripts/addUsersToResend.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.tsapps/web/utils/email/microsoft.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
!{.cursor/rules/*.mdc}
📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/scripts/addUsersToResend.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxpackages/resend/src/contacts.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.tsapps/web/utils/email/microsoft.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/scripts/addUsersToResend.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxpackages/resend/src/contacts.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.tsapps/web/utils/email/microsoft.ts
apps/web/app/(app)/*/**
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts
apps/web/app/(app)/*/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
If you need to use onClick in a component, that component is a client component and file must start with 'use client'
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/app/(app)/*/**/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
If we're in a deeply nested component we will use swr to fetch via API
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
apps/web/app/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Components with
onClickmust be client components withuse clientdirective
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/scripts/addUsersToResend.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxpackages/resend/src/contacts.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.tsapps/web/utils/email/microsoft.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/scripts/addUsersToResend.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxpackages/resend/src/contacts.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.tsapps/web/utils/email/microsoft.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.
Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{html,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...Files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsxapps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using ZodFiles:
apps/web/scripts/addUsersToResend.tspackages/resend/src/contacts.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.tsapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.tsapps/web/utils/email/microsoft.tsapps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logicFiles:
apps/web/utils/email/microsoft.tsapps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle sizeFiles:
apps/web/utils/email/microsoft.ts🧠 Learnings (5)
📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/security-audit.mdc:0-0 Timestamp: 2025-07-20T09:00:41.968Z Learning: Applies to apps/web/app/api/**/*.{ts,js} : Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx📚 Learning: 2025-07-08T13:14:03.250Z
Learnt from: elie222 PR: elie222/inbox-zero#537 File: apps/web/app/api/user/stats/newsletters/route.ts:235-239 Timestamp: 2025-07-08T13:14:03.250Z Learning: The user prefers proper TypeScript error type checking (using `error instanceof Error`) over type assertions with `as any` for better type safety and code quality.Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/server-actions.mdc:0-0 Timestamp: 2025-07-18T17:27:58.249Z Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR PR: elie222/inbox-zero#0 File: apps/web/CLAUDE.md:0-0 Timestamp: 2025-07-18T15:04:30.467Z Learning: Applies to apps/web/hooks/**/*.{ts,tsx} : Call `mutate()` after successful mutations to refresh dataApplied to files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/fullstack-workflow.mdc:0-0 Timestamp: 2025-07-18T15:05:16.146Z Learning: Applies to apps/web/hooks/**/*.ts : Call `mutate()` after successful mutations to refresh SWR data on the client.Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkActions.tsx🧬 Code graph analysis (6)
apps/web/scripts/addUsersToResend.ts (1)
packages/resend/src/contacts.ts (1)
createContact(3-14)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx (1)
apps/web/app/api/user/me/route.ts (1)
UserResponse(7-7)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/types.ts (1)
apps/web/app/api/user/me/route.ts (1)
UserResponse(7-7)apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
apps/web/utils/actions/rule.validation.ts (1)
CreateRuleBody(159-159)apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (1)
apps/web/app/api/user/me/route.ts (1)
UserResponse(7-7)apps/web/utils/email/microsoft.ts (2)
apps/web/utils/email/google.ts (3)
draftEmail(256-276)createFilter(415-421)createAutoArchiveFilter(423-432)apps/web/utils/outlook/filter.ts (2)
createFilter(14-51)createAutoArchiveFilter(53-91)⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Jit Security
🔇 Additional comments (8)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
1038-1038: Typed form errors look good
FieldErrors<CreateRuleBody>is the right shape for RHF + Zod.
1380-1383: Good UX fallback for delay errorProviding "Invalid delay value" when the message is missing improves clarity.
apps/web/app/(app)/[emailAccountId]/assistant/rule/[ruleId]/error.tsx (1)
7-11: LGTM: stricter Error typingNicer than
any, aligns with the “no explicit any” effort.apps/web/utils/email/microsoft.ts (4)
1-1: Importing Message type is fineType import aligns with Graph usage.
406-409: Robust label fallbacksUsing
label.idas a fallback prevents empty names/ids from leaking.Also applies to: 418-421
298-302: Remove unnecessary shape verification
ThedraftEmailhelper returns a GraphMessageobject withidat the top level, so accessingresult.idis correct.
439-453: No change needed: Outlook addLabelIds uses category names
Outlook’s assignCategories field returns category NAMES and the Outlook adapter passes these directly into its createFilter call—which expects category names—so the current mapping is correct.apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/hooks.ts (1)
18-19: LGTM: consistent refetchPremium/mutate typings across hooksThe new signatures are consistent and improve safety.
Also applies to: 28-29, 57-58, 116-119, 170-172, 204-207, 290-292, 341-343, 371-372, 471-472, 570-571, 604-605
| // biome-ignore lint/suspicious/noExplicitAny: lazy | ||
| mutate?: (data?: any, options?: any) => void; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Eliminate noExplicitAny on mutate by using the hook’s actual type
Use the mutate type from useRule instead of any.
- // biome-ignore lint/suspicious/noExplicitAny: lazy
- mutate?: (data?: any, options?: any) => void;
+ mutate?: ReturnType<typeof useRule>["mutate"];📝 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.
| // biome-ignore lint/suspicious/noExplicitAny: lazy | |
| mutate?: (data?: any, options?: any) => void; | |
| - // biome-ignore lint/suspicious/noExplicitAny: lazy | |
| mutate?: ReturnType<typeof useRule>["mutate"]; |
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx around lines
136-137, the prop type for mutate is declared as any with a biome-ignore
comment; replace this with the actual mutate type exported/returned by the
useRule hook (import or derive the type from useRule's return type), update the
mutate prop signature to use that specific type (e.g., mutate?:
ReturnType<typeof useRule>["mutate"] or an exported type alias from useRule),
and remove the biome-ignore comment so noExplicitAny is no longer needed.
| const result = await createContact({ email: user.email }); | ||
| const error = result && "error" in result ? result.error : undefined; | ||
| if (error) console.error(error); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Strengthen the runtime type guard; also replace console with scoped logger and use shared Prisma util
- The
"error" in resultcheck is a good move. Add antypeof result === "object"guard to avoid usinginon non-objects. - Per guidelines, avoid
consolein backend TS; usecreateScopedLogger. - Prefer the shared Prisma singleton (
import prisma from "@/utils/prisma";) over instantiatingnew PrismaClient(). - Optionally await disconnect in
finally.
Apply these diffs:
-import { PrismaClient } from "@prisma/client";
+import prisma from "@/utils/prisma";
+import { createScopedLogger } from "@/utils/logger";
-const prisma = new PrismaClient();
+const log = createScopedLogger("scripts/addUsersToResend");
@@
- console.log("Adding user", user.email);
+ log.info("Adding user", { email: user.email });
- const result = await createContact({ email: user.email });
- const error = result && "error" in result ? result.error : undefined;
- if (error) console.error(error);
+ const result = await createContact({ email: user.email });
+ const error =
+ result && typeof result === "object" && "error" in result
+ ? // narrow for TS; adjust the shape if you type `createContact`
+ (result as { error?: unknown }).error
+ : undefined;
+ if (error) log.error("Resend createContact error", { email: user.email, error });
@@
- } catch (error) {
- console.error("Error creating contact for user: ", user.email, error);
+ } catch (error) {
+ log.error("Error creating contact for user", { email: user.email, error });
}
@@
-main().finally(() => {
- prisma.$disconnect();
-});
+main().finally(async () => {
+ await prisma.$disconnect();
+});If createContact is updated to always throw on failures (as suggested in contacts.ts), you can simplify to:
await createContact({ email: user.email });
// only catch/LOG exceptions; no need to inspect `result.error`🤖 Prompt for AI Agents
In apps/web/scripts/addUsersToResend.ts around lines 15 to 17, strengthen the
runtime guard and replace console and Prisma usage: ensure you check typeof
result === "object" before using "error" in result (or, if createContact now
throws on failure, simplify to just await createContact(...) inside a
try/catch), replace console.error with a createScopedLogger instance (import and
use createScopedLogger for this script), import the shared Prisma singleton
(import prisma from "@/utils/prisma") instead of instantiating new
PrismaClient(), and if you keep a manually-managed client ensure you await
client.$disconnect() in a finally block.
Summary by CodeRabbit
Bug Fixes
Refactor
Chores