Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
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 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. ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (11)
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughConverted 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this comment.
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 orderByThe
executedRule.findMany()query returns results in undefined order when multiple records match the criteria (sameemailAccountId,threadId,messageId). TakingexecutedRules[0]without ordering causes nondeterministic behavior across runs.The
ExecutedRulemodel includes acreatedAtfield, 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 renderingInitialize 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/errorsWrap 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 objectiveYou 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 labelSince 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 rulesWhen 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 failuresPer 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
📒 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.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/utils/ai/choose-rule/run-rules.tsapps/web/app/api/user/executed-rules/batch/route.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/prisma/schema.prismaapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/utils/ai/choose-rule/run-rules.tsapps/web/app/api/user/executed-rules/batch/route.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/app/api/user/executed-rules/batch/route.tsapps/web/utils/ai/choose-rule/match-rules.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/assistant/process-assistant-email.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/utils/ai/choose-rule/run-rules.tsapps/web/app/api/user/executed-rules/batch/route.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/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.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/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 useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/assistant/process-assistant-email.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/utils/ai/choose-rule/run-rules.tsapps/web/app/api/user/executed-rules/batch/route.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/webhook/process-history-item.tsapps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/prisma/schema.prismaapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/utils/ai/choose-rule/run-rules.tsapps/web/app/api/user/executed-rules/batch/route.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/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 thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient).
UseactionClientUserwhen only authenticated user context (userId) is needed.
UseactionClientwhen both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.
UseadminActionClientfor actions restricted to admin users.
Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject in the.action()handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
UseSafeErrorfor expected/handled errors within actions if needed.next-safe-actionprovides centralized error handling.
Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied viawithServerActionInstrumentationwithin the safe action clients.
If an action modifies data displayed elsewhere, userevalidatePathorrevalidateTagfromnext/cachewithin the action handler as needed.Server action files must start with
use server
Files:
apps/web/utils/actions/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/app/api/user/executed-rules/batch/route.tsapps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
apps/web/app/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Components with
onClickmust be client components withuse clientdirective
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ProcessRules.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/FixWithChat.tsxapps/web/app/(app)/[emailAccountId]/assistant/TestCustomEmailForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsxapps/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 featuresFiles:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/web/prisma/schema.prisma
📄 CodeRabbit inference engine (.cursor/rules/prisma.mdc)
The Prisma schema file must be located at
apps/web/prisma/schema.prismaFiles:
apps/web/prisma/schema.prismaapps/web/app/api/**/route.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/app/api/**/route.ts: UsewithAuthfor user-level operations
UsewithEmailAccountfor 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 withwithAuthorwithEmailAccountmiddleware 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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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.tsapps/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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.matchedRulesdirectly 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
📒 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.tsxapps/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
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
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.tsxapps/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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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
onClickmust be client components withuse clientdirective
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 useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.tsxapps/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 ZodFiles:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logicFiles:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle sizeFiles:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/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 featuresFiles:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/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 failuresApplied 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
isDefinedutility andsystemTypefield 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
isDefinedutilityThe generic type constraint properly includes
systemTypefor 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: whileRuleCelldisplays one rule,FixWithChatnow accepts an array to support the PR's multi-rule functionality.
There was a problem hiding this comment.
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
groupsfield is initialized to[], which is truthy, so the checkif (!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 whengroupsis 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 returnsnull(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.matchedRuleswithout checking ifobjectexists. 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
📒 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.tsapps/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.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/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.tsapps/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.tsapps/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 useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/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.tsapps/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.tsapps/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.tsapps/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
matchesGroupRuleandmatchesCategoryRuleare 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.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/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 anyAlso 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
📒 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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/web/utils/ai/choose-rule/run-rules.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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 useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/ai/choose-rule/ai-choose-rule.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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.tsapps/web/utils/ai/choose-rule/match-rules.tsapps/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)
There was a problem hiding this comment.
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.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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. |
There was a problem hiding this comment.
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: Useconsole.debuginstead ofconsole.logfor LLM test output.As per coding guidelines for LLM tests, use
console.debugfor 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
📒 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.tsapps/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.tsapps/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.tsapps/web/__tests__/helpers.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/__tests__/ai-choose-rule.test.tsapps/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 useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/__tests__/ai-choose-rule.test.tsapps/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.tsapps/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.tsapps/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 theserver-onlymodule withvi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it withvi.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()inbeforeEach)
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.tsapps/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
nameparameter 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
rulesarray instead of a single rule result.
23-38: LGTM! Correct multi-rule assertions.The test properly validates the new result shape with
rulesarray andisPrimaryflag 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
There was a problem hiding this comment.
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 ifcreatedAtis a string (common when coming from JSON).- Compute once with
useMemoto 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: Preferconsole.debugoverconsole.logfor LLM test output.Per the LLM test guidelines, use
console.debugto 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 ruleIf upstream ever omits
rule(it’sRule | 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/sortBywith a tiny util andArray.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
📒 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.tsapps/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.tsapps/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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/__tests__/ai-choose-rule.test.tsapps/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 useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/__tests__/ai-choose-rule.test.tsapps/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.tsapps/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 theserver-onlymodule withvi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it withvi.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()inbeforeEach)
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
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ProcessResultDisplay.tsx
**/*.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
onClickmust be client components withuse clientdirective
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 requiredApplied 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 commentsApplied 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
isPrimaryflag- 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 foundVerification confirms all three usages of
ProcessResultDisplaynow pass theresultsprop as intended:
TestCustomEmailForm.tsx:65ProcessRules.tsx:363FixWithChat.tsx:226No remnants of the old
resultprop remain.
There was a problem hiding this comment.
❌ 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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-checkpage.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
stringand keepnew 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: Addrel="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
ruleIdever contains special chars, the key/query may break. Encode parameters or build withURLSearchParams.- 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
.tsfor 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
getExecutedRulesinapps/web/app/api/user/planned/get-executed-rules.tswith similar logic. Import and reuse it (or move shared logic to a single module) to avoid divergent behavior/bugs.
82-97: Consider capping concurrentemailProvider.getMessagecalls.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 unuseduseSWRimport.Minor cleanup.
-import useSWR from "swr";apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1)
21-21: Remove unused import.
RunRulesResultisn’t referenced.-import { RunRulesResult } from "@/utils/ai/choose-rule/run-rules";
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 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.tsxapps/web/app/api/user/planned/history/route.tsapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/hooks/useExecutedRules.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.tsxapps/web/app/api/user/planned/history/route.tsapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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 theuseprefix (e.g.,useAccounts.ts).
For fetching data from API endpoints in custom hooks, prefer usinguseSWR.
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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/hooks/useExecutedRules.tsxapps/web/app/api/user/planned/history/route.tsapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/assistant/History.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/hooks/useExecutedRules.tsxapps/web/app/api/user/planned/history/route.tsapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.tsxapps/web/app/api/user/planned/history/route.tsapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/assistant/History.tsxapps/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.tsapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/assistant/History.tsxapps/web/app/api/**/route.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/app/api/**/route.ts: UsewithAuthfor user-level operations
UsewithEmailAccountfor 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 withwithAuthorwithEmailAccountmiddleware 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 ZodFiles:
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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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.tsapps/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.tsapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/History.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/History.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/History.tsxapps/web/app/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Components with
onClickmust be client components withuse clientdirectiveFiles:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/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.
withEmailProvideris used instead of the mandatedwithAuth/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"; | |||
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| export type GetExecutedRulesResponse = Awaited< | ||
| ReturnType<typeof getExecutedRules> | ||
| >; |
There was a problem hiding this comment.
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.
apps/web/hooks/useExecutedRules.tsx
Outdated
There was a problem hiding this comment.
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.
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
Improvements
Chores