Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. 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. WalkthroughCentralizes webhook history processing into a shared processor, migrates cold-email and reply-tracker flows to rule/SystemType-driven logic, adds AI thread-status determination, expands EmailProvider APIs, removes TRACK_THREAD, updates Prisma schema/migrations, refactors UI/settings, and adds unit and E2E tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant WH as Webhook
participant SP as SharedProcessor
participant PR as EmailProvider
participant DB as Prisma
participant AI as LLM
WH->>SP: processHistoryItem({messageId, threadId}, options)
SP->>PR: getMessage(messageId)
SP->>DB: find executedRule by threadId/messageId
alt already processed or ignored
SP-->>WH: return 200 (skip)
else
PR->>SP: isSentMessage(message)
alt outbound
SP->>SP: handleOutboundMessage(...)
SP->>DB: update trackers / save executed actions
SP-->>WH: done
else inbound
SP->>DB: check newsletter/unsubscribed
alt unsubscribed
SP->>PR: blockUnsubscribedEmail(messageId)
SP-->>WH: done
else has AI access
SP->>AI: runRules / choose-rule (may invoke meta-rule)
AI-->>SP: matched rule or conversation-status
SP->>PR: perform actions (label/move/etc.)
SP->>DB: save executed actions, update trackers
SP-->>WH: done
end
end
end
sequenceDiagram
autonumber
participant RR as runRules
participant CR as prepareRulesWithMetaRule
participant CS as determineConversationStatus
participant PR as Provider
participant AI as aiDetermineThreadStatus
participant DB as Prisma
RR->>CR: partition rules (conversation-status vs regular)
CR-->>RR: rules + meta-rule (if present)
RR->>RR: match rule
alt meta-rule matched
RR->>CS: determineConversationStatus(conversationRules, message,...)
CS->>PR: getThreadMessages(threadId)
PR-->>CS: messages[]
CS->>AI: aiDetermineThreadStatus(threadMessages)
AI-->>CS: {status, rationale}
CS->>RR: specificRule + reason
RR->>DB: save executed actions, update trackers/labels
else regular rule matched
RR->>DB: execute matched rule actions
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120–180 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
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: 2
🧹 Nitpick comments (3)
apps/web/utils/outlook/draft.ts (1)
17-28: Consider logging when converting errors to null.The error handling correctly returns null for not-found scenarios, but adding debug/info logging would improve observability for troubleshooting draft-related issues.
Apply this diff to add logging:
} catch (error) { // Handle 404 errors if (error instanceof Error && "code" in error && error.code === 404) { + logger.debug("Draft not found (404)", { draftId }); return null; } // Handle Outlook's "object not found in store" error if ( error instanceof Error && error.message.includes("not found in the store") ) { + logger.debug("Draft not found in store", { draftId }); return null; } throw error; }apps/web/utils/ai/reply/determine-thread-status.ts (1)
1-98: Consider adding scoped logger for observability.The implementation follows coding guidelines for AI features (separate prompts, Zod schema, proper structure), but adding a scoped logger would improve observability for debugging and monitoring.
Based on coding guidelines
Apply this diff to add logging:
import { z } from "zod"; import { createGenerateObject } from "@/utils/llms"; import type { EmailAccountWithAI } from "@/utils/llms/types"; import type { EmailForLLM } from "@/utils/types"; import { getModel } from "@/utils/llms/model"; import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers"; import type { ThreadStatus } from "@/utils/reply-tracker/conversation-status-config"; import { SystemType } from "@prisma/client"; +import { createScopedLogger } from "@/utils/logger"; + +const logger = createScopedLogger("ai-determine-thread-status"); export async function aiDetermineThreadStatus({ emailAccount, threadMessages, }: { emailAccount: EmailAccountWithAI; threadMessages: EmailForLLM[]; }): Promise<{ status: ThreadStatus; rationale: string }> { + logger.trace("Determining thread status", { + emailAccountId: emailAccount.id, + messageCount: threadMessages.length, + }); + const system = `You are an AI assistant that analyzes email threads to determine their current status.apps/web/utils/actions/rule.ts (1)
788-877: Review error handling and cache invalidation.The new toggle action has solid logic but consider these refinements:
Error type inconsistency (Line 801): Uses generic
Errorinstead ofSafeError. For consistency with other actions in this file (e.g., lines 218, 367), consider usingSafeError.Missing cache invalidation: Unlike other rule actions (e.g., lines 359-361, 395-399), this action doesn't call
revalidatePathafter toggling the rule. This could lead to stale UI state.Hardcoded AI instructions (Line 839): The instruction "Personal conversations with real people..." is hardcoded. Should this be configurable via
statusConfig.descriptionor similar?Apply these refinements:
if (!statusConfig) { - throw new Error(`Invalid system type: ${systemType}`); + throw new SafeError(`Invalid system type: ${systemType}`); }Add cache invalidation after the enable/disable operations:
} } + + revalidatePath(prefixPath(emailAccountId, "/assistant")); + revalidatePath(prefixPath(emailAccountId, "/automation")); + revalidatePath(prefixPath(emailAccountId, "/reply-zero")); }, );
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
apps/web/__tests__/determine-thread-status.test.ts(1 hunks)apps/web/__tests__/outlook-operations.test.ts(2 hunks)apps/web/app/api/ai/digest/route.ts(1 hunks)apps/web/app/api/google/webhook/process-history-item.ts(2 hunks)apps/web/app/api/outlook/webhook/process-history-item.ts(2 hunks)apps/web/components/PlanBadge.tsx(2 hunks)apps/web/prisma/schema.prisma(2 hunks)apps/web/utils/actions/rule.ts(4 hunks)apps/web/utils/actions/rule.validation.ts(1 hunks)apps/web/utils/ai/actions.ts(1 hunks)apps/web/utils/ai/reply/determine-thread-status.ts(1 hunks)apps/web/utils/email/google.ts(1 hunks)apps/web/utils/email/microsoft.ts(1 hunks)apps/web/utils/email/types.ts(1 hunks)apps/web/utils/label-config.ts(5 hunks)apps/web/utils/outlook/draft.ts(1 hunks)apps/web/utils/outlook/mail.ts(1 hunks)apps/web/utils/reply-tracker/consts.ts(1 hunks)apps/web/utils/reply-tracker/conversation-status-config.ts(1 hunks)apps/web/utils/webhook/process-history-item.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (29)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/__tests__/determine-thread-status.test.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/components/PlanBadge.tsxapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/__tests__/outlook-operations.test.ts
!{.cursor/rules/*.mdc}
📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/prisma/schema.prismaapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/__tests__/determine-thread-status.test.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/components/PlanBadge.tsxapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/__tests__/outlook-operations.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/__tests__/determine-thread-status.test.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/__tests__/outlook-operations.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/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/__tests__/determine-thread-status.test.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/components/PlanBadge.tsxapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/__tests__/outlook-operations.test.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/__tests__/determine-thread-status.test.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/components/PlanBadge.tsxapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/__tests__/outlook-operations.test.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/utils/reply-tracker/consts.tsapps/web/utils/actions/rule.validation.tsapps/web/prisma/schema.prismaapps/web/utils/ai/actions.tsapps/web/utils/actions/rule.tsapps/web/__tests__/determine-thread-status.test.tsapps/web/utils/ai/reply/determine-thread-status.tsapps/web/utils/outlook/mail.tsapps/web/utils/email/google.tsapps/web/utils/email/types.tsapps/web/utils/outlook/draft.tsapps/web/utils/email/microsoft.tsapps/web/utils/label-config.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/components/PlanBadge.tsxapps/web/app/api/outlook/webhook/process-history-item.tsapps/web/__tests__/outlook-operations.test.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/rule.validation.tsapps/web/utils/actions/rule.ts
apps/web/utils/actions/*.validation.ts
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
Define Zod schemas for validation in dedicated files and use them for both client and server validation.
Define input validation schemas using Zod in the corresponding
.validation.tsfile. These schemas are used bynext-safe-action(.schema()) and can also be reused on the client for form validation.
Files:
apps/web/utils/actions/rule.validation.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/rule.validation.tsapps/web/utils/actions/rule.ts
apps/web/prisma/schema.prisma
📄 CodeRabbit inference engine (.cursor/rules/prisma.mdc)
The Prisma schema file must be located at
apps/web/prisma/schema.prisma
Files:
apps/web/prisma/schema.prisma
apps/web/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/actions.tsapps/web/utils/ai/reply/determine-thread-status.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/actions.tsapps/web/utils/ai/reply/determine-thread-status.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__/determine-thread-status.test.tsapps/web/__tests__/outlook-operations.test.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__/determine-thread-status.test.tsapps/web/__tests__/outlook-operations.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__/determine-thread-status.test.tsapps/web/__tests__/outlook-operations.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__/determine-thread-status.test.tsapps/web/__tests__/outlook-operations.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__/determine-thread-status.test.tsapps/web/__tests__/outlook-operations.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__/determine-thread-status.test.tsapps/web/__tests__/outlook-operations.test.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history-item.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/google/webhook/process-history-item.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history-item.ts
apps/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/ai/digest/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/ai/digest/route.ts
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/components/PlanBadge.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use React Hook Form with Zod validation for form handling
Use the
LoadingContentcomponent to handle loading and error states consistently in data-fetching components.Use PascalCase for components (e.g.
components/Button.tsx)
Files:
apps/web/components/PlanBadge.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/components/PlanBadge.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/components/PlanBadge.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/components/PlanBadge.tsx🧠 Learnings (7)
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/fullstack-workflow.mdc:0-0 Timestamp: 2025-07-18T15:05:16.146Z Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod schemas for validation in dedicated files and use them for both client and server validation.Applied to files:
apps/web/utils/actions/rule.validation.ts📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR PR: elie222/inbox-zero#0 File: apps/web/CLAUDE.md:0-0 Timestamp: 2025-07-18T15:04:30.467Z Learning: Applies to apps/web/utils/actions/**/*.ts : Use Zod schemas for validation on both client and serverApplied to files:
apps/web/utils/actions/rule.validation.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/**/*.{ts,tsx} : Always define a Zod schema for response validationApplied to files:
apps/web/utils/actions/rule.validation.ts📚 Learning: 2025-10-02T23:23:48.053Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/llm-test.mdc:0-0 Timestamp: 2025-10-02T23:23:48.053Z 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__/determine-thread-status.test.ts📚 Learning: 2025-10-02T23:23:48.053Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/llm-test.mdc:0-0 Timestamp: 2025-10-02T23:23:48.053Z Learning: Applies to apps/web/__tests__/**/*.test.ts : Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")Applied to files:
apps/web/__tests__/determine-thread-status.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__/determine-thread-status.test.ts📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/gmail-api.mdc:0-0 Timestamp: 2025-07-18T15:05:34.899Z Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')Applied to files:
apps/web/utils/email/google.ts🧬 Code graph analysis (12)
apps/web/utils/actions/rule.ts (6)
apps/web/utils/actions/safe-action.ts (1)
actionClient(62-113)apps/web/utils/actions/rule.validation.ts (1)
toggleConversationStatusBody(210-218)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
CONVERSATION_STATUSES(17-50)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/label/resolve-label.ts (1)
resolveLabelNameAndId(13-65)apps/web/utils/rule/rule.ts (1)
safeCreateRule(46-112)apps/web/__tests__/determine-thread-status.test.ts (2)
apps/web/__tests__/helpers.ts (2)
getEmail(25-42)getEmailAccount(6-23)apps/web/utils/ai/reply/determine-thread-status.ts (1)
aiDetermineThreadStatus(10-98)apps/web/utils/ai/reply/determine-thread-status.ts (5)
apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/types.ts (1)
EmailForLLM(117-131)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
ThreadStatus(9-9)apps/web/utils/ai/helpers.ts (2)
getUserInfoPrompt(9-35)getEmailListPrompt(54-68)apps/web/utils/llms/index.ts (1)
createGenerateObject(119-180)apps/web/utils/email/google.ts (2)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/gmail/label.ts (1)
GmailLabel(24-38)apps/web/utils/email/types.ts (1)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/email/microsoft.ts (1)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/label-config.ts (1)
apps/web/utils/reply-tracker/consts.ts (2)
FYI_LABEL_NAME(3-3)ACTIONED_LABEL_NAME(4-4)apps/web/utils/webhook/process-history-item.ts (13)
apps/web/utils/email/types.ts (1)
EmailProvider(43-225)apps/web/utils/types.ts (1)
RuleWithActionsAndCategories(16-18)apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/logger.ts (1)
Logger(5-5)apps/web/utils/assistant/process-assistant-email.ts (1)
processAssistantEmail(20-34)apps/web/utils/mail.ts (1)
emailToContent(72-101)apps/web/utils/date.ts (1)
internalDateToDate(59-71)apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(162-188)apps/web/utils/digest/index.ts (1)
enqueueDigestItem(11-48)apps/web/utils/email.ts (1)
extractEmailAddress(19-52)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)apps/web/utils/ai/choose-rule/run-rules.ts (1)
runRules(43-97)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
apps/web/utils/reply-tracker/consts.ts (4)
NEEDS_REPLY_LABEL_NAME(1-1)FYI_LABEL_NAME(3-3)AWAITING_REPLY_LABEL_NAME(2-2)ACTIONED_LABEL_NAME(4-4)apps/web/app/api/google/webhook/process-history-item.ts (2)
apps/web/app/api/google/webhook/types.ts (2)
HistoryEventType(6-10)HistoryEventType(12-13)apps/web/app/api/google/webhook/process-label-removed-event.ts (1)
handleLabelRemovedEvent(23-107)apps/web/app/api/outlook/webhook/process-history-item.ts (1)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/__tests__/outlook-operations.test.ts (2)
apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/email/microsoft.ts (1)
OutlookProvider(66-1246)🪛 GitHub Actions: Run Tests
apps/web/utils/webhook/process-history-item.ts
[error] 13-13: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'. Does the file exist?
🪛 GitHub Check: test
apps/web/utils/webhook/process-history-item.ts
[failure] 13-13: app/api/sso/signin/route.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: app/api/google/webhook/process-history-item.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: utils/outlook/subscription-manager.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: utils/middleware.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: utils/api-auth.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: tests/outlook-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: tests/gmail-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 13-13: tests/ai-process-user-request.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:13:1⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33⏰ 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 (15)
apps/web/components/PlanBadge.tsx (1)
10-10: LGTM! Type-safe status comparison.The refactor from string literal
"APPLIED"toExecutedRuleStatus.APPLIEDimproves type safety and aligns with Prisma best practices. The logic remains unchanged while reducing the risk of typos in status comparisons.Also applies to: 74-81
apps/web/utils/ai/actions.ts (1)
154-171: Verify removal of reply tracking is intentional.The reply action no longer calls
coordinateReplyProcess, which means automatic reply tracking after sending replies has been removed. This appears to be a significant behavioral change where reply tracking must now be explicitly configured via theTRACK_THREADaction instead.Please confirm this behavioral change is intentional and that users who previously relied on automatic reply tracking after replies are aware they need to add explicit
TRACK_THREADactions to their rules.apps/web/__tests__/determine-thread-status.test.ts (1)
1-467: Excellent test coverage for AI thread status determination.The test suite comprehensively covers various scenarios including:
- Basic status types (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED)
- Edge cases (FYI message with unanswered questions)
- Multi-person threads from different perspectives
- Complex back-and-forth conversations
The tests follow all coding guidelines:
- Properly gated behind RUN_AI_TESTS
- Uses vitest with appropriate mocking
- Leverages existing test helpers
- Clear, descriptive test names
Based on coding guidelines
apps/web/utils/outlook/mail.ts (1)
216-218: Good defensive fix for draft ID stability.Returning the original
replyDraft.idensures a stable, predictable draft ID is returned to callers, even if the PATCH response omits or changes the ID. The comment clearly explains the rationale.apps/web/utils/email/types.ts (1)
217-217: LGTM - Clean interface extension.The addition of
isSentMessageto theEmailProviderinterface is straightforward and follows the existing pattern of message inspection methods likeisReplyInThread.apps/web/utils/email/microsoft.ts (1)
1171-1174: LGTM - Correct implementation.The
isSentMessageimplementation correctly checks for the "SENT" label and safely handles the optionallabelIdsarray. The comment clarifies the dependency on internal label mapping that happens beforehand.apps/web/app/api/ai/digest/route.ts (1)
226-249: No duplicate getRuleNameByExecutedAction found
This function is only defined here; extracting it to a shared utility isn’t necessary at this time.apps/web/app/api/google/webhook/process-history-item.ts (3)
4-7: LGTM! Clean refactor to shared processor.The refactor successfully delegates the main processing logic to a centralized shared processor while maintaining Google-specific label event handling. The import changes appropriately support this architectural shift.
39-43: Proper logger scoping.Good use of
.with()to create a context-specific logger within the function scope, following the coding guidelines.
52-55: Verify LABEL_ADDED event handling.The LABEL_ADDED event just logs and returns without any processing. Is this intentional, or should learning logic be implemented similar to LABEL_REMOVED?
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
1-93: LGTM! Well-structured configuration module.This new configuration module provides a clean, type-safe abstraction for conversation status mappings. Key strengths:
- Proper use of
as constfor immutability- Comprehensive mapping functions with null handling for non-conversation SystemTypes
- Clear type definitions with explicit readonly arrays
- Serves as single source of truth for status configuration
apps/web/utils/webhook/process-history-item.ts (3)
67-72: Good concurrency guard.The early concurrency check prevents duplicate processing of the same message, avoiding race conditions.
93-111: Thorough threadId resolution.Good defensive programming to re-check for existing rules when the threadId is resolved from the message after not being provided initially.
270-286: Robust error handling.Good provider-specific error handling that gracefully handles "not found" errors for both Google and Outlook, preventing unnecessary exceptions.
apps/web/utils/actions/rule.ts (1)
834-865: safeCreateRule already supports nested fields and optional labelId for LABEL actions. TheCreateRuleWithLabelIdtype andmapActionFieldsimplementation explicitly handlefieldsandlabelId, so no changes are needed.
There was a problem hiding this comment.
2 issues found across 20 files
Prompt for AI agents (all 2 issues)
Understand the root cause of the following 2 issues and fix them.
<file name="apps/web/utils/actions/rule.ts">
<violation number="1" location="apps/web/utils/actions/rule.ts:834">
When the user already has a rule with the same name as this conversation status, safeCreateRule silently returns that conflicting rule and no new system rule is created or enabled. This leaves the toggle broken for those accounts.</violation>
</file>
<file name="apps/web/utils/ai/actions.ts">
<violation number="1" location="apps/web/utils/ai/actions.ts:154">
Removing the coordinateReplyProcess call from the reply action stops us from updating thread trackers after an automated reply, so rules that only run REPLY will no longer move the thread into the NEEDS_REPLY state. Please restore that call (and emailAccountId) to keep reply tracking intact.</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/webhook/process-history-item.ts (1)
9-9: Critical: Missing import causing test failures.The import
@/utils/reply-tracker/handle-outboundis missing and causing all tests to fail. Based on line 152 wherehandleOutboundMessageis called, this file needs to exist.Verify the import path and ensure the file exists:
#!/bin/bash # Check if the file exists and show its location fd -t f "handle-outbound" apps/web/utils/reply-tracker/ # If not found, search for handleOutboundMessage function rg -n "export.*handleOutboundMessage" apps/web/
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/app/api/google/webhook/block-unsubscribed-emails.ts(0 hunks)apps/web/app/api/google/webhook/process-history-item.ts(3 hunks)apps/web/app/api/outlook/webhook/block-unsubscribed-emails.ts(0 hunks)apps/web/utils/email/google.ts(2 hunks)apps/web/utils/email/microsoft.ts(2 hunks)apps/web/utils/email/types.ts(2 hunks)apps/web/utils/webhook/process-history-item.ts(1 hunks)
💤 Files with no reviewable changes (2)
- apps/web/app/api/outlook/webhook/block-unsubscribed-emails.ts
- apps/web/app/api/google/webhook/block-unsubscribed-emails.ts
🧰 Additional context used
📓 Path-based instructions (10)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/email/types.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
!{.cursor/rules/*.mdc}
📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/utils/email/types.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/email/types.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/email/types.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/email/types.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/email/types.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/email/types.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/utils/email/types.tsapps/web/app/api/google/webhook/process-history-item.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/email/microsoft.tsapps/web/utils/email/google.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/google/webhook/process-history-item.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/google/webhook/process-history-item.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
Applied to files:
apps/web/utils/email/google.ts
🧬 Code graph analysis (5)
apps/web/utils/email/types.ts (1)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)
apps/web/app/api/google/webhook/process-history-item.ts (2)
apps/web/app/api/google/webhook/types.ts (3)
ProcessHistoryOptions(15-30)HistoryEventType(6-10)HistoryEventType(12-13)apps/web/app/api/google/webhook/process-label-removed-event.ts (1)
handleLabelRemovedEvent(23-107)
apps/web/utils/webhook/process-history-item.ts (10)
apps/web/utils/email/types.ts (1)
EmailProvider(43-226)apps/web/app/api/google/webhook/process-history-item.ts (1)
processHistoryItem(9-63)apps/web/app/api/outlook/webhook/process-history-item.ts (1)
processHistoryItem(8-34)apps/web/utils/assistant/process-assistant-email.ts (1)
processAssistantEmail(20-34)apps/web/utils/email.ts (1)
extractEmailAddress(19-52)apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(162-188)apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/digest/index.ts (1)
enqueueDigestItem(11-48)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)apps/web/utils/ai/choose-rule/run-rules.ts (1)
runRules(43-97)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)
apps/web/utils/email/google.ts (2)
apps/web/utils/gmail/label.ts (2)
labelMessage(133-149)GmailLabel(24-38)apps/web/utils/types.ts (1)
ParsedMessage(55-70)
🪛 GitHub Actions: Run Tests
apps/web/utils/webhook/process-history-item.ts
[error] 9-9: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'. Does the file exist?
🪛 GitHub Check: test
apps/web/utils/webhook/process-history-item.ts
[failure] 9-9: app/api/sso/signin/route.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: app/api/google/webhook/process-history-item.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: utils/outlook/subscription-manager.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: utils/middleware.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: utils/api-auth.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: tests/outlook-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: tests/gmail-operations.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
[failure] 9-9: tests/ai-process-user-request.test.ts
Error: Cannot find package '@/utils/reply-tracker/handle-outbound' imported from '/home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts'
❯ utils/webhook/process-history-item.ts:9:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
Caused by: Caused by: Error: Failed to load url @/utils/reply-tracker/handle-outbound (resolved id: @/utils/reply-tracker/handle-outbound) in /home/runner/work/inbox-zero/inbox-zero/apps/web/utils/webhook/process-history-item.ts. Does the file exist?
❯ loadAndTransform ../../node_modules/.pnpm/vite@7.1.9_@types+node@24.3.0_jiti@2.6.1/node_modules/vite/dist/node/chunks/dep-Chhhsdoe.js:26136:33
🔇 Additional comments (9)
apps/web/utils/email/microsoft.ts (1)
1176-1179: LGTM!The
isSentMessageimplementation correctly checks for the "SENT" label. The comment clarifies that internal mapping ensures this works as expected for Outlook.apps/web/utils/email/types.ts (1)
138-138: LGTM!The new method signatures
blockUnsubscribedEmailandisSentMessageare properly added to theEmailProviderinterface, extending the public API surface for both Gmail and Outlook providers.Also applies to: 218-218
apps/web/utils/email/google.ts (2)
363-373: LGTM!The
blockUnsubscribedEmailimplementation correctly:
- Creates/retrieves the unsubscribed label
- Adds the unsubscribed label to the message
- Removes the INBOX label to archive the message
Based on learnings: Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')
881-883: LGTM!The
isSentMessageimplementation correctly checks for Gmail's SENT label, aligning with the corresponding Outlook provider implementation.apps/web/utils/webhook/process-history-item.ts (4)
35-62: LGTM!The function signature and concurrency guard are well-implemented:
- Uses
markMessageAsProcessingto prevent duplicate processing- Early returns if message is already being processed
- Proper logging for skipped messages
67-107: LGTM!The message retrieval and existing rule check logic is correctly implemented:
- Properly handles cases where
threadIdmay not be initially provided- Re-checks with the actual
threadIdfrom the message if needed- Early return to avoid duplicate rule execution
238-254: LGTM!The error handling correctly identifies and gracefully handles provider-specific "not found" errors:
- Google: "Requested entity was not found."
- Outlook: "ItemNotFound" or "ResourceNotFound"
- Re-throws other errors for proper error propagation
184-192: Email construction verified:getEmailForLLMincludesreplyTo,cc, andattachments, satisfyingrunColdEmailBlocker’sEmailForLLMrequirements.apps/web/app/api/google/webhook/process-history-item.ts (1)
4-7: LGTM! Clean refactor to shared processor.The refactoring successfully centralizes processing logic:
- Imports and delegates to
processHistoryItemSharedfor the main flow- Maintains Google-specific label event handling (LABEL_REMOVED, LABEL_ADDED)
- Uses scoped logger with contextual fields (email, messageId, threadId)
- Creates provider instance for label event handling and shared processing
This aligns with the broader PR architecture of centralizing webhook processing.
Also applies to: 29-62
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
apps/web/utils/actions/rule.ts (1)
834-878: Address the duplicate rule name issue.As noted in the past review comment, when a user already has a rule with the same name as the conversation status (e.g., a user-created rule named "To Reply"),
safeCreateRulewithshouldCreateIfDuplicate: falsewill return that existing rule instead of creating a new system rule. This creates several problems:
- The returned rule may not have the correct
systemType(it won't be linked to the conversation status system)- The rule may not have the required
TRACK_THREADaction- The toggle will appear enabled but won't work correctly for conversation tracking
Based on the past review suggestion, apply this diff to force creation even when a duplicate name exists:
await safeCreateRule({ result: { name: statusConfig.name, condition: { aiInstructions: "Personal conversations with real people. Excludes: automated notifications and bulk emails.", conditionalOperator: null, static: null, }, actions: [ { type: ActionType.LABEL, labelId: labelInfo.labelId, fields: { label: labelInfo.label, to: null, subject: null, content: null, cc: null, bcc: null, webhookUrl: null, folderName: null, }, }, { type: ActionType.TRACK_THREAD, fields: { label: null, to: null, subject: null, content: null, cc: null, bcc: null, webhookUrl: null, folderName: null, }, }, ], }, emailAccountId, systemType, triggerType: "manual_creation", - shouldCreateIfDuplicate: false, + shouldCreateIfDuplicate: true, provider, });
🧹 Nitpick comments (4)
apps/web/utils/reply-tracker/draft-tracking.ts (1)
7-232: LGTM! Dependency-injected logger improves testability.The shift from module-scoped logger creation to dependency-injected
loggerparameters in bothtrackSentDraftStatusandcleanupThreadAIDraftsis a recommended refactor. This approach:
- Enables easier testing by allowing mock loggers to be injected
- Maintains consistent logging context from calling code
- Aligns with broader patterns in the codebase (e.g., process-history-item flows)
All logging calls have been updated consistently to use the injected logger parameter.
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
52-65: Consider defining the status types array at module level.The
CONVERSATION_STATUS_TYPESarray is recreated on every call toisConversationStatusType. For better performance, define it once at the module level.+const CONVERSATION_STATUS_TYPES: SystemType[] = [ + SystemType.TO_REPLY, + SystemType.FYI, + SystemType.AWAITING_REPLY, + SystemType.ACTIONED, +]; + export function isConversationStatusType( systemType: SystemType | null | undefined, ): boolean { if (!systemType) return false; - const CONVERSATION_STATUS_TYPES: string[] = [ - SystemType.TO_REPLY, - SystemType.FYI, - SystemType.AWAITING_REPLY, - SystemType.ACTIONED, - ]; - return CONVERSATION_STATUS_TYPES.includes(systemType); }Alternatively, you could derive it from
CONVERSATION_STATUSES:const CONVERSATION_STATUS_TYPES = CONVERSATION_STATUSES.map(s => s.systemType);apps/web/utils/reply-tracker/handle-outbound.ts (1)
26-46: Consider simplifying error handling pattern.The current approach uses
.catch()on each promise before passing toPromise.allSettled, which meansPromise.allSettledwill always see both promises as fulfilled (the catch blocks prevent rejection). Since the results aren't used, this works fine but is slightly redundant.You can simplify by removing the individual
.catch()calls and handling errors in the settled results:- await Promise.allSettled([ + const results = await Promise.allSettled([ trackSentDraftStatus({ emailAccountId: emailAccount.id, message, provider, logger, - }).catch((error) => { - logger.error("Error tracking sent draft status", { - error: formatError(error), - }); - }), + }), handleOutboundReply({ emailAccount, message, provider, - }).catch((error) => { - logger.error("Error handling outbound reply", { - error: formatError(error), - }); - }), + }), ]); + + results.forEach((result, index) => { + if (result.status === "rejected") { + const operation = index === 0 ? "tracking sent draft status" : "handling outbound reply"; + logger.error(`Error ${operation}`, { + error: formatError(result.reason), + }); + } + });Alternatively, keep the current pattern if you prefer the readability of inline error handling.
apps/web/utils/actions/rule.ts (1)
815-820: Verify and restore rule configuration on re-enable
When toggling a rule back on we only setenabled: true(apps/web/utils/actions/rule.ts:815-820). If its actions or labels were modified, re-enabling won’t restore the expected behavior. Add logic to verify (and upsert) required actions (e.g.TRACK_THREAD) and other settings whenenable: true.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx(3 hunks)apps/web/app/api/google/webhook/process-history-item.test.ts(8 hunks)apps/web/utils/actions/rule.ts(4 hunks)apps/web/utils/ai/choose-rule/run-rules.ts(5 hunks)apps/web/utils/auth.ts(1 hunks)apps/web/utils/outlook/label.ts(1 hunks)apps/web/utils/reply-tracker/conversation-status-config.ts(1 hunks)apps/web/utils/reply-tracker/draft-tracking.ts(8 hunks)apps/web/utils/reply-tracker/handle-conversation-status.ts(1 hunks)apps/web/utils/reply-tracker/handle-outbound.ts(1 hunks)apps/web/utils/reply-tracker/inbound.ts(3 hunks)apps/web/utils/reply-tracker/label-helpers.test.ts(1 hunks)apps/web/utils/reply-tracker/label-helpers.ts(1 hunks)apps/web/utils/reply-tracker/outbound.ts(3 hunks)apps/web/utils/rule/rule.ts(3 hunks)apps/web/utils/scheduled-actions/executor.test.ts(2 hunks)apps/web/utils/webhook/process-history-item.ts(1 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/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/rule/rule.ts
!{.cursor/rules/*.mdc}
📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/rule/rule.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/rule/rule.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/rule/rule.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/utils/reply-tracker/label-helpers.test.tsapps/web/utils/scheduled-actions/executor.test.tsapps/web/app/api/google/webhook/process-history-item.test.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/utils/rule/rule.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/utils/rule/rule.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/rule/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/utils/reply-tracker/label-helpers.test.tsapps/web/utils/reply-tracker/handle-outbound.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/actions/rule.tsapps/web/utils/auth.tsapps/web/utils/webhook/process-history-item.tsapps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/conversation-status-config.tsapps/web/utils/outlook/label.tsapps/web/utils/reply-tracker/inbound.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/scheduled-actions/executor.test.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/reply-tracker/draft-tracking.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/google/webhook/process-history-item.test.tsapps/web/utils/rule/rule.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/utils/reply-tracker/label-helpers.test.tsapps/web/utils/scheduled-actions/executor.test.tsapps/web/app/api/google/webhook/process-history-item.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/utils/reply-tracker/label-helpers.test.tsapps/web/utils/scheduled-actions/executor.test.tsapps/web/app/api/google/webhook/process-history-item.test.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/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/run-rules.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/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/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/Rules.tsxapps/web/app/api/google/webhook/process-history-item.test.ts
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/app/(app)/[emailAccountId]/assistant/Rules.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/Rules.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/Rules.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/Rules.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/Rules.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/Rules.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/Rules.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/Rules.tsxapps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.Files:
apps/web/app/api/google/webhook/process-history-item.test.ts🧠 Learnings (4)
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/prisma.mdc:0-0 Timestamp: 2025-06-23T12:26:53.882Z Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.Applied to files:
apps/web/utils/reply-tracker/inbound.tsapps/web/utils/rule/rule.ts📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/testing.mdc:0-0 Timestamp: 2025-09-20T18:24:34.280Z Learning: Applies to **/*.test.{ts,tsx} : In tests, mock the `server-only` module with `vi.mock("server-only", () => ({}));`Applied to files:
apps/web/app/api/google/webhook/process-history-item.test.ts📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/testing.mdc:0-0 Timestamp: 2025-09-20T18:24:34.280Z Learning: Applies to **/*.test.{ts,tsx} : When testing code that uses Prisma, mock it with `vi.mock("@/utils/prisma")` and use the mock from `@/utils/__mocks__/prisma`Applied to files:
apps/web/app/api/google/webhook/process-history-item.test.ts📚 Learning: 2025-10-02T23:23:48.053Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/llm-test.mdc:0-0 Timestamp: 2025-10-02T23:23:48.053Z Learning: Applies to apps/web/__tests__/**/*.test.ts : Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM testsApplied to files:
apps/web/app/api/google/webhook/process-history-item.test.ts🧬 Code graph analysis (16)
apps/web/utils/reply-tracker/label-helpers.test.ts (1)
apps/web/utils/reply-tracker/label-helpers.ts (2)
applyThreadStatusLabel(108-171)removeAllThreadStatusLabels(177-203)apps/web/utils/reply-tracker/handle-outbound.ts (6)
apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/logger.ts (1)
createScopedLogger(17-80)apps/web/utils/reply-tracker/draft-tracking.ts (2)
trackSentDraftStatus(12-112)cleanupThreadAIDrafts(119-233)apps/web/utils/error.ts (1)
formatError(51-61)apps/web/utils/reply-tracker/outbound.ts (1)
handleOutboundReply(12-91)apps/web/utils/ai/choose-rule/run-rules.ts (4)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
findMatchingRule(202-226)apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
determineConversationStatus(21-100)apps/web/utils/types.ts (1)
RuleWithActionsAndCategories(16-18)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(52-65)apps/web/utils/actions/rule.ts (6)
apps/web/utils/actions/safe-action.ts (1)
actionClient(62-113)apps/web/utils/actions/rule.validation.ts (1)
toggleConversationStatusBody(210-218)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
CONVERSATION_STATUSES(17-50)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/label/resolve-label.ts (1)
resolveLabelNameAndId(13-65)apps/web/utils/rule/rule.ts (1)
safeCreateRule(43-109)apps/web/utils/auth.ts (2)
apps/web/utils/error.ts (1)
captureException(63-75)apps/web/utils/dub.ts (1)
trackDubSignUp(13-41)apps/web/utils/webhook/process-history-item.ts (12)
apps/web/utils/email/types.ts (1)
EmailProvider(43-226)apps/web/utils/types.ts (1)
RuleWithActionsAndCategories(16-18)apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/logger.ts (1)
Logger(5-5)apps/web/utils/assistant/process-assistant-email.ts (1)
processAssistantEmail(20-34)apps/web/utils/reply-tracker/handle-outbound.ts (1)
handleOutboundMessage(9-60)apps/web/utils/email.ts (1)
extractEmailAddress(19-52)apps/web/utils/cold-email/is-cold-email.ts (1)
runColdEmailBlocker(162-188)apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/digest/index.ts (1)
enqueueDigestItem(11-48)apps/web/utils/categorize/senders/categorize.ts (1)
categorizeSender(17-57)apps/web/utils/ai/choose-rule/run-rules.ts (1)
runRules(47-129)apps/web/utils/reply-tracker/outbound.ts (5)
apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/ai/reply/determine-thread-status.ts (1)
aiDetermineThreadStatus(10-98)apps/web/utils/reply-tracker/label-helpers.ts (1)
applyThreadStatusLabel(108-171)apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
updateThreadTrackers(102-156)apps/web/utils/date.ts (1)
internalDateToDate(59-71)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
apps/web/utils/reply-tracker/consts.ts (4)
NEEDS_REPLY_LABEL_NAME(1-1)FYI_LABEL_NAME(3-3)AWAITING_REPLY_LABEL_NAME(2-2)ACTIONED_LABEL_NAME(4-4)apps/web/utils/outlook/label.ts (1)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/reply-tracker/inbound.ts (7)
apps/web/utils/email/types.ts (1)
EmailProvider(43-226)apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/logger.ts (1)
createScopedLogger(17-80)apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/ai/reply/determine-thread-status.ts (1)
aiDetermineThreadStatus(10-98)apps/web/utils/reply-tracker/label-helpers.ts (1)
applyThreadStatusLabel(108-171)apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
updateThreadTrackers(102-156)apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (3)
isConversationStatusType(52-65)CONVERSATION_STATUSES(17-50)ThreadStatus(9-9)apps/web/utils/actions/rule.ts (1)
toggleConversationStatusAction(788-890)apps/web/utils/reply-tracker/label-helpers.ts (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (2)
CONVERSATION_STATUSES(17-50)ConversationStatusLabelType(11-15)apps/web/utils/email/types.ts (1)
EmailProvider(43-226)apps/web/utils/reply-tracker/draft-tracking.ts (3)
apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/email/types.ts (1)
EmailProvider(43-226)apps/web/utils/logger.ts (1)
Logger(5-5)apps/web/utils/reply-tracker/handle-conversation-status.ts (6)
apps/web/utils/logger.ts (1)
createScopedLogger(17-80)apps/web/utils/types.ts (2)
RuleWithActionsAndCategories(16-18)ParsedMessage(55-70)apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/llms/model.ts (1)
ModelType(17-17)apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/ai/reply/determine-thread-status.ts (1)
aiDetermineThreadStatus(10-98)apps/web/app/api/google/webhook/process-history-item.test.ts (3)
apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/webhook/process-history-item.ts (1)
processHistoryItem(35-255)apps/web/app/api/google/webhook/process-history-item.ts (1)
processHistoryItem(9-63)apps/web/utils/rule/rule.ts (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(52-65)apps/web/utils/email/provider-types.ts (1)
isMicrosoftProvider(5-7)🔇 Additional comments (7)
apps/web/utils/auth.ts (4)
179-179: Silent failures in parallel operations.Since each promise has a
.catchhandler that doesn't rethrow,Promise.allwill always resolve successfully even if all operations fail. This means the sign-in flow continues normally regardless of whether Loops, Resend, or Dub integrations succeed. While this may be intentional for non-critical operations, consider whether the caller should have visibility into these failures (e.g., returning success/failure counts).
148-179: Simplified error handling approach.The migration from
Promise.allSettledto individual.catchhandlers simplifies the code and treats these operations as best-effort (failing silently without blocking user sign-in). This aligns with the PR's goal of modularizing error handling.
163-169: Align Resend duplicate handling with Loops. Add a 409-conflict check in the.catchand skip logging/capturing when the contact already exists, mirroring thecreateLoopsContactblock.
152-153: Avoid brittleerror.messagematching for 409
createLoopsContactis imported from@inboxzero/loops, so its error messages aren’t under our control. Rather thanerror.message.includes("409"), verify whether the thrown error exposes a status code (e.g.error.statusCode === 409orerror.response?.status === 409) or a dedicated error class, and use that for duplicate-resource detection.apps/web/utils/scheduled-actions/executor.test.ts (1)
73-75: LGTM! Test mocks updated to match new ExecutedAction schema.The addition of
labelId,folderName, andfolderIdfields (all set tonull) correctly aligns the test mocks with the expandedExecutedActiondata shape introduced in this PR.Also applies to: 162-164
apps/web/utils/rule/rule.ts (1)
176-202: LGTM! Automatic TRACK_THREAD action for conversation status rules.The logic correctly ensures that conversation-status system rules (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED) automatically include a TRACK_THREAD action if one isn't already present. This prevents users from creating conversation-status rules that don't actually track threads.
The conditional spreading for Microsoft providers (line 199) and proper null initialization for all fields are appropriate.
apps/web/utils/reply-tracker/label-helpers.test.ts (1)
1-289: LGTM! Comprehensive test coverage for label helpers.The test suite thoroughly covers:
- All four conversation status types (TO_REPLY, FYI, AWAITING_REPLY, ACTIONED)
- Correct label application via
labelMessage- Removal of other thread status labels via
removeThreadLabel- Verification that the target label is not removed
- Graceful error handling for both label application and removal failures
- Complete cleanup of all thread status labels via
removeAllThreadStatusLabelsThe mock setup is correct with proper cleanup in
beforeEach, and the test assertions are clear and comprehensive.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/ai/choose-rule/run-rules.ts (1)
62-108: Defer sender-pattern analysis until after resolving the meta ruleCalling
analyzeSenderPatternIfAiMatchbefore we swap the meta rule for the concrete conversation-status rule means the guard inshouldAnalyzeSenderPatternnow seesSystemType === null. As soon as the meta rule matches a To Reply thread, we no longer skip analysis—the safeguard from the original code regresses and we start running sender-pattern learning for TO_REPLY conversations. Please move the analysis call to run againstruleToExecute(after the meta rule has been resolved) so the system type check continues to work as intended.Here’s one way to adjust it:
- analyzeSenderPatternIfAiMatch({ - isTest, - result, - message, - emailAccountId: emailAccount.id, - }); - if (result.rule) { let ruleToExecute = result.rule; let reasonToUse = result.reason; @@ + analyzeSenderPatternIfAiMatch({ + isTest, + result: { + rule: ruleToExecute, + matchReasons: result.matchReasons, + }, + message, + emailAccountId: emailAccount.id, + });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/utils/actions/rule.ts(4 hunks)apps/web/utils/ai/choose-rule/run-rules.ts(5 hunks)apps/web/utils/email/microsoft.ts(2 hunks)apps/web/utils/outlook/label.ts(1 hunks)apps/web/utils/reply-tracker/label-helpers.ts(1 hunks)apps/web/utils/rule/rule.ts(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/utils/outlook/label.ts
- apps/web/utils/email/microsoft.ts
🧰 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/utils/ai/choose-rule/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.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/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.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/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.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/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.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/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/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/utils/ai/choose-rule/run-rules.tsapps/web/utils/rule/rule.tsapps/web/utils/reply-tracker/label-helpers.tsapps/web/utils/actions/rule.ts
apps/web/utils/ai/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features
Files:
apps/web/utils/ai/choose-rule/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/run-rules.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/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/rule.ts
🧠 Learnings (3)
📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/prisma.mdc:0-0
Timestamp: 2025-06-23T12:26:53.882Z
Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.
Applied to files:
apps/web/utils/rule/rule.ts
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.
Applied to files:
apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation
Applied to files:
apps/web/utils/actions/rule.ts
🧬 Code graph analysis (4)
apps/web/utils/ai/choose-rule/run-rules.ts (4)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
findMatchingRule(202-226)apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
determineConversationStatus(21-100)apps/web/utils/types.ts (1)
RuleWithActionsAndCategories(16-18)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(52-65)
apps/web/utils/rule/rule.ts (2)
apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(52-65)apps/web/utils/email/provider-types.ts (1)
isMicrosoftProvider(5-7)
apps/web/utils/reply-tracker/label-helpers.ts (3)
apps/web/utils/logger.ts (1)
createScopedLogger(17-80)apps/web/utils/reply-tracker/conversation-status-config.ts (2)
CONVERSATION_STATUSES(17-50)ConversationStatusLabelType(11-15)apps/web/utils/email/types.ts (1)
EmailProvider(43-226)
apps/web/utils/actions/rule.ts (7)
apps/web/utils/actions/safe-action.ts (1)
actionClient(62-113)apps/web/utils/actions/rule.validation.ts (1)
toggleConversationStatusBody(210-218)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
CONVERSATION_STATUSES(17-50)apps/web/utils/error.ts (1)
SafeError(86-96)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/label/resolve-label.ts (1)
resolveLabelNameAndId(13-65)apps/web/utils/rule/rule.ts (1)
safeCreateRule(43-132)
⏰ 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: Static Code Analysis Js
- GitHub Check: Jit Security
🔇 Additional comments (5)
apps/web/utils/reply-tracker/label-helpers.ts (5)
1-10: LGTM! Clean imports and logger setup.The imports are well-organized and the logger is correctly initialized at the top level using
createScopedLoggeras per coding guidelines.
12-22: LGTM! Well-structured constants and type mappings.The derived constants and helper function provide a clean abstraction layer. The
statusToLabelTypefunction appropriately throws for unknown status types, enforcing type safety at runtime.
178-201: LGTM! Past review concern is properly addressed.This implementation correctly addresses the previous review comment by calling
getAllThreadStatusLabels(line 186) instead of the create variant. The helper function (defined at lines 207-276) only fetches existing labels by name without creating new ones (see line 257), ensuring no unexpected label creation during status clearing.Based on past review comments that flagged unwanted label creation.
207-276: LGTM! Fetch-only implementation is correct.This helper correctly implements fetch-only behavior (no label creation). The key difference from
getOrCreateAllThreadStatusLabelsis at line 257 where it only updatesdbLabelIds[type]if the label already exists in the provider—no creation occurs.This design properly supports the use case in
removeAllThreadStatusLabelswhere we only want to clear existing labels without side effects.Note: Similar to
getOrCreateAllThreadStatusLabels, this function returns partial results on error (line 274) and has the same DB update failure considerations at lines 264-269. See the earlier comment for details.
64-100: Consider DB update failures leading to orphaned provider labels
Ifprisma.emailAccount.updatefails after creating labels viaprovider.createLabel, remote labels persist but the DB isn’t updated, and the catch returns partial IDs—subsequent calls will retry creation (risking duplicates or errors). Log or propagate the update error, or roll back provider changes to maintain consistency.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/api/google/webhook/process-label-removed-event.test.ts (1)
94-94: Remove debug console.log statement.This console.log statement violates the coding guideline "Don't use console." Debug statements should be removed before merging.
As per coding guidelines.
Apply this diff:
- console.log("Test data:", JSON.stringify(historyItem.item, null, 2)); - try {
🧹 Nitpick comments (2)
apps/web/app/api/google/webhook/process-label-removed-event.test.ts (2)
35-35: Consider extracting the hard-coded label name to a constant.The hard-coded "Cold Email" string makes the test more brittle if the label name changes. Consider defining a constant at the top of the test file or importing it from a shared location.
Example:
+const COLD_EMAIL_LABEL_NAME = "Cold Email"; + vi.mock("@/utils/gmail/label", () => ({ GmailLabel: { // ... }, getLabelById: vi.fn().mockImplementation(({ id }: { id: string }) => { const labelMap: Record<string, { name: string }> = { - "label-1": { name: "Cold Email" }, + "label-1": { name: COLD_EMAIL_LABEL_NAME }, // ... }; return Promise.resolve(labelMap[id] || { name: "Unknown Label" }); }), }));
76-76: Consider using the same constant here as suggested for line 35.If you extract the label name to a constant as suggested above, apply the same change here for consistency.
Example:
getLabels: vi.fn().mockResolvedValue([ - { id: "label-1", name: "Cold Email", type: "user" }, + { id: "label-1", name: COLD_EMAIL_LABEL_NAME, type: "user" }, { id: "label-2", name: "Newsletter", type: "user" }, // ... ]),
📜 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(2 hunks)apps/web/app/api/google/webhook/process-label-removed-event.test.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/__tests__/ai-choose-rule.test.tsapps/web/app/api/google/webhook/process-label-removed-event.test.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/app/api/google/webhook/process-label-removed-event.test.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/app/api/google/webhook/process-label-removed-event.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/api/google/webhook/process-label-removed-event.test.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.tsapps/web/app/api/google/webhook/process-label-removed-event.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/api/google/webhook/process-label-removed-event.test.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/app/api/google/webhook/process-label-removed-event.test.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.tsapps/web/app/api/google/webhook/process-label-removed-event.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.tsapps/web/app/api/google/webhook/process-label-removed-event.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/api/google/webhook/process-label-removed-event.test.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/google/webhook/process-label-removed-event.test.ts
🧠 Learnings (5)
📚 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
📚 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 : Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"
Applied to files:
apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use provided helpers for mocks: import `{ getEmail, getEmailAccount, getRule }` from `@/__tests__/helpers`
Applied to files:
apps/web/__tests__/ai-choose-rule.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest (`vitest`) as the testing framework
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-63)
⏰ 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 (2)
apps/web/__tests__/ai-choose-rule.test.ts (2)
72-72: LGTM! Schema alignment for label references.The addition of
labelId: nullaligns the mock action with the updated schema that now supports both string labels and label ID references, consistent with the PR's migration to dynamic rule/label configurations.
107-107: Confirm literal captures original intent
I couldn't locatedefaultReplyTrackerInstructionsin the codebase—ensure"Match emails requiring a response"fully encompasses the original instructions and matching criteria.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (6)
apps/web/app/api/ai/digest/route.ts (2)
47-54: Exception bypasses null check; account-scope the lookup.
getRuleNameByExecutedActionthrows at line 245 if the executed action is not found, so the!ruleNameguard never runs—this will cause 500 errors. Additionally, returning early whenactionIdis falsy may skip valid digest items without an action ID. Per the past review, the lookup must also be scoped byemailAccountIdto prevent cross-account leakage.Apply this diff:
- const ruleName = actionId - ? await getRuleNameByExecutedAction(actionId) - : null; - - if (!ruleName) { - logger.warn("Rule name not found for executed action", { actionId }); - return new NextResponse("OK", { status: 200 }); - } + const ruleName = actionId + ? await getRuleNameByExecutedAction(actionId, emailAccountId) + : null; + + if (!ruleName) { + logger.warn("Rule name not found for executed action", { actionId }); + return new NextResponse("OK", { status: 200 }); + }And update
getRuleNameByExecutedActionbelow to accept and useemailAccountId.
226-249: Missing account scoping enables IDOR; returnundefinedinstead of throwing.This function has two critical issues:
- IDOR vulnerability: The
findUniquequery lacks anemailAccountIdfilter, allowing any caller with a validactionIdto retrieve rule names from other accounts.- Inconsistent error handling: The function signature promises
Promise<string | undefined>, but line 245 throws instead of returningundefined.As per coding guidelines, all Prisma queries in API routes must include ownership filters to prevent unauthorized data access.
Apply this diff to scope by account and return
undefinedon missing action:async function getRuleNameByExecutedAction( actionId: string, + emailAccountId: string, ): Promise<string | undefined> { - const executedAction = await prisma.executedAction.findUnique({ - where: { id: actionId }, + const executedAction = await prisma.executedAction.findFirst({ + where: { + id: actionId, + executedRule: { + rule: { + emailAccountId, + }, + }, + }, select: { executedRule: { select: { rule: { select: { name: true, }, }, }, }, }, }); - if (!executedAction) { - throw new Error("Executed action not found"); - } - - return executedAction.executedRule?.rule?.name; + return executedAction?.executedRule?.rule?.name; }apps/web/utils/ai/choose-rule/run-rules.ts (1)
86-113: Critical: Sender pattern analysis still skipped for conversation rulesThe
analyzeSenderPatternIfAiMatchcall is placed in theelsebranch (lines 107-112), meaning it only runs for non-meta rules. Conversation rules (FYI, ACTIONED, AWAITING_REPLY) are resolved through the meta-rule path, so they never trigger sender learning—exactly the regression flagged in the previous review. Move the analysis call to after the entire if/else block so it uses the finalruleToExecute.Apply this diff to restore sender learning for conversation matches:
// Check if this is the conversation tracking meta-rule if (result.rule.id === CONVERSATION_TRACKING_META_RULE_ID) { // Determine which specific sub-rule applies const { specificRule, reason: statusReason } = await determineConversationStatus({ conversationRules, message, emailAccount, provider, modelType, }); if (!specificRule) { return { rule: null, reason: statusReason || "No enabled conversation status rule found", }; } ruleToExecute = specificRule; reasonToUse = statusReason; - } else { - analyzeSenderPatternIfAiMatch({ - isTest, - result, - message, - emailAccountId: emailAccount.id, - }); } + + analyzeSenderPatternIfAiMatch({ + isTest, + result: { + rule: ruleToExecute, + matchReasons: result.matchReasons, + }, + message, + emailAccountId: emailAccount.id, + }); return await executeMatchedRule( ruleToExecute,apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
69-72: Add error handling around AI call.External AI calls can throw; add try/catch to avoid unhandled rejections and log failures. As per coding guidelines and learnings, use proper error handling with try/catch and implement fallbacks for AI failures.
Apply this diff to add error handling:
+ let status: SystemType; + let rationale: string; + try { - const { status, rationale } = await aiDetermineThreadStatus({ + const aiResult = await aiDetermineThreadStatus({ emailAccount, threadMessages: threadMessagesForLLM, }); + status = aiResult.status; + rationale = aiResult.rationale; + } catch (error) { + logger.error("AI thread status determination failed", { error }); + return { + specificRule: null, + reason: "AI analysis failed", + }; + }apps/web/utils/reply-tracker/outbound.ts (2)
70-73: Add error handling around AI call.External AI calls can throw; add try/catch to avoid unhandled rejections and log failures. As per coding guidelines and learnings, use proper error handling with try/catch and implement fallbacks for AI failures.
Apply this diff to add error handling:
+ let aiResult; + try { - const aiResult = await aiDetermineThreadStatus({ + aiResult = await aiDetermineThreadStatus({ emailAccount, threadMessages: threadMessagesForLLM, }); + } catch (error) { + logger.error("AI thread status determination failed", { error }); + return; + }
85-91: Add error handling around database update.Database operations can fail; add try/catch to handle potential errors. As per coding guidelines, use proper error handling with try/catch blocks.
Apply this diff to add error handling:
+ try { await updateThreadTrackers({ emailAccountId: emailAccount.id, threadId: message.threadId, messageId: message.id, sentAt: internalDateToDate(message.internalDate), status: aiResult.status, }); + } catch (error) { + logger.error("Failed to update thread trackers", { error }); + }
🧹 Nitpick comments (3)
apps/web/app/api/google/webhook/process-history.ts (2)
66-78: Use consistent variable for lastSyncedHistoryId.Lines 69 and 75 reference
emailAccount?.lastSyncedHistoryId, but the rest of the function usesvalidatedEmailAccount. While technically safe (both reference the same object), this inconsistency could confuse future maintainers.Apply this diff for consistency:
const startHistoryId = options?.startHistoryId || Math.max( - Number.parseInt(emailAccount?.lastSyncedHistoryId || "0"), + Number.parseInt(validatedEmailAccount.lastSyncedHistoryId || "0"), historyId - 500, // avoid going too far back ).toString(); logger.info("Listing history", { startHistoryId, - lastSyncedHistoryId: emailAccount?.lastSyncedHistoryId, + lastSyncedHistoryId: validatedEmailAccount.lastSyncedHistoryId, gmailHistoryId: startHistoryId, email, });
95-117: Consider simplifying emailAccount reconstruction.Lines 102-116 manually reconstruct the
emailAccountobject by selecting fields fromvalidatedEmailAccount. If theProcessHistoryOptions.emailAccounttype is compatible with the validated account structure, you could passvalidatedEmailAccountdirectly to reduce code duplication.Verify type compatibility and consider:
await processHistory({ history: history.history, gmail, accessToken: accountAccessToken, hasAutomationRules, hasAiAccess: userHasAiAccess, rules: validatedEmailAccount.rules, emailAccount: validatedEmailAccount, // if types match });apps/web/app/api/outlook/webhook/process-history.ts (1)
49-63: Consider simplifying emailAccount reconstruction.Similar to the Google webhook, lines 49-63 manually reconstruct the
emailAccountobject. If theProcessHistoryOptions.emailAccounttype matches the validated structure, passingvalidatedEmailAccountdirectly would reduce duplication.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/web/app/api/ai/digest/route.ts(2 hunks)apps/web/app/api/google/webhook/process-history.ts(4 hunks)apps/web/app/api/outlook/webhook/process-history.ts(2 hunks)apps/web/utils/ai/choose-rule/run-rules.ts(5 hunks)apps/web/utils/reply-tracker/handle-conversation-status.ts(1 hunks)apps/web/utils/reply-tracker/outbound.ts(4 hunks)apps/web/utils/webhook/validate-webhook-account.test.ts(1 hunks)apps/web/utils/webhook/validate-webhook-account.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (17)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/app/api/google/webhook/process-history.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.test.ts
!{.cursor/rules/*.mdc}
📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/app/api/google/webhook/process-history.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.test.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/app/api/google/webhook/process-history.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.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/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/app/api/google/webhook/process-history.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.test.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.test.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.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/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/app/api/google/webhook/process-history.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.test.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/utils/reply-tracker/outbound.tsapps/web/utils/reply-tracker/handle-conversation-status.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/utils/webhook/validate-webhook-account.tsapps/web/app/api/google/webhook/process-history.tsapps/web/utils/ai/choose-rule/run-rules.tsapps/web/utils/webhook/validate-webhook-account.test.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/app/api/google/webhook/process-history.ts
apps/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/ai/digest/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/ai/digest/route.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/ai/digest/route.tsapps/web/app/api/outlook/webhook/process-history.tsapps/web/app/api/google/webhook/process-history.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/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/run-rules.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/utils/webhook/validate-webhook-account.test.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/utils/webhook/validate-webhook-account.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/utils/webhook/validate-webhook-account.test.ts
🧠 Learnings (4)
📚 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} : Use proper error types and logging for failures
Applied to files:
apps/web/utils/reply-tracker/outbound.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/**/*.{ts,tsx} : Implement fallbacks for AI failures
Applied to files:
apps/web/utils/reply-tracker/outbound.ts
📚 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 : Prefer existing helpers from @/__tests__/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) over custom helpers
Applied to files:
apps/web/utils/webhook/validate-webhook-account.test.ts
📚 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 : Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats
Applied to files:
apps/web/utils/webhook/validate-webhook-account.test.ts
🧬 Code graph analysis (7)
apps/web/utils/reply-tracker/outbound.ts (6)
apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/ai/reply/determine-thread-status.ts (1)
aiDetermineThreadStatus(10-97)apps/web/utils/reply-tracker/label-helpers.ts (1)
applyThreadStatusLabel(17-94)apps/web/utils/reply-tracker/handle-conversation-status.ts (1)
updateThreadTrackers(105-159)apps/web/utils/date.ts (1)
internalDateToDate(59-71)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
CONVERSATION_STATUS_TYPES(3-8)
apps/web/utils/reply-tracker/handle-conversation-status.ts (7)
apps/web/utils/logger.ts (1)
createScopedLogger(17-80)apps/web/utils/types.ts (2)
RuleWithActionsAndCategories(16-18)ParsedMessage(55-70)apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/llms/model.ts (1)
ModelType(17-17)apps/web/utils/date.ts (1)
internalDateToDate(59-71)apps/web/utils/get-email-from-message.ts (1)
getEmailForLLM(6-25)apps/web/utils/ai/reply/determine-thread-status.ts (1)
aiDetermineThreadStatus(10-97)
apps/web/app/api/outlook/webhook/process-history.ts (5)
apps/web/utils/webhook/validate-webhook-account.ts (2)
getWebhookEmailAccount(7-59)validateWebhookAccount(75-155)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/webhook/process-history-item.ts (1)
processHistoryItem(26-216)apps/web/app/api/outlook/webhook/process-history-item.ts (1)
processHistoryItem(18-44)apps/web/utils/error.ts (1)
captureException(63-75)
apps/web/utils/webhook/validate-webhook-account.ts (3)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/premium/index.ts (2)
isPremium(14-22)hasAiAccess(87-101)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)
apps/web/app/api/google/webhook/process-history.ts (2)
apps/web/utils/webhook/validate-webhook-account.ts (2)
getWebhookEmailAccount(7-59)validateWebhookAccount(75-155)apps/web/utils/gmail/client.ts (1)
getGmailClientWithRefresh(49-103)
apps/web/utils/ai/choose-rule/run-rules.ts (8)
apps/web/utils/ai/choose-rule/match-rules.ts (1)
findMatchingRule(207-259)apps/web/utils/reply-tracker/handle-conversation-status.ts (2)
determineConversationStatus(22-103)updateThreadTrackers(105-159)apps/web/utils/types.ts (1)
RuleWithActionsAndCategories(16-18)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
isConversationStatusType(16-22)apps/web/utils/cold-email/is-cold-email.ts (1)
saveColdEmail(165-191)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/webhook/validate-webhook-account.test.ts (3)
apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/webhook/validate-webhook-account.ts (2)
ValidatedWebhookAccountData(61-63)validateWebhookAccount(75-155)apps/web/utils/premium/index.ts (2)
isPremium(14-22)hasAiAccess(87-101)
⏰ 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 (8)
apps/web/utils/ai/choose-rule/run-rules.ts (4)
32-38: LGTM!The new imports are appropriate for the conversation tracking and cold email features.
50-50: LGTM!The constant is properly defined for identifying the conversation tracking meta-rule.
136-168: LGTM!The meta-rule preparation correctly separates conversation rules and creates a meta-rule with
systemType: null(line 159), which properly addresses the previous review concern about systemType inheritance.
197-270: LGTM!The execution flow is well-structured:
- Immediate actions are saved and executed right away
- Cold email metadata is correctly persisted for COLD_EMAIL rules
- Thread trackers are updated for conversation status types
- Delayed actions are properly scheduled after canceling any existing ones
- Rules with no actions are marked as APPLIED immediately
apps/web/utils/webhook/validate-webhook-account.test.ts (1)
1-314: LGTM! Comprehensive test coverage.The test suite thoroughly covers all validation paths including null accounts, non-premium scenarios, missing AI access, missing automation rules, missing tokens, and success cases. The use of a custom
createMockEmailAccounthelper is appropriate here since it creates the specificValidatedWebhookAccountDatatype which differs from the generic test helpers in@/__tests__/helpers.apps/web/utils/webhook/validate-webhook-account.ts (1)
7-59: LGTM! Well-structured account query.The conditional query logic correctly handles both email and subscription ID lookups, and the select clause includes all necessary fields for downstream validation and processing.
apps/web/app/api/google/webhook/process-history.ts (1)
46-52: Token check after validation is redundant but acceptable.The validation at line 34 already ensures tokens exist (see
validateWebhookAccountlines 137-145). However, this check provides explicit type narrowing for TypeScript and adds a helpful debug log at a critical point in the flow.apps/web/app/api/outlook/webhook/process-history.ts (1)
19-41: LGTM! Correct provider fallback for Outlook.The validation flow is clean, and the
"microsoft"fallback at line 36 correctly handles Outlook accounts when the provider field is null. This approach works around the issue flagged invalidateWebhookAccountby creating the provider separately with the correct fallback.
| message: ParsedMessage; | ||
| emailAccount: EmailAccountWithAI; | ||
| provider: EmailProvider; | ||
| modelType: ModelType; |
There was a problem hiding this comment.
Remove unused parameter.
The modelType parameter is declared but never used in the function body.
Apply this diff to remove the unused parameter:
provider: EmailProvider;
- modelType: ModelType;
}): Promise<{📝 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.
| modelType: ModelType; | |
| provider: EmailProvider; | |
| }): Promise<{ |
🤖 Prompt for AI Agents
In apps/web/utils/reply-tracker/handle-conversation-status.ts around line 32,
the function declaration includes a modelType parameter that is never used;
remove modelType from the function signature (and any corresponding
type/interface for that function) and update all call sites to stop passing
modelType so the parameter is no longer required; ensure TypeScript types are
adjusted (remove modelType from the parameter type) and run type checks to
confirm there are no remaining references.
| const provider = await createEmailProvider({ | ||
| emailAccountId: emailAccount.id, | ||
| provider: emailAccount.account?.provider || "google", | ||
| }); |
There was a problem hiding this comment.
Provider fallback may be incorrect for Outlook webhooks.
The fallback to "google" at line 97 could cause issues for Outlook webhooks when provider is null. The Outlook webhook handler uses "microsoft" as its fallback (see apps/web/app/api/outlook/webhook/process-history.ts lines 35-36), creating an inconsistency. If an Outlook account has a null provider field, this function will attempt to create a Google provider for unwatchEmails, which will fail when createEmailProvider tries to initialize a Gmail client for an Outlook account.
Consider one of these solutions:
- Accept a
providerTypeparameter to use the correct fallback - Determine the provider based on the query type (
watchEmailsSubscriptionIdlikely indicates Outlook) - Handle the provider creation error gracefully in the unwatchEmails flow
Apply this diff to accept a provider fallback parameter:
export async function validateWebhookAccount(
emailAccount: ValidatedWebhookAccountData | null,
+ providerFallback: string,
logger: {
error: (message: string, context?: Record<string, unknown>) => void;
info: (message: string, context?: Record<string, unknown>) => void;
trace: (message: string, context?: Record<string, unknown>) => void;
},
): Promise<ValidationResult> {
if (!emailAccount) {
logger.error("Account not found");
return { success: false, response: NextResponse.json({ ok: true }) };
}
const premium = isPremium(
emailAccount.user.premium?.lemonSqueezyRenewsAt || null,
emailAccount.user.premium?.stripeSubscriptionStatus || null,
)
? emailAccount.user.premium
: undefined;
const provider = await createEmailProvider({
emailAccountId: emailAccount.id,
- provider: emailAccount.account?.provider || "google",
+ provider: emailAccount.account?.provider || providerFallback,
});Then update the callers to pass the appropriate fallback:
// In Google webhook
const validation = await validateWebhookAccount(emailAccount, "google", logger);
// In Outlook webhook
const validation = await validateWebhookAccount(emailAccount, "microsoft", logger);There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/web/utils/email/google.ts (1)
364-377: Fix unsubscribed labeling guard before archiving.The guard is inverted:
unsubscribeLabel?.idis truthy when the label exists, so we always log “not found” yet still proceed even if creation failed. Worse, ifgetOrCreateInboxZeroLabelever returns without an id, we still strip INBOX, leaving the message untracked. Please bail out when the label is missing and only archive once we have a concrete label id.const unsubscribeLabel = await this.getOrCreateInboxZeroLabel("unsubscribed"); - if (unsubscribeLabel?.id) { - logger.warn("Unsubscribe label not found", { messageId }); - } - - await labelMessage({ + if (!unsubscribeLabel?.id) { + logger.error("Failed to get or create unsubscribed label", { messageId }); + throw new Error("Cannot block email without unsubscribed label"); + } + + await labelMessage({ gmail: this.client, messageId, - addLabelIds: unsubscribeLabel?.id ? [unsubscribeLabel.id] : undefined, + addLabelIds: [unsubscribeLabel.id], removeLabelIds: [GmailLabel.INBOX], });apps/web/utils/actions/rule.ts (1)
546-619: Avoid creating a rule when toggling off an absent system rule.If the user is turning a status off (
enabled === false) and there is no existing system rule, we currently drop into the creation branch andsafeCreateRulebuilds a brand-new (enabled) rule. That makes the toggle behave backwards for those accounts. Add a guard to bail out before provisioning anything whenenabledis false and no rule exists.if (existingRule) { await prisma.rule.update({ where: { id: existingRule.id }, data: { enabled }, }); return; } + if (!enabled) { + return; + } + const emailProvider = await createEmailProvider({ emailAccountId, provider, });
🧹 Nitpick comments (1)
apps/web/app/api/user/folders/route.ts (1)
22-25: Consider inlining the trivial wrapper function.The
getFoldersfunction now simply callsemailProvider.getFolders()without additional logic. Consider inlining this directly in the GET handler for simplicity.Apply this diff to inline the function:
export const GET = withEmailProvider(async (request) => { const emailProvider = request.emailProvider; if (!isMicrosoftProvider(emailProvider.name)) { return NextResponse.json( { error: "Only Microsoft email providers are supported" }, { status: 400 }, ); } - const result = await getFolders({ emailProvider }); + const folders = await emailProvider.getFolders(); - return NextResponse.json(result); + return NextResponse.json(folders); }); - -async function getFolders({ emailProvider }: { emailProvider: EmailProvider }) { - const folders = await emailProvider.getFolders(); - return folders; -}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
apps/web/app/(app)/accounts/page.tsx(1 hunks)apps/web/app/api/user/folders/route.ts(1 hunks)apps/web/components/FolderCombobox.tsx(0 hunks)apps/web/components/FolderSelector.tsx(2 hunks)apps/web/hooks/useFolders.ts(1 hunks)apps/web/utils/actions/ai-rule.ts(3 hunks)apps/web/utils/actions/rule.ts(13 hunks)apps/web/utils/email/google.ts(5 hunks)apps/web/utils/email/microsoft.ts(7 hunks)apps/web/utils/email/types.ts(5 hunks)apps/web/utils/outlook/errors.ts(1 hunks)apps/web/utils/outlook/folders.ts(3 hunks)apps/web/utils/rule/rule.ts(14 hunks)
💤 Files with no reviewable changes (1)
- apps/web/components/FolderCombobox.tsx
✅ Files skipped from review due to trivial changes (1)
- apps/web/utils/outlook/errors.ts
🧰 Additional context used
📓 Path-based instructions (28)
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)/accounts/page.tsxapps/web/hooks/useFolders.tsapps/web/components/FolderSelector.tsxapps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/app/api/user/folders/route.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.ts
apps/web/app/**
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/accounts/page.tsxapps/web/app/api/user/folders/route.ts
apps/web/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/app/(app)/accounts/page.tsxapps/web/components/FolderSelector.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)/accounts/page.tsxapps/web/hooks/useFolders.tsapps/web/components/FolderSelector.tsxapps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/app/api/user/folders/route.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.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)/accounts/page.tsxapps/web/components/FolderSelector.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)/accounts/page.tsxapps/web/hooks/useFolders.tsapps/web/components/FolderSelector.tsxapps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/app/api/user/folders/route.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.ts
apps/web/app/(app)/*/page.tsx
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/*/page.tsx: Create new pages at: apps/web/app/(app)/PAGE_NAME/page.tsx
Pages are Server components so you can load data into them directly
apps/web/app/(app)/*/page.tsx: Create new pages at:apps/web/app/(app)/PAGE_NAME/page.tsx
Pages are Server components for direct data loading
Files:
apps/web/app/(app)/accounts/page.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)/accounts/page.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)/accounts/page.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)/accounts/page.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)/accounts/page.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)/accounts/page.tsxapps/web/hooks/useFolders.tsapps/web/components/FolderSelector.tsxapps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/app/api/user/folders/route.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/app/(app)/accounts/page.tsxapps/web/hooks/useFolders.tsapps/web/components/FolderSelector.tsxapps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/app/api/user/folders/route.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.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)/accounts/page.tsxapps/web/components/FolderSelector.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)/accounts/page.tsxapps/web/components/FolderSelector.tsxapps/web/hooks/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use SWR for efficient data fetching and caching
apps/web/hooks/**/*.ts: Use SWR for client-side data fetching and caching.
Callmutate()after successful mutations to refresh SWR data on the client.Files:
apps/web/hooks/useFolders.tsapps/web/hooks/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Call
mutate()after successful mutations to refresh dataFiles:
apps/web/hooks/useFolders.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/hooks/useFolders.tsapps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/app/api/user/folders/route.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.tsapps/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/useFolders.tsapps/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/useFolders.tsapps/web/components/**/*.tsx
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use React Hook Form with Zod validation for form handling
Use the
LoadingContentcomponent to handle loading and error states consistently in data-fetching components.Use PascalCase for components (e.g.
components/Button.tsx)Files:
apps/web/components/FolderSelector.tsxapps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.Files:
apps/web/utils/actions/rule.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/actions/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server-actions.mdc)
apps/web/utils/actions/*.ts: Implement all server actions using thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient).
UseactionClientUserwhen only authenticated user context (userId) is needed.
UseactionClientwhen both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.
UseadminActionClientfor actions restricted to admin users.
Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject in the.action()handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
UseSafeErrorfor expected/handled errors within actions if needed.next-safe-actionprovides centralized error handling.
Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied viawithServerActionInstrumentationwithin the safe action clients.
If an action modifies data displayed elsewhere, userevalidatePathorrevalidateTagfromnext/cachewithin the action handler as needed.Server action files must start with
use serverFiles:
apps/web/utils/actions/rule.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logicFiles:
apps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.tsapps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle sizeFiles:
apps/web/utils/actions/rule.tsapps/web/utils/outlook/folders.tsapps/web/utils/email/google.tsapps/web/utils/rule/rule.tsapps/web/utils/email/microsoft.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/email/types.tsapps/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/folders/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/folders/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/folders/route.ts🧠 Learnings (4)
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/server-actions.mdc:0-0 Timestamp: 2025-07-18T17:27:58.249Z Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.Applied to files:
apps/web/utils/actions/rule.ts📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR PR: elie222/inbox-zero#0 File: apps/web/CLAUDE.md:0-0 Timestamp: 2025-07-18T15:04:30.467Z Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidationApplied to files:
apps/web/utils/actions/rule.ts📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/gmail-api.mdc:0-0 Timestamp: 2025-07-18T15:05:34.899Z Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')Applied to files:
apps/web/utils/email/google.ts📚 Learning: 2025-06-23T12:26:53.882Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/prisma.mdc:0-0 Timestamp: 2025-06-23T12:26:53.882Z Learning: In this project, Prisma should be imported using 'import prisma from "@/utils/prisma";' in TypeScript files.Applied to files:
apps/web/utils/rule/rule.ts🧬 Code graph analysis (8)
apps/web/app/(app)/accounts/page.tsx (1)
apps/web/components/ui/card.tsx (1)
CardContent(143-143)apps/web/hooks/useFolders.ts (1)
apps/web/app/api/user/folders/route.ts (1)
GetFoldersResponse(6-6)apps/web/utils/actions/rule.ts (8)
apps/web/utils/actions/rule.validation.ts (2)
toggleRuleBody(212-220)CategoryAction(192-192)apps/web/utils/rule/rule.ts (3)
createRule(190-252)deleteRule(329-345)safeCreateRule(43-137)apps/web/utils/rule/consts.ts (3)
getRuleConfig(112-116)getCategoryAction(126-134)getSystemRuleActionTypes(277-308)apps/web/utils/prisma-helpers.ts (1)
isDuplicateError(3-12)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/label/resolve-label.ts (1)
resolveLabelNameAndId(13-65)apps/web/utils/action-item.ts (1)
sanitizeActionFields(201-287)apps/web/utils/logger.ts (1)
Logger(5-5)apps/web/app/api/user/folders/route.ts (5)
apps/web/utils/email/google.ts (1)
getFolders(910-913)apps/web/utils/email/microsoft.ts (1)
getFolders(1288-1290)apps/web/utils/middleware.ts (1)
withEmailProvider(300-304)apps/web/utils/email/provider-types.ts (1)
isMicrosoftProvider(5-7)apps/web/utils/email/types.ts (1)
EmailProvider(44-230)apps/web/utils/email/google.ts (2)
apps/web/utils/gmail/label.ts (3)
labelMessage(129-145)GmailLabel(20-34)labelThread(36-61)apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/rule/rule.ts (3)
apps/web/utils/ai/rule/create-rule-schema.ts (1)
CreateOrUpdateRuleSchemaWithCategories(163-166)apps/web/utils/email/provider-types.ts (1)
isMicrosoftProvider(5-7)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/email/microsoft.ts (4)
apps/web/utils/outlook/label.ts (1)
labelMessage(194-206)apps/web/utils/outlook/odata-escape.ts (1)
escapeODataString(13-20)apps/web/utils/types.ts (1)
ParsedMessage(55-70)apps/web/utils/outlook/folders.ts (1)
getOutlookFolderTree(83-118)apps/web/utils/email/types.ts (3)
apps/web/utils/outlook/folders.ts (1)
OutlookFolder(11-15)apps/web/utils/label.ts (1)
InboxZeroLabel(63-63)apps/web/utils/types.ts (1)
ParsedMessage(55-70)⏰ 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). (4)
- GitHub Check: Static Code Analysis Js
- GitHub Check: Software Component Analysis Js
- GitHub Check: Jit Security
- GitHub Check: test
🔇 Additional comments (5)
apps/web/app/(app)/accounts/page.tsx (1)
92-92: LGTM! Good responsive design improvement.Adding
flex-wrapallows the action buttons to wrap onto multiple lines on smaller viewports, preventing overflow or compression. This is a sensible defensive measure for responsive layouts.apps/web/app/api/user/folders/route.ts (2)
8-20: LGTM! Provider validation is appropriate.The Microsoft-only provider check is correct, as Gmail's
getFolders()returns an empty array with a warning (seeapps/web/utils/email/google.ts:909-912). The 400 status code is appropriate for this client error.
2-4: withEmailProvider correctly handles authentication and scoping. Verified it invokesemailAccountMiddlewareand enforcesuserId/emailAccountIdownership via Prisma—no changes required.apps/web/components/FolderSelector.tsx (2)
221-234: Consistent conditional logic for the Clear button.The condition now uses
value.idinstead ofselectedFolder?.displayName, which is consistent with the main display logic at line 211. This allows users to clear the selection even if the folder is not currently in the folders list, improving the user experience in edge cases.
211-218: Approve fallback logic forvalue.id
Updating the condition to checkvalue.idensures that when a folder ID is set but not found infolders, the component displaysvalue.name(last-known name) instead of the placeholder, improving resilience. No changes required.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/actions/rule.ts (1)
248-275: Skip creating a new rule when disabling drafts without an existing rule. toggleRule unconditionally falls back to creating a system rule if none exists, even whenenabledisfalse; add a guard to return early when disabling a non-existent TO_REPLY rule. [apps/web/utils/actions/rule.ts:515–632]
♻️ Duplicate comments (1)
apps/web/utils/actions/rule.ts (1)
557-632: Return early when disabling non-existent system rule.When
enabledisfalseand no existing rule is found, the function should return early instead of creating a new rule. Currently, it proceeds to create a rule even when the intent is to disable it.Apply this diff:
if (existingRule) { return await prisma.rule.update({ where: { id: existingRule.id }, data: { enabled }, include: { actions: true }, }); } + if (!enabled) { + // Don't create a new rule when trying to disable a non-existent one + return null; + } + const emailProvider = await createEmailProvider({ emailAccountId, provider, });
🧹 Nitpick comments (1)
apps/web/utils/actions/rule.ts (1)
784-818: Consider avoiding unnecessary label action creation.The function always creates a label action first (lines 784-786), even for
move_folderandmove_folder_delayedcases where it's immediately replaced (line 805). While not a bug, you could optimize by checking the category action type first.const { label: labelName, labelId } = await resolveLabelNameAndId({ emailProvider, label, labelId: null, }); - let actions: Prisma.ActionCreateManyRuleInput[] = [ - { type: ActionType.LABEL, label: labelName, labelId }, - ]; + let actions: Prisma.ActionCreateManyRuleInput[] = []; switch (categoryAction) { case "label_archive": case "label_archive_delayed": { + actions.push({ type: ActionType.LABEL, label: labelName, labelId }); actions.push({ type: ActionType.ARCHIVE, delayInMinutes: categoryAction === "label_archive_delayed" ? ONE_WEEK_MINUTES : undefined, }); break; } + case "label": { + actions.push({ type: ActionType.LABEL, label: labelName, labelId }); + break; + } case "move_folder": case "move_folder_delayed": { const folderId = await emailProvider.getOrCreateOutlookFolderIdByName( rule.name, ); actions = [ { type: ActionType.MOVE_FOLDER, folderId, folderName: rule.name, delayInMinutes: categoryAction === "move_folder_delayed" ? ONE_WEEK_MINUTES : undefined, }, ]; break; } + default: { + // Default case: just label + actions.push({ type: ActionType.LABEL, label: labelName, labelId }); + break; + } }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/utils/actions/rule.ts(14 hunks)apps/web/utils/reply-tracker/enable.ts(0 hunks)
💤 Files with no reviewable changes (1)
- apps/web/utils/reply-tracker/enable.ts
🧰 Additional context used
📓 Path-based instructions (10)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/actions/rule.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/rule.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/actions/rule.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/actions/rule.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/actions/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/rule.ts
apps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logic
Files:
apps/web/utils/actions/rule.ts
apps/web/utils/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size
Files:
apps/web/utils/actions/rule.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/actions/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/utils/actions/rule.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.
Applied to files:
apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation
Applied to files:
apps/web/utils/actions/rule.ts
🧬 Code graph analysis (1)
apps/web/utils/actions/rule.ts (8)
apps/web/utils/actions/rule.validation.ts (3)
CategoryConfig(205-205)toggleRuleBody(212-220)CategoryAction(192-192)apps/web/utils/rule/rule.ts (3)
createRule(190-252)deleteRule(329-345)safeCreateRule(43-137)apps/web/utils/rule/consts.ts (3)
getRuleConfig(112-116)getCategoryAction(126-134)getSystemRuleActionTypes(277-308)apps/web/utils/actions/safe-action.ts (1)
actionClient(62-113)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/label/resolve-label.ts (1)
resolveLabelNameAndId(13-65)apps/web/utils/action-item.ts (1)
sanitizeActionFields(201-287)apps/web/utils/logger.ts (1)
Logger(5-5)
⏰ 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: Jit Security
- GitHub Check: test
🔇 Additional comments (8)
apps/web/utils/actions/rule.ts (8)
1-41: LGTM!The new imports are correctly added to support the refactored rule management logic, including config-driven rule creation, centralized error handling, and the new toggleRule functionality.
73-73: LGTM!Good refactor to centralize action field sanitization through
mapActionToSanitizedFields. This improves consistency and reduces code duplication.Also applies to: 183-197
104-104: LGTM!Good refactor to centralize error handling. The
handleRuleErrorfunction provides consistent error messages for duplicate name and group ID conflicts.Also applies to: 216-216, 668-679
307-495: LGTM!Excellent refactor to use a config-driven approach. The code is more maintainable by:
- Using
Mapfor system category lookups- Iterating over
systemRulesarray instead of explicit per-type logic- Leveraging
getActionsFromCategoryActionto generate actions consistently
497-513: LGTM!Clean action wrapper that follows the established pattern for other actions in this file.
635-666: LGTM!Clean helper function that extracts nested values from action objects and delegates to
sanitizeActionFieldsfor consistent field mapping.
681-694: LGTM!Simple helper that reduces code duplication when creating action fields.
735-750: LGTM!Good addition to support
MOVE_FOLDERactions. The folder resolution logic follows the same pattern as label resolution.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/actions/rule.ts (1)
248-278: Potential null reference iftoggleRulereturns undefined.The
toggleRulefunction can theoretically returnundefinedbased on its implementation (see lines 515-633). If toggling fails or if the rule doesn't exist when disabling,rulecould be undefined, leading to a runtime error when accessingrule.actionsorrule.id.Apply this diff to add null checks:
const rule = await toggleRule({ emailAccountId, enabled: enable, systemType: SystemType.TO_REPLY, provider, ruleId: undefined, }); + if (!rule) { + throw new SafeError("Failed to toggle reply tracking rule"); + } + if (enable) { const alreadyDraftingReplies = rule?.actions?.find( (a) => a.type === ActionType.DRAFT_EMAIL, );
♻️ Duplicate comments (1)
apps/web/utils/webhook/validate-webhook-account.ts (1)
95-98: Critical: Provider can be null, causing runtime errors.When
emailAccount.account?.provideris null or undefined,createEmailProviderwill receive an invalid value and throw an error, since itsproviderparameter is typed asstring(non-nullable). This matches the concern raised in the previous review about provider fallback inconsistencies.The issue is compounded because provider creation occurs before the premium check, meaning the function will crash early when it should proceed to validation and potentially unwatch emails gracefully.
Apply this diff to add a fallback based on the webhook source:
export async function validateWebhookAccount( emailAccount: ValidatedWebhookAccountData | null, + providerFallback: "google" | "microsoft", logger: { error: (message: string, context?: Record<string, unknown>) => void; info: (message: string, context?: Record<string, unknown>) => void; trace: (message: string, context?: Record<string, unknown>) => void; }, ): Promise<ValidationResult> { if (!emailAccount) { logger.error("Account not found"); return { success: false, response: NextResponse.json({ ok: true }) }; } const premium = isPremium( emailAccount.user.premium?.lemonSqueezyRenewsAt || null, emailAccount.user.premium?.stripeSubscriptionStatus || null, ) ? emailAccount.user.premium : undefined; const provider = await createEmailProvider({ emailAccountId: emailAccount.id, - provider: emailAccount.account?.provider, + provider: emailAccount.account?.provider ?? providerFallback, });Then update the callers to pass the appropriate fallback:
// In apps/web/app/api/google/webhook/process-history.ts const validation = await validateWebhookAccount(emailAccount, "google", logger); // In apps/web/app/api/outlook/webhook/process-history.ts const validation = await validateWebhookAccount(emailAccount, "microsoft", logger);
🧹 Nitpick comments (5)
apps/web/utils/webhook/validate-webhook-account.ts (1)
95-98: Add error handling for provider creation and cleanup operations.The function lacks error handling around
createEmailProviderandunwatchEmails, both of which can throw exceptions. Unhandled errors will return a 500 response to the webhook caller, potentially causing retries and cascading failures.Consider wrapping these operations in try-catch blocks:
+ let provider: Awaited<ReturnType<typeof createEmailProvider>>; + try { + provider = await createEmailProvider({ + emailAccountId: emailAccount.id, + provider: emailAccount.account?.provider ?? providerFallback, + }); + } catch (error) { + logger.error("Failed to create email provider", { + email: emailAccount.email, + error: error instanceof Error ? error.message : String(error), + }); + return { success: false, response: NextResponse.json({ ok: true }) }; + } if (!premium) { logger.info("Account not premium", { /* ... */ }); - await unwatchEmails({ - emailAccountId: emailAccount.id, - provider, - subscriptionId: emailAccount.watchEmailsSubscriptionId, - }); + try { + await unwatchEmails({ + emailAccountId: emailAccount.id, + provider, + subscriptionId: emailAccount.watchEmailsSubscriptionId, + }); + } catch (error) { + logger.error("Failed to unwatch emails", { + email: emailAccount.email, + error: error instanceof Error ? error.message : String(error), + }); + // Continue - best effort cleanup + } return { success: false, response: NextResponse.json({ ok: true }) }; }Apply similar wrapping for the AI access check unwatchEmails call.
Also applies to: 108-112, 123-127
apps/web/utils/ai/reply/determine-thread-status.ts (3)
10-18: Add input validation for robustness.The function should validate that
threadMessagesis not empty to prevent errors downstream and provide clearer error messages.As per coding guidelines, add early validation:
export async function aiDetermineThreadStatus({ emailAccount, threadMessages, modelType, }: { emailAccount: EmailAccountWithAI; threadMessages: EmailForLLM[]; modelType: ModelType; }): Promise<{ status: ConversationStatus; rationale: string }> { + if (threadMessages.length === 0) { + logger.warn("Empty thread messages provided"); + throw new Error("Thread messages cannot be empty"); + } + const system = `You are an AI assistant that analyzes email threads to determine their current status.
60-71: Clarify message ordering responsibility.The comment at Line 62 states "most recent message first" but the function doesn't verify or enforce this ordering. If the caller must ensure this order, consider documenting it in the function's JSDoc or parameter description.
Add a JSDoc comment to document the ordering requirement:
+/** + * Determines the status of an email thread using AI analysis. + * @param emailAccount - User's email account with AI configuration + * @param threadMessages - Email messages in the thread (ordered with most recent first) + * @param modelType - Type of AI model to use + * @returns The determined status and rationale + */ export async function aiDetermineThreadStatus({
81-89: Consider type consistency between schema and return type.The Zod schema uses
SystemTypeenum values (Lines 82-87) but the function's return type usesConversationStatusstring union (Line 18). While these may be compatible ifSystemTypeis a string enum, this inconsistency could lead to type safety issues.For better type safety and maintainability, align the schema with the return type:
const schema = z.object({ status: z.enum([ - SystemType.TO_REPLY, - SystemType.FYI, - SystemType.AWAITING_REPLY, - SystemType.ACTIONED, + "TO_REPLY", + "FYI", + "AWAITING_REPLY", + "ACTIONED", - ]), + ] as const), rationale: z.string(), });Alternatively, if
SystemTypeshould be used throughout, update the return type andConversationStatustype to useSystemTypeinstead of a string union.apps/web/utils/actions/rule.ts (1)
756-829: Consider a more precise type for theruleparameter.The
ruleparameter is typed asRulefrom Prisma, but the function only accessesrule.name. IncreateRulesOnboardingAction, this is called with mock objects like{ name: customCategory.name } as Rule, which is a type assertion that bypasses type safety.Consider defining a narrower interface:
+interface RuleNameOnly { + name: string; +} + async function getActionsFromCategoryAction({ emailAccountId, rule, categoryAction, label, draftReply, hasDigest, provider, }: { emailAccountId: string; - rule: Rule; + rule: RuleNameOnly; categoryAction: CategoryAction; label: string; hasDigest: boolean; draftReply: boolean; provider: string; }): Promise<Prisma.ActionCreateManyRuleInput[]> {This makes the function's requirements explicit and eliminates the need for type assertions at call sites.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/utils/actions/rule.ts(14 hunks)apps/web/utils/ai/reply/determine-thread-status.ts(1 hunks)apps/web/utils/reply-tracker/enable.ts(0 hunks)apps/web/utils/reply-tracker/handle-conversation-status.ts(1 hunks)apps/web/utils/webhook/validate-webhook-account.ts(1 hunks)
💤 Files with no reviewable changes (1)
- apps/web/utils/reply-tracker/enable.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/utils/reply-tracker/handle-conversation-status.ts
🧰 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/utils/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.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/webhook/validate-webhook-account.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/reply/determine-thread-status.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin server actions to invalidate cache after mutations.
Files:
apps/web/utils/actions/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/rule.ts
apps/web/utils/ai/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features
Files:
apps/web/utils/ai/reply/determine-thread-status.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/reply/determine-thread-status.ts
🧠 Learnings (2)
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `SafeError` for expected/handled errors within actions if needed. `next-safe-action` provides centralized error handling.
Applied to files:
apps/web/utils/actions/rule.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `revalidatePath` in server actions for cache invalidation
Applied to files:
apps/web/utils/actions/rule.ts
🧬 Code graph analysis (3)
apps/web/utils/webhook/validate-webhook-account.ts (3)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/premium/index.ts (2)
isPremium(14-22)hasAiAccess(87-101)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)
apps/web/utils/actions/rule.ts (10)
apps/web/utils/actions/rule.validation.ts (2)
toggleRuleBody(212-220)CategoryAction(192-192)apps/web/utils/rule/rule.ts (3)
createRule(190-252)deleteRule(329-345)safeCreateRule(43-137)apps/web/utils/rule/consts.ts (3)
getRuleConfig(112-116)getCategoryAction(126-134)getSystemRuleActionTypes(277-308)apps/web/utils/prisma-helpers.ts (1)
isDuplicateError(3-12)apps/web/utils/actions/safe-action.ts (1)
actionClient(62-113)apps/web/utils/error.ts (1)
SafeError(86-96)apps/web/utils/email/provider.ts (1)
createEmailProvider(13-29)apps/web/utils/label/resolve-label.ts (1)
resolveLabelNameAndId(13-65)apps/web/utils/action-item.ts (1)
sanitizeActionFields(201-287)apps/web/utils/logger.ts (1)
Logger(5-5)
apps/web/utils/ai/reply/determine-thread-status.ts (6)
apps/web/utils/llms/types.ts (1)
EmailAccountWithAI(10-29)apps/web/utils/types.ts (1)
EmailForLLM(117-131)apps/web/utils/llms/model.ts (1)
ModelType(17-17)apps/web/utils/reply-tracker/conversation-status-config.ts (1)
ConversationStatus(10-14)apps/web/utils/ai/helpers.ts (2)
getUserInfoPrompt(9-35)getEmailListPrompt(54-68)apps/web/utils/llms/index.ts (1)
createGenerateObject(119-180)
⏰ 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 (7)
apps/web/utils/webhook/validate-webhook-account.ts (1)
108-112: Verify the selective unwatchEmails pattern is intentional.The function calls
unwatchEmailswhen premium or AI access is missing but skips it when automation rules are absent or tokens are invalid. This inconsistency might be intentional (e.g., rules and tokens are temporary conditions that could self-resolve), but it should be verified.If this pattern is intentional, consider adding comments to clarify why cleanup is selective:
// No need to unwatch - user may add rules later if (!hasAutomationRules) { logger.trace("Has no rules enabled", { email: emailAccount.email }); return { success: false, response: NextResponse.json({ ok: true }) }; } // No need to unwatch - tokens may be refreshed via OAuth flow if (!emailAccount.account?.access_token || !emailAccount.account?.refresh_token) { logger.error("Missing access or refresh token", { email: emailAccount.email }); return { success: false, response: NextResponse.json({ ok: true }) }; }Otherwise, consider unwatching in all failure cases for consistency.
Also applies to: 123-127, 132-135, 137-145
apps/web/utils/ai/reply/determine-thread-status.ts (1)
19-58: LGTM! Well-structured system prompt.The system prompt is comprehensive and well-designed with:
- Clear status definitions with concrete examples
- Critical rules section to prevent common classification mistakes
- Emphasis on checking the entire thread, not just the latest message
- Explicit JSON response format
This thoughtful prompt structure should help the LLM make accurate status determinations.
apps/web/utils/actions/rule.ts (5)
437-454: LGTM! System rules loop is correctly implemented.The systemType-driven loop correctly uses UPPER_SNAKE_CASE enum values and properly handles creation/deletion based on configuration.
497-513: LGTM! Action correctly delegates to internal toggleRule.The action properly validates inputs via
toggleRuleBodyschema and delegates to the internaltoggleRulefunction. Error handling is managed by thenext-safe-actionframework.
635-666: LGTM! Helper correctly maps nested action fields.The
mapActionToSanitizedFieldshelper properly extracts nested field values and delegates tosanitizeActionFieldsfor consistent field normalization.
668-694: LGTM! Error handling and field creation helpers are well-designed.Both
handleRuleErrorandcreateEmptyActionFieldsare clean, focused utility functions:
handleRuleErrorproperly usesSafeErrorfor consistent error handlingcreateEmptyActionFieldsprovides a convenient way to create empty action field objects with optional overrides
696-754: LGTM! Folder resolution follows the same pattern as label resolution.The added
MOVE_FOLDERhandling correctly resolves folder IDs using the email provider, mirroring the existing label resolution logic. The type definitions are consistent with the existing pattern.
| import { z } from "zod"; | ||
| import { createGenerateObject } from "@/utils/llms"; | ||
| import type { EmailAccountWithAI } from "@/utils/llms/types"; | ||
| import type { EmailForLLM } from "@/utils/types"; | ||
| import { getModel, type ModelType } from "@/utils/llms/model"; | ||
| import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers"; | ||
| import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config"; | ||
| import { SystemType } from "@prisma/client"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add scoped logger for observability.
Per coding guidelines, backend TypeScript files should use createScopedLogger, and AI feature functions should log inputs and outputs with appropriate log levels.
As per coding guidelines, add a scoped logger at the top of the file:
+import { createScopedLogger } from "@/utils/logger";
import { z } from "zod";
import { createGenerateObject } from "@/utils/llms";
import type { EmailAccountWithAI } from "@/utils/llms/types";
import type { EmailForLLM } from "@/utils/types";
import { getModel, type ModelType } from "@/utils/llms/model";
import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers";
import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config";
import { SystemType } from "@prisma/client";
+
+const logger = createScopedLogger("aiDetermineThreadStatus");Then add logging at key points within the function (see subsequent comments).
📝 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 { z } from "zod"; | |
| import { createGenerateObject } from "@/utils/llms"; | |
| import type { EmailAccountWithAI } from "@/utils/llms/types"; | |
| import type { EmailForLLM } from "@/utils/types"; | |
| import { getModel, type ModelType } from "@/utils/llms/model"; | |
| import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers"; | |
| import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config"; | |
| import { SystemType } from "@prisma/client"; | |
| import { createScopedLogger } from "@/utils/logger"; | |
| import { z } from "zod"; | |
| import { createGenerateObject } from "@/utils/llms"; | |
| import type { EmailAccountWithAI } from "@/utils/llms/types"; | |
| import type { EmailForLLM } from "@/utils/types"; | |
| import { getModel, type ModelType } from "@/utils/llms/model"; | |
| import { getUserInfoPrompt, getEmailListPrompt } from "@/utils/ai/helpers"; | |
| import type { ConversationStatus } from "@/utils/reply-tracker/conversation-status-config"; | |
| import { SystemType } from "@prisma/client"; | |
| const logger = createScopedLogger("aiDetermineThreadStatus"); |
🤖 Prompt for AI Agents
In apps/web/utils/ai/reply/determine-thread-status.ts around lines 1 to 8, add a
scoped logger and instrumentation: import createScopedLogger from the shared
logging utility, create a logger instance for this module (e.g., const logger =
createScopedLogger("ai:determine-thread-status")), then instrument the main
function(s) to log incoming inputs at debug/info when the function is entered,
log key decision points and constructed prompts/models at debug, log the final
returned thread status at info, and log any caught errors at error with the
error object; keep logs concise and avoid sensitive data.
Add FYI and Actioned statuses
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Tests & Docs
Removals