Conversation
WalkthroughMigrates reply-tracking to a provider-centric model: introduces EmailProvider label/draft APIs, moves outboundReplyTracking from User → EmailAccount, consolidates inbound/outbound flows, updates Gmail/Outlook providers, webhooks, draft tracking, cold-email, categorization, and hides TRACK_THREAD/DIGEST in the UI. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant WH as Webhook Handler
participant EP as EmailProvider
participant RT as ReplyTracker (Outbound)
participant DB as Database
rect rgb(244,248,255)
note right of WH: Outbound message/event arrives
WH->>EP: createEmailProvider(emailAccount)
WH->>RT: handleOutboundReply({ emailAccount, message, provider: EP })
RT->>EP: getThreadMessages(threadId)
RT->>DB: resolveReplyTrackers(emailAccount.userId, threadId)
alt outbound tracking enabled & AI decides to track
RT->>DB: createReplyTrackerOutbound(...)
RT->>EP: labelAwaitingReply(messageId)
RT-->>WH: tracked (Awaiting Reply added)
else not tracked or remove
RT->>EP: removeNeedsReplyLabel(threadId)
RT-->>WH: no track
end
end
sequenceDiagram
autonumber
participant Job as Inbound / Job
participant EP as EmailProvider
participant IN as ReplyTracker (Inbound)
participant DB as Database
Job->>EP: createEmailProvider(emailAccount)
par outbound & inbound
Job->>IN: handleOutboundReply({ provider: EP, ... })
and
Job->>IN: handleInboundReply({ client: EP, ... })
end
IN->>EP: removeAwaitingReplyLabel(threadId)
IN->>DB: add "To Reply" trackers / update statuses
IN-->>Job: done
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested reviewers
Poem
✨ 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
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
apps/web/utils/reply-tracker/draft-tracking.ts (4)
96-104: Guard similarity calc when sent body is missing; fall back to HTML
sentTextPlaincan be undefined. Use a safe fallback, otherwisecalculateSimilaritymay misbehave.- const similarityScore = calculateSimilarity( - executedAction.content, - sentTextPlain, - ); + const sentBody = + sentTextPlain ?? message.textHtml ?? ""; + if (!sentBody) { + logger.info("No body content available to compute similarity", { + ...loggerOptionsWithAction, + }); + } + const similarityScore = calculateSimilarity(executedAction.content, sentBody);
127-239: Remove duplicated implementation; forward to the primary function
trackSentDraftStatusWithProviderduplicatestrackSentDraftStatus. This will drift and complicate fixes.-export async function trackSentDraftStatusWithProvider({ - emailAccountId, - message, - provider, -}: { - emailAccountId: string; - message: ParsedMessage; - provider: EmailProvider; -}) { - const { threadId, id: sentMessageId, textPlain: sentTextPlain } = message; - ... - logger.info( - "Successfully created draft send log and updated action status via transaction", - loggerOptionsWithAction, - ); -} +export async function trackSentDraftStatusWithProvider(args: { + emailAccountId: string; + message: ParsedMessage; + provider: EmailProvider; +}) { + return trackSentDraftStatus(args); +}
296-336: Treat drafts with only HTML as existing; don’t misclassify as “not found”Some providers may store draft content as HTML only. Use a body fallback rather than
textPlainonly.- const draftDetails = await provider.getDraft(action.draftId); - - if (draftDetails?.textPlain) { + const draftDetails = await provider.getDraft(action.draftId); + const draftBody = draftDetails?.textPlain ?? draftDetails?.textHtml ?? ""; + if (draftBody) { // Draft exists, check if modified // Using calculateSimilarity == 1.0 as the check for "unmodified" const similarityScore = calculateSimilarity( - action.content, - draftDetails.textPlain, + action.content, + draftBody, );
364-481: Remove duplicated cleanup implementation; forward to the primary function
cleanupThreadAIDraftsWithProviderduplicatescleanupThreadAIDrafts. Create a thin wrapper to avoid divergence.-export async function cleanupThreadAIDraftsWithProvider({ - threadId, - emailAccountId, - provider, -}: { - threadId: string; - emailAccountId: string; - provider: EmailProvider; -}) { - const loggerOptions = { emailAccountId, threadId }; - logger.info("Starting cleanup of old AI drafts for thread", loggerOptions); - ... - } -} +export async function cleanupThreadAIDraftsWithProvider(args: { + threadId: string; + emailAccountId: string; + provider: EmailProvider; +}) { + return cleanupThreadAIDrafts(args); +}apps/web/utils/reply-tracker/outbound.ts (2)
274-277: Bug: Passing a label ID tolabelMessage(expects name) — uselabelMessageById
EmailProvider.labelMessage(messageId, labelName)expects a label name, butawaitingReplyLabelIdis an ID. This will mislabel or fail at runtime (provider-dependent). Switch tolabelMessageByIdand include the constant name to keep both providers consistent.Apply this diff:
- const labelPromise = awaitingReplyLabelId - ? provider.labelMessage(messageId, awaitingReplyLabelId) - : Promise.resolve(); + const labelPromise = awaitingReplyLabelId + ? provider.labelMessageById(messageId, { + id: awaitingReplyLabelId, + name: AWAITING_REPLY_LABEL_NAME, + }) + : Promise.resolve();
36-90: Add try/catch around provider/AI work to avoid unhandled rejectionsPer project guidelines, wrap external calls. This prevents one-off provider failures from bubbling up and losing observability.
Apply this diff:
logger.info("Checking outbound reply"); - // 2. Get necessary labels - const { awaitingReplyLabelId } = await provider.getReplyTrackingLabels(); + try { + // 2. Get necessary labels + const { awaitingReplyLabelId } = await provider.getReplyTrackingLabels(); - // 3. Resolve existing NEEDS_REPLY trackers for this thread - await resolveReplyTrackers(provider, emailAccount.userId, message.threadId); + // 3. Resolve existing NEEDS_REPLY trackers for this thread + await resolveReplyTrackers(provider, emailAccount.userId, message.threadId); - // 4. Get thread context - const threadMessages = await provider.getThreadMessages(message.threadId); - if (!threadMessages?.length) { - logger.error("No thread messages found, cannot proceed."); - return; - } + // 4. Get thread context + const threadMessages = await provider.getThreadMessages(message.threadId); + if (!threadMessages?.length) { + logger.error("No thread messages found, cannot proceed."); + return; + } - // 5. Check if this message is the latest - const { isLatest, sortedMessages } = isMessageLatestInThread( - message, - threadMessages, - logger, - ); - if (!isLatest) { - logger.info( - "Skipping outbound reply check: message is not the latest in the thread", - ); - return; // Stop processing if not the latest - } + // 5. Check if this message is the latest + const { isLatest, sortedMessages } = isMessageLatestInThread( + message, + threadMessages, + logger, + ); + if (!isLatest) { + logger.info( + "Skipping outbound reply check: message is not the latest in the thread", + ); + return; // Stop processing if not the latest + } - // 6. Prepare data for AI - const { messageToSendForLLM, threadContextMessagesForLLM } = - prepareDataForAICheck(message, sortedMessages); + // 6. Prepare data for AI + const { messageToSendForLLM, threadContextMessagesForLLM } = + prepareDataForAICheck(message, sortedMessages); - // 7. Perform AI check - const aiResult = await aiCheckIfNeedsReply({ - emailAccount, - messageToSend: messageToSendForLLM, - threadContextMessages: threadContextMessagesForLLM, - }); + // 7. Perform AI check + const aiResult = await aiCheckIfNeedsReply({ + emailAccount, + messageToSend: messageToSendForLLM, + threadContextMessages: threadContextMessagesForLLM, + }); - // 8. If yes, create a tracker - if (aiResult.needsReply) { - logger.info("Needs reply. Creating reply tracker outbound"); - await createReplyTrackerOutbound({ - provider, - emailAccountId: emailAccount.id, - threadId: message.threadId, - messageId: message.id, - awaitingReplyLabelId, - sentAt: internalDateToDate(message.internalDate), - logger, - }); - } else { - logger.trace("No need to reply"); - } + // 8. If yes, create a tracker + if (aiResult.needsReply) { + logger.info("Needs reply. Creating reply tracker outbound"); + await createReplyTrackerOutbound({ + provider, + emailAccountId: emailAccount.id, + threadId: message.threadId, + messageId: message.id, + awaitingReplyLabelId, + sentAt: internalDateToDate(message.internalDate), + logger, + }); + } else { + logger.trace("No need to reply"); + } + } catch (error) { + logger.error("Outbound reply tracking failed", { error }); + }
🧹 Nitpick comments (13)
apps/web/utils/gmail/label.ts (1)
17-20: Add non-empty assertions for reply-tracker label constantsI’ve confirmed that both constants in
apps/web/utils/reply-tracker/consts.tsare currently defined as non-empty, trimmed strings:
export const NEEDS_REPLY_LABEL_NAME = "To Reply";export const AWAITING_REPLY_LABEL_NAME = "Awaiting Reply";However, to guard against future modifications that might introduce empty or whitespace-only values—and to fail fast rather than surface a runtime “Label names cannot be empty” error—please add a module-load assertion. For example, in
apps/web/utils/gmail/label.ts(or centrally inconsts.ts), you could add:// Before using the imported constants if (!AWAITING_REPLY_LABEL_NAME.trim() || !NEEDS_REPLY_LABEL_NAME.trim()) { throw new Error( "Reply-tracker label constants must be non-empty. " + "Found empty or whitespace-only value." ); }This ensures any misconfiguration is caught immediately at startup rather than deeper in the helper logic.
apps/web/utils/email/types.ts (2)
67-69: API symmetry: consider a generic label removal API or a state setterYou’ve added removeAwaitingReplyLabel and removeNeedsReplyLabel. That’s OK short-term, but long-term a single
setReplyTrackerState(threadId, "awaiting" | "needs" | "none")orremoveThreadLabel(threadId, labelId)plus helpers might reduce surface area and duplication across providers.
61-64: RefactorlabelMessageByIdsignature for clarity and type safetyBoth
GmailProvider(apps/web/utils/email/google.ts) andOutlookProvider(apps/web/utils/email/microsoft.ts) already implementlabelMessageById, so the interface change won’t leave any provider unimplemented.• Rename the second parameter from
labeltolabelReffor readability.
• Introduce a stricter union type instead of an object with optional keys:// apps/web/utils/email/types.ts export type LabelRef = { id: string } | { name: string }; interface EmailUtils { labelMessageById( messageId: string, labelRef: LabelRef, ): Promise<void>; }• Update the implementations in both providers to use the new parameter name and type.
apps/web/utils/reply-tracker/inbound.ts (1)
46-51: Consider also adding the Needs Reply label (not only removing Awaiting)The DB transaction creates a NEEDS_REPLY tracker, but no label is applied. If labels are part of the user-facing experience, add the “needs reply” label to the inbound message (or thread) in parallel.
Proposed minimal change within this block:
- const labelsPromise = client.removeAwaitingReplyLabel(threadId); + const removeAwaitingPromise = client.removeAwaitingReplyLabel(threadId); + const addNeedsPromise = (async () => { + try { + const { needsReplyLabelId } = await client.getReplyTrackingLabels(); + if (needsReplyLabelId) { + // Apply label to the inbound message (alternatively, expose a labelThread add API) + await client.labelMessageById(messageId, { id: needsReplyLabelId }); + } + } catch (e) { + // Let Promise.allSettled handle/report + throw e; + } + })(); - const [dbResult, labelsResult] = await Promise.allSettled([ - dbPromise, - labelsPromise, - ]); + const [dbResult, removeAwaitingResult, addNeedsResult] = + await Promise.allSettled([dbPromise, removeAwaitingPromise, addNeedsPromise]);And adjust the subsequent settled checks accordingly.
apps/web/app/api/google/webhook/process-history-item.ts (3)
64-68: Consider lazy-instantiating the provider to avoid unnecessary client creationYou eagerly create a provider for every history item, including LABEL_ADDED events where you early-return. If provider creation performs I/O or token exchange, defer instantiation until it’s actually needed to reduce overhead.
- const provider = await createEmailProvider({ - emailAccountId, - provider: "google", - }); + // Defer provider creation until first use + let provider: EmailProvider | undefined; + const getProvider = async () => + (provider ??= + await createEmailProvider({ emailAccountId, provider: "google" }));And replace usages with
await getProvider().
140-145: Outbound detection via Gmail label is fine in Gmail webhook, but add a noteThis webhook is Gmail-specific, so
GmailLabel.SENTis valid. If you later share this handler across providers, switch to a provider-aware check.-const isOutbound = parsedMessage.labelIds?.includes(GmailLabel.SENT); +// Gmail-specific: for Outlook this path uses a different webhook +const isOutbound = parsedMessage.labelIds?.includes(GmailLabel.SENT);
287-296: Wrap cleanup errors with formatError for consistent logging metadataYou already do this above; keep logs uniform for easier search.
- } catch (cleanupError) { + } catch (cleanupError) { logger.error("Error during thread draft cleanup", { ...loggerOptions, - error: cleanupError, + error: formatError(cleanupError), }); }.cursor/rules/features/reply-tracker.mdc (1)
25-28: Clarify lifecycle: who adds vs. who removes labelsGood distinction: rules on inbound, TRACK_THREAD for removals, outboundReplyTracking for setting Awaiting Reply. Consider a short example to avoid ambiguity.
-Typically rules apply to emails as they come in. Rules have no impact afterwards. So to remove a label from a thread later we use the `TRACK_THREAD` action. This action is in charge of removing the Awaiting Reply or To Reply label. Note, adding labels is handled in the usual manner or with `outboundReplyTracking`. +Rules apply to incoming emails only. Later updates (e.g., removing "Awaiting Reply" when a reply is detected) are performed by the `TRACK_THREAD` action. Adding reply-tracking labels happens either via inbound rule actions or via `EmailAccount.outboundReplyTracking` on outgoing mail.apps/web/utils/email/google.ts (1)
319-327: Minor: avoid name shadowing between class method and imported functionThis method calls the imported
removeThreadLabelwith the same identifier as the class method declared above. Consider aliasing the import to reduce confusion.-import { +import { archiveThread, getOrCreateLabel, labelMessage, markReadThread, - removeThreadLabel, + removeThreadLabel as removeGmailThreadLabel, } from "@/utils/gmail/label"; ... - async removeAwaitingReplyLabel(threadId: string): Promise<void> { - const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); - await removeThreadLabel(this.client, threadId, awaitingReplyLabelId); + async removeAwaitingReplyLabel(threadId: string): Promise<void> { + const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); + await removeGmailThreadLabel(this.client, threadId, awaitingReplyLabelId); }apps/web/utils/reply-tracker/outbound.ts (4)
92-175: DRY: CollapsehandleOutboundReplyWithProviderinto a thin wrapperThis function duplicates
handleOutboundReplyalmost verbatim, increasing maintenance and divergence risk. Prefer a wrapper to keep logic single-sourced.Apply this diff:
export async function handleOutboundReplyWithProvider({ - emailAccount, - message, - provider, -}: { - emailAccount: EmailAccountWithAI; - message: ParsedMessage; - provider: EmailProvider; -}) { - const logger = createScopedLogger("reply-tracker/outbound").with({ - email: emailAccount.email, - messageId: message.id, - threadId: message.threadId, - }); - - // 1. Check if feature enabled - const isEnabled = await isOutboundTrackingEnabled({ - email: emailAccount.email, - }); - if (!isEnabled) { - logger.info("Outbound reply tracking disabled, skipping."); - return; - } - - logger.info("Checking outbound reply"); - - // 2. Get necessary labels - const { awaitingReplyLabelId } = await provider.getReplyTrackingLabels(); - - // 3. Resolve existing NEEDS_REPLY trackers for this thread - await resolveReplyTrackersWithProvider( - provider, - emailAccount.userId, - message.threadId, - ); - - // 4. Get thread context - const threadMessages = await provider.getThreadMessages(message.threadId); - if (!threadMessages?.length) { - logger.error("No thread messages found, cannot proceed."); - return; - } - - // 5. Check if this message is the latest - const { isLatest, sortedMessages } = isMessageLatestInThread( - message, - threadMessages, - logger, - ); - if (!isLatest) { - logger.info( - "Skipping outbound reply check: message is not the latest in the thread", - ); - return; // Stop processing if not the latest - } - - // 6. Prepare data for AI - const { messageToSendForLLM, threadContextMessagesForLLM } = - prepareDataForAICheck(message, sortedMessages); - - // 7. Perform AI check - const aiResult = await aiCheckIfNeedsReply({ - emailAccount, - messageToSend: messageToSendForLLM, - threadContextMessages: threadContextMessagesForLLM, - }); - - // 8. If yes, create a tracker - if (aiResult.needsReply) { - logger.info("Needs reply. Creating reply tracker outbound"); - await createReplyTrackerOutboundWithProvider({ - provider, - emailAccountId: emailAccount.id, - threadId: message.threadId, - messageId: message.id, - awaitingReplyLabelId, - sentAt: internalDateToDate(message.internalDate), - logger, - }); - } else { - logger.trace("No need to reply"); - } -} + emailAccount, + message, + provider, +}: { + emailAccount: EmailAccountWithAI; + message: ParsedMessage; + provider: EmailProvider; +}) { + return handleOutboundReply({ emailAccount, message, provider }); +}
237-295: DRY: DelegatecreateReplyTrackerOutboundWithProviderto the base helperNow that
createReplyTrackerOutboundsafely guards label application, this variant can just delegate.Apply this diff:
-async function createReplyTrackerOutboundWithProvider({ - provider, - emailAccountId, - threadId, - messageId, - awaitingReplyLabelId, - sentAt, - logger, -}: { - provider: EmailProvider; - emailAccountId: string; - threadId: string; - messageId: string; - awaitingReplyLabelId: string; - sentAt: Date; - logger: Logger; -}) { - if (!threadId || !messageId) return; - - const upsertPromise = prisma.threadTracker.upsert({ - where: { - emailAccountId_threadId_messageId: { - emailAccountId, - threadId, - messageId, - }, - }, - update: {}, - create: { - emailAccountId, - threadId, - messageId, - type: ThreadTrackerType.AWAITING, - sentAt, - }, - }); - - // For Outlook, if awaitingReplyLabelId is empty, we'll skip labeling - const labelPromise = awaitingReplyLabelId - ? provider.labelMessage(messageId, awaitingReplyLabelId) - : Promise.resolve(); - - const [upsertResult, labelResult] = await Promise.allSettled([ - upsertPromise, - labelPromise, - ]); - - if (upsertResult.status === "rejected") { - logger.error("Failed to upsert reply tracker", { - error: upsertResult.reason, - }); - } - - if (labelResult.status === "rejected") { - logger.error("Failed to label reply tracker", { - error: labelResult.reason, - }); - } -} +async function createReplyTrackerOutboundWithProvider(args: { + provider: EmailProvider; + emailAccountId: string; + threadId: string; + messageId: string; + awaitingReplyLabelId: string; + sentAt: Date; + logger: Logger; +}) { + return createReplyTrackerOutbound(args); +}
319-339: DRY: DelegateresolveReplyTrackersWithProvidertoresolveReplyTrackersBoth bodies are identical. Keep one source of truth.
Apply this diff:
-async function resolveReplyTrackersWithProvider( - provider: EmailProvider, - emailAccountId: string, - threadId: string, -) { - const updateDbPromise = prisma.threadTracker.updateMany({ - where: { - emailAccountId, - threadId, - resolved: false, - type: ThreadTrackerType.NEEDS_REPLY, - }, - data: { - resolved: true, - }, - }); - - const labelPromise = provider.removeNeedsReplyLabel(threadId); - - await Promise.allSettled([updateDbPromise, labelPromise]); -} +async function resolveReplyTrackersWithProvider( + provider: EmailProvider, + emailAccountId: string, + threadId: string, +) { + return resolveReplyTrackers(provider, emailAccountId, threadId); +}
360-362: Make latest-message check resilient wheninternalDateis missingOutlook messages may lack
internalDate. Fall back to parsingdateto avoid mis-ordering.Apply this diff:
- const sortedMessages = [...threadMessages].sort( - (a, b) => (Number(b.internalDate) || 0) - (Number(a.internalDate) || 0), - ); + const sortedMessages = [...threadMessages].sort((a, b) => { + const bt = + (b.internalDate ? Number(b.internalDate) : Date.parse(b.date)) || 0; + const at = + (a.internalDate ? Number(a.internalDate) : Date.parse(a.date)) || 0; + return bt - at; + });
📜 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)
.cursor/rules/features/reply-tracker.mdc(1 hunks)apps/web/app/api/google/webhook/process-history-item.ts(5 hunks)apps/web/utils/email/google.ts(4 hunks)apps/web/utils/email/microsoft.ts(4 hunks)apps/web/utils/email/types.ts(1 hunks)apps/web/utils/gmail/label.ts(2 hunks)apps/web/utils/outlook/label.ts(1 hunks)apps/web/utils/reply-tracker/check-previous-emails.ts(1 hunks)apps/web/utils/reply-tracker/draft-tracking.ts(5 hunks)apps/web/utils/reply-tracker/inbound.ts(1 hunks)apps/web/utils/reply-tracker/label.ts(0 hunks)apps/web/utils/reply-tracker/outbound.ts(9 hunks)
💤 Files with no reviewable changes (1)
- apps/web/utils/reply-tracker/label.ts
🧰 Additional context used
📓 Path-based instructions (11)
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/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/email/types.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.ts
!{.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/utils/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/email/types.ts.cursor/rules/features/reply-tracker.mdcapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.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/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/email/types.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.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/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/email/types.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.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/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/utils/email/types.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.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/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/utils/email/types.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.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/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/email/types.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.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/utils/reply-tracker/inbound.tsapps/web/utils/reply-tracker/check-previous-emails.tsapps/web/utils/gmail/label.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/email/types.ts.cursor/rules/features/reply-tracker.mdcapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.ts
apps/web/utils/gmail/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Files:
apps/web/utils/gmail/label.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/google/webhook/process-history-item.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/google/webhook/process-history-item.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/reply-tracker/check-previous-emails.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.ts
📚 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/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operations
Applied to files:
apps/web/app/api/google/webhook/process-history-item.ts
🧬 Code graph analysis (8)
apps/web/utils/reply-tracker/check-previous-emails.ts (3)
apps/web/utils/email/provider.ts (1)
createEmailProvider(13-28)apps/web/utils/gmail/label.ts (1)
GmailLabel(24-38)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(12-90)
apps/web/utils/gmail/label.ts (3)
apps/web/utils/email/google.ts (3)
getAwaitingReplyLabel(311-313)getNeedsReplyLabel(315-317)getReplyTrackingLabels(644-649)apps/web/utils/email/microsoft.ts (2)
getAwaitingReplyLabel(778-785)getReplyTrackingLabels(787-801)apps/web/utils/outlook/label.ts (1)
getOrCreateLabels(159-191)
apps/web/app/api/google/webhook/process-history-item.ts (2)
apps/web/utils/email/types.ts (1)
EmailProvider(36-171)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(12-90)
apps/web/utils/outlook/label.ts (2)
apps/web/utils/email/microsoft.ts (1)
removeThreadLabel(298-309)apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)
apps/web/utils/reply-tracker/draft-tracking.ts (2)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/email/types.ts (1)
EmailProvider(36-171)
apps/web/utils/email/google.ts (1)
apps/web/utils/gmail/label.ts (4)
labelMessage(133-149)getAwaitingReplyLabel(363-371)getNeedsReplyLabel(373-381)removeThreadLabel(67-77)
apps/web/utils/reply-tracker/outbound.ts (3)
apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/email/types.ts (1)
EmailProvider(36-171)
apps/web/utils/email/microsoft.ts (2)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/outlook/label.ts (3)
labelMessage(194-206)getLabelById(70-80)removeThreadLabel(233-284)
🔇 Additional comments (9)
apps/web/utils/reply-tracker/check-previous-emails.ts (1)
98-99: Provider-based usage looks correctPassing provider into handleOutboundReply aligns with the new abstraction and unblocks Outlook support in that path.
apps/web/app/api/google/webhook/process-history-item.ts (3)
27-27: Type-only import for EmailProvider is correct and helps tree-shakingGood use of
import typefor the provider interface. Keeps runtime bundle clean and enforces intent.
71-75: Passing provider into label-removed handler is the right directionAligns webhook learning path with provider abstraction. Double-check
handleLabelRemovedEventdoesn’t still import Gmail utilities directly.
242-301: Nice concurrency with Promise.allSettled and targeted error loggingRunning draft tracking and outbound reply analysis in parallel with independent error handling is robust. Cleanup after both complete is also correctly sequenced.
.cursor/rules/features/reply-tracker.mdc (1)
29-31: Good: added direct pointers to source filesLinking to inbound/outbound implementations will help future maintainers.
apps/web/utils/email/microsoft.ts (1)
311-325: Good convenience helpers for removing specific categoriesUsing constant names for Awaiting/Needs reply keeps behavior consistent with Gmail’s label IDs concept.
apps/web/utils/reply-tracker/draft-tracking.ts (1)
14-22: Signature migration to EmailProvider looks goodSwitching from Gmail client to provider in public API matches the refactor and keeps call sites uniform.
apps/web/utils/reply-tracker/outbound.ts (2)
39-46: Good move to provider abstraction for labels, cleanup, and thread readsFetching reply-tracking labels via
provider.getReplyTrackingLabels(), resolving NEEDS_REPLY viaprovider.removeNeedsReplyLabel(threadId), and getting thread context throughprovider.getThreadMessagesalign with the new provider-agnostic design.
10-10: LGTM: Centralized label name constantUsing
AWAITING_REPLY_LABEL_NAMEkeeps provider implementations consistent and avoids string drift.
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 (2)
apps/web/utils/reply-tracker/outbound.ts (2)
274-276: Bug: passing a label ID into provider.labelMessage expects a name, not an IDprovider.labelMessage(messageId, awaitingReplyLabelId) treats the second arg as a label/category name for both Gmail and Outlook. You’re passing an ID, which can create a bogus label or fail. Use provider.labelAwaitingReply(messageId, labelId) instead and keep the existing empty-ID guard.
- const labelPromise = awaitingReplyLabelId - ? provider.labelMessage(messageId, awaitingReplyLabelId) - : Promise.resolve(); + const labelPromise = awaitingReplyLabelId + ? provider.labelAwaitingReply(messageId, awaitingReplyLabelId) + : Promise.resolve();
1-404: Replaceprovider.labelMessagewithprovider.labelAwaitingReplyincreateReplyTrackerOutboundWithProvider
In apps/web/utils/reply-tracker/outbound.ts (lines 273–276), you’re callingprovider.labelMessage(messageId, awaitingReplyLabelId)—butEmailProvideronly exposeslabelAwaitingReply. Switch to:await provider.labelAwaitingReply(messageId, awaitingReplyLabelId);to stay within the abstraction and avoid runtime errors.
♻️ Duplicate comments (1)
apps/web/utils/email/microsoft.ts (1)
13-14: Harden Outlook removeThreadLabel; also alias import to avoid method name collisionIf Graph returns 404/empty, categoryName becomes "", leading to a bad patch. Wrap resolution in try/catch, guard missing names, and alias getLabelById to avoid confusion with the class method. This mirrors prior feedback.
-import { - getLabels, - createLabel, - getOrCreateInboxZeroLabel, - getLabelById, -} from "@/utils/outlook/label"; +import { + getLabels, + createLabel, + getOrCreateInboxZeroLabel, + getLabelById as getOutlookLabelById, +} from "@/utils/outlook/label";async removeThreadLabel(threadId: string, labelId: string): Promise<void> { - // TODO: this can be more efficient by using the label name directly - // Get the label to convert ID to name (Outlook uses names) - const label = await getLabelById({ client: this.client, id: labelId }); - const categoryName = label.displayName || ""; - - await removeThreadLabel({ - client: this.client, - threadId, - categoryName, - }); + try { + const label = await getOutlookLabelById({ + client: this.client, + id: labelId, + }); + const categoryName = label?.displayName; + if (!categoryName) { + logger.warn("Outlook category not found; skipping removal", { + threadId, + labelId, + }); + return; + } + await removeThreadLabel({ + client: this.client, + threadId, + categoryName, + }); + } catch (error) { + logger.warn("Failed to resolve Outlook category by id; skipping removal", { + threadId, + labelId, + error, + }); + } }Also applies to: 280-291
🧹 Nitpick comments (4)
apps/web/utils/email/google.ts (1)
16-18: Avoid shadowing: alias Gmail label helpers to prevent accidental self-callsMethod names match imported function names (getAwaitingReplyLabel/getNeedsReplyLabel/getReplyTrackingLabels). This is easy to misread and risky if “this.” is omitted. Alias imports and call the aliases explicitly.
-import { - getLabels, - getLabelById, - createLabel, - getOrCreateInboxZeroLabel, - GmailLabel, - getNeedsReplyLabel, - getAwaitingReplyLabel, -} from "@/utils/gmail/label"; +import { + getLabels, + getLabelById, + createLabel, + getOrCreateInboxZeroLabel, + GmailLabel, + getNeedsReplyLabel as gmailGetNeedsReplyLabel, + getAwaitingReplyLabel as gmailGetAwaitingReplyLabel, +} from "@/utils/gmail/label";-import { getReplyTrackingLabels } from "@/utils/gmail/label"; +import { getReplyTrackingLabels as gmailGetReplyTrackingLabels } from "@/utils/gmail/label";- async getAwaitingReplyLabel(): Promise<string> { - return getAwaitingReplyLabel(this.client); - } + async getAwaitingReplyLabel(): Promise<string> { + return gmailGetAwaitingReplyLabel(this.client); + }- async getNeedsReplyLabel(): Promise<string> { - return getNeedsReplyLabel(this.client); - } + async getNeedsReplyLabel(): Promise<string> { + return gmailGetNeedsReplyLabel(this.client); + }Additionally, update the class method getReplyTrackingLabels() to call gmailGetReplyTrackingLabels(this.client).
Also applies to: 51-51, 293-299
apps/web/utils/reply-tracker/outbound.ts (3)
91-174: Deduplicate: route handleOutboundReplyWithProvider to handleOutboundReplyBoth functions implement the same flow; keep one source of truth and call it. This reduces drift and test surface.
export async function handleOutboundReplyWithProvider({ emailAccount, message, provider, }: { emailAccount: EmailAccountWithAI; message: ParsedMessage; provider: EmailProvider; }) { - const logger = createScopedLogger("reply-tracker/outbound").with({ - email: emailAccount.email, - messageId: message.id, - threadId: message.threadId, - }); - // 1. Check if feature enabled - const isEnabled = await isOutboundTrackingEnabled({ - email: emailAccount.email, - }); - if (!isEnabled) { - logger.info("Outbound reply tracking disabled, skipping."); - return; - } - logger.info("Checking outbound reply"); - // 2. Get necessary labels - const awaitingReplyLabelId = await provider.getAwaitingReplyLabel(); - // 3. Resolve existing NEEDS_REPLY trackers for this thread - await resolveReplyTrackersWithProvider( - provider, - emailAccount.userId, - message.threadId, - ); - // 4. Get thread context - const threadMessages = await provider.getThreadMessages(message.threadId); - if (!threadMessages?.length) { - logger.error("No thread messages found, cannot proceed."); - return; - } - // 5. Check if this message is the latest - const { isLatest, sortedMessages } = isMessageLatestInThread( - message, - threadMessages, - logger, - ); - if (!isLatest) { - logger.info( - "Skipping outbound reply check: message is not the latest in the thread", - ); - return; // Stop processing if not the latest - } - // 6. Prepare data for AI - const { messageToSendForLLM, threadContextMessagesForLLM } = - prepareDataForAICheck(message, sortedMessages); - // 7. Perform AI check - const aiResult = await aiCheckIfNeedsReply({ - emailAccount, - messageToSend: messageToSendForLLM, - threadContextMessages: threadContextMessagesForLLM, - }); - // 8. If yes, create a tracker - if (aiResult.needsReply) { - logger.info("Needs reply. Creating reply tracker outbound"); - await createReplyTrackerOutboundWithProvider({ - provider, - emailAccountId: emailAccount.id, - threadId: message.threadId, - messageId: message.id, - awaitingReplyLabelId, - sentAt: internalDateToDate(message.internalDate), - logger, - }); - } else { - logger.trace("No need to reply"); - } + return handleOutboundReply({ emailAccount, message, provider }); }Optionally, alias helpers to remove duplication as well:
// near the helpers export const createReplyTrackerOutboundWithProvider = createReplyTrackerOutbound; export const resolveReplyTrackersWithProvider = resolveReplyTrackers;
213-216: Add a defensive guard for empty awaitingReplyLabelId (Gmail safety, symmetry)While Gmail usually returns a valid ID, mirroring the Outlook guard avoids accidental 400s if label resolution fails.
-const labelPromise = provider.labelAwaitingReply( - messageId, - awaitingReplyLabelId, -); +const labelPromise = awaitingReplyLabelId + ? provider.labelAwaitingReply(messageId, awaitingReplyLabelId) + : Promise.resolve();
296-316: Consolidate identical resolversresolveReplyTrackers and resolveReplyTrackersWithProvider are identical. Keep one and export an alias to reduce drift.
-async function resolveReplyTrackersWithProvider( - provider: EmailProvider, - emailAccountId: string, - threadId: string, -) { - const updateDbPromise = prisma.threadTracker.updateMany({ - where: { - emailAccountId, - threadId, - resolved: false, - type: ThreadTrackerType.NEEDS_REPLY, - }, - data: { resolved: true }, - }); - const labelPromise = provider.removeNeedsReplyLabel(threadId); - await Promise.allSettled([updateDbPromise, labelPromise]); -} +export const resolveReplyTrackersWithProvider = resolveReplyTrackers;Also applies to: 335-338
📜 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 (4)
apps/web/utils/email/google.ts(4 hunks)apps/web/utils/email/microsoft.ts(4 hunks)apps/web/utils/email/types.ts(1 hunks)apps/web/utils/reply-tracker/outbound.ts(9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/utils/email/types.ts
🧰 Additional context used
📓 Path-based instructions (8)
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/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.ts
!{.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/utils/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.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/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.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/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.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/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.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/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.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/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.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/utils/email/google.tsapps/web/utils/email/microsoft.tsapps/web/utils/reply-tracker/outbound.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/email/google.tsapps/web/utils/reply-tracker/outbound.ts
🧬 Code graph analysis (3)
apps/web/utils/email/google.ts (1)
apps/web/utils/gmail/label.ts (4)
getAwaitingReplyLabel(363-371)getNeedsReplyLabel(373-381)removeThreadLabel(67-77)labelMessage(133-149)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/outlook/label.ts (3)
getLabelById(70-80)removeThreadLabel(233-284)labelMessage(194-206)
apps/web/utils/reply-tracker/outbound.ts (3)
apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/email/types.ts (1)
EmailProvider(36-170)
⏰ 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: test
🔇 Additional comments (2)
apps/web/utils/reply-tracker/outbound.ts (1)
11-19: Provider injection looks goodSignature now includes provider: EmailProvider. This aligns outbound tracking with the provider abstraction.
apps/web/utils/email/microsoft.ts (1)
760-776: Provider-specific reply label methods are correct and resilient for OutlookUsing constant category names and ignoring the ID parameter is the right trade-off for Graph categories. Removal by name is consistent.
Also applies to: 778-808
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/utils/reply-tracker/outbound.ts (2)
74-85: Bug:sentAtderives frominternalDatewhich is undefined for OutlookOutlook-parsed messages typically lack
internalDate. This will store an incorrect timestamp. Fallback tomessage.datewheninternalDateis absent.- sentAt: internalDateToDate(message.internalDate), + sentAt: message.internalDate + ? internalDateToDate(message.internalDate) + : new Date(message.date),
188-191: Wrong API usage in duplicate function (label expects name vs. ID)If you keep this function, switch to the proper provider method that accepts an ID.
- const labelPromise = awaitingReplyLabelId - ? provider.labelMessage(messageId, awaitingReplyLabelId) + const labelPromise = awaitingReplyLabelId + ? provider.labelAwaitingReply(messageId, awaitingReplyLabelId) : Promise.resolve();
🧹 Nitpick comments (3)
apps/web/app/api/outlook/webhook/process-history-item.ts (1)
329-333: Nit: use property shorthand and consistent error formattingUse shorthand for
providerhere; also preferformatErrorin the catch below for consistency.- await cleanupThreadAIDrafts({ - threadId: conversationId || messageId, - emailAccountId: emailAccount.id, - provider: provider, - }); + await cleanupThreadAIDrafts({ + threadId: conversationId || messageId, + emailAccountId: emailAccount.id, + provider, + });Outside the selected lines (for your reference), consider:
} catch (cleanupError) { logger.error("Error during thread draft cleanup", { ...loggerOptions, error: formatError(cleanupError), }); }apps/web/utils/reply-tracker/outbound.ts (2)
151-209: Dead duplicate: removecreateReplyTrackerOutboundWithProvider(and it misuses API)This function duplicates
createReplyTrackerOutboundand callsprovider.labelMessage(messageId, awaitingReplyLabelId)where the provider expects a label name, not an ID. Either fix and dedupe or delete. Recommend removal.-async function createReplyTrackerOutboundWithProvider({ - provider, - emailAccountId, - threadId, - messageId, - awaitingReplyLabelId, - sentAt, - logger, -}: { - provider: EmailProvider; - emailAccountId: string; - threadId: string; - messageId: string; - awaitingReplyLabelId: string; - sentAt: Date; - logger: Logger; -}) { - if (!threadId || !messageId) return; - - const upsertPromise = prisma.threadTracker.upsert({ - where: { - emailAccountId_threadId_messageId: { - emailAccountId, - threadId, - messageId, - }, - }, - update: {}, - create: { - emailAccountId, - threadId, - messageId, - type: ThreadTrackerType.AWAITING, - sentAt, - }, - }); - - // For Outlook, if awaitingReplyLabelId is empty, we'll skip labeling - const labelPromise = awaitingReplyLabelId - ? provider.labelMessage(messageId, awaitingReplyLabelId) - : Promise.resolve(); - - const [upsertResult, labelResult] = await Promise.allSettled([ - upsertPromise, - labelPromise, - ]); - - if (upsertResult.status === "rejected") { - logger.error("Failed to upsert reply tracker", { - error: upsertResult.reason, - }); - } - - if (labelResult.status === "rejected") { - logger.error("Failed to label reply tracker", { - error: labelResult.reason, - }); - } -}
233-253: Remove redundantresolveReplyTrackersWithProviderDuplicate of
resolveReplyTrackers. Keep one to avoid drift.-async function resolveReplyTrackersWithProvider( - provider: EmailProvider, - emailAccountId: string, - threadId: string, -) { - const updateDbPromise = prisma.threadTracker.updateMany({ - where: { - emailAccountId, - threadId, - resolved: false, - type: ThreadTrackerType.NEEDS_REPLY, - }, - data: { - resolved: true, - }, - }); - - const labelPromise = provider.removeNeedsReplyLabel(threadId); - - await Promise.allSettled([updateDbPromise, labelPromise]); -}
📜 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 (3)
apps/web/app/api/outlook/webhook/process-history-item.ts(4 hunks)apps/web/utils/reply-tracker/draft-tracking.ts(0 hunks)apps/web/utils/reply-tracker/outbound.ts(7 hunks)
💤 Files with no reviewable changes (1)
- apps/web/utils/reply-tracker/draft-tracking.ts
🧰 Additional context used
📓 Path-based instructions (10)
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/api/outlook/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/outlook/webhook/process-history-item.ts
!{.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/api/outlook/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.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/app/api/outlook/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.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/app/api/outlook/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/outlook/webhook/process-history-item.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/app/api/outlook/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.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/api/outlook/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.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/reply-tracker/outbound.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/reply-tracker/outbound.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/reply-tracker/outbound.ts
🧬 Code graph analysis (2)
apps/web/app/api/outlook/webhook/process-history-item.ts (2)
apps/web/utils/reply-tracker/draft-tracking.ts (2)
trackSentDraftStatus(14-125)cleanupThreadAIDrafts(132-248)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(11-89)
apps/web/utils/reply-tracker/outbound.ts (1)
apps/web/utils/email/types.ts (1)
EmailProvider(36-170)
🔇 Additional comments (7)
apps/web/app/api/outlook/webhook/process-history-item.ts (3)
19-21: Draft-tracking imports migrated to provider API — LGTM
300-309: Pass-through ofproviderto both tasks — LGTMConcurrent execution with Promise.allSettled is appropriate; errors are handled below.
10-10: Verified: provider parameter present in all handleOutboundReply calls
No instances of calls missingprovideror of the deprecated alias remain.apps/web/utils/reply-tracker/outbound.ts (4)
38-42: Provider label retrieval and tracker resolution — LGTMShifts label/thread ops to the provider abstraction as intended.
92-107: Signature now includesprovider— LGTMKeeps DB write and label ops together behind one entry point.
212-231: Provider-based resolution — LGTMDB resolve + provider label removal via Promise.allSettled is robust to partial failures.
38-88: Resolved: all handleOutboundReply call sites includeprovider
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
apps/web/app/api/google/webhook/process-history-item.test.ts (1)
261-275: Update expectations once the 'to' field bug is fixed in the handler.process-history-item currently passes to: "" into runColdEmailBlocker. After fixing the handler to forward the actual recipient, update these assertions to expect to: "user@test.com".
Apply after production fix (see comment in the handler file).
Also applies to: 327-340, 428-441
apps/web/app/api/google/webhook/process-history-item.ts (1)
170-183: Pass the actual 'to' address to the cold email blocker.Using an empty string drops recipient context and diverges from the Outlook path.
- const response = await runColdEmailBlocker({ - email: { - from: parsedMessage.headers.from, - to: "", + const response = await runColdEmailBlocker({ + email: { + from: parsedMessage.headers.from, + to: parsedMessage.headers.to ?? "", subject: parsedMessage.headers.subject, content, id: messageId, threadId, date: internalDateToDate(parsedMessage.internalDate), }, provider, emailAccount, modelType: "default", });apps/web/app/api/outlook/webhook/process-history-item.ts (2)
174-195: Parity gap: Outlook path doesn’t enqueue cold-email digests.Gmail handler enqueues when coldEmailDigest is true; Outlook returns early without queuing, leading to inconsistent UX.
-import { runColdEmailBlocker } from "@/utils/cold-email/is-cold-email"; +import { runColdEmailBlocker } from "@/utils/cold-email/is-cold-email"; +import { enqueueDigestItem } from "@/utils/digest/index"; @@ - if (response.isColdEmail) { - logger.info("Skipping. Cold email detected.", loggerOptions); - return; - } + if (response.isColdEmail) { + if (emailAccount.coldEmailDigest && response.coldEmailId) { + logger.info("Enqueuing a cold email digest item", { + coldEmailId: response.coldEmailId, + }); + await enqueueDigestItem({ + email: { + id: messageId, + threadId: resourceData.conversationId || messageId, + headers: { + from, + to: to.join(","), + subject, + date: message.receivedDateTime + ? new Date(message.receivedDateTime).toISOString() + : new Date().toISOString(), + }, + snippet: message.bodyPreview || "", + inline: [], + subject, + date: message.receivedDateTime + ? new Date(message.receivedDateTime).toISOString() + : new Date().toISOString(), + conversationIndex: message.conversationIndex, + }, + emailAccountId, + coldEmailId: response.coldEmailId, + }); + } + logger.info("Skipping. Cold email detected.", loggerOptions); + return; + }
283-295: Missing internalDate and textPlain break reply tracking on Outlook.handleOutboundReply uses internalDate; trackSentDraftStatus reads textPlain. The constructed parsedMessage lacks both, which can cause incorrect timestamps and low similarity scores.
const parsedMessage = { id: messageId, threadId: conversationId || messageId, headers: messageHeaders, snippet: message.bodyPreview || "", historyId: message.id || messageId, + // Needed for outbound reply tracker parity + internalDate: String( + Date.parse( + message.sentDateTime ?? + message.createdDateTime ?? + new Date().toISOString(), + ), + ), + textPlain: message.bodyPreview || "", inline: [], subject: message.subject || "", date: message.receivedDateTime ? new Date(message.receivedDateTime).toISOString() : new Date().toISOString(), conversationIndex: message.conversationIndex, };Also applies to: 299-309
apps/web/utils/reply-tracker/outbound.ts (1)
77-85: Propagate optional label idPass through an optional label id to avoid forcing callers to fabricate a value. Update the function signature accordingly.
await createReplyTrackerOutbound({ provider, emailAccountId: emailAccount.id, threadId: message.threadId, messageId: message.id, - awaitingReplyLabelId, + awaitingReplyLabelId, sentAt: internalDateToDate(message.internalDate), logger, });And update the definition:
- awaitingReplyLabelId, + awaitingReplyLabelId,(See full signature diff below.)
apps/web/utils/cold-email/is-cold-email.ts (2)
97-106: Prisma: findUnique cannot filter by non-unique fieldstatus
findUniqueonly accepts a unique selector. Includingstatusis invalid and will fail type-checking/runtime. UsefindFirstwith a properwhere.- const coldEmail = await prisma.coldEmail.findUnique({ - where: { - emailAccountId_fromEmail: { - emailAccountId, - fromEmail: from, - }, - status: ColdEmailStatus.AI_LABELED_COLD, - }, - select: { id: true }, - }); + const coldEmail = await prisma.coldEmail.findFirst({ + where: { + emailAccountId, + fromEmail: from, + status: ColdEmailStatus.AI_LABELED_COLD, + }, + select: { id: true }, + });
216-267: Hide Outlook-specific logic behind the provider and address TODOAvoid importing Outlook clients in provider-agnostic code; push folder discovery/move into the provider. Also replace the TODO with a constant.
- if (shouldArchive) { - if (provider.name === "microsoft") { - const outlook = await getOutlookClientForEmail({ - emailAccountId: emailAccount.id, - }); - // TODO: move "Cold Emails"toa const or allow the user to set the folder - const folderId = await getOrCreateOutlookFolderIdByName( - outlook, - "Cold Emails", - ); - await provider.moveThreadToFolder( - email.threadId, - emailAccount.email, - folderId, - ); - } else { - await provider.archiveThread(email.threadId, emailAccount.email); - } - } + if (shouldArchive) { + // Provider implements the right behavior (archive on Gmail, move to named folder/category on Outlook) + await provider.archiveOrMoveToFolderByName?.({ + threadId: email.threadId, + userEmail: emailAccount.email, + folderName: COLD_EMAILS_FOLDER_NAME, + }); + } @@ - if (shouldMarkRead) { - await provider.markReadThread(email.threadId, true); - } + if (shouldMarkRead) { + await provider.markReadThread(email.threadId, true); + }Add near the top of the file:
// Shared constant; consider exposing via settings later const COLD_EMAILS_FOLDER_NAME = "Cold Emails";If you prefer not to extend the provider, minimally wrap the Outlook branch in a helper inside the provider module to avoid leaking Outlook client usage here.
♻️ Duplicate comments (1)
apps/web/utils/reply-tracker/outbound.ts (1)
100-107: Don't call label APIs with falsy IDsUnconditional
provider.labelAwaitingReplywill throw when the provider lacks an awaiting label. Guard it and make the param optional.-async function createReplyTrackerOutbound({ +async function createReplyTrackerOutbound({ provider, emailAccountId, threadId, messageId, - awaitingReplyLabelId, + awaitingReplyLabelId, sentAt, logger, }: { provider: EmailProvider; emailAccountId: string; threadId: string; messageId: string; - awaitingReplyLabelId: string; + awaitingReplyLabelId?: string | null; sentAt: Date; logger: Logger; }) { @@ - const labelPromise = provider.labelAwaitingReply( - messageId, - awaitingReplyLabelId, - ); + const labelPromise = awaitingReplyLabelId + ? provider.labelAwaitingReply(messageId, awaitingReplyLabelId) + : Promise.resolve();Also applies to: 128-131
🧹 Nitpick comments (5)
apps/web/utils/cold-email/is-cold-email.test.ts (2)
98-124: Add Outlook-specific path coverage (provider.name === "microsoft").blockColdEmail has a Microsoft-only branch that moves a thread to a folder via provider.moveThreadToFolder. Current tests never set provider.name or mock Outlook helpers, so that path is untested.
Minimal test addition outline:
- vi.mock getOutlookClientForEmail and getOrCreateOutlookFolderIdByName to return a fake client and a fixed folderId.
- Use a mock provider with name: "microsoft" and a moveThreadToFolder vi.fn().
- Expect moveThreadToFolder to be called and archiveThread NOT to be called.
Also applies to: 126-156
171-189: Also test when label name is missing.If getOrCreateInboxZeroLabel resolves to { name: "" } (or undefined), provider.labelMessage should not be called. Add a case mirroring this test but with name: "" and assert no labeling.
apps/web/app/api/outlook/webhook/process-history-item.ts (1)
329-333: Nit: use property shorthand.- provider: provider, + provider,apps/web/utils/reply-tracker/outbound.ts (1)
57-59: Duplicate logging for “message not latest”You log the same reason at the call site and inside
isMessageLatestInThread. Prefer logging in one place to reduce noise.- logger.info( - "Skipping outbound reply check: message is not the latest in the thread", - ); + // Rely on isMessageLatestInThread() to log details.Or remove the warn inside
isMessageLatestInThreadand keep the info here.Also applies to: 198-205
apps/web/utils/cold-email/is-cold-email.ts (1)
198-214: Preserve reason/message/thread on upsert update pathWhen we reclassify an existing sender, you likely want to refresh
reason,messageId, andthreadIdtoo, not juststatus.const coldEmail = await prisma.coldEmail.upsert({ @@ - update: { status: ColdEmailStatus.AI_LABELED_COLD }, + update: { + status: ColdEmailStatus.AI_LABELED_COLD, + reason: aiReason, + messageId: email.id, + threadId: email.threadId, + },
📜 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 (8)
apps/web/app/api/google/webhook/process-history-item.test.ts(11 hunks)apps/web/app/api/google/webhook/process-history-item.ts(8 hunks)apps/web/app/api/outlook/webhook/process-history-item.ts(6 hunks)apps/web/utils/actions/categorize.ts(2 hunks)apps/web/utils/categorize/senders/categorize.ts(0 hunks)apps/web/utils/cold-email/is-cold-email.test.ts(8 hunks)apps/web/utils/cold-email/is-cold-email.ts(3 hunks)apps/web/utils/reply-tracker/outbound.ts(4 hunks)
💤 Files with no reviewable changes (1)
- apps/web/utils/categorize/senders/categorize.ts
🧰 Additional context used
📓 Path-based instructions (15)
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/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/google/webhook/process-history-item.test.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/app/api/google/webhook/process-history-item.ts
!{.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/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.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/app/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.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/app/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.ts
**/*.test.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
Include security tests in your test suites to verify authentication, authorization, and error handling.
Files:
apps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.ts
**/*.test.{ts,js,tsx,jsx}
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
**/*.test.{ts,js,tsx,jsx}: Tests are colocated next to the tested file (e.g.,dir/format.tsanddir/format.test.ts)
Usevi.mock("server-only", () => ({}));to mock theserver-onlymodule in tests
Mock@/utils/prismain tests usingvi.mock("@/utils/prisma")and use the provided prisma mock
Mock external dependencies in tests
Clean up mocks between tests
Do not mock the Logger
Files:
apps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/google/webhook/process-history-item.test.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/app/api/google/webhook/process-history-item.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/app/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.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/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.
Files:
apps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/cold-email/is-cold-email.test.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/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.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/cold-email/is-cold-email.test.tsapps/web/utils/actions/categorize.tsapps/web/utils/cold-email/is-cold-email.tsapps/web/utils/reply-tracker/outbound.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/categorize.ts
apps/web/utils/actions/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)
apps/web/utils/actions/*.ts: Implement all server actions using thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient).
UseactionClientUserwhen only authenticated user context (userId) is needed.
UseactionClientwhen both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.
UseadminActionClientfor actions restricted to admin users.
Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject in the.action()handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
UseSafeErrorfor expected/handled errors within actions if needed.next-safe-actionprovides centralized error handling.
Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied viawithServerActionInstrumentationwithin the safe action clients.
If an action modifies data displayed elsewhere, userevalidatePathorrevalidateTagfromnext/cachewithin the action handler as needed.Server action files must start with
use server
Files:
apps/web/utils/actions/categorize.ts
🧠 Learnings (5)
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths in LLM-related tests
Applied to files:
apps/web/utils/cold-email/is-cold-email.test.ts
📚 Learning: 2025-06-23T12:27:30.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-06-23T12:27:30.570Z
Learning: When mocking Prisma in Vitest, import the Prisma mock from '@/utils/__mocks__/prisma', mock '@/utils/prisma', and clear all mocks in a beforeEach hook to ensure test isolation.
Applied to files:
apps/web/utils/cold-email/is-cold-email.test.ts
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/prisma.mdc:0-0
Timestamp: 2025-06-23T12:26:53.882Z
Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.
Applied to files:
apps/web/utils/cold-email/is-cold-email.test.tsapps/web/app/api/google/webhook/process-history-item.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/actions/categorize.tsapps/web/utils/reply-tracker/outbound.tsapps/web/app/api/google/webhook/process-history-item.ts
📚 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/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operations
Applied to files:
apps/web/app/api/google/webhook/process-history-item.ts
🧬 Code graph analysis (5)
apps/web/app/api/google/webhook/process-history-item.test.ts (1)
apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(161-187)
apps/web/utils/cold-email/is-cold-email.test.ts (1)
apps/web/utils/cold-email/is-cold-email.ts (1)
blockColdEmail(190-270)
apps/web/utils/actions/categorize.ts (2)
apps/web/utils/email/provider.ts (1)
createEmailProvider(13-28)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)
apps/web/app/api/outlook/webhook/process-history-item.ts (4)
apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(161-187)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)apps/web/utils/reply-tracker/draft-tracking.ts (2)
trackSentDraftStatus(14-125)cleanupThreadAIDrafts(132-248)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(11-89)
apps/web/app/api/google/webhook/process-history-item.ts (4)
apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(161-187)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)apps/web/utils/email/types.ts (1)
EmailProvider(36-170)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(11-89)
🔇 Additional comments (8)
apps/web/utils/cold-email/is-cold-email.test.ts (1)
49-55: LGTM: rename and new entry-point usage.Importing and calling blockColdEmail directly is consistent with the provider-based refactor; mocks are set up correctly.
apps/web/utils/actions/categorize.ts (1)
113-123: Resolved:ctx.provideris wired inactionClientmiddleware.
TheactionClientsetup insafe-action.tsinjectsprovider: emailAccount.account.providerintoctxbefore invoking any action, soctx.providerwill always be defined here.apps/web/app/api/google/webhook/process-history-item.ts (1)
242-301: Outbound handling looks correct with provider abstraction.Concurrent tracking + reply handling with cleanup and error isolation is solid.
apps/web/utils/reply-tracker/outbound.ts (3)
41-41: LGTM: resolve trackers via providerSwitch to
resolveReplyTrackers(provider, ...)reads well and aligns with the abstraction.
11-19: AllhandleOutboundReplycalls includeprovider, so the signature change is safe and no updates are needed.
168-171: No changes required for removeNeedsReplyLabel
Both providers already no-op when the label isn’t present—Outlook skips patching if a message lacks the category, and Gmail’s modify call simply ignores removeLabelIds that aren’t applied.apps/web/utils/cold-email/is-cold-email.ts (2)
161-187: runColdEmailBlocker flow looks goodThe orchestration is clean and uses the provider abstraction correctly.
265-266: markReadThread signature is consistent
All providers implement the two-arg signature(threadId: string, read: boolean)as defined in apps/web/utils/email/types.ts. No changes required.
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 (1)
apps/web/utils/reply-tracker/outbound.ts (1)
94-105: Make label application optional inside creatorAllow
createReplyTrackerOutboundto accept an optional label and no-op when absent to support Outlook.async function createReplyTrackerOutbound({ provider, emailAccountId, threadId, messageId, - awaitingReplyLabelId, + awaitingReplyLabelId, sentAt, logger, }: { provider: EmailProvider; emailAccountId: string; threadId: string; messageId: string; - awaitingReplyLabelId: string; + awaitingReplyLabelId?: string; sentAt: Date; logger: Logger; }) { @@ - const labelPromise = provider.labelAwaitingReply( - messageId, - awaitingReplyLabelId, - ); + const labelPromise = awaitingReplyLabelId + ? provider.labelAwaitingReply(messageId, awaitingReplyLabelId) + : Promise.resolve();Also applies to: 126-129
♻️ Duplicate comments (1)
apps/web/app/api/google/webhook/process-history-item.ts (1)
141-144: Fixed:handleOutboundnow passesproviderThis resolves earlier signature mismatch reports for
handleOutboundReply.
🧹 Nitpick comments (2)
apps/web/utils/email/types.ts (1)
62-71: Reduce duplication: prefer a shared type alias for label pairIntroduce a single
ReplyTrackingLabelstype to keep provider/client code consistent.+export type ReplyTrackingLabels = { + awaitingReplyLabelId: string; + needsReplyLabelId: string; +}; @@ - getReplyTrackingLabels(): Promise<{ - awaitingReplyLabelId: string; - needsReplyLabelId: string; - }>; + getReplyTrackingLabels(): Promise<ReplyTrackingLabels>;apps/web/utils/reply-tracker/outbound.ts (1)
51-55: Remove duplicate “not latest in thread” logsYou log the same condition both here and in
isMessageLatestInThread. Keep one.- if (!isLatest) { - logger.info( - "Skipping outbound reply check: message is not the latest in the thread", - ); - return; // Stop processing if not the latest - } + if (!isLatest) return; // Helper already logs contextAlso applies to: 195-203
📜 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 (4)
apps/web/app/api/google/webhook/process-history-item.ts(8 hunks)apps/web/utils/email/google.ts(4 hunks)apps/web/utils/email/types.ts(1 hunks)apps/web/utils/reply-tracker/outbound.ts(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/utils/email/google.ts
🧰 Additional context used
📓 Path-based instructions (10)
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/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/types.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/google/webhook/process-history-item.ts
!{.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/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/types.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/app/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/types.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/app/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/types.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/google/webhook/process-history-item.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/app/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/types.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/api/google/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/types.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/reply-tracker/outbound.tsapps/web/utils/email/types.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/reply-tracker/outbound.tsapps/web/utils/email/types.ts
🧠 Learnings (3)
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/prisma.mdc:0-0
Timestamp: 2025-06-23T12:26:53.882Z
Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.
Applied to files:
apps/web/app/api/google/webhook/process-history-item.ts
📚 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/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operations
Applied to files:
apps/web/app/api/google/webhook/process-history-item.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/reply-tracker/outbound.ts
🧬 Code graph analysis (1)
apps/web/app/api/google/webhook/process-history-item.ts (4)
apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(161-187)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)apps/web/utils/email/types.ts (1)
EmailProvider(36-170)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(11-87)
🔇 Additional comments (10)
apps/web/app/api/google/webhook/process-history-item.ts (8)
63-67: Good: provider created once and reusedCreating the EmailProvider up front avoids redundant instantiation and keeps flows provider-agnostic.
169-182: Provider-based cold-email blocker call looks correctPassing
providerand normalized email payload aligns with the refactor.
211-212: Categorization now provider-backedSwitch to provider for historical lookup is consistent with the new abstraction.
218-225: Rules engine client switched to providerAlignment with provider interface acknowledged.
244-245: Type-safeEmailProviderparam onhandleOutboundExplicit type import improves clarity.
257-267: Concurrent tracking + reply handling passing provider is correctPattern with
Promise.allSettledand proper logging is sensible.
286-291: Cleanup uses provider as wellConsistent with the refactor.
141-144: All handleOutboundReply calls include the provider parameter; no stale usages remain.apps/web/utils/reply-tracker/outbound.ts (2)
36-37: Correct: resolve NEEDS_REPLY trackers before creating AWAITINGEnsures old "needs reply" state is cleared when we send a reply.
166-169: Good: label removal wrapped with DB resolution
Promise.allSettledprevents label issues from blocking DB resolution.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
apps/web/utils/email/types.ts (1)
62-71: Clarify create-if-missing vs lookup-only semantics (and document them).Please document that getReplyTrackingLabels creates labels (non-null) while the single getters are lookup-only and may return null. This avoids accidental creation in callers and aligns provider implementations.
removeThreadLabel(threadId: string, labelId: string): Promise<void>; - getReplyTrackingLabels(): Promise<{ + /** + * Returns IDs for reply-tracking labels, creating them if missing. + * Must not return nulls. + */ + getReplyTrackingLabels(): Promise<{ awaitingReplyLabelId: string; needsReplyLabelId: string; }>; - getNeedsReplyLabel(): Promise<string | null>; - getAwaitingReplyLabel(): Promise<string | null>; + /** + * Lightweight lookup; must not create labels. May return null. + */ + getNeedsReplyLabel(): Promise<string | null>; + /** + * Lightweight lookup; must not create labels. May return null. + */ + getAwaitingReplyLabel(): Promise<string | null>;apps/web/utils/email/microsoft.ts (1)
280-291: Harden removeThreadLabel against 404/missing category and API errors.Wrap id→name resolution in try/catch and skip when not found to avoid patching with an empty name.
async removeThreadLabel(threadId: string, labelId: string): Promise<void> { - // TODO: this can be more efficient by using the label name directly - // Get the label to convert ID to name (Outlook uses names) - const label = await getLabelById({ client: this.client, id: labelId }); - const categoryName = label.displayName || ""; - - await removeThreadLabel({ - client: this.client, - threadId, - categoryName, - }); + try { + const label = await getLabelById({ client: this.client, id: labelId }); + const categoryName = label?.displayName; + if (!categoryName) { + logger.warn("Outlook category not found; skipping removal", { + threadId, + labelId, + }); + return; + } + await removeThreadLabel({ + client: this.client, + threadId, + categoryName, + }); + } catch (error) { + logger.warn("Failed to resolve Outlook category by id; skipping removal", { + threadId, + labelId, + error, + }); + } }
🧹 Nitpick comments (3)
apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx (1)
61-61: Nit: streamline user-facing copy.
Avoid the extra conjunction/comma to read more cleanly.- description={`Our AI detects when your sent emails need a response and labels them '${AWAITING_REPLY_LABEL_NAME}', and removes the '${NEEDS_REPLY_LABEL_NAME}' label.`} + description={`Our AI detects when your sent emails need a response, labels them '${AWAITING_REPLY_LABEL_NAME}', and removes the '${NEEDS_REPLY_LABEL_NAME}' label.`}apps/web/utils/email/types.ts (1)
69-69: Remove commented-out API from interface.The placeholder // labelNeedsReply(...) should be deleted to keep the public surface clear.
- // labelNeedsReply(messageId: string): Promise<void>;apps/web/utils/email/microsoft.ts (1)
777-784: Remove commented-out method.Dead/commented code adds noise.
- // async labelNeedsReply(messageId: string): Promise<void> { - // await labelMessage({ - // client: this.client, - // messageId, - // categories: [NEEDS_REPLY_LABEL_NAME], - // }); - // }
📜 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 (6)
apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx(2 hunks)apps/web/prisma/schema.prisma(1 hunks)apps/web/utils/email/google.ts(4 hunks)apps/web/utils/email/microsoft.ts(5 hunks)apps/web/utils/email/types.ts(1 hunks)apps/web/utils/reply-tracker/outbound.ts(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/prisma/schema.prisma
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/utils/email/google.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]/assistant/settings/AwaitingReplySetting.tsxapps/web/utils/email/types.tsapps/web/utils/reply-tracker/outbound.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]/assistant/settings/AwaitingReplySetting.tsx
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]/assistant/settings/AwaitingReplySetting.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]/assistant/settings/AwaitingReplySetting.tsxapps/web/utils/email/types.tsapps/web/utils/reply-tracker/outbound.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]/assistant/settings/AwaitingReplySetting.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]/assistant/settings/AwaitingReplySetting.tsxapps/web/utils/email/types.tsapps/web/utils/reply-tracker/outbound.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]/assistant/settings/AwaitingReplySetting.tsx
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]/assistant/settings/AwaitingReplySetting.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]/assistant/settings/AwaitingReplySetting.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]/assistant/settings/AwaitingReplySetting.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]/assistant/settings/AwaitingReplySetting.tsxapps/web/utils/email/types.tsapps/web/utils/reply-tracker/outbound.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]/assistant/settings/AwaitingReplySetting.tsxapps/web/utils/email/types.tsapps/web/utils/reply-tracker/outbound.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]/assistant/settings/AwaitingReplySetting.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]/assistant/settings/AwaitingReplySetting.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/utils/email/types.tsapps/web/utils/reply-tracker/outbound.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/types.tsapps/web/utils/reply-tracker/outbound.tsapps/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/types.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/email/microsoft.ts🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/gmail-api.mdc:0-0 Timestamp: 2025-07-18T15:05:34.899Z Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')Applied to files:
apps/web/utils/reply-tracker/outbound.ts🧬 Code graph analysis (1)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/outlook/label.ts (4)
getLabelById(70-80)removeThreadLabel(233-284)getOrCreateLabels(159-191)labelMessage(194-206)🔇 Additional comments (8)
apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx (1)
11-14: LGTM: constants import keeps copy in sync with source-of-truth.
No concerns.apps/web/utils/reply-tracker/outbound.ts (4)
36-38: Verify ordering: resolving NEEDS_REPLY before “latest message” gate.This resolves DB trackers and removes labels even if the processed message isn’t latest. If that’s unintended, move after the isLatest check.
- // Resolve existing NEEDS_REPLY trackers for this thread - await resolveReplyTrackers(provider, emailAccount.userId, message.threadId); const threadMessages = await provider.getThreadMessages(message.threadId); @@ if (!isLatest) { logger.info( "Skipping outbound reply check: message is not the latest in the thread", ); return; // Stop processing if not the latest } + // Resolve existing NEEDS_REPLY trackers for this thread + await resolveReplyTrackers(provider, emailAccount.userId, message.threadId);
69-76: LGTM: persist tracker with robust logging.Creation path looks solid and uses Promise.allSettled downstream for resilience.
117-118: Ensure provider labels can be applied without prior creation.provider.labelAwaitingReply(messageId) assumes the label exists. Confirm provider ensures existence (or fetch/create labels here first).
154-157: LGTM: non-blocking cleanup of NEEDS_REPLY.AllSettled prevents failures from blocking DB updates.
apps/web/utils/email/microsoft.ts (3)
13-14: LGTM: importing required Outlook label helpers.These imports are correct and used below.
Also applies to: 28-29, 39-39
793-799: LGTM: thread-wide removal by category name.Uses utility that safely no-ops on missing category.
801-807: LGTM: symmetric removal for NEEDS_REPLY.Consistent with awaiting variant.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
156-164: Possible runtime error: spreading possibly undefinedaction.content
{ ...action.content }throws ifcontentis undefined for actions that don’t use it. Guard the spread.Apply:
- .map((action) => ({ + .map((action) => ({ ...action, delayInMinutes: action.delayInMinutes, - content: { - ...action.content, - setManually: !!action.content?.value, - }, + content: action.content + ? { ...action.content, setManually: !!action.content.value } + : undefined, folderName: action.folderName, folderId: action.folderId, })),
239-245: Optimistic update uses index mapping that’s now skewed by filtered actionsBecause
data.actionsexcludes DIGEST/TRACK_THREAD whilerule.actionsmay include them, the index-based map can assign the wrongdelayInMinutes. Recompute delays only for editable actions.Apply:
- const optimisticData = { - rule: { - ...rule, - actions: rule.actions.map((action, index) => ({ - ...action, - delayInMinutes: data.actions[index]?.delayInMinutes, - })), - }, - }; + // Avoid index skew: only editable actions get delay updates + const editableServerActions = rule.actions.filter( + (a) => a.type !== ActionType.DIGEST && a.type !== ActionType.TRACK_THREAD + ); + let editableIdx = 0; + const updatedActions = rule.actions.map((a) => { + if (a.type === ActionType.DIGEST || a.type === ActionType.TRACK_THREAD) return a; + const delayInMinutes = data.actions[editableIdx++]?.delayInMinutes; + return { ...a, delayInMinutes }; + }); + const optimisticData = { + rule: { + ...rule, + actions: updatedActions, + }, + };
🧹 Nitpick comments (3)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (3)
373-373: Streamlined deps — fine; minor nitIncluding
provideris enough sinceterminologyderives from it; the extraterminology.label.actiondep is redundant. Your call.Apply:
- }, [provider, terminology.label.action]); + }, [provider]);
1343-1344: Dead UI path for TRACK_THREAD — remove or gate with a flagWith TRACK_THREAD filtered from defaults and not present in
typeOptions, this branch and component won’t render. Clean up to reduce surface area (also drops unused imports).Apply:
- {action.type === ActionType.TRACK_THREAD && <ReplyTrackerAction />}- import { NEEDS_REPLY_LABEL_NAME } from "@/utils/reply-tracker/consts"; - import { Badge } from "@/components/Badge";-function ReplyTrackerAction() { - return ( - <div className="flex h-full items-center justify-center"> - <div className="max-w-sm text-center text-sm text-muted-foreground"> - This action tracks emails this rule is applied to and removes the{" "} - <Badge color="green">{NEEDS_REPLY_LABEL_NAME}</Badge> label after you - reply to the email. - </div> - </div> - ); -} +// Reply-tracker action UI removed as it’s no longer user-selectable.Also applies to: 1498-1508
255-256: Remove console usage in UIProject guidelines disallow
console. Errors are already surfaced via toasts.Apply:
- console.error(res); + // Removed console logging per guidelines- console.error(res); + // Removed console logging per guidelinesAlso applies to: 287-288
📜 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 (1)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
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]/assistant/RuleForm.tsx
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
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]/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]/assistant/RuleForm.tsx
**/*.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]/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]/assistant/RuleForm.tsx
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]/assistant/RuleForm.tsx
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]/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]/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]/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]/assistant/RuleForm.tsx
!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]/assistant/RuleForm.tsx
**/*.{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]/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]/assistant/RuleForm.tsx🔇 Additional comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
150-154: Validate migration for legacy TRACK_THREAD/DIGEST rulesPrisma schema (apps/web/prisma/schema.prisma, lines 785–796) still includes
TRACK_THREADandDIGEST. Ensure you’ve added a migration to handle or remove legacy rules that reference these actions now excluded from the editable UI.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx (1)
21-27: Tighten the guard and clarify the intent.Minor readability nit: compress the condition and make the comment explicit about which types are hidden.
Apply this diff:
- // don't display - if ( - action.type === ActionType.TRACK_THREAD || - action.type === ActionType.DIGEST - ) { - return null; - } + // Skip rendering for reply-tracker UI-only actions + if ([ActionType.TRACK_THREAD, ActionType.DIGEST].includes(action.type)) { + return null; + }apps/web/utils/actions/settings.ts (1)
98-112: Handle “no matching rules” as an explicit case.If no rules label NEEDS_REPLY_LABEL_NAME, enable is a no-op but returns success. Either create a rule, or return a typed result indicating zero rules updated so the client can inform the user.
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx (2)
35-37: Return early with void, not null, from async handler.Keep type as Promise for onChange handlers.
Apply:
- if (!emailAccountData) return null; + if (!emailAccountData) return;
67-71: Optional: Disable the toggle while loading or when no To Reply rules exist.Prevents no-op toggles and clarifies UX.
Example:
<Toggle name="auto-remove-to-reply" enabled={enabled} - onChange={handleToggle} + onChange={handleToggle} + disabled={isLoading || rulesLoading || !rules?.length} />
📜 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 (5)
apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx(2 hunks)apps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx(1 hunks)apps/web/utils/actions/settings.ts(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx
🧰 Additional context used
📓 Path-based instructions (19)
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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/utils/actions/settings.tsapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/utils/actions/settings.tsapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
**/*.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/utils/actions/settings.tsapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/utils/actions/settings.tsapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
!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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/utils/actions/settings.tsapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
**/*.{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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.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]/assistant/settings/ToReplySetting.tsxapps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsxapps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.Files:
apps/web/utils/actions/settings.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 ZodFiles:
apps/web/utils/actions/settings.tsapps/web/utils/actions/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)
apps/web/utils/actions/*.ts: Implement all server actions using thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient).
UseactionClientUserwhen only authenticated user context (userId) is needed.
UseactionClientwhen both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.
UseadminActionClientfor actions restricted to admin users.
Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject in the.action()handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
UseSafeErrorfor expected/handled errors within actions if needed.next-safe-actionprovides centralized error handling.
Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied viawithServerActionInstrumentationwithin the safe action clients.
If an action modifies data displayed elsewhere, userevalidatePathorrevalidateTagfromnext/cachewithin the action handler as needed.Server action files must start with
use serverFiles:
apps/web/utils/actions/settings.tsapps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logicFiles:
apps/web/utils/actions/settings.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/actions/settings.ts🧬 Code graph analysis (3)
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx (5)
apps/web/hooks/useEmailAccountFull.ts (1)
useEmailAccountFull(5-10)apps/web/hooks/useRules.tsx (1)
useRules(4-8)apps/web/utils/actions/settings.ts (1)
toggleToReplyTrackingAction(94-139)apps/web/components/SettingCard.tsx (1)
SettingCard(3-26)apps/web/components/LoadingContent.tsx (1)
LoadingContent(13-27)apps/web/utils/actions/settings.ts (1)
apps/web/utils/actions/safe-action.ts (1)
actionClient(46-86)apps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx (1)
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx (1)
ToReplySetting(15-76)🔇 Additional comments (3)
apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx (2)
21-27: Hiding TRACK_THREAD and DIGEST in UI is correct.Early-return cleanly suppresses these action types and avoids unnecessary icon/text lookups. Matches the PR’s intent to remove them from user-facing summaries.
21-27: Verify upstream filtering in parent lists before mapping to ActionSummaryCard.
ActionSummaryCard hides DIGEST and TRACK_THREAD, but in RuleForm.tsx (line 869) you’re mapping all actions directly without filtering, which can cause transient null renders or count mismatches. Ensure you.filter(action => action.type !== ActionType.DIGEST && action.type !== ActionType.TRACK_THREAD)on the actions array before mapping.apps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx (1)
7-7: LGTM: Added ToReplySetting integration is correct.Import path uses "@/"; placement after AwaitingReplySetting matches UX intent. No further action.
Also applies to: 14-14
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx
Outdated
Show resolved
Hide resolved
|
Claude finished @elie222's task —— View job PR Code Review: Reply Tracker for OutlookI've conducted a comprehensive analysis of this PR focusing on code quality, potential bugs, performance, security, and test coverage. Review Checklist:
🏗️ Architecture AnalysisStrengths:
Areas for improvement:
🐛 Critical Issues Found1. Data Inconsistency Risk in Settings (
|
|
@claude review |
|
Claude encountered an error —— View job I'll conduct a comprehensive code review of this PR that adds reply tracking support for Outlook. Review Checklist:
|
|
Claude finished @elie222's task —— View job I'll conduct a comprehensive code review of this PR focusing on code quality, potential bugs, performance, security, and test coverage. Review Checklist:
🏗️ Architecture AnalysisStrengths:
Areas for improvement:
🐛 Critical Issues Found1. Data Inconsistency Risk in Settings (
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (4)
apps/web/utils/email/google.ts (2)
300-307: Guard against blank IDs and use instance remover for consistencyasync removeAwaitingReplyLabel(threadId: string): Promise<void> { - const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); - if (!awaitingReplyLabelId) { - logger.warn("No awaiting reply label found"); + const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); + if (!awaitingReplyLabelId?.trim()) { + logger.warn("No awaiting reply label found", { threadId }); return; } - await removeThreadLabel(this.client, threadId, awaitingReplyLabelId); + await this.removeThreadLabel(threadId, awaitingReplyLabelId); }
309-316: Ditto for removeNeedsReplyLabelasync removeNeedsReplyLabel(threadId: string): Promise<void> { const needsReplyLabelId = await this.getNeedsReplyLabel(); - if (!needsReplyLabelId) { - logger.warn("No needs reply label found"); + if (!needsReplyLabelId?.trim()) { + logger.warn("No needs reply label found", { threadId }); return; } - await removeThreadLabel(this.client, threadId, needsReplyLabelId); + await this.removeThreadLabel(threadId, needsReplyLabelId); }apps/web/utils/email/microsoft.ts (2)
762-764: Resolved prior concern: ensures category exists before labelingUsing labelMessage helper is correct here.
280-291: Harden removeThreadLabel: handle 404/missing names and avoid empty patchesCurrent code may PATCH with an empty category when the id can’t be resolved.
async removeThreadLabel(threadId: string, labelId: string): Promise<void> { - // TODO: this can be more efficient by using the label name directly - // Get the label to convert ID to name (Outlook uses names) - const label = await getLabelById({ client: this.client, id: labelId }); - const categoryName = label.displayName || ""; - - await removeThreadLabel({ - client: this.client, - threadId, - categoryName, - }); + try { + // Resolve ID -> displayName (Graph uses names for categories) + const label = await getLabelById({ client: this.client, id: labelId }); + const categoryName = label?.displayName; + if (!categoryName?.trim()) { + logger.warn("Outlook category not found; skipping removal", { + threadId, + labelId, + }); + return; + } + await removeThreadLabel({ + client: this.client, + threadId, + categoryName, + }); + } catch (error) { + logger.warn("Failed to resolve Outlook category by id; skipping removal", { + threadId, + labelId, + error, + }); + } }
🧹 Nitpick comments (5)
apps/web/utils/email/types.ts (2)
62-66: Clarify create-vs-lookup semantics for reply-tracking gettersThese single-label getters are nullable, but current provider impls create-if-missing. Document the behavior to avoid misuse with strict null checks.
export interface EmailProvider { @@ - getNeedsReplyLabel(): Promise<string | null>; - getAwaitingReplyLabel(): Promise<string | null>; + /** + * Returns the provider-specific ID for the "Needs reply" label. + * Implementations MAY create the label if missing. Returns null only if the + * provider doesn't support labels/categories. + */ + getNeedsReplyLabel(): Promise<string | null>; + /** + * Returns the provider-specific ID for the "Awaiting reply" label. + * Implementations MAY create the label if missing. Returns null only if the + * provider doesn't support labels/categories. + */ + getAwaitingReplyLabel(): Promise<string | null>; labelAwaitingReply(messageId: string): Promise<void>; removeAwaitingReplyLabel(threadId: string): Promise<void>; removeNeedsReplyLabel(threadId: string): Promise<void>;
62-66: Add symmetric helper: labelNeedsReply(messageId) for parityYou added removeNeedsReplyLabel but not labelNeedsReply; symmetry simplifies callers and aligns with labelAwaitingReply.
getAwaitingReplyLabel(): Promise<string | null>; labelAwaitingReply(messageId: string): Promise<void>; + labelNeedsReply(messageId: string): Promise<void>; removeAwaitingReplyLabel(threadId: string): Promise<void>; removeNeedsReplyLabel(threadId: string): Promise<void>;If you accept, I can follow up with provider impls in Gmail/Outlook.
apps/web/utils/email/google.ts (1)
633-644: Add context to warning and guard trimmed ID before labelingImproves observability and avoids sending [""] to Gmail.
async labelAwaitingReply(messageId: string): Promise<void> { const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); - if (!awaitingReplyLabelId) { - logger.warn("No awaiting reply label found"); + if (!awaitingReplyLabelId?.trim()) { + logger.warn("No awaiting reply label found", { messageId }); return; } await labelMessage({ gmail: this.client, messageId, addLabelIds: [awaitingReplyLabelId], }); }apps/web/utils/email/microsoft.ts (2)
744-751: Confirm intended behavior: single getter creates the categoryThis creates the NEEDS_REPLY category on lookup. If lookup-only was intended, switch to a non-creating list+find. Otherwise, keep and document in the interface (see types.ts comment).
753-760: Same question for AWAITING_REPLY getterBehavior matches the above; ensure consistency with Gmail and the documented contract.
📜 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 (6)
apps/web/utils/email/google.ts(3 hunks)apps/web/utils/email/microsoft.ts(5 hunks)apps/web/utils/email/types.ts(1 hunks)apps/web/utils/gmail/label.ts(2 hunks)apps/web/utils/outlook/label.ts(1 hunks)version.txt(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- version.txt
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/utils/outlook/label.ts
- apps/web/utils/gmail/label.ts
🧰 Additional context used
📓 Path-based instructions (8)
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/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.ts
!{.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/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.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/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.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/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.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/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.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/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.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/email/google.tsapps/web/utils/email/types.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/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/email/microsoft.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/email/google.ts
🧬 Code graph analysis (2)
apps/web/utils/email/google.ts (1)
apps/web/utils/gmail/label.ts (4)
getAwaitingReplyLabel(363-371)getNeedsReplyLabel(373-381)removeThreadLabel(67-77)labelMessage(133-149)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/outlook/label.ts (3)
getLabelById(70-80)removeThreadLabel(234-285)getOrCreateLabels(159-191)
🔇 Additional comments (3)
apps/web/utils/email/google.ts (1)
16-18: LGTM: Imports aligned with provider-based label utilitiesapps/web/utils/email/microsoft.ts (2)
766-772: LGTM: removeAwaitingReplyLabel by category nameHelper is resilient and logs per-message failures.
774-780: LGTM: removeNeedsReplyLabel mirrors awaiting-removal path
| async getAwaitingReplyLabel(): Promise<string | null> { | ||
| return getAwaitingReplyLabel(this.client); | ||
| } | ||
|
|
||
| async getNeedsReplyLabel(): Promise<string | null> { | ||
| return getNeedsReplyLabel(this.client); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Normalize empty IDs to null to honor the interface
gmail/label.getAwaitingReplyLabel may return "", which violates the nullable contract and can leak through.
async getAwaitingReplyLabel(): Promise<string | null> {
- return getAwaitingReplyLabel(this.client);
+ const id = await getAwaitingReplyLabel(this.client);
+ return id || null;
}📝 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.
| async getAwaitingReplyLabel(): Promise<string | null> { | |
| return getAwaitingReplyLabel(this.client); | |
| } | |
| async getNeedsReplyLabel(): Promise<string | null> { | |
| return getNeedsReplyLabel(this.client); | |
| } | |
| async getAwaitingReplyLabel(): Promise<string | null> { | |
| const id = await getAwaitingReplyLabel(this.client); | |
| return id || null; | |
| } |
🤖 Prompt for AI Agents
In apps/web/utils/email/google.ts around lines 292 to 298, the wrapper methods
getAwaitingReplyLabel and getNeedsReplyLabel can return an empty string from the
underlying gmail/label functions which violates the nullable interface; update
each method to await the helper call, check if the returned value is an empty
string (""), and return null in that case (otherwise return the value) so
callers always receive either a non-empty string or null.
| async getNeedsReplyLabel(): Promise<string | null> { | ||
| return getNeedsReplyLabel(this.client); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Same normalization for getNeedsReplyLabel
async getNeedsReplyLabel(): Promise<string | null> {
- return getNeedsReplyLabel(this.client);
+ const id = await getNeedsReplyLabel(this.client);
+ return id || null;
}📝 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.
| async getNeedsReplyLabel(): Promise<string | null> { | |
| return getNeedsReplyLabel(this.client); | |
| } | |
| async getNeedsReplyLabel(): Promise<string | null> { | |
| const id = await getNeedsReplyLabel(this.client); | |
| return id || null; | |
| } |
🤖 Prompt for AI Agents
In apps/web/utils/email/google.ts around lines 296 to 298, the method simply
returns getNeedsReplyLabel(this.client) without applying the same label
normalization used elsewhere; call the existing normalization helper on the
result (e.g., const label = await getNeedsReplyLabel(this.client); return label
? normalizeLabel(label) : null), handling the null case and ensuring the
normalizeLabel helper is imported or referenced the same way other label getters
do.
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 (4)
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx (2)
295-295: Clamp and guard progress calculation (avoid Infinity/NaN).Divide-by-zero when totalSteps is 0, and values can exceed 100. Clamp to [0,100].
Apply:
- const progressPercentage = (completedCount / totalSteps) * 100; + const progressPercentage = + totalSteps > 0 + ? Math.min(100, Math.max(0, (completedCount / totalSteps) * 100)) + : 0;
191-200: Keep progress accessible on mobile.Hiding the text with hidden removes it from screen readers. Add ARIA to the bar.
- <div className="h-2 w-32 overflow-hidden rounded-full bg-muted"> + <div + className="h-2 w-32 overflow-hidden rounded-full bg-muted" + role="progressbar" + aria-valuenow={Math.round(progressPercentage)} + aria-valuemin={0} + aria-valuemax={100} + aria-label={`${completedCount} of ${totalSteps} completed`} + > <div className="h-2 rounded-full bg-primary" style={{ width: `${progressPercentage}%` }} /> - </div> + </div>.github/workflows/claude-code-review.yml (2)
19-24: Insufficient permissions to post reviews/comments; remove unused id-tokenThe action won’t be able to comment with read-only scopes. Grant write for PRs/issues and drop id-token unless explicitly needed.
permissions: - contents: read - pull-requests: read - issues: read - id-token: write + contents: read + pull-requests: write + issues: write
33-33: Pin Claude Code Action to a specific commit SHA
Replace the floating@betatag with the vetted SHA for v1.0.2:- uses: anthropics/claude-code-action@beta + uses: anthropics/claude-code-action@34163815a57f583c8631d0b99240b9e560dd6e37(line 33, .github/workflows/claude-code-review.yml)
🧹 Nitpick comments (1)
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx (1)
135-150: Verify Tailwind size- support and align usage.*size-10/size-6 need Tailwind v3.3+. Confirm version; otherwise revert to h-/w-. Also, FeatureCard (Lines 43–46) still uses h-10 w-10—consider switching to size-10 for consistency.
If supported, update FeatureCard’s icon container similarly:
<div className={`size-10 ${iconBg} inline-flex items-center justify-center rounded-full mb-4`}> <Icon className={`h-5 w-5 ${iconColor}`} /> </div>
📜 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 (2)
.github/workflows/claude-code-review.yml(2 hunks)apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
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]/setup/SetupContent.tsx
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx
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]/setup/SetupContent.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]/setup/SetupContent.tsx.github/workflows/claude-code-review.yml
**/*.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]/setup/SetupContent.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]/setup/SetupContent.tsx
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]/setup/SetupContent.tsx
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]/setup/SetupContent.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]/setup/SetupContent.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]/setup/SetupContent.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]/setup/SetupContent.tsx
!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]/setup/SetupContent.tsx.github/workflows/claude-code-review.yml
**/*.{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]/setup/SetupContent.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]/setup/SetupContent.tsx🪛 YAMLlint (1.37.1)
.github/workflows/claude-code-review.yml
[error] 48-48: trailing spaces
(trailing-spaces)
🔇 Additional comments (3)
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx (1)
132-132: Double-check layout with increased gap.gap-8 may cause truncation on very narrow screens (e.g., 320px). Please verify no wrapping/overflow of the action button.
.github/workflows/claude-code-review.yml (2)
8-8: workflow_dispatch lacks inputs; add PR selector for reliable manual runsManual runs won’t know which PR to review. Add an input (e.g., pr_number) and pass it to the action if supported.
- workflow_dispatch: # Manual trigger from GitHub UI + workflow_dispatch: # Manual trigger from GitHub UI + inputs: + pr_number: + description: "Pull request number to review" + required: true + type: stringThen pass it to the action (adjust the input name per the action API):
with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} # e.g.: # pr_number: ${{ inputs.pr_number }}Would you like me to confirm the exact input name supported by anthropics/claude-code-action and wire it up?
40-49: Fix trailing whitespace; removal of direct_prompt looks goodRemove the trailing spaces to satisfy yamllint and keep diffs clean.
- # + #[ suggest_nitpick ]
[ approve_code_changes ]
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/claude-code-review.yml (1)
20-24: Grant only the minimal write scopes needed to post comments; drop unused OIDCThe action likely needs to write PR comments/reviews. Read-only perms will fail; id-token is unnecessary unless you’re using OIDC.
permissions: contents: read - pull-requests: read - issues: read - id-token: write + pull-requests: write + issues: write
♻️ Duplicate comments (8)
apps/web/utils/email/google.ts (4)
292-294: Return null instead of empty string to honor the contractUnderlying helper may return "", so normalize here.
async getAwaitingReplyLabel(): Promise<string | null> { - return getAwaitingReplyLabel(this.client); + const id = await getAwaitingReplyLabel(this.client); + return id || null; }
296-298: Same normalization for getNeedsReplyLabelMirror the null normalization.
async getNeedsReplyLabel(): Promise<string | null> { - return getNeedsReplyLabel(this.client); + const id = await getNeedsReplyLabel(this.client); + return id || null; }
300-307: Use instance remover and add context to logPrefer the provider’s method and include threadId in the warning. Prevents accidental API calls if labelId is empty.
async removeAwaitingReplyLabel(threadId: string): Promise<void> { const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); if (!awaitingReplyLabelId) { - logger.warn("No awaiting reply label found"); + logger.warn("Skipping removeAwaitingReplyLabel: empty labelId", { threadId }); return; } - await removeThreadLabel(this.client, threadId, awaitingReplyLabelId); + await this.removeThreadLabel(threadId, awaitingReplyLabelId); }
309-316: Mirror the same fix for needs-reply removalConsistency + safer logging.
async removeNeedsReplyLabel(threadId: string): Promise<void> { const needsReplyLabelId = await this.getNeedsReplyLabel(); if (!needsReplyLabelId) { - logger.warn("No needs reply label found"); + logger.warn("Skipping removeNeedsReplyLabel: empty labelId", { threadId }); return; } - await removeThreadLabel(this.client, threadId, needsReplyLabelId); + await this.removeThreadLabel(threadId, needsReplyLabelId); }apps/web/utils/email/microsoft.ts (4)
744-751: Getter should not create; perform lookup-only and return null if absentCalling a “get” method shouldn’t mutate mailbox state. Switch to
getLabels+ name match.- async getNeedsReplyLabel(): Promise<string | null> { - const [needsReplyLabel] = await getOrCreateLabels({ - client: this.client, - names: [NEEDS_REPLY_LABEL_NAME], - }); - - return needsReplyLabel.id || null; - } + async getNeedsReplyLabel(): Promise<string | null> { + const labels = await getLabels(this.client); + const match = labels.find( + (l) => + (l.displayName || "").trim().toLowerCase() === + NEEDS_REPLY_LABEL_NAME.toLowerCase(), + ); + return match?.id || null; + }
753-760: Same here: lookup-only for awaiting labelAvoid implicit creation; return null if not found.
- async getAwaitingReplyLabel(): Promise<string | null> { - const [awaitingReplyLabel] = await getOrCreateLabels({ - client: this.client, - names: [AWAITING_REPLY_LABEL_NAME], - }); - - return awaitingReplyLabel.id || null; - } + async getAwaitingReplyLabel(): Promise<string | null> { + const labels = await getLabels(this.client); + const match = labels.find( + (l) => + (l.displayName || "").trim().toLowerCase() === + AWAITING_REPLY_LABEL_NAME.toLowerCase(), + ); + return match?.id || null; + }
762-764: Nice: labelAwaitingReply now ensures category existsSwitching to the class helper avoids failures when the category is missing.
280-291: Harden removeThreadLabel against missing/invalid IDs and API errorsUncaught 404s or missing
displayNamewill currently throw or issue a no-op with an empty category name. Guard, log, and bail; and use the aliased util to avoid name confusion.- async removeThreadLabel(threadId: string, labelId: string): Promise<void> { - // TODO: this can be more efficient by using the label name directly - // Get the label to convert ID to name (Outlook uses names) - const label = await getLabelById({ client: this.client, id: labelId }); - const categoryName = label.displayName || ""; - - await removeThreadLabel({ - client: this.client, - threadId, - categoryName, - }); - } + async removeThreadLabel(threadId: string, labelId: string): Promise<void> { + try { + const label = await getLabelById({ client: this.client, id: labelId }); + const categoryName = label?.displayName; + if (!categoryName) { + logger.warn("Outlook category not found; skipping removal", { + threadId, + labelId, + }); + return; + } + await removeThreadLabelUtil({ + client: this.client, + threadId, + categoryName, + }); + } catch (error) { + logger.warn("Failed to resolve Outlook category by id; skipping removal", { + threadId, + labelId, + error, + }); + } + }
🧹 Nitpick comments (9)
apps/web/utils/email/google.ts (2)
16-18: Avoid shadowing: alias helpers to reduce ambiguity with class methodsThe class also defines methods named getAwaitingReplyLabel/getNeedsReplyLabel. Alias the imports to avoid confusion.
getOrCreateInboxZeroLabel, GmailLabel, - getNeedsReplyLabel, - getAwaitingReplyLabel, + getNeedsReplyLabel as gmailGetNeedsReplyLabel, + getAwaitingReplyLabel as gmailGetAwaitingReplyLabel, } from "@/utils/gmail/label";
633-643: Nit: include messageId in the warning contextGuard looks good; add messageId to aid debugging.
async labelAwaitingReply(messageId: string): Promise<void> { const awaitingReplyLabelId = await this.getAwaitingReplyLabel(); if (!awaitingReplyLabelId) { - logger.warn("No awaiting reply label found"); + logger.warn("Skipping labelAwaitingReply: empty labelId", { messageId }); return; } await labelMessage({ gmail: this.client, messageId, addLabelIds: [awaitingReplyLabelId], }); }apps/web/utils/email/microsoft.ts (2)
28-29: Avoid name collision: alias the imported removeThreadLabelThe class also defines a method named
removeThreadLabel. Alias the import to prevent confusion and accidental shadowing.import { archiveThread, getOrCreateLabel, labelMessage, markReadThread, - removeThreadLabel, + removeThreadLabel as removeThreadLabelUtil, } from "@/utils/outlook/label";
39-39: Drop getOrCreateLabels import (switch to lookup-only getters below)Once you refactor the getters to lookup-only, this import becomes unused.
-import { getOrCreateLabels } from "@/utils/outlook/label";apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (2)
60-63: Avoid using array index as React key.Prevents reconciliation bugs when the list changes.
If actions have a stable id, prefer it:
- <div - key={i} + <div + key={action.id ?? `${action.type}-${i}`} className="space-y-2 rounded-md border border-border bg-muted p-3" >Verify whether
action.idexists inRunRulesResult['actionItems'][number]. If not, consider supplying a stable id from the server.
67-90: Render arrays/URLs cleanly and label “URL/CC/BCC” correctly.Prevents “[object Object]” output and improves a11y.
Apply this diff:
- <span className="min-w-16 font-medium text-muted-foreground"> - {capitalCase(key)}: - </span> - <span className="ml-2 max-h-40 flex-1 overflow-y-auto"> - {value} - </span> + <span className="min-w-16 font-medium text-muted-foreground"> + {key === "url" ? "URL" : key === "cc" ? "CC" : key === "bcc" ? "BCC" : capitalCase(key)}: + </span> + <span className="ml-2 max-h-40 flex-1 overflow-y-auto break-words whitespace-pre-wrap"> + {Array.isArray(value) + ? value.join(", ") + : key === "url" + ? ( + <a + href={String(value)} + target="_blank" + rel="noopener noreferrer" + className="underline break-all" + > + {String(value)} + </a> + ) + : String(value)} + </span>.github/workflows/claude-code-review.yml (3)
32-37: Optional: Defense-in-depth and supply-chain hardening
- Add the same guard at step-level to ensure no secret-using step runs on unintended events.
- Pin the action to a stable tag or commit SHA instead of “beta”.
- name: Run Claude Code Review id: claude-review - uses: anthropics/claude-code-action@beta + # TODO: Pin to a release tag or commit SHA for supply-chain safety + uses: anthropics/claude-code-action@v1 + if: | + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + contains(toLower(github.event.comment.body), '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + ) || + ( + github.event_name == 'pull_request_review_comment' && + contains(toLower(github.event.comment.body), '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + )
41-50: Fix trailing whitespace to satisfy yamllintLine 49 has trailing spaces per the linter hint.
- # + #
3-7: Optional: Prevent duplicate runs from rapid multiple mentionsAdd a concurrency group to auto-cancel superseded runs on the same PR.
on: pull_request_review_comment: types: [created] workflow_dispatch: # Manual trigger from GitHub UI + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true
📜 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 (4)
.github/workflows/claude-code-review.yml(2 hunks)apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx(2 hunks)apps/web/utils/email/google.ts(3 hunks)apps/web/utils/email/microsoft.ts(5 hunks)
🧰 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]/assistant/ProcessResultDisplay.tsxapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
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]/assistant/ProcessResultDisplay.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]/assistant/ProcessResultDisplay.tsxapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts.github/workflows/claude-code-review.yml
**/*.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]/assistant/ProcessResultDisplay.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]/assistant/ProcessResultDisplay.tsxapps/web/utils/email/microsoft.tsapps/web/utils/email/google.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]/assistant/ProcessResultDisplay.tsx
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]/assistant/ProcessResultDisplay.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]/assistant/ProcessResultDisplay.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]/assistant/ProcessResultDisplay.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]/assistant/ProcessResultDisplay.tsxapps/web/utils/email/microsoft.tsapps/web/utils/email/google.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]/assistant/ProcessResultDisplay.tsxapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts.github/workflows/claude-code-review.yml
**/*.{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]/assistant/ProcessResultDisplay.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]/assistant/ProcessResultDisplay.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/utils/email/microsoft.tsapps/web/utils/email/google.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/email/google.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.tsapps/web/utils/email/google.ts🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/gmail-api.mdc:0-0 Timestamp: 2025-07-18T15:05:34.899Z Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')Applied to files:
apps/web/utils/email/google.ts🧬 Code graph analysis (2)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/outlook/label.ts (3)
getLabelById(70-80)removeThreadLabel(234-285)getOrCreateLabels(159-191)apps/web/utils/email/google.ts (1)
apps/web/utils/gmail/label.ts (4)
getAwaitingReplyLabel(363-371)getNeedsReplyLabel(373-381)removeThreadLabel(67-77)labelMessage(133-149)🪛 YAMLlint (1.37.1)
.github/workflows/claude-code-review.yml
[error] 49-49: trailing spaces
(trailing-spaces)
🔇 Additional comments (4)
apps/web/utils/email/microsoft.ts (3)
13-14: Import looks good
getLabelByIdis correctly added and used below.
766-772: OK to remove by well-known category nameUsing the constant category name with the utility is fine; it no-ops cleanly when absent.
774-780: Same as above: removal by name is fineConsistent with the Awaiting label removal.
.github/workflows/claude-code-review.yml (1)
4-6: No change needed: this workflow is intentionally scoped topull_request_review_comment(and manual dispatch); top-levelissue_commentevents are already handled byclaude.yml.Likely an incorrect or invalid review comment.
| # Only run when manually triggered or when trusted users mention @claude in PR review comments | ||
| if: | | ||
| github.event_name == 'workflow_dispatch' || | ||
| ( | ||
| github.event_name == 'pull_request_review_comment' && | ||
| contains(github.event.comment.body, '@claude') && | ||
| contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Harden the “@claude” match and support the issue_comment path in the job guard
- Make the mention check case-insensitive.
- Gate the issue_comment case on PR context (github.event.issue.pull_request).
- if: |
- github.event_name == 'workflow_dispatch' ||
- (
- github.event_name == 'pull_request_review_comment' &&
- contains(github.event.comment.body, '@claude') &&
- contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
- )
+ if: |
+ github.event_name == 'workflow_dispatch' ||
+ (
+ github.event_name == 'issue_comment' &&
+ github.event.issue.pull_request &&
+ contains(toLower(github.event.comment.body), '@claude') &&
+ contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
+ ) ||
+ (
+ github.event_name == 'pull_request_review_comment' &&
+ contains(toLower(github.event.comment.body), '@claude') &&
+ contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
+ )📝 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.
| # Only run when manually triggered or when trusted users mention @claude in PR review comments | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event_name == 'pull_request_review_comment' && | |
| contains(github.event.comment.body, '@claude') && | |
| contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) | |
| ) | |
| # Only run when manually triggered or when trusted users mention @claude in PR review comments | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(toLower(github.event.comment.body), '@claude') && | |
| contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) | |
| ) || | |
| ( | |
| github.event_name == 'pull_request_review_comment' && | |
| contains(toLower(github.event.comment.body), '@claude') && | |
| contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) | |
| ) |
🤖 Prompt for AI Agents
.github/workflows/claude-code-review.yml around lines 10 to 17: the job guard
currently only matches '@claude' case-sensitively and ignores issue_comment
events; update the IF to perform a case-insensitive mention check (e.g., compare
toLower(github.event.comment.body) or toLower(github.event.comment.body)
contains '@claude') and also include the issue_comment event path by adding a
branch for github.event_name == 'issue_comment' that also ensures the comment is
on a PR (github.event.issue.pull_request != null) before checking author
association; keep the existing owner/member/collaborator association gate for
both pull_request_review_comment and issue_comment branches.
| import { Badge } from "@/components/Badge"; | ||
| import { isAIRule } from "@/utils/condition"; | ||
| import { prefixPath } from "@/utils/path"; | ||
| import { ActionType } from "@prisma/client"; |
There was a problem hiding this comment.
Don’t import @prisma/client in a client component.
This pulls Prisma into the browser bundle and can break Next.js (and bloats JS). Use local string literals or a shared web-safe constant instead.
Apply this diff to drop the import:
-import { ActionType } from "@prisma/client";Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx around
line 12, remove the client-side import of @prisma/client (import { ActionType }
from "@prisma/client";) because it pulls Prisma into the browser bundle; replace
usages of ActionType with web-safe alternatives such as string literals (e.g.
"CREATE", "UPDATE") or import a small shared constant/enum from a server-safe or
web-safe module (e.g. a constants file under app/(lib) or shared/types) and
update any type/value references accordingly so no @prisma/client symbols are
imported in this client component.

Summary by CodeRabbit
New Features
UI
Bug Fixes
Chore