Skip to content

Allow multiple rule matches#854

Merged
elie222 merged 41 commits intomainfrom
feat/multi-rule
Oct 21, 2025
Merged

Allow multiple rule matches#854
elie222 merged 41 commits intomainfrom
feat/multi-rule

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Oct 19, 2025

Previously, an email message could only match a single rule. We're now adding support to allow a message to match multiple rules

Summary by CodeRabbit

  • New Features

    • Multi-rule matching per email and chat-context for submitting “fix rule” actions.
    • Conversation empty state UI and a shimmering “thinking” indicator.
    • Select dropdown adds scroll-up/down buttons.
  • Improvements

    • Explanations, rule lists, history and processing views show grouped/multiple rules and reasons.
    • Scheduled-action cancellation and conversation-tracking can target specific rules.
    • Cold-email detection improved; folder fetching gated by provider.
  • Chores

    • Removed legacy category-based rule UI and related category plumbing.

@vercel
Copy link

vercel bot commented Oct 19, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Oct 21, 2025 0:48am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Warning

Rate limit exceeded

@elie222 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 6 seconds before requesting another review.

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d6fe48a and 8a648e6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx (6 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (3 hunks)
  • apps/web/app/api/user/executed-rules/history/route.ts (1 hunks)
  • apps/web/components/assistant-chat/chat.tsx (3 hunks)
  • apps/web/hooks/useExecutedRules.tsx (1 hunks)
  • apps/web/package.json (2 hunks)
  • apps/web/prisma/migrations/20251021123040_drop_executed_rule_unique/migration.sql (1 hunks)
  • apps/web/providers/ChatProvider.tsx (6 hunks)
  • version.txt (1 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Converted single-result rule flows into multi-result arrays and removed category-based rule support; updated matching, execution, UI, types, DB projections, and chat context plumbing to operate on RunRulesResult[] and to send MessageContext for fix submissions.

Changes

Cohort / File(s) Change Summary
Assistant UI
apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx, apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
Props and rendering changed from a single RunRulesResult to RunRulesResult[]; UI now groups/sorts batches, memoizes selected rule, builds fix messages inline, and pushes MessageContext via chat hook.
Process rules UI & state
apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx
Replaced single-result state with resultsMap: Record<string, RunRulesResult[]>; merging, onRun, and child props updated to pass arrays.
Rule matching & AI selection
apps/web/utils/ai/choose-rule/match-rules.ts, apps/web/utils/ai/choose-rule/ai-choose-rule.ts, apps/web/utils/ai/choose-rule/types.ts
Reworked to multi-match API (findMatchingRules, MatchingRulesResult), added learned-pattern support, multi-rule AI path, updated types and reasoning shape.
Rule execution & run-rules
apps/web/utils/ai/choose-rule/run-rules.ts, apps/web/utils/actions/ai-rule.ts
runRules and runRulesAction now return RunRulesResult[] (with createdAt); execution iterates matches, ensures conversation continuity, persists multiple executed rules, and uses findMany where appropriate.
Batch executed rules API
apps/web/app/api/user/executed-rules/batch/route.ts
rulesMap now maps messageIdExecutedRule[]; added createdAt in projection; populates arrays per message.
Assistant processing & webhooks
apps/web/utils/assistant/process-assistant-email.ts, apps/web/utils/webhook/process-history-item.ts, apps/web/app/api/outlook/webhook/process-history-item.ts, apps/web/app/api/google/webhook/types.ts
Switched unique lookups to first/many, removed categoryFilters includes, adapted handlers/types to arrays and RuleWithActions.
DB schema / indexes
apps/web/prisma/schema.prisma
Category fields marked deprecated; removed composite unique on (emailAccountId,threadId,messageId) and added indexes on (emailAccountId,threadId,messageId,ruleId) and (emailAccountId,messageId).
Rule creation / schema & types
apps/web/utils/ai/rule/create-rule-schema.ts, apps/web/utils/ai/rule/prompt-to-rules*.ts, apps/web/utils/rule/types.ts, apps/web/utils/types.ts, apps/web/utils/rule/rule.ts, apps/web/utils/actions/rule.validation.ts, apps/web/utils/actions/rule.ts
Removed category/smart-category support from schemas, prompts, and DB includes; introduced CreateOrUpdateRuleSchema; removed category helper types/functions and associated plumbing.
Condition / category refactor
apps/web/utils/condition.ts, apps/web/utils/config.ts, apps/web/utils/ai/choose-rule/match-rules.ts, apps/web/utils/ai/choose-rule/run-rules.ts
Dropped CATEGORY handling and isCategoryRule; introduced LEARNED_PATTERN replacing GROUP usage; removed categoryFilterType/categoryFilters from condition shapes and flows.
UI removals / simplifications
apps/web/app/(app)/[emailAccountId]/assistant/ConditionSummaryCard.tsx, apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx, apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx, apps/web/components/GroupedTable.tsx, apps/web/components/CategoryBadge.tsx, apps/web/components/email-list/*
Removed CategoryBadge and category-specific UI (tabs, per-category links); simplified ConditionSummaryCard and RuleForm to omit category UI/logic.
Chat & context plumbing
apps/web/app/api/chat/validation.ts, apps/web/app/api/chat/route.ts, apps/web/providers/ChatProvider.tsx, apps/web/components/assistant-chat/chat.tsx, apps/web/utils/ai/assistant/chat.ts
Added MessageContext schema/type; chat route/provider accept optional context; aiProcessAssistantChat injects a hidden context message for fix-rule; chat UI surfaces and clears context.
Scheduled actions
apps/web/utils/scheduled-actions/scheduler.ts, apps/web/utils/scheduled-actions/scheduler.test.ts
cancelScheduledActions gains optional ruleId filtering; tests updated to assert rule-scoped cancellations.
Dependencies & motion import changes
apps/web/package.json, various components
Replaced framer-motion import paths with motion/react; updated package.json (added motion, nanoid, tokenlens, @vitest/coverage-v8).
Misc / tests / small fixes
assorted files (apps/web/app/api/user/complete-registration/route.ts, apps/web/utils/cold-email/is-cold-email.ts, apps/web/__tests__/helpers.ts, debug pages, constants, many components and tests)
Minor fixes: lowered log level, normalized cold-email fromEmail extraction, removed getUserCategoriesForNames, updated tests to plural result shapes and renamed functions, swapped many imports to @/utils/index, added Shimmer, and small UI tweaks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Matcher as "findMatchingRules / aiChooseRule"
    participant Executor as "runRules"
    participant DB

    Note over UI,Matcher: Multi-match path and chat fix context
    User->>UI: Click "Run" or "Fix"
    UI->>Matcher: findMatchingRules(email, rules, modelType?)
    Matcher-->>UI: MatchingRulesResult { matches[], reasoning }
    alt matches found
        UI->>Executor: runRules(matchedRules[])
        loop for each matched rule
            Executor->>DB: create ExecutedRule (+ actions)
            DB-->>Executor: ExecutedRule (with createdAt)
        end
        Executor-->>UI: RunRulesResult[] (aggregated)
        UI->>ChatProvider: setContext(MessageContext{ type:"fix-rule", message, results, expected })
    else no matches
        UI-->>User: show "No rule matched" / new rule flow
    end
    UI-->>User: display grouped/sorted RunRulesResult[] and allow fix submission
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • anakarentorosserrano-star
  • johnlowe399-blip

Poem

🐇 I hopped through rules and found a crowd,

Multiple matches, each chirpy and loud,
I bundled reasons, sorted by date,
Pushed them to chat to help decide fate,
The rabbit grins — many fixes, celebrate!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "WIP: Allow multiple rule matches" directly and clearly describes the primary change in this changeset. The PR objectives confirm that the main goal is to enable email messages to match multiple rules instead of just a single rule, which is exactly what the title conveys. Throughout the changeset, the dominant pattern is converting from single result/rule handling (result: RunRulesResult) to arrays of results/rules (results: RunRulesResult[]) across numerous components, API endpoints, and utility functions. The title is concise, avoids vague terminology, and a teammate scanning the git history would immediately understand the central feature being introduced.

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
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: 5

Caution

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

⚠️ Outside diff range comments (8)
apps/web/utils/actions/ai-rule.ts (1)

48-50: Use SafeError for expected failures in server actions.

Prefer SafeError for user-facing, expected errors (missing account/provider, AI failures) to align with safe-action error handling. As per coding guidelines.

- if (!emailAccount) throw new Error("Email account not found");
- if (!provider) throw new Error("Provider not found");
+ if (!emailAccount) throw new SafeError("Email account not found");
+ if (!provider) throw new SafeError("Provider not found");
-    } catch (error) {
-      if (error instanceof Error) {
-        throw new Error(`AI error creating rule. ${error.message}`);
-      }
-      throw new Error("AI error creating rule.");
-    }
+    } catch (error) {
+      if (error instanceof Error) {
+        throw new SafeError(`AI error creating rule. ${error.message}`);
+      }
+      throw new SafeError("AI error creating rule.");
+    }

Also applies to: 114-176

apps/web/app/api/user/executed-rules/batch/route.ts (1)

22-32: Least-privilege select for rule fields.

Avoid returning full Rule objects from an API route. Select only fields the client needs to prevent leaking internal data (e.g., instructions). As per API guidelines.

-    select: {
-      id: true,
-      messageId: true,
-      threadId: true,
-      reason: true,
-      actionItems: true,
-      rule: true,
-      status: true,
-    },
+    select: {
+      id: true,
+      messageId: true,
+      threadId: true,
+      reason: true,
+      actionItems: true,
+      status: true,
+      rule: {
+        select: {
+          id: true,
+          name: true,
+          systemType: true,
+          actions: {
+            select: {
+              type: true,
+              label: true,
+              labelId: true,
+              subject: true,
+              content: true,
+              to: true,
+              cc: true,
+              bcc: true,
+              url: true,
+              folderName: true,
+              folderId: true,
+              delayInMinutes: true,
+            },
+          },
+        },
+      },
+    },
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (1)

55-71: Add stable React keys and avoid re‑mounting dialog per item

  • Missing key on mapped elements can cause warnings/hydration issues.
  • Rendering RuleDialogComponent inside the map mounts multiple instances.
-  return results.map((result) => (
-    <>
-      <HoverCard
+  return (
+    <>
+      {results
+        .filter((r) => r.rule) // defensive: skip null rule entries
+        .map((result) => (
+          <HoverCard
+            key={result.rule!.id}
             className="w-auto max-w-5xl"
             content={
               <ActionSummaryCard result={result} onViewRule={handleViewRule} />
             }
           >
             <Badge color="green">
               {prefix ? prefix : ""}
               {result.rule?.name}
               <EyeIcon className="ml-1.5 size-3.5 opacity-70" />
             </Badge>
           </HoverCard>
-      <RuleDialogComponent />
-    </>
-  ));
+        ))}
+      <RuleDialogComponent />
+    </>
+  );
apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx (1)

1-6: Missing "use client" directive (hooks and onClick used in app router)

This component uses hooks and onClick; per guidelines, it must be a client component.

As per coding guidelines: apps/web/app/**/*.tsx components with onClick must include 'use client'.

+ "use client";
+
 import { MessageCircleIcon } from "lucide-react";
apps/web/utils/assistant/process-assistant-email.ts (1)

138-154: Ensure deterministic rule selection by adding orderBy

The executedRule.findMany() query returns results in undefined order when multiple records match the criteria (same emailAccountId, threadId, messageId). Taking executedRules[0] without ordering causes nondeterministic behavior across runs.

The ExecutedRule model includes a createdAt field, making this fix straightforward:

      ? prisma.executedRule.findMany({
          where: {
            emailAccountId,
            threadId: originalMessage.threadId,
            messageId: originalMessage.id,
          },
          select: {
            rule: {
              include: {
                actions: true,
                categoryFilters: true,
                group: true,
              },
            },
          },
+         orderBy: { createdAt: "desc" }
        })
      : null,

This ensures the most recent executed rule is selected consistently. Update line 153 to include this ordering.

apps/web/utils/ai/choose-rule/run-rules.ts (1)

36-42: Fix TS shape mismatch: include executedRule in RunRulesResult.

executeMatchedRule returns an object with executedRule, but RunRulesResult doesn’t declare it. With strict typing this won’t compile.

Apply:

@@
-import type { ActionItem } from "@/utils/ai/types";
+import type { ActionItem, ExecutedRuleWithActionItems } from "@/utils/ai/types";
@@
 export type RunRulesResult = {
   rule?: Rule | null;
   actionItems?: ActionItem[];
   reason?: string | null;
   matchReasons?: MatchReason[];
+  executedRule?: ExecutedRuleWithActionItems | null;
   existing?: boolean;
 };

Also applies to: 204-212, 301-307

apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx (2)

296-305: Prop type vs usage: results can be undefined.

You pass allResults[message.id] which may be undefined, but prop is typed as RunRulesResult[]. Strict null checks will fail.

Apply:

@@
-function ProcessRulesRow({
+function ProcessRulesRow({
   message,
   userEmail,
   isRunning,
   results,
   onRun,
   testMode,
   setInput,
 }: {
   message: Message;
   userEmail: string;
   isRunning: boolean;
-  results: RunRulesResult[];
+  results?: RunRulesResult[];
   onRun: (rerun?: boolean) => void;
   testMode: boolean;
   setInput: (input: string) => void;
 }) {

And guard on length:

@@
-            {results ? (
+            {results?.length ? (
@@
-                  <ProcessResultDisplay results={results} />
+                  <ProcessResultDisplay results={results} />

Also applies to: 337-346


211-215: Remove console usage in UI.

console.error is disallowed in this codebase. You already toast the error.

Apply:

-            console.error(`Failed to process message ${message.id}:`, error);
             toastError({
               title: "Failed to process email",
               description: `Error processing email from ${message.headers.from}: ${error instanceof Error ? error.message : "Unknown error"}`,
             });

As per coding guidelines.

🧹 Nitpick comments (14)
apps/web/utils/actions/ai-rule.ts (3)

59-76: Skip only when a meaningful execution exists (status filter).

If you want to avoid skipping due to past ERROR/SKIPPED records, filter statuses to APPLIED/APPLYING.

-  const executedRules = fetchExecutedRule
-    ? await prisma.executedRule.findMany({
-        where: {
-          emailAccountId,
-          threadId,
-          messageId,
-        },
+  const executedRules = fetchExecutedRule
+    ? await prisma.executedRule.findMany({
+        where: {
+          emailAccountId,
+          threadId,
+          messageId,
+          status: { in: ["APPLIED", "APPLYING"] },
+        },
         select: {
           id: true,
           reason: true,
           actionItems: true,
           rule: true,
         },
       })
     : [];

Also applies to: 78-84


159-188: Invalidate caches after mutations (revalidatePath/revalidateTag).

These actions create/update/delete rules; add cache revalidation to keep UI consistent. Coordinate the exact path/tag with the consuming pages. As per coding guidelines.

Example:

+import { revalidatePath, revalidateTag } from "next/cache";
...
   const createdRule = await safeCreateRule({ ... });
-  return createdRule;
+  // TODO: choose appropriate path/tag used by rules UI
+  // revalidatePath(`/app/${emailAccountId}/rules`);
+  // revalidateTag("rules");
+  return createdRule;

Apply similar after: setRuleRunOnThreadsAction update, saveRulesPromptAction mutations, and createRulesAction loop completion.

Also applies to: 190-204, 219-454, 456-523


107-157: Consider moving non-mutating test action to a GET route.

testAiCustomContentAction doesn't persist changes; per project rules, prefer GET API routes + SWR for reads. Optional if this must run as an action for auth/context.

apps/web/app/api/user/executed-rules/batch/route.ts (1)

35-42: Tighten rulesMap typing for clarity.

This avoids the slightly opaque Record<string, typeof executedRules> pattern.

-  const rulesMap: Record<string, typeof executedRules> = {};
+  type ExecutedRuleItem = (typeof executedRules)[number];
+  const rulesMap: Record<string, ExecutedRuleItem[]> = {};
apps/web/utils/webhook/process-history-item.ts (1)

61-68: Skip only when a successful execution exists (status filter).

Aligns with multi-rule semantics and avoids skipping due to ERROR/SKIPPED rows.

-        ? prisma.executedRule.findFirst({
+        ? prisma.executedRule.findFirst({
             where: {
               emailAccountId,
               threadId,
               messageId,
+              status: { in: ["APPLIED", "APPLYING"] },
             },
             select: { id: true },
           })
...
-          ? await prisma.executedRule.findFirst({
+          ? await prisma.executedRule.findFirst({
               where: {
                 emailAccountId,
                 threadId: actualThreadId,
                 messageId,
+                status: { in: ["APPLIED", "APPLYING"] },
               },
               select: { id: true },
             })

Also applies to: 80-88, 91-94

apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx (2)

20-20: Initialize to [] and pluralize setter for clarity and simpler rendering

Initialize state to an empty array and rename setter to match the plural variable. This avoids undefined checks and keeps naming consistent.

Apply:

-  const [testResults, setTestResult] = useState<RunRulesResult[]>();
+  const [testResults, setTestResults] = useState<RunRulesResult[]>([]);

And update usages:

-        setTestResult(result?.data);
+        setTestResults(result?.data ?? []);

Also applies to: 63-66


31-44: Add try/catch around async submit to handle network/errors

Wrap the action call to surface unexpected failures via toast.

-  const onSubmit: SubmitHandler<TestAiCustomContentBody> = useCallback(
-    async (data) => {
-      const result = await testAiCustomContentAction(emailAccountId, data);
-      if (result?.serverError) {
-        toastError({
-          title: "Error testing email",
-          description: result.serverError,
-        });
-      } else {
-        setTestResult(result?.data);
-      }
-    },
-    [emailAccountId],
-  );
+  const onSubmit: SubmitHandler<TestAiCustomContentBody> = useCallback(
+    async (data) => {
+      try {
+        const result = await testAiCustomContentAction(emailAccountId, data);
+        if (result?.serverError) {
+          toastError({
+            title: "Error testing email",
+            description: result.serverError,
+          });
+          return;
+        }
+        setTestResults(result?.data ?? []);
+      } catch {
+        toastError({
+          title: "Error testing email",
+          description: "Something went wrong. Please try again.",
+        });
+      }
+    },
+    [emailAccountId],
+  );
apps/web/utils/assistant/process-assistant-email.ts (1)

218-221: Only the first matched rule is propagated; align with multi‑rule objective

You still pass a single matchedRule (first element). To fully support multi‑match, propagate all matched rules to processUserRequest (e.g., matchedRules: Rule[]), or adapt its signature accordingly.

Confirm processUserRequest’s current contract. If it can’t accept multiple yet, track this with a TODO and follow‑up issue to avoid partial feature rollout.

apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx (1)

224-231: Clarify multi‑match label

Since multiple rules can be shown, “Matched:” may confuse. Consider “Matched rule(s):”.

-      <Label name="matchedRule" label="Matched:" />
+      <Label name="matchedRule" label="Matched rule(s):" />
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (2)

41-60: Confirm branching: single‑rule path when there are no custom rules

When users have only system rules, you route to the single‑rule flow. Is selecting multiple system rules ever desired (e.g., “To Reply” + “Team Emails”)? If yes, consider always using the multi‑rule flow and let the model choose one vs many per instructions.

If the current behavior is intentional, add a comment noting the product decision and expected user experience.


243-260: Add retry for transient LLM failures

Per AI utils guidelines, wrap generateObject with retry for resilience.

For example (conceptual):

// withRetry(fn, { retries: 2, backoffMs: 500 })
const aiResponse = await withRetry(() =>
  generateObject({
    ...modelOptions,
    system,
    prompt,
    schema,
  }),
);

I can wire withRetry if there’s a shared helper available. Based on learnings.

apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx (3)

167-168: Avoid non‑null assertion.

result.data! violates guidelines and is unnecessary inside the guarded branch.

Apply:

-        setResultsMap((prev) => ({ ...prev, [message.id]: result.data! }));
+        setResultsMap((prev) => ({ ...prev, [message.id]: result.data }));

As per coding guidelines.


231-239: Reset handledThreadsRef on each new run.

Without resetting, subsequent “Run on All” calls may skip messages in threads seen in prior runs.

Apply:

 const handleStart = () => {
   setIsRunningAll(true);
   isRunningAllRef.current = true;
+  handledThreadsRef.current = new Set();
 };

368-377: Handle empty result arrays explicitly.

[] is truthy, so the “Run” button won’t show when results is an empty array. Check length instead.

Apply:

-            {results ? (
+            {results?.length ? (
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc4896b and 7ceee97.

📒 Files selected for processing (12)
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx (7 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (3 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx (5 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx (2 hunks)
  • apps/web/app/api/user/executed-rules/batch/route.ts (1 hunks)
  • apps/web/prisma/schema.prisma (3 hunks)
  • apps/web/utils/actions/ai-rule.ts (3 hunks)
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts (3 hunks)
  • apps/web/utils/ai/choose-rule/match-rules.ts (12 hunks)
  • apps/web/utils/ai/choose-rule/run-rules.ts (10 hunks)
  • apps/web/utils/assistant/process-assistant-email.ts (3 hunks)
  • apps/web/utils/webhook/process-history-item.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (25)
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/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • 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/utils/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/prisma/schema.prisma
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • 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/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
apps/web/utils/**

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

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.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/utils/assistant/process-assistant-email.ts
  • apps/web/utils/actions/ai-rule.ts
  • apps/web/utils/webhook/process-history-item.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/prisma/schema.prisma
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.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/ai-rule.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/ai-rule.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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/app/api/user/executed-rules/batch/route.ts
  • 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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • 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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • 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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • 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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • 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/ProcessRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
apps/web/utils/ai/**/*.{ts,tsx}

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

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

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

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
apps/web/prisma/schema.prisma

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

The Prisma schema file must be located at apps/web/prisma/schema.prisma

Files:

  • apps/web/prisma/schema.prisma
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/user/executed-rules/batch/route.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/user/executed-rules/batch/route.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/user/executed-rules/batch/route.ts
🧬 Code graph analysis (8)
apps/web/utils/actions/ai-rule.ts (1)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • RunRulesResult (36-42)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsx (3)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • RunRulesResult (36-42)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (1)
  • ProcessResultDisplay (13-72)
apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx (1)
  • FixWithChat (27-166)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (6)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/types.ts (2)
  • EmailForLLM (117-131)
  • isDefined (8-10)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmail (4-27)
apps/web/utils/llms/index.ts (1)
  • createGenerateObject (119-180)
apps/web/utils/ai/helpers.ts (1)
  • getUserInfoPrompt (9-35)
apps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsx (3)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • RunRulesResult (36-42)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (1)
  • ProcessResultDisplay (13-72)
apps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsx (2)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • RunRulesResult (36-42)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (1)
  • ProcessResultDisplay (13-72)
apps/web/utils/ai/choose-rule/run-rules.ts (8)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
  • findMatchingRules (228-285)
apps/web/utils/action-item.ts (1)
  • sanitizeActionFields (201-287)
apps/web/utils/cold-email/is-cold-email.ts (1)
  • saveColdEmail (161-187)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (16-22)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • updateThreadTrackers (107-161)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/scheduled-actions/scheduler.ts (2)
  • cancelScheduledActions (167-247)
  • scheduleDelayedActions (116-165)
apps/web/utils/ai/choose-rule/execute.ts (1)
  • executeAct (14-73)
apps/web/utils/ai/choose-rule/match-rules.ts (4)
apps/web/utils/types.ts (2)
  • RuleWithActionsAndCategories (16-18)
  • ParsedMessage (55-70)
apps/web/utils/ai/choose-rule/types.ts (2)
  • MatchingRuleResult (36-42)
  • MatchReason (29-34)
apps/web/utils/group/find-matching-group.ts (1)
  • getGroupsWithRules (8-17)
apps/web/utils/email.ts (1)
  • extractEmailAddress (19-52)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (3)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • RunRulesResult (36-42)
apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx (1)
  • useRuleDialog (27-42)
apps/web/components/HoverCard.tsx (1)
  • HoverCard (7-20)
⏰ 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: cubic · AI code reviewer
🔇 Additional comments (3)
apps/web/utils/ai/choose-rule/match-rules.ts (2)

151-174: Calendar preset match: nice addition.

Early return for CALENDAR via attachments is clean and low-cost. LGTM.


274-285: Multi-result plumbing looks consistent.

Returning arrays from findMatchingRules/findMatchingRuleWithReasons aligns with multi-rule flow; reason derivation via getMatchReason is sound.

Also applies to: 310-324

apps/web/utils/ai/choose-rule/run-rules.ts (1)

324-329: Verify after() context.

after() requires a request-lifecycle context. Confirm runRules is always invoked from a Route Handler or Server Action; otherwise this may no-op or throw at runtime. If not guaranteed, fall back to fire-and-forget with a setImmediate/queue or wrap in a try/catch.

Would you like me to add a safe fallback wrapper that calls analyzeSenderPattern directly when after() isn’t available?

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 12 files

Prompt for AI agents (all 4 issues)

Understand the root cause of the following 4 issues and fix them.


<file name="apps/web/prisma/schema.prisma">

<violation number="1" location="apps/web/prisma/schema.prisma:490">
Switching from the prior unique constraint to a plain index allows duplicate ExecutedRule rows for the same rule/message, so retries could apply the same rule multiple times. Please keep a uniqueness guarantee per rule/message (e.g., make this @@unique).</violation>
</file>

<file name="apps/web/utils/ai/choose-rule/match-rules.ts">

<violation number="1" location="apps/web/utils/ai/choose-rule/match-rules.ts:184">
Initializing `this.groups` to [] prevents the lazy loader from ever fetching groups, so learned-pattern rules can no longer match.</violation>

<violation number="2" location="apps/web/utils/ai/choose-rule/match-rules.ts:195">
This guard re-fetches the sender on every call when no newsletter entry exists, causing unnecessary repeated DB hits.</violation>
</file>

<file name="apps/web/utils/ai/choose-rule/run-rules.ts">

<violation number="1" location="apps/web/utils/ai/choose-rule/run-rules.ts:265">
When multiple rules match, this call cancels the delayed actions created by earlier matches, so only the last rule keeps its scheduled follow-ups.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

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

♻️ Duplicate comments (1)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

259-288: Guard against missing AI object on failure paths.

The code accesses aiResponse.object.matchedRules directly at multiple points. If the AI generation fails or returns no object, this will throw. The single-rule path (lines 134-135) has proper guards; this path should match that pattern.

Apply this diff to add consistent null safety:

-  const selectedSystemRules = aiResponse.object.matchedRules.filter((match) =>
+  const selectedSystemRules =
+    aiResponse.object?.matchedRules?.filter((match) =>
       systemRules.some(
         (r) => r.name.toLowerCase() === match.ruleName.toLowerCase(),
       ),
-  );
+    ) ?? [];
 
   if (selectedSystemRules.length > 1) {
     logger.warn("Multiple system rules selected", {
       systemRules: selectedSystemRules.map((r) => r.ruleName),
       emailId: email.id,
       model: modelOptions.modelName,
     });
   }
 
-  if (aiResponse.object.matchedRules.length > 1) {
+  if ((aiResponse.object?.matchedRules?.length ?? 0) > 1) {
     logger.info("Multiple rules selected", {
-      rules: aiResponse.object.matchedRules.map((r) => r.ruleName),
+      rules: (aiResponse.object?.matchedRules ?? []).map((r) => r.ruleName),
       emailId: email.id,
     });
   }
 
   return {
     result: {
-      matchedRules: aiResponse.object.matchedRules || [],
+      matchedRules: aiResponse.object?.matchedRules ?? [],
       noMatchFound: aiResponse.object?.noMatchFound ?? false,
     },
     modelOptions,
   };
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ceee97 and bb46440.

📒 Files selected for processing (2)
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1 hunks)
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts (3 hunks)
🧰 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/ExecutedRulesTable.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.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/ExecutedRulesTable.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/ExecutedRulesTable.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/ExecutedRulesTable.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.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/ExecutedRulesTable.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/ExecutedRulesTable.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.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/ExecutedRulesTable.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/ExecutedRulesTable.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/ExecutedRulesTable.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/ExecutedRulesTable.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/ExecutedRulesTable.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.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/ExecutedRulesTable.tsx
  • apps/web/utils/ai/choose-rule/ai-choose-rule.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/ExecutedRulesTable.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/ExecutedRulesTable.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/ai/choose-rule/ai-choose-rule.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/ai/choose-rule/ai-choose-rule.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/ai/choose-rule/ai-choose-rule.ts
apps/web/utils/ai/**/*.{ts,tsx}

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

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

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

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
🧠 Learnings (1)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
🧬 Code graph analysis (1)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (7)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/types.ts (2)
  • EmailForLLM (117-131)
  • isDefined (8-10)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/llms/model.ts (1)
  • ModelType (17-17)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmail (4-27)
apps/web/utils/llms/index.ts (1)
  • createGenerateObject (119-180)
apps/web/utils/ai/helpers.ts (1)
  • getUserInfoPrompt (9-35)
⏰ 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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (4)

4-4: LGTM! Type and import additions support multi-rule flow.

The isDefined utility and systemType field enable type-safe filtering of matched rules and differentiation between system and custom rules.

Also applies to: 15-15


19-60: LGTM! Clean separation of single-rule vs multi-rule flows.

The routing logic preserves the proven single-rule path for users without custom rules while enabling multi-rule selection for complex scenarios. The shared model setup and consistent return type maintain code clarity.


62-139: LGTM! Proper null safety in single-rule path.

The refactored function correctly guards against missing AI response objects with safe fallbacks, and wraps the single result in an array to align with the multi-rule return type.


290-329: LGTM! Type-safe multi-rule selection with proper edge case handling.

The refactored function correctly:

  • Returns an array to support multiple matched rules (aligns with PR objective)
  • Handles edge cases with early returns (empty rules, no match)
  • Maps AI results to actual rule objects with case-insensitive matching
  • Filters out unmatched rules using the isDefined utility

The generic type constraint properly includes systemType for compatibility with the new routing logic.

apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1)

139-139: LGTM: Correct adaptation to multi-result API.

The change correctly wraps the single rule-reason pair in an array to match FixWithChat's updated signature. This pattern makes sense: while RuleCell displays one rule, FixWithChat now accepts an array to support the PR's multi-rule functionality.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

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

♻️ Duplicate comments (4)
apps/web/utils/ai/choose-rule/match-rules.ts (3)

182-186: Fix ContextLoader initialization to enable lazy loading.

The groups field is initialized to [], which is truthy, so the check if (!this.groups) on line 189 will never trigger, preventing groups from ever loading. This breaks learned pattern matching entirely.

Apply this diff to fix the initialization:

   constructor(message: ParsedMessage) {
     this.message = message;
-    this.groups = [];
+    this.groups = null;
     this.sender = undefined;
   }

Also update the type on line 179:

-  private groups: Awaited<ReturnType<typeof getGroupsWithRules>>;
+  private groups: Awaited<ReturnType<typeof getGroupsWithRules>> | null;

188-192: Fix getGroups lazy-load check.

The check if (!this.groups) will always be false when groups is initialized to [], preventing groups from loading.

Apply this diff:

   async getGroups(emailAccountId: string) {
-    if (!this.groups)
+    if (this.groups === null)
       this.groups = await getGroupsWithRules({ emailAccountId });
     return this.groups;
   }

194-206: Fix getSender to cache null results.

The check if (!this.sender) will re-query the database every time when the DB returns null (no newsletter found), causing unnecessary repeated queries.

Apply this diff:

   async getSender(emailAccountId: string) {
-    if (!this.sender) {
+    if (this.sender === undefined) {
       this.sender = await prisma.newsletter.findUnique({
         where: {
           email_emailAccountId: {
             email: extractEmailAddress(this.message.headers.from),
             emailAccountId,
           },
         },
       });
     }
     return this.sender;
   }
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

267-271: Add null safety for AI response object.

Line 268 directly accesses aiResponse.object.matchedRules without checking if object exists. If the AI call fails or returns no object, this will throw a runtime exception.

Based on learnings.

Apply this diff to add proper null guards:

   return {
-    matchedRules: aiResponse.object.matchedRules || [],
+    matchedRules: aiResponse.object?.matchedRules ?? [],
     noMatchFound: aiResponse.object?.noMatchFound ?? false,
     reasoning: aiResponse.object?.reasoning ?? "",
   };
🧹 Nitpick comments (1)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

203-207: Consider clarifying multi-rule selection guidance.

The prompt states "it's rare that you need to select more than 1-2 rules" which may be too restrictive given the PR's goal to support multiple rule matches. Consider whether this guidance should be relaxed to better align with the multi-rule capability.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb46440 and feeffff.

📒 Files selected for processing (2)
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts (3 hunks)
  • apps/web/utils/ai/choose-rule/match-rules.ts (12 hunks)
🧰 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/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
apps/web/utils/ai/**/*.{ts,tsx}

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

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

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

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
🧠 Learnings (1)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
🧬 Code graph analysis (2)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (6)
apps/web/utils/types.ts (2)
  • EmailForLLM (117-131)
  • isDefined (8-10)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/llms/model.ts (1)
  • ModelType (17-17)
apps/web/utils/llms/index.ts (1)
  • createGenerateObject (119-180)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmail (4-27)
apps/web/utils/ai/helpers.ts (1)
  • getUserInfoPrompt (9-35)
apps/web/utils/ai/choose-rule/match-rules.ts (5)
apps/web/utils/types.ts (2)
  • RuleWithActionsAndCategories (16-18)
  • ParsedMessage (55-70)
apps/web/utils/ai/choose-rule/types.ts (2)
  • MatchingRuleResult (36-42)
  • MatchReason (29-34)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/utils/group/find-matching-group.ts (1)
  • getGroupsWithRules (8-17)
apps/web/utils/email.ts (1)
  • extractEmailAddress (19-52)
⏰ 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: cubic · AI code reviewer
🔇 Additional comments (6)
apps/web/utils/ai/choose-rule/match-rules.ts (3)

151-174: Well-structured calendar match extraction.

The calendar matching logic is cleanly extracted into a dedicated function with appropriate early returns and logging.


228-285: Multi-rule return type correctly implemented.

The function signature and implementation properly support returning multiple rule matches as arrays, consistent with the PR objective.


396-441: Clean helper function extractions.

Both matchesGroupRule and matchesCategoryRule are well-structured extractions that improve code organization and readability.

apps/web/utils/ai/choose-rule/ai-choose-rule.ts (3)

16-56: Well-structured multi-rule API with proper type constraints.

The generic function signature correctly supports multiple rule matching with appropriate type safety and early returns.


58-97: Routing logic appropriately separates single vs multi-rule paths.

The decision to route based on custom rules presence is reasonable and maintains consistent return types.


99-176: Single-rule path has proper null safety.

The function correctly handles potential missing AI responses with optional chaining and appropriate fallbacks.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

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/ai/choose-rule/match-rules.ts (1)

114-126: Static/category/calendar/cold‑email paths still short‑circuit to a single match.

Early returns restrict results to at most one pre‑AI match, which conflicts with “allow multiple rule matches.” Consider accumulating matches and returning all that satisfy AND/OR semantics; keep learned‑pattern exclusions as hard stops where intended.

Sketch:

-  // if we find a match, return it
-  // if we don't find a match, return the potential matches
+  // collect all deterministic matches; AI handles potential matches
@@
-  for (const rule of rules) {
+  const matches: { rule: RuleWithActionsAndCategories; matchReasons: MatchReason[] }[] = [];
+  for (const rule of rules) {
@@
-        return { match: rule, matchReasons };
+        matches.push({ rule, matchReasons });
+        continue;
@@
-        if (operator === LogicalOperator.OR || !unmatchedConditions.size)
-          return { match: rule, matchReasons };
+        if (operator === LogicalOperator.OR || !unmatchedConditions.size) {
+          matches.push({ rule, matchReasons });
+          continue;
+        }
@@
-        if (operator === LogicalOperator.OR || !unmatchedConditions.size)
-          return { match: rule, matchReasons };
+        if (operator === LogicalOperator.OR || !unmatchedConditions.size) {
+          matches.push({ rule, matchReasons });
+          continue;
+        }
@@
-  const rule = rules.find((r) => r.systemType === SystemType.CALENDAR);
-  if (!rule) return;
+  const calendarRules = rules.filter((r) => r.systemType === SystemType.CALENDAR);
+  if (!calendarRules.length) return;
@@
-  return {
-    match: rule,
-    matchReasons: [{ type: ConditionType.PRESET, systemType: SystemType.CALENDAR }],
-  };
+  return {
+    matches: calendarRules.map((rule) => ({
+      rule,
+      matchReasons: [{ type: ConditionType.PRESET, systemType: SystemType.CALENDAR }],
+    })),
+  };

Then update MatchingRuleResult to support matches[], and in findMatchingRulesWithReasons merge matches (if any) with AI‑selected rules. Also avoid early return on cold‑email; include it in matches and continue filtering other rules.

Also applies to: 128-146, 163-186, 269-272, 309-325

♻️ Duplicate comments (4)
apps/web/utils/ai/choose-rule/run-rules.ts (2)

262-278: Fix multi-rule scheduling: cancel once per message, not per rule.

Per‑rule cancelScheduledActions causes later matches to cancel delayed actions scheduled by earlier matches in the same run (“last rule wins”). Move one global cancellation before the loop and remove per‑rule cancellations so each matched rule can schedule its own delays.

Apply Option B:

@@
-  const executedRules: RunRulesResult[] = [];
+  const executedRules: RunRulesResult[] = [];
+  // Cancel any leftover scheduled actions for this message once before scheduling new ones for all matched rules
+  if (!isTest) {
+    await cancelScheduledActions({
+      emailAccountId: emailAccount.id,
+      messageId: message.id,
+      threadId: message.threadId,
+      reason: "Rescheduling for multi-rule run",
+    });
+  }
@@ async function executeMatchedRule(
-  if (executedRule) {
-    if (delayedActions?.length > 0) {
-      // Attempts to cancel any existing scheduled actions to avoid duplicates
-      await cancelScheduledActions({
-        emailAccountId: emailAccount.id,
-        messageId: message.id,
-        threadId: message.threadId,
-        reason: "Superseded by new rule execution",
-      });
-      await scheduleDelayedActions({
-        executedRuleId: executedRule.id,
-        actionItems: delayedActions,
-        messageId: message.id,
-        threadId: message.threadId,
-        emailAccountId: emailAccount.id,
-      });
-    }
-    // Execute immediate actions if any
+  if (executedRule) {
+    if (delayedActions?.length > 0) {
+      await scheduleDelayedActions({
+        executedRuleId: executedRule.id,
+        actionItems: delayedActions,
+        messageId: message.id,
+        threadId: message.threadId,
+        emailAccountId: emailAccount.id,
+      });
+    }
+    // Execute immediate actions if any

Also applies to: 91-96, 129-139


240-250: Normalize and lowercase “from” when saving cold emails to avoid key fragmentation.

Persist the canonical address (strip display name/angles, trim, lowercase) to keep unique emailAccountId_fromEmail stable.

   if (rule.systemType === SystemType.COLD_EMAIL) {
     await saveColdEmail({
       email: {
         id: message.id,
         threadId: message.threadId,
-        from: message.headers.from,
+        from:
+          (extractEmailAddress(message.headers.from) ||
+            message.headers.from).trim().toLowerCase(),
       },
       emailAccount,
       aiReason: reason ?? null,
     });
   }
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

280-284: Guard against missing AI object in multi‑rule path.

aiResponse.object may be undefined on failures; this will throw. Use optional chaining and fallbacks.

Based on learnings

   return {
-    matchedRules: aiResponse.object.matchedRules || [],
-    noMatchFound: aiResponse.object?.noMatchFound ?? false,
-    reasoning: aiResponse.object?.reasoning ?? "",
+    matchedRules: aiResponse.object?.matchedRules ?? [],
+    noMatchFound: aiResponse.object?.noMatchFound ?? false,
+    reasoning: aiResponse.object?.reasoning ?? "",
   };
apps/web/utils/ai/choose-rule/match-rules.ts (1)

188-206: ContextLoader never loads due to null/undefined mismatch.

Fields are initialized to null but checked against undefined, so groups/sender never load and/or re‑query repeatedly. Initialize to undefined and use strict undefined checks.

 class ContextLoader {
   private readonly message: ParsedMessage;
-  private groups?: Awaited<ReturnType<typeof getGroupsWithRules>> | null = null;
-  private sender?: { categoryId: string | null } | null = null;
+  private groups: Awaited<ReturnType<typeof getGroupsWithRules>> | undefined;
+  private sender: { categoryId: string | null } | null | undefined;
@@
   constructor(message: ParsedMessage) {
     this.message = message;
   }
@@
   async getGroups(emailAccountId: string) {
-    if (typeof this.groups === "undefined")
+    if (this.groups === undefined) {
       this.groups = await getGroupsWithRules({ emailAccountId });
-    return this.groups;
+    }
+    return this.groups;
   }
@@
   async getSender(emailAccountId: string) {
-    if (typeof this.sender === "undefined") {
+    if (this.sender === undefined) {
       this.sender = await prisma.newsletter.findUnique({
         where: {
           email_emailAccountId: {
             email: extractEmailAddress(this.message.headers.from),
             emailAccountId,
           },
         },
       });
     }
     return this.sender;
   }
🧹 Nitpick comments (4)
apps/web/utils/ai/choose-rule/run-rules.ts (2)

71-73: Nit: pluralize trace log.

Use “Matching rules” to reflect array semantics.

-  logger.trace("Matching rule", () => ({
+  logger.trace("Matching rules", () => ({
     results: results.matches.map(filterNullProperties),
   }));

36-42: Align RunRulesResult with returned shape (executedRule field).

executeMatchedRule returns executedRule, but RunRulesResult doesn’t declare it. Either extend the type or drop the field from the returned object.

Option A (extend type minimally with id and status to avoid heavy imports):

 export type RunRulesResult = {
   rule?: Rule | null;
   actionItems?: ActionItem[];
   reason?: string | null;
   matchReasons?: MatchReason[];
   existing?: boolean;
+  executedRule?: { id: string; status: ExecutedRuleStatus } | null;
 };

Option B: remove executedRule from the returned object here and fetch by message/thread if needed elsewhere.

Also applies to: 301-307

apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)

287-307: Unused helper (filterMultipleSystemRules).

Not referenced; remove or wire into selection flow.

apps/web/utils/ai/choose-rule/match-rules.ts (1)

281-289: Consistent return typing and reasoning.

findMatchingRulesWithReasons returns empty reasoning on deterministic matches but uses AI reasoning otherwise. Consider populating reasoning via getMatchReason for deterministic matches for UI consistency.

Also applies to: 319-329

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4441c91 and c284968.

📒 Files selected for processing (3)
  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts (3 hunks)
  • apps/web/utils/ai/choose-rule/match-rules.ts (18 hunks)
  • apps/web/utils/ai/choose-rule/run-rules.ts (10 hunks)
🧰 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/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.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/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/ai/**/*.{ts,tsx}

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

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

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

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
  • apps/web/utils/ai/choose-rule/match-rules.ts
  • apps/web/utils/ai/choose-rule/run-rules.ts
🧠 Learnings (1)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/ai/choose-rule/ai-choose-rule.ts
🧬 Code graph analysis (3)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (6)
apps/web/utils/types.ts (2)
  • EmailForLLM (117-131)
  • isDefined (8-10)
apps/web/utils/llms/types.ts (1)
  • EmailAccountWithAI (10-29)
apps/web/utils/llms/model.ts (1)
  • ModelType (17-17)
apps/web/utils/llms/index.ts (1)
  • createGenerateObject (119-180)
apps/web/utils/stringify-email.ts (1)
  • stringifyEmail (4-27)
apps/web/utils/ai/helpers.ts (1)
  • getUserInfoPrompt (9-35)
apps/web/utils/ai/choose-rule/match-rules.ts (5)
apps/web/utils/types.ts (2)
  • RuleWithActionsAndCategories (16-18)
  • ParsedMessage (55-70)
apps/web/utils/ai/choose-rule/types.ts (2)
  • MatchReason (29-34)
  • MatchingRuleResult (36-42)
apps/web/utils/group/find-matching-group.ts (1)
  • getGroupsWithRules (8-17)
apps/web/utils/email.ts (1)
  • extractEmailAddress (19-52)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (16-22)
apps/web/utils/ai/choose-rule/run-rules.ts (8)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
  • findMatchingRules (239-290)
apps/web/utils/action-item.ts (1)
  • sanitizeActionFields (201-287)
apps/web/utils/cold-email/is-cold-email.ts (1)
  • saveColdEmail (161-187)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
  • isConversationStatusType (16-22)
apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
  • updateThreadTrackers (107-161)
apps/web/utils/date.ts (1)
  • internalDateToDate (59-71)
apps/web/utils/scheduled-actions/scheduler.ts (2)
  • cancelScheduledActions (167-247)
  • scheduleDelayedActions (116-165)
apps/web/utils/ai/choose-rule/execute.ts (1)
  • executeAct (14-73)
⏰ 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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Analyze (javascript-typescript)

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

Reviewed changes from recent commits (found 3 issues).

3 issues found across 4 files

Prompt for AI agents (all 3 issues)

Understand the root cause of the following 3 issues and fix them.


<file name="apps/web/utils/ai/choose-rule/match-rules.ts">

<violation number="1" location="apps/web/utils/ai/choose-rule/match-rules.ts:191">
Initializing `this.groups` to null prevents `getGroups` from ever loading learned-pattern data, so group-based rules now never match.</violation>

<violation number="2" location="apps/web/utils/ai/choose-rule/match-rules.ts:192">
Initializing `this.sender` to null means `getSender` never loads newsletter data, causing every category-based rule to miss its sender match.</violation>
</file>

<file name="apps/web/utils/cold-email/is-cold-email.ts">

<violation number="1" location="apps/web/utils/cold-email/is-cold-email.ts:171">
Normalizing the sender before persistence means `prisma.coldEmail.findUnique` (still using the raw `email.from` string) no longer matches entries when `email.from` includes a display name, so known cold senders stop being detected.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 4 files

@socket-security
Copy link

socket-security bot commented Oct 21, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​vitest/​coverage-v8@​3.2.4991007298100

View full report

@socket-security
Copy link

socket-security bot commented Oct 21, 2025

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

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

🧹 Nitpick comments (2)
apps/web/__tests__/ai-choose-rule.test.ts (2)

63-106: Past review comment addressed; consider simplifying action fixture.

The test name now correctly reflects the assertions, addressing the previous review feedback. However, the action object (lines 72-89) is quite verbose.

Consider creating a helper function or using spread with defaults:

function getAction(overrides: Partial<Action> = {}): Action {
  return {
    id: "id",
    createdAt: new Date(),
    updatedAt: new Date(),
    type: ActionType.REPLY,
    ruleId: "ruleId",
    label: null,
    labelId: null,
    subject: null,
    content: "{{Write a joke}}",
    to: null,
    cc: null,
    bcc: null,
    url: null,
    folderName: null,
    delayInMinutes: null,
    folderId: null,
    ...overrides,
  };
}

Then use it in tests:

const rule2 = getRule(
  "Match emails asking for a joke",
  [getAction({ type: ActionType.REPLY, content: "{{Write a joke}}" })],
  "Joke requests",
);

218-228: Use console.debug instead of console.log for LLM test output.

As per coding guidelines for LLM tests, use console.debug for logging generated content and test debugging information.

Apply this pattern throughout:

-        console.log("⚠️  Technical Issues test matched multiple rules:");
-        console.log(
+        console.debug("⚠️  Technical Issues test matched multiple rules:");
+        console.debug(
           result.rules.map((r) => ({
             name: r.rule.name,
             isPrimary: r.isPrimary,
           })),
         );
-        console.log("Reasoning:", result.reason);
+        console.debug("Reasoning:", result.reason);

Based on coding guidelines.

Also applies to: 279-289, 309-321, 387-396, 417-426

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb2636f and ebc9202.

📒 Files selected for processing (2)
  • apps/web/__tests__/ai-choose-rule.test.ts (15 hunks)
  • apps/web/__tests__/helpers.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
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/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.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/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.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/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.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/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.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/__tests__/ai-choose-rule.test.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/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.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/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.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/__tests__/ai-choose-rule.test.ts
apps/web/__tests__/**/*.{ts,tsx}

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

Place LLM-specific tests under apps/web/tests/

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
**/__tests__/**

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

Place AI tests in the __tests__ directory and exclude them from the default test run (they use a real LLM)

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
  • apps/web/__tests__/helpers.ts
apps/web/__tests__/**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/llm-test.mdc)

apps/web/__tests__/**/*.test.ts: Place all LLM-related tests under apps/web/tests/
Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"
Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests
Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")
Call vi.clearAllMocks() in a beforeEach for LLM tests
Set a TIMEOUT of 15_000ms for LLM-related tests and pass it to long-running tests/describe blocks
Create helper functions for common test data (e.g., getUser, getTestData) to reduce duplication
Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats
Use console.debug to log generated LLM content for inspection (e.g., console.debug("Generated content:\n", result.content))
Do not mock the actual LLM call in these tests; exercise real LLM integrations
Test both AI and non-AI paths, including cases where no AI processing is required
Prefer existing helpers from @/tests/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) over custom helpers

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
🧠 Learnings (2)
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths, including cases where no AI processing is required

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
🧬 Code graph analysis (1)
apps/web/__tests__/ai-choose-rule.test.ts (1)
apps/web/__tests__/helpers.ts (1)
  • getRule (44-66)
⏰ 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). (2)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (4)
apps/web/__tests__/helpers.ts (1)

44-66: LGTM! Clean enhancement to test helper.

The optional name parameter with a sensible default allows tests to create more descriptive rule fixtures while maintaining backward compatibility.

apps/web/__tests__/ai-choose-rule.test.ts (3)

13-21: LGTM! Correctly updated for multi-rule API.

The assertion now properly expects an empty rules array instead of a single rule result.


23-38: LGTM! Correct multi-rule assertions.

The test properly validates the new result shape with rules array and isPrimary flag for single-rule matches.


230-236: Excellent defensive testing for multi-rule matches.

The tests appropriately handle the possibility that the AI may match multiple rules, while still validating that the primary match is correct. This approach acknowledges AI non-determinism while maintaining meaningful assertions.

Also applies to: 291-297, 323-329, 398-404, 428-434

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

♻️ Duplicate comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (1)

57-66: Stabilize grouping key, harden date handling, and memoize computation

  • Grouping by createdAt.toString() is locale-dependent and brittle; prefer a stable numeric or ISO key.
  • .getTime() will throw if createdAt is a string (common when coming from JSON).
  • Compute once with useMemo to avoid recomputing on every render.

Apply:

+import { useMemo } from "react";
@@
-  const groupedResults = groupBy(results, (result) => {
-    return result.createdAt.toString();
-  });
-
-  const sortedBatches = sortBy(
-    Object.entries(groupedResults),
-    ([, batchResults]) => {
-      return -batchResults[0]?.createdAt.getTime(); // Negative for descending order
-    },
-  );
+  const sortedBatches = useMemo(() => {
+    const grouped = groupBy(results, (r) => {
+      const ts =
+        r.createdAt instanceof Date
+          ? r.createdAt.getTime()
+          : new Date(r.createdAt as unknown as string).getTime();
+      return String(ts); // stable key
+    });
+    return sortBy(Object.entries(grouped), ([ts]) => -Number(ts));
+  }, [results]);
🧹 Nitpick comments (4)
apps/web/__tests__/ai-choose-rule.test.ts (2)

220-228: Prefer console.debug over console.log for LLM test output.

Per the LLM test guidelines, use console.debug to log generated LLM content for inspection. This maintains consistency with the project's testing standards.

Apply this pattern to all console logging statements:

-        console.log("⚠️  Technical Issues test matched multiple rules:");
-        console.log(
+        console.debug("⚠️  Technical Issues test matched multiple rules:");
+        console.debug(
           result.rules.map((r) => ({
             name: r.rule.name,
             isPrimary: r.isPrimary,
           })),
         );
-        console.log("Reasoning:", result.reason);
+        console.debug("Reasoning:", result.reason);

Also applies to: 281-289, 311-321, 388-396, 418-426


12-12: Consider adding explicit timeout for LLM tests.

Per the LLM test guidelines, it's recommended to set a timeout of 15,000ms for long-running LLM tests to prevent premature test failures.

Apply this diff to add timeouts:

-describe.runIf(isAiTest)("aiChooseRule", () => {
+describe.runIf(isAiTest)("aiChooseRule", { timeout: 15_000 }, () => {

If individual tests need more time, you can also pass the timeout to specific test calls:

test("Should match technical issues", async () => {
  // test body
}, 15_000);

Based on learnings.

Also applies to: 108-108

apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (2)

26-27: Broaden “no match” guard to include undefined rule

If upstream ever omits rule (it’s Rule | null | undefined), this branch won’t trigger. Guard both null/undefined explicitly without using ==.

-  if (results.length === 1 && results[0].rule === null) {
+  if (results.length === 1 && (results[0].rule === null || results[0].rule === undefined)) {

3-4: Consider removing lodash here to trim client bundle (optional)

Two helpers can be done with native code; if lodash isn’t already on the hot path, inlining avoids extra KB.

Option A (keep lodash): current is fine.

Option B (native): replace groupBy/sortBy with a tiny util and Array.prototype.sort. I can provide a follow‑up diff if you want to proceed.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebc9202 and cf18bde.

📒 Files selected for processing (2)
  • apps/web/__tests__/ai-choose-rule.test.ts (15 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
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/__tests__/ai-choose-rule.test.ts
  • 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/__tests__/ai-choose-rule.test.ts
  • 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/__tests__/ai-choose-rule.test.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/__tests__/ai-choose-rule.test.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
**/*.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/__tests__/ai-choose-rule.test.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/__tests__/ai-choose-rule.test.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.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/__tests__/ai-choose-rule.test.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
**/*.{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/__tests__/ai-choose-rule.test.ts
apps/web/__tests__/**/*.{ts,tsx}

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

Place LLM-specific tests under apps/web/tests/

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
**/__tests__/**

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

Place AI tests in the __tests__ directory and exclude them from the default test run (they use a real LLM)

Files:

  • apps/web/__tests__/ai-choose-rule.test.ts
apps/web/__tests__/**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/llm-test.mdc)

apps/web/__tests__/**/*.test.ts: Place all LLM-related tests under apps/web/tests/
Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"
Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests
Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")
Call vi.clearAllMocks() in a beforeEach for LLM tests
Set a TIMEOUT of 15_000ms for LLM-related tests and pass it to long-running tests/describe blocks
Create helper functions for common test data (e.g., getUser, getTestData) to reduce duplication
Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats
Use console.debug to log generated LLM content for inspection (e.g., console.debug("Generated content:\n", result.content))
Do not mock the actual LLM call in these tests; exercise real LLM integrations
Test both AI and non-AI paths, including cases where no AI processing is required
Prefer existing helpers from @/tests/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) over custom helpers

Files:

  • apps/web/__tests__/ai-choose-rule.test.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
**/*.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
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
**/*.{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
🧠 Learnings (2)
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths, including cases where no AI processing is required

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/__tests__/ai-choose-rule.test.ts
🧬 Code graph analysis (2)
apps/web/__tests__/ai-choose-rule.test.ts (2)
apps/web/__tests__/helpers.ts (3)
  • getRule (44-66)
  • getEmail (25-42)
  • getEmailAccount (6-23)
apps/web/utils/ai/choose-rule/ai-choose-rule.ts (1)
  • aiChooseRule (16-56)
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (4)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
  • RunRulesResult (37-44)
apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx (1)
  • useRuleDialog (27-42)
apps/web/components/HoverCard.tsx (1)
  • HoverCard (7-20)
apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx (1)
  • ActionSummaryCard (13-228)
⏰ 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). (2)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (6)
apps/web/__tests__/ai-choose-rule.test.ts (4)

1-12: LGTM! Test setup follows LLM test guidelines.

The test file correctly:

  • Uses Vitest framework
  • Mocks the server-only module
  • Gates tests behind RUN_AI_TESTS
  • Imports helpers from the correct location

13-106: LGTM! Basic tests properly updated for multi-rule API.

The tests correctly validate:

  • Empty rules array returns empty result
  • Single rule match includes isPrimary flag
  • Multiple rules scenario selects the correct match
  • Test renamed to accurately reflect assertions (addressing past review)

191-434: LGTM! Multi-match test patterns are well-designed.

The tests appropriately handle both single-match and multi-match scenarios:

  • Simple cases expect exactly one rule match
  • Complex cases gracefully handle multiple matches by verifying the primary rule
  • Consistent pattern of logging multiple matches for debugging
  • Assertions verify both the primary rule and reasoning

This approach accurately tests the new multi-rule matching behavior while remaining flexible to LLM variations.


436-465: LGTM! Excellent addition for no-match scenario coverage.

This test properly validates the case where an email doesn't match any rule by:

  • Using a focused rule set without catch-all rules
  • Testing with clearly out-of-scope content (weather notification)
  • Asserting both empty rules array and empty reason

This adds important coverage for the multi-rule matching logic.

apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx (2)

69-96: Minor UX polish

  • Show “Previous:” before all older groups (not just index 1) or intentionally keep it at one divider? If you want a single divider, consider placing it once before the second group regardless of length; current logic matches that intent.

16-21: All call sites correctly updated — no issues found

Verification confirms all three usages of ProcessResultDisplay now pass the results prop as intended:

  • TestCustomEmailForm.tsx:65
  • ProcessRules.tsx:363
  • FixWithChat.tsx:226

No remnants of the old result prop remain.

Copy link

@jit-ci jit-ci bot left a comment

Choose a reason for hiding this comment

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

❌ The following Jit checks failed to run:

  • secret-detection

#jit_bypass_commit in this PR to bypass, Jit Admin privileges required.

More info in the Jit platform.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

Reviewed changes from recent commits (found 1 issue).

1 issue found across 7 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/components/assistant-chat/chat.tsx">

<violation number="1" location="apps/web/components/assistant-chat/chat.tsx:112">
Allowing submission when only context is set causes handleSubmit to send an empty user message, which the /api/chat endpoint rejects (text must be at least one character), so context-only fixes will fail.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

Reviewed changes from recent commits (found 1 issue).

1 issue found across 9 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/components/assistant-chat/chat.tsx">

<violation number="1" location="apps/web/components/assistant-chat/chat.tsx:112">
Submitting with only nextContext lets an empty message reach handleSubmit, but the API rejects empty text (min length 1), so the new flow will fail.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

@elie222 elie222 changed the title WIP: Allow multiple rule matches Allow multiple rule matches Oct 21, 2025
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: 6

Caution

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

⚠️ Outside diff range comments (3)
apps/web/app/api/user/planned/history/route.ts (1)

20-23: Validate query params (page, ruleId) with Zod and bound-check page.

API routes must validate inputs. Guard against NaN/negative pages and constrain ruleId.

-  const url = new URL(request.url);
-  const page = Number.parseInt(url.searchParams.get("page") || "1");
-  const ruleId = url.searchParams.get("ruleId") || "all";
+  const url = new URL(request.url);
+  const QuerySchema = z.object({
+    page: z.coerce.number().int().min(1).default(1),
+    ruleId: z.string().min(1).max(64).default("all"),
+  });
+  const { page, ruleId } = QuerySchema.parse({
+    page: url.searchParams.get("page"),
+    ruleId: url.searchParams.get("ruleId"),
+  });
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (2)

23-39: Dates from API are strings; type accordingly and parse locally.

Adjust props to string and keep new Date(...) where used.

 export function EmailCell({
   from,
   subject,
   snippet,
   threadId,
   messageId,
   userEmail,
   createdAt,
 }: {
   from: string;
   subject: string;
   snippet: string;
   threadId: string;
   messageId: string;
   userEmail: string;
-  createdAt: Date;
+  createdAt: string;
 }) {
…
-export function DateCell({ createdAt }: { createdAt: Date }) {
+export function DateCell({ createdAt }: { createdAt: string }) {
   return (
     <div className="whitespace-nowrap">
       <Tooltip content={new Date(createdAt).toLocaleString()}>
         <EmailDate date={new Date(createdAt)} />
       </Tooltip>
     </div>
   );
 }

Also applies to: 165-171


191-197: Add rel="noopener noreferrer" to external link.

Security best practice when using target="_blank".

   return (
     <Link
       href={getEmailUrlForMessage(messageId, threadId, userEmail, provider)}
       target="_blank"
+      rel="noopener noreferrer"
       className="ml-2 text-muted-foreground hover:text-foreground"
     >
🧹 Nitpick comments (6)
apps/web/hooks/useExecutedRules.tsx (2)

11-13: Make the SWR key URL-safe.

If ruleId ever contains special chars, the key/query may break. Encode parameters or build with URLSearchParams.

-  return useSWR<GetExecutedRulesResponseJson>(
-    `/api/user/planned/history?page=${page}&ruleId=${ruleId}`,
-  );
+  const key = `/api/user/planned/history?` +
+    new URLSearchParams({ page: String(page), ruleId }).toString();
+  return useSWR<GetExecutedRulesResponseJson>(key);

1-14: Rename file to .ts (no JSX).

Minor hygiene: this file contains no JSX. Rename to .ts for clarity.

apps/web/app/api/user/planned/history/route.ts (2)

34-44: Avoid duplication: reuse existing getExecutedRules util to prevent drift.

There’s another getExecutedRules in apps/web/app/api/user/planned/get-executed-rules.ts with similar logic. Import and reuse it (or move shared logic to a single module) to avoid divergent behavior/bugs.


82-97: Consider capping concurrent emailProvider.getMessage calls.

Batching 50 parallel calls can hit provider limits. Use a small concurrency limit (e.g., p-limit 8–10).

-  const executedRulesWithMessages = await Promise.all(
-    executedRules.map(async (p) => {
+  const pLimit = (await import("p-limit")).default;
+  const limit = pLimit(10);
+  const executedRulesWithMessages = await Promise.all(
+    executedRules.map((p) =>
+      limit(async () => {
         try {
           return {
             ...p,
             message: await emailProvider.getMessage(p.messageId),
           };
         } catch (error) {
           logger.error("Error getting message", {
             error,
             messageId: p.messageId,
             threadId: p.threadId,
           });
         }
-    }),
+      }),
     ),
   );
apps/web/app/(app)/[emailAccountId]/assistant/History.tsx (1)

3-3: Remove unused useSWR import.

Minor cleanup.

-import useSWR from "swr";
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1)

21-21: Remove unused import.

RunRulesResult isn’t referenced.

-import { RunRulesResult } from "@/utils/ai/choose-rule/run-rules";
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf18bde and d6fe48a.

📒 Files selected for processing (4)
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (5 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx (4 hunks)
  • apps/web/app/api/user/planned/history/route.ts (2 hunks)
  • apps/web/hooks/useExecutedRules.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
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/hooks/useExecutedRules.tsx
  • apps/web/app/api/user/planned/history/route.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/hooks/useExecutedRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx
apps/web/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Call mutate() after successful mutations to refresh data

Files:

  • apps/web/hooks/useExecutedRules.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/hooks/useExecutedRules.tsx
  • apps/web/app/api/user/planned/history/route.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/hooks/useExecutedRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx
apps/web/hooks/**/*.{js,jsx,ts,tsx}

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

Place custom hooks in the apps/web/hooks/ directory.

Files:

  • apps/web/hooks/useExecutedRules.tsx
apps/web/hooks/use*.{js,jsx,ts,tsx}

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

apps/web/hooks/use*.{js,jsx,ts,tsx}: Name custom hooks with the use prefix (e.g., useAccounts.ts).
For fetching data from API endpoints in custom hooks, prefer using useSWR.
Create dedicated hooks for specific data types (e.g., useAccounts, useLabels).
Custom hooks should encapsulate reusable stateful logic, especially for data fetching or complex UI interactions.
Keep custom hooks focused on a single responsibility.

Files:

  • apps/web/hooks/useExecutedRules.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/hooks/useExecutedRules.tsx
  • apps/web/app/api/user/planned/history/route.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/hooks/useExecutedRules.tsx
  • apps/web/app/api/user/planned/history/route.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/hooks/useExecutedRules.tsx
  • apps/web/app/api/user/planned/history/route.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/hooks/useExecutedRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/hooks/useExecutedRules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/user/planned/history/route.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/user/planned/history/route.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/user/planned/history/route.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/user/planned/history/route.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/user/planned/history/route.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/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.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/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/History.tsx
🧬 Code graph analysis (4)
apps/web/hooks/useExecutedRules.tsx (1)
apps/web/app/api/user/planned/history/route.ts (1)
  • GetExecutedRulesResponse (13-15)
apps/web/app/api/user/planned/history/route.ts (3)
apps/web/app/api/user/planned/get-executed-rules.ts (1)
  • getExecutedRules (11-80)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/types.ts (1)
  • isDefined (8-10)
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1)
apps/web/app/api/user/planned/history/route.ts (1)
  • GetExecutedRulesResponse (13-15)
apps/web/app/(app)/[emailAccountId]/assistant/History.tsx (3)
apps/web/hooks/useExecutedRules.tsx (1)
  • useExecutedRules (4-14)
apps/web/app/api/user/planned/history/route.ts (1)
  • GetExecutedRulesResponse (13-15)
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (2)
  • EmailCell (23-63)
  • RuleCell (65-147)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
🔇 Additional comments (1)
apps/web/app/api/user/planned/history/route.ts (1)

17-33: Confirm middleware guarantees auth + emailAccount scoping.

withEmailProvider is used instead of the mandated withAuth/withEmailAccount. If it wraps auth + ownership checks, fine; otherwise switch to the required middleware or compose it.

@@ -1,6 +1,6 @@
import Link from "next/link";
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 | 🔴 Critical

This file uses hooks and onClick; mark as a client component.

Without "use client", Next.js will error at runtime/build.

+"use client";
+
 import Link from "next/link";
📝 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
import Link from "next/link";
"use client";
import Link from "next/link";
🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx at line
1, the component uses hooks and onClick but lacks the Next.js client directive;
add the exact string "use client" as the very first line of the file (before any
imports) so the module is treated as a client component, then save and rebuild
to ensure Next.js recognizes it as client-side.

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 | 🟠 Major

Don’t import Prisma client enum in client code; use string union from API types.

Importing @prisma/client into the browser bundle is undesirable. Compare to string literal "SKIPPED" or derive the type from the API response.

-import { ExecutedRuleStatus } from "@prisma/client";
+// Use status from API types (string union) instead of Prisma enum at runtime.-export function RuleCell({
+export function RuleCell({
   rule,
   executedAt,
   status,
   reason,
   message,
   setInput,
 }: {
-  rule: GetExecutedRulesResponse["executedRules"][number]["rule"];
-  executedAt: Date;
-  status: ExecutedRuleStatus;
+  rule: GetExecutedRulesResponse["executedRules"][number]["rule"];
+  executedAt: string;
+  status: GetExecutedRulesResponse["executedRules"][number]["status"];
   reason?: string | null;
   message: ParsedMessage;
   setInput: (input: string) => void;
 }) {
…
-            {rule
-              ? rule.name
-              : status === ExecutedRuleStatus.SKIPPED
+            {rule
+              ? rule.name
+              : status === "SKIPPED"
                 ? "Skipped"
                 : `Unknown rule. Status: ${status}`}

Also applies to: 65-79, 129-136

🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx around
lines 3, 65-79, and 129-136, the component is importing Prisma client enums into
client-side code; replace those imports and enum uses with the string union/type
derived from the GetExecutedRulesResponse (or explicit string literals like
"SKIPPED") so nothing from @prisma/client is bundled in the browser. Update the
type references to use GetExecutedRulesResponse fields (or string literals) for
comparisons and switch/case checks, and remove any direct imports from
@prisma/client; ensure runtime comparisons use strings and TypeScript types
match the API response.

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 | 🔴 Critical

Align client types with JSON response (dates as strings).

After fixing the API to export GetExecutedRulesResponseJson, update imports and props to match.

-import type { GetExecutedRulesResponse } from "@/app/api/user/planned/history/route";
+import type { GetExecutedRulesResponseJson } from "@/app/api/user/planned/history/route";-  data: GetExecutedRulesResponse["executedRules"];
+  data: GetExecutedRulesResponseJson["executedRules"];

Also applies to: 64-65

🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/History.tsx around lines 6 and
64-65, the component is still importing and using GetExecutedRulesResponse (with
Date types) but the API now returns GetExecutedRulesResponseJson where date
fields are strings; update the import to GetExecutedRulesResponseJson and change
the component props/types to use that JSON type (or a Local type where date
fields are string), and ensure any code that treats dates calls new Date(...) or
otherwise parses the string before using date methods.

Comment on lines 13 to 15
export type GetExecutedRulesResponse = Awaited<
ReturnType<typeof getExecutedRules>
>;
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 | 🔴 Critical

Export a JSON-safe response type and serialize dates before returning JSON.

Clients receive ISO strings, not Date. Exporting Awaited<ReturnType<typeof getExecutedRules>> leaks server types to the client and is unsafe. Define a DTO type with string dates and map createdAt to ISO.

-import { NextResponse } from "next/server";
+import { NextResponse } from "next/server";
+import { z } from "zod";-export type GetExecutedRulesResponse = Awaited<
-  ReturnType<typeof getExecutedRules>
->;
+export type GetExecutedRulesResponseJson = {
+  executedRules: Array<
+    Omit<
+      Awaited<ReturnType<typeof getExecutedRules>>["executedRules"][number],
+      "createdAt"
+    > & { createdAt: string }
+  >;
+  totalPages: number;
+};-  return NextResponse.json(result);
+  const json: GetExecutedRulesResponseJson = {
+    executedRules: result.executedRules.map((er) => ({
+      ...er,
+      createdAt: er.createdAt.toISOString(),
+    })),
+    totalPages: result.totalPages,
+  };
+  return NextResponse.json(json);

Also applies to: 31-31, 99-103

🤖 Prompt for AI Agents
In apps/web/app/api/user/planned/history/route.ts around lines 13-15 (also apply
changes referenced at lines 31 and 99-103), the exported type
Awaited<ReturnType<typeof getExecutedRules>> leaks server-side Date objects to
the client; define and export a JSON-safe DTO type where any Date fields (e.g.,
createdAt) are strings, update return types to that DTO, and map/serialize all
Date instances to ISO strings before returning JSON so clients receive only
string dates.

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 | 🔴 Critical

Fix server-to-client type leak: import a JSON-safe response type, not the server return type.

GetExecutedRulesResponse (from the route) includes Date fields on the server. After NextResponse.json, clients receive strings. Importing that server type in a client hook is unsound and can mask bugs. Define/export a JSON-safe type from the API (e.g., GetExecutedRulesResponseJson) and use it here. As per coding guidelines.

Apply this diff after introducing GetExecutedRulesResponseJson in the API:

-import type { GetExecutedRulesResponse } from "@/app/api/user/planned/history/route";
+import type { GetExecutedRulesResponseJson } from "@/app/api/user/planned/history/route";-  return useSWR<GetExecutedRulesResponse>(
+  return useSWR<GetExecutedRulesResponseJson>(
     `/api/user/planned/history?page=${page}&ruleId=${ruleId}`,
   );

Also applies to: 11-13

🤖 Prompt for AI Agents
In apps/web/hooks/useExecutedRules.tsx around line 2 (and also update lines
11-13), the hook imports the server route return type GetExecutedRulesResponse
which contains Date objects; replace that with the JSON-safe export from the API
(e.g., GetExecutedRulesResponseJson) so client code types match the shape
returned by NextResponse.json (dates as strings). Update the import statement to
pull GetExecutedRulesResponseJson from "@/app/api/user/planned/history/route"
and update all local type references/usages accordingly to the new JSON-safe
type.

@elie222 elie222 merged commit 1b1331f into main Oct 21, 2025
7 of 9 checks passed
@elie222 elie222 deleted the feat/multi-rule branch October 21, 2025 12:42
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