Skip to content

Reply tracker for Outlook#721

Merged
elie222 merged 23 commits intomainfrom
feat/track-replies-setting
Aug 31, 2025
Merged

Reply tracker for Outlook#721
elie222 merged 23 commits intomainfrom
feat/track-replies-setting

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Aug 26, 2025

Summary by CodeRabbit

  • New Features

    • Unified "Reply tracking" setting now controls inbound/outbound flows across Gmail and Outlook, with automatic "To Reply" / "Awaiting Reply" label management and provider-based handling.
  • UI

    • Settings toggle shows loading, clearer success/error toasts, and refreshes data/rules concurrently.
    • Rule builder and summaries hide track-thread and digest options.
  • Bug Fixes

    • Improved cross-provider label application/removal, draft tracking, and outbound/inbound flow consistency.
  • Chore

    • Version bumped to v2.7.0.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 26, 2025

Walkthrough

Migrates 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

Cohort / File(s) Summary
Feature spec
​.cursor/rules/features/reply-tracker.mdc
Rewrites feature doc: inbound/outbound flows, moves outboundReplyTrackingEmailAccount, removes DRAFT_EMAIL enabling flow, consolidates "Reply tracking" setting and label definitions.
Webhook handlers & tests
apps/web/app/api/google/webhook/process-history-item.ts, apps/web/app/api/outlook/webhook/process-history-item.ts, apps/web/app/api/google/webhook/process-history-item.test.ts
Create an EmailProvider early and pass it through outbound handling, rules, categorization and cold-email blocker; remove accessToken destructure; update renamed call sites and tests.
EmailProvider interface & implementations
apps/web/utils/email/types.ts, apps/web/utils/email/google.ts, apps/web/utils/email/microsoft.ts
Replace grouped getReplyTrackingLabels with explicit methods: getNeedsReplyLabel, getAwaitingReplyLabel, labelAwaitingReply, removeAwaitingReplyLabel, removeNeedsReplyLabel; change return types to allow null; add draft helpers and update draftEmail return.
Gmail / Outlook label utilities
apps/web/utils/gmail/label.ts, apps/web/utils/outlook/label.ts, (deleted) apps/web/utils/reply-tracker/label.ts
Add Gmail helpers for awaiting/needs labels; add Outlook removeThreadLabel that removes a category across a thread; delete legacy reply-tracker label module.
Reply-tracker core (inbound/outbound/drafts)
apps/web/utils/reply-tracker/outbound.ts, apps/web/utils/reply-tracker/inbound.ts, apps/web/utils/reply-tracker/draft-tracking.ts, apps/web/utils/reply-tracker/check-previous-emails.ts
Migrate flows to provider abstraction: replace Gmail-specific calls with provider methods, introduce provider-based entry points, consolidate provider creation/reuse, preserve tracking logic but use provider.labelAwaitingReply / provider.removeNeedsReplyLabel.
Draft tracking exports & call sites
apps/web/utils/reply-tracker/draft-tracking.ts, apps/web/app/api/outlook/webhook/process-history-item.ts
Rename/add provider-oriented exports; trackSentDraftStatus and cleanupThreadAIDrafts now accept provider; update call sites to new names/args.
Cold-email blocker
apps/web/utils/cold-email/is-cold-email.ts, apps/web/utils/cold-email/is-cold-email.test.ts
Consolidate wrappers; rename runColdEmailBlockerWithProviderrunColdEmailBlocker, blockColdEmailWithProviderblockColdEmail, and use provider-based flow directly.
Categorization actions
apps/web/utils/categorize/senders/categorize.ts, apps/web/utils/actions/categorize.ts
Switch Gmail+token categorizeSender → provider-based categorizeSenderWithProvider; action handlers create/use EmailProvider instead of Gmail session/token.
Settings, rules UI & actions
apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx, apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx, apps/web/utils/actions/settings.ts
Consolidate to single "Reply tracking" setting; rename updateAwaitingReplyTrackingActionupdateReplyTrackingAction which toggles EmailAccount.outboundReplyTracking and batch-adds/removes TRACK_THREAD actions for rules with NEEDS_REPLY label; hide TRACK_THREAD and DIGEST in UI; refresh data and rules on toggle with server-error handling.
Label removal utility
apps/web/utils/outlook/label.ts
Add removeThreadLabel to remove a category across all messages in a thread.
Reply-tracker label helpers removed
(deleted) apps/web/utils/reply-tracker/label.ts
Remove legacy Gmail helper functions in favor of provider-specific implementations.
Misc: schema & version
apps/web/prisma/schema.prisma, version.txt
Add inline doc for EmailAccount.outboundReplyTracking; bump version to v2.7.0.
Workflow
.github/workflows/claude-code-review.yml
Change triggers to pull_request_review_comment and workflow_dispatch; gate job on @claude mentions/manual dispatch; remove active direct_prompt usage.
UI tweaks & displays
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx, apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
Presentational spacing/sizing tweaks; filter/hide TRACK_THREAD and DIGEST from UIs and generated action summaries; ProcessResultDisplay now excludes TRACK_THREAD/DIGEST actions and renders allowed fields only.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested reviewers

  • edulelis
  • johnlowe399-blip

Poem

I hop through threads with tidy paws,
I tag "To Reply" and clear "Awaiting" without pause.
Providers stitched, labels kept neat,
Drafts tucked safe in a cozy seat.
Inbox in bloom — a burrow complete! 🐇✉️

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/track-replies-setting

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel
Copy link

vercel bot commented Aug 26, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Aug 31, 2025 6:27pm

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

sentTextPlain can be undefined. Use a safe fallback, otherwise calculateSimilarity may 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

trackSentDraftStatusWithProvider duplicates trackSentDraftStatus. 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 textPlain only.

-        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

cleanupThreadAIDraftsWithProvider duplicates cleanupThreadAIDrafts. 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 to labelMessage (expects name) — use labelMessageById

EmailProvider.labelMessage(messageId, labelName) expects a label name, but awaitingReplyLabelId is an ID. This will mislabel or fail at runtime (provider-dependent). Switch to labelMessageById and 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 rejections

Per 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 constants

I’ve confirmed that both constants in apps/web/utils/reply-tracker/consts.ts are 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 in consts.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 setter

You’ve added removeAwaitingReplyLabel and removeNeedsReplyLabel. That’s OK short-term, but long-term a single setReplyTrackerState(threadId, "awaiting" | "needs" | "none") or removeThreadLabel(threadId, labelId) plus helpers might reduce surface area and duplication across providers.


61-64: Refactor labelMessageById signature for clarity and type safety

Both GmailProvider (apps/web/utils/email/google.ts) and OutlookProvider (apps/web/utils/email/microsoft.ts) already implement labelMessageById, so the interface change won’t leave any provider unimplemented.

• Rename the second parameter from label to labelRef for 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 creation

You 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 note

This webhook is Gmail-specific, so GmailLabel.SENT is 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 metadata

You 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 labels

Good 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 function

This method calls the imported removeThreadLabel with 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: Collapse handleOutboundReplyWithProvider into a thin wrapper

This function duplicates handleOutboundReply almost 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: Delegate createReplyTrackerOutboundWithProvider to the base helper

Now that createReplyTrackerOutbound safely 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: Delegate resolveReplyTrackersWithProvider to resolveReplyTrackers

Both 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 when internalDate is missing

Outlook messages may lack internalDate. Fall back to parsing date to 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.

📥 Commits

Reviewing files that changed from the base of the PR and between db5af30 and ba3389c.

📒 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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/email/types.ts
  • .cursor/rules/features/reply-tracker.mdc
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/email/microsoft.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/email/microsoft.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements 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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/check-previous-emails.ts
  • apps/web/utils/gmail/label.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/email/types.ts
  • .cursor/rules/features/reply-tracker.mdc
  • apps/web/utils/outlook/label.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
  • apps/web/utils/reply-tracker/draft-tracking.ts
  • apps/web/utils/email/google.ts
  • apps/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 correct

Passing 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-shaking

Good use of import type for the provider interface. Keeps runtime bundle clean and enforces intent.


71-75: Passing provider into label-removed handler is the right direction

Aligns webhook learning path with provider abstraction. Double-check handleLabelRemovedEvent doesn’t still import Gmail utilities directly.


242-301: Nice concurrency with Promise.allSettled and targeted error logging

Running 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 files

Linking to inbound/outbound implementations will help future maintainers.

apps/web/utils/email/microsoft.ts (1)

311-325: Good convenience helpers for removing specific categories

Using 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 good

Switching 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 reads

Fetching reply-tracking labels via provider.getReplyTrackingLabels(), resolving NEEDS_REPLY via provider.removeNeedsReplyLabel(threadId), and getting thread context through provider.getThreadMessages align with the new provider-agnostic design.


10-10: LGTM: Centralized label name constant

Using AWAITING_REPLY_LABEL_NAME keeps provider implementations consistent and avoids string drift.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
apps/web/utils/reply-tracker/outbound.ts (2)

274-276: Bug: passing a label ID into provider.labelMessage expects a name, not an ID

provider.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: Replace provider.labelMessage with provider.labelAwaitingReply in createReplyTrackerOutboundWithProvider
In apps/web/utils/reply-tracker/outbound.ts (lines 273–276), you’re calling provider.labelMessage(messageId, awaitingReplyLabelId)—but EmailProvider only exposes labelAwaitingReply. 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 collision

If 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-calls

Method 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 handleOutboundReply

Both 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 resolvers

resolveReplyTrackers 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.

📥 Commits

Reviewing files that changed from the base of the PR and between ba3389c and 752dade.

📒 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.ts
  • apps/web/utils/email/microsoft.ts
  • apps/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.ts
  • apps/web/utils/email/microsoft.ts
  • apps/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.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/reply-tracker/outbound.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.ts
  • apps/web/utils/email/microsoft.ts
  • apps/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.ts
  • apps/web/utils/email/microsoft.ts
  • 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/email/google.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/reply-tracker/outbound.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements 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.ts
  • apps/web/utils/email/microsoft.ts
  • apps/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.ts
  • apps/web/utils/email/microsoft.ts
  • 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/email/google.ts
  • apps/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 good

Signature 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 Outlook

Using 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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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: sentAt derives from internalDate which is undefined for Outlook

Outlook-parsed messages typically lack internalDate. This will store an incorrect timestamp. Fallback to message.date when internalDate is 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 formatting

Use shorthand for provider here; also prefer formatError in 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: remove createReplyTrackerOutboundWithProvider (and it misuses API)

This function duplicates createReplyTrackerOutbound and calls provider.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 redundant resolveReplyTrackersWithProvider

Duplicate 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 752dade and c3cdddb.

📒 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.ts
  • apps/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.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/outbound.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.ts
  • apps/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 use elements 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.ts
  • apps/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.ts
  • apps/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 of provider to both tasks — LGTM

Concurrent execution with Promise.allSettled is appropriate; errors are handled below.


10-10: Verified: provider parameter present in all handleOutboundReply calls
No instances of calls missing provider or of the deprecated alias remain.

apps/web/utils/reply-tracker/outbound.ts (4)

38-42: Provider label retrieval and tracker resolution — LGTM

Shifts label/thread ops to the provider abstraction as intended.


92-107: Signature now includes provider — LGTM

Keeps DB write and label ops together behind one entry point.


212-231: Provider-based resolution — LGTM

DB resolve + provider label removal via Promise.allSettled is robust to partial failures.


38-88: Resolved: all handleOutboundReply call sites include provider

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 id

Pass 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 field status

findUnique only accepts a unique selector. Including status is invalid and will fail type-checking/runtime. Use findFirst with a proper where.

-  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 TODO

Avoid 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 IDs

Unconditional provider.labelAwaitingReply will 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 isMessageLatestInThread and 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 path

When we reclassify an existing sender, you likely want to refresh reason, messageId, and threadId too, not just status.

   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.

📥 Commits

Reviewing files that changed from the base of the PR and between c3cdddb and 6972181.

📒 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.ts
  • apps/web/utils/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • 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.test.ts
  • apps/web/utils/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/app/api/google/webhook/process-history-item.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.ts
  • apps/web/utils/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/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.ts and dir/format.test.ts)
Use vi.mock("server-only", () => ({})); to mock the server-only module in tests
Mock @/utils/prisma in tests using vi.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.ts
  • apps/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.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • 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 use elements 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.ts
  • apps/web/utils/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/outlook/webhook/process-history-item.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/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.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • 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/cold-email/is-cold-email.test.ts
  • apps/web/utils/actions/categorize.ts
  • apps/web/utils/cold-email/is-cold-email.ts
  • apps/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-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in 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 the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object 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.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within 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.ts
  • 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/actions/categorize.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • 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
🧬 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.provider is wired in actionClient middleware.
The actionClient setup in safe-action.ts injects provider: emailAccount.account.provider into ctx before invoking any action, so ctx.provider will 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 provider

Switch to resolveReplyTrackers(provider, ...) reads well and aligns with the abstraction.


11-19: All handleOutboundReply calls include provider, 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 good

The 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
apps/web/utils/reply-tracker/outbound.ts (1)

94-105: Make label application optional inside creator

Allow createReplyTrackerOutbound to 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: handleOutbound now passes provider

This 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 pair

Introduce a single ReplyTrackingLabels type 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” logs

You 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 context

Also 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 6972181 and 09dbe47.

📒 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.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/web/utils/email/types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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 use elements 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.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.ts
  • apps/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.ts
  • apps/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 reused

Creating the EmailProvider up front avoids redundant instantiation and keeps flows provider-agnostic.


169-182: Provider-based cold-email blocker call looks correct

Passing provider and normalized email payload aligns with the refactor.


211-212: Categorization now provider-backed

Switch to provider for historical lookup is consistent with the new abstraction.


218-225: Rules engine client switched to provider

Alignment with provider interface acknowledged.


244-245: Type-safe EmailProvider param on handleOutbound

Explicit type import improves clarity.


257-267: Concurrent tracking + reply handling passing provider is correct

Pattern with Promise.allSettled and proper logging is sensible.


286-291: Cleanup uses provider as well

Consistent 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 AWAITING

Ensures old "needs reply" state is cleared when we send a reply.


166-169: Good: label removal wrapped with DB resolution

Promise.allSettled prevents label issues from blocking DB resolution.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 09dbe47 and 1e401e2.

📒 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.tsx
  • apps/web/utils/email/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

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.tsx
  • apps/web/utils/email/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.tsx
  • apps/web/utils/email/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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 onClick must be client components with use client directive

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 use elements 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.tsx
  • apps/web/utils/email/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.tsx
  • apps/web/utils/email/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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 Zod

Files:

  • apps/web/utils/email/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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/types.ts
  • apps/web/utils/reply-tracker/outbound.ts
  • apps/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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 undefined action.content

{ ...action.content } throws if content is 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 actions

Because data.actions excludes DIGEST/TRACK_THREAD while rule.actions may include them, the index-based map can assign the wrong delayInMinutes. 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 nit

Including provider is enough since terminology derives from it; the extra terminology.label.action dep is redundant. Your call.

Apply:

-  }, [provider, terminology.label.action]);
+  }, [provider]);

1343-1344: Dead UI path for TRACK_THREAD — remove or gate with a flag

With 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 UI

Project 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 guidelines

Also 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 1e401e2 and 60bc444.

📒 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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

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}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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 onClick must be client components with use client directive

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 use elements 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 rules

Prisma schema (apps/web/prisma/schema.prisma, lines 785–796) still includes TRACK_THREAD and DIGEST. Ensure you’ve added a migration to handle or remove legacy rules that reference these actions now excluded from the editable UI.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 60bc444 and 44a4606.

📒 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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/utils/actions/settings.ts
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/utils/actions/settings.ts
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/utils/actions/settings.ts
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/settings/ToReplySetting.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/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 use elements 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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/utils/actions/settings.ts
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/utils/actions/settings.ts
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/settings/SettingsTab.tsx
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-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in 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 Zod

Files:

  • apps/web/utils/actions/settings.ts
apps/web/utils/actions/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object 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.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/settings.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/actions/settings.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/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

@claude
Copy link

claude bot commented Aug 28, 2025

Claude finished @elie222's task —— View job


PR Code Review: Reply Tracker for Outlook

I've conducted a comprehensive analysis of this PR focusing on code quality, potential bugs, performance, security, and test coverage.

Review Checklist:

  • Analyze the provider abstraction architecture
  • Review EmailProvider interface and implementations
  • Check reply tracking logic and settings consolidation
  • Evaluate test coverage
  • Identify potential security concerns
  • Assess performance implications
  • Provide overall recommendations

🏗️ Architecture Analysis

Strengths:

  • Well-designed provider abstraction: The EmailProvider interface provides a clean abstraction layer that successfully unifies Gmail and Outlook functionality
  • Consistent interface: Both GmailProvider and OutlookProvider implement the same interface methods, enabling provider-agnostic code
  • Consolidation of settings: The unified "Reply tracking" setting that controls both outbound tracking and thread tracking is a good UX improvement

Areas for improvement:

  • The interface is quite large (165+ lines) - consider breaking into smaller, focused interfaces using composition
  • Some methods have provider-specific behavior that could be better abstracted

🐛 Critical Issues Found

1. Data Inconsistency Risk in Settings (apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx:43-86)

// Current: Parallel execution can leave inconsistent state
const [outboundResult, trackThreadResult] = await Promise.allSettled([
  updateAwaitingReplyTrackingAction(emailAccountId, { enabled: enable }),
  toggleToReplyTrackingAction(emailAccountId, { enabled: enable }),
]);

Risk: If the outbound update fails but TRACK_THREAD succeeds, the system state becomes inconsistent.

Recommendation: Implement proper rollback logic or use a single atomic transaction for both operations.

2. Race Condition in Settings Updates (apps/web/utils/actions/settings.ts:117-141)

// Current: Non-transactional rule updates
const rulesToAddTrackThread = toReplyRules.filter(...);
await prisma.$transaction([
  ...rulesToAddTrackThread.map((rule) => prisma.action.create(...))
]);

Risk: Rules are read outside the transaction, creating potential race conditions.

Recommendation: Move rule queries inside an interactive transaction (prisma.$transaction(async (tx) => {...})) and use createMany with skipDuplicates for better performance.

🔒 Security Assessment

Good practices identified:

  • Proper authentication checks in webhook handlers
  • User/account filtering in Prisma queries
  • Input validation using Zod schemas

Potential concerns:

  • Label name inputs should be sanitized before being used in provider APIs
  • Outlook Graph API queries use string interpolation - ensure proper escaping

Performance Considerations

Positive aspects:

  • Use of Promise.allSettled() for parallel operations
  • Efficient database queries with proper indexes
  • Provider-agnostic caching can be applied uniformly

Areas for optimization:

  • Outlook pagination issue (apps/web/utils/outlook/label.ts:233-284): The removeThreadLabel function only processes the first page of messages in a conversation, potentially missing older messages in long threads.
  • Database operations: Consider batch operations for rule updates instead of individual creates/deletes

🧪 Test Coverage Analysis

Strengths:

  • Good test coverage for the cold email blocking functionality
  • Tests properly mock external dependencies
  • Provider abstraction is well-tested

Gaps identified:

  • Missing Outlook-specific tests: Most tests focus on Gmail functionality
  • Integration test coverage: Limited testing of provider interactions
  • Error handling: Insufficient tests for failure scenarios

Recommendations:

  1. Add comprehensive Outlook provider tests
  2. Test provider failover scenarios
  3. Add integration tests for the complete reply tracking flow

🎯 Code Quality Issues

1. Inconsistent Error Handling

Some areas use try-catch while others rely on Promise.allSettled(). Standardize error handling patterns.

2. Type Safety

The EmailProvider interface could benefit from stricter typing:

// Consider more specific types
getNeedsReplyLabel(): Promise<string | null>;
getAwaitingReplyLabel(): Promise<string | null>;

3. Code Duplication

Several functions have duplicate implementations (e.g., handleOutboundReplyWithProvider and handleOutboundReply).

📊 Technical Debt

High Priority:

  1. Database constraints: Add unique index on (ruleId, type) for Action table to prevent duplicates
  2. Provider interface: Split large interface into focused sub-interfaces

Medium Priority:

  1. Logging consistency: Standardize logging patterns across providers
  2. Configuration: Move magic strings to constants

🚀 Performance Recommendations

  1. Batch Operations:

    // Instead of individual creates
    ...rules.map(rule => prisma.action.create(...))
    
    // Use batch operations
    await tx.action.createMany({
      data: rules.map(rule => ({ ruleId: rule.id, type: ActionType.TRACK_THREAD })),
      skipDuplicates: true
    })
  2. Caching: Implement provider-level caching for frequently accessed labels

  3. Connection Pooling: Ensure database connection pooling is optimized for the increased provider abstraction load

🎉 Overall Assessment

This is a well-architected refactor that successfully extends Gmail-only functionality to support Outlook. The provider abstraction is sound and the consolidation of reply tracking settings improves the user experience.

Strengths:

  • Clean provider abstraction
  • Comprehensive functionality coverage
  • Good separation of concerns
  • Proper async/await usage

Areas for immediate attention:

  • Fix the settings consistency issue
  • Add proper database constraints
  • Improve test coverage for Outlook scenarios
  • Address the pagination bug in Outlook label removal

Overall Score: 7.5/10 - Solid architecture with some critical issues that need addressing before production deployment.


@claude
Copy link

claude bot commented Aug 28, 2025

Claude finished @elie222's task —— View job


I'll analyze this and get back to you.

@elie222
Copy link
Owner Author

elie222 commented Aug 28, 2025

@claude review

@claude
Copy link

claude bot commented Aug 28, 2025

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:

  • Analyze the provider abstraction architecture
  • Review EmailProvider interface and implementations
  • Check reply tracking logic and settings consolidation
  • Evaluate test coverage
  • Identify potential security concerns
  • Assess performance implications
  • Provide overall recommendations

@claude
Copy link

claude bot commented Aug 29, 2025

Claude finished @elie222's task —— View job


I'll analyze this and get back to you.

@claude
Copy link

claude bot commented Aug 29, 2025

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:

  • Analyze the provider abstraction architecture
  • Review EmailProvider interface and implementations
  • Check reply tracking logic and settings consolidation
  • Evaluate test coverage
  • Identify potential security concerns
  • Assess performance implications
  • Provide overall recommendations

🏗️ Architecture Analysis

Strengths:

  • Well-designed provider abstraction: The EmailProvider interface provides a clean abstraction layer that successfully unifies Gmail and Outlook functionality
  • Consistent interface: Both GmailProvider and OutlookProvider implement the same interface methods, enabling provider-agnostic code
  • Consolidation of settings: The unified "Reply tracking" setting that controls both outbound tracking and thread tracking is a good UX improvement

Areas for improvement:

  • The interface is quite large (165+ lines) - consider breaking into smaller, focused interfaces using composition
  • Some methods have provider-specific behavior that could be better abstracted

🐛 Critical Issues Found

1. Data Inconsistency Risk in Settings (apps/web/app/(app)/[emailAccountId]/assistant/settings/AwaitingReplySetting.tsx:43-86)

// Current: Parallel execution can leave inconsistent state
const [outboundResult, trackThreadResult] = await Promise.allSettled([
  updateAwaitingReplyTrackingAction(emailAccountId, { enabled: enable }),
  toggleToReplyTrackingAction(emailAccountId, { enabled: enable }),
]);

Risk: If the outbound update fails but TRACK_THREAD succeeds, the system state becomes inconsistent.

Recommendation: Implement proper rollback logic or use a single atomic transaction for both operations.

2. Race Condition in Settings Updates (apps/web/utils/actions/settings.ts:117-141)

// Current: Non-transactional rule updates
const rulesToAddTrackThread = toReplyRules.filter(...);
await prisma.$transaction([
  ...rulesToAddTrackThread.map((rule) => prisma.action.create(...))
]);

Risk: Rules are read outside the transaction, creating potential race conditions.

Recommendation: Move rule queries inside an interactive transaction (prisma.$transaction(async (tx) => {...})) and use createMany with skipDuplicates for better performance.

🔒 Security Assessment

Good practices identified:

  • Proper authentication checks in webhook handlers
  • User/account filtering in Prisma queries
  • Input validation using Zod schemas

Potential concerns:

  • Label name inputs should be sanitized before being used in provider APIs
  • Outlook Graph API queries use string interpolation - ensure proper escaping

Performance Considerations

Positive aspects:

  • Use of Promise.allSettled() for parallel operations
  • Efficient database queries with proper indexes
  • Provider-agnostic caching can be applied uniformly

Areas for optimization:

  • Outlook pagination issue (apps/web/utils/outlook/label.ts:233-284): The removeThreadLabel function only processes the first page of messages in a conversation, potentially missing older messages in long threads.
  • Database operations: Consider batch operations for rule updates instead of individual creates/deletes

🧪 Test Coverage Analysis

Strengths:

  • Good test coverage for the cold email blocking functionality
  • Tests properly mock external dependencies
  • Provider abstraction is well-tested

Gaps identified:

  • Missing Outlook-specific tests: Most tests focus on Gmail functionality
  • Integration test coverage: Limited testing of provider interactions
  • Error handling: Insufficient tests for failure scenarios

Recommendations:

  1. Add comprehensive Outlook provider tests
  2. Test provider failover scenarios
  3. Add integration tests for the complete reply tracking flow

🎯 Code Quality Issues

1. Inconsistent Error Handling

Some areas use try-catch while others rely on Promise.allSettled(). Standardize error handling patterns.

2. Type Safety

The EmailProvider interface could benefit from stricter typing:

// Consider more specific types
getNeedsReplyLabel(): Promise<string | null>;
getAwaitingReplyLabel(): Promise<string | null>;

3. Code Duplication

Several functions have duplicate implementations (e.g., handleOutboundReplyWithProvider and handleOutboundReply).

📊 Technical Debt

High Priority:

  1. Database constraints: Add unique index on (ruleId, type) for Action table to prevent duplicates
  2. Provider interface: Split large interface into focused sub-interfaces

Medium Priority:

  1. Logging consistency: Standardize logging patterns across providers
  2. Configuration: Move magic strings to constants

🚀 Performance Recommendations

  1. Batch Operations:

    // Instead of individual creates
    ...rules.map(rule => prisma.action.create(...))
    
    // Use batch operations
    await tx.action.createMany({
      data: rules.map(rule => ({ ruleId: rule.id, type: ActionType.TRACK_THREAD })),
      skipDuplicates: true
    })
  2. Caching: Implement provider-level caching for frequently accessed labels

  3. Connection Pooling: Ensure database connection pooling is optimized for the increased provider abstraction load

🎉 Overall Assessment

This is a well-architected refactor that successfully extends Gmail-only functionality to support Outlook. The provider abstraction is sound and the consolidation of reply tracking settings improves the user experience.

Strengths:

  • Clean provider abstraction
  • Comprehensive functionality coverage
  • Good separation of concerns
  • Proper async/await usage

Areas for immediate attention:

  • Fix the settings consistency issue
  • Add proper database constraints
  • Improve test coverage for Outlook scenarios
  • Address the pagination bug in Outlook label removal

Overall Score: 7.5/10 - Solid architecture with some critical issues that need addressing before production deployment.


Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
apps/web/utils/email/google.ts (2)

300-307: Guard against blank IDs and use instance remover for consistency

   async 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 removeNeedsReplyLabel

   async 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 labeling

Using labelMessage helper is correct here.


280-291: Harden removeThreadLabel: handle 404/missing names and avoid empty patches

Current 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 getters

These 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 parity

You 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 labeling

Improves 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 category

This 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 getter

Behavior 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 227cb14 and 3e6e2f2.

📒 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.ts
  • apps/web/utils/email/types.ts
  • apps/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.ts
  • apps/web/utils/email/types.ts
  • apps/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.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/microsoft.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.ts
  • apps/web/utils/email/types.ts
  • apps/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.ts
  • apps/web/utils/email/types.ts
  • apps/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.ts
  • apps/web/utils/email/types.ts
  • apps/web/utils/email/microsoft.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements 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.ts
  • apps/web/utils/email/types.ts
  • apps/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.ts
  • apps/web/utils/email/types.ts
  • apps/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 utilities

apps/web/utils/email/microsoft.ts (2)

766-772: LGTM: removeAwaitingReplyLabel by category name

Helper is resilient and logs per-message failures.


774-780: LGTM: removeNeedsReplyLabel mirrors awaiting-removal path

Comment on lines +292 to +298
async getAwaitingReplyLabel(): Promise<string | null> {
return getAwaitingReplyLabel(this.client);
}

async getNeedsReplyLabel(): Promise<string | null> {
return getNeedsReplyLabel(this.client);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Comment on lines +296 to +298
async getNeedsReplyLabel(): Promise<string | null> {
return getNeedsReplyLabel(this.client);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (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-token

The 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 @beta tag 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6e2f2 and 32ebfec.

📒 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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

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}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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 onClick must be client components with use client directive

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 use elements 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 runs

Manual 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: string

Then 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 good

Remove the trailing spaces to satisfy yamllint and keep diffs clean.

-          #   
+          #

[ suggest_nitpick ]
[ approve_code_changes ]

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 OIDC

The 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 contract

Underlying 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 getNeedsReplyLabel

Mirror 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 log

Prefer 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 removal

Consistency + 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 absent

Calling 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 label

Avoid 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 exists

Switching to the class helper avoids failures when the category is missing.


280-291: Harden removeThreadLabel against missing/invalid IDs and API errors

Uncaught 404s or missing displayName will 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 methods

The 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 context

Guard 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 removeThreadLabel

The 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.id exists in RunRulesResult['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 yamllint

Line 49 has trailing spaces per the linter hint.

-          #   
+          #

3-7: Optional: Prevent duplicate runs from rapid multiple mentions

Add 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 32ebfec and b76b6d5.

📒 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.tsx
  • apps/web/utils/email/microsoft.ts
  • apps/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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

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.tsx
  • apps/web/utils/email/microsoft.ts
  • apps/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}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.tsx
  • apps/web/utils/email/microsoft.ts
  • apps/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 onClick must be client components with use client directive

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 use elements 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.tsx
  • apps/web/utils/email/microsoft.ts
  • apps/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.tsx
  • apps/web/utils/email/microsoft.ts
  • apps/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 Zod

Files:

  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/email/google.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/microsoft.ts
  • apps/web/utils/email/google.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/microsoft.ts
  • apps/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

getLabelById is correctly added and used below.


766-772: OK to remove by well-known category name

Using the constant category name with the utility is fine; it no-ops cleanly when absent.


774-780: Same as above: removal by name is fine

Consistent with the Awaiting label removal.

.github/workflows/claude-code-review.yml (1)

4-6: No change needed: this workflow is intentionally scoped to pull_request_review_comment (and manual dispatch); top-level issue_comment events are already handled by claude.yml.

Likely an incorrect or invalid review comment.

Comment on lines +10 to +17
# 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)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
# 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";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

@elie222 elie222 merged commit 46b6ed7 into main Aug 31, 2025
4 of 6 checks passed
@elie222 elie222 deleted the feat/track-replies-setting branch August 31, 2025 18:20
This was referenced Oct 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments