Refactor Outlook folder functionality#678
Conversation
WalkthroughAdds end-to-end folderId support: DB schema + migration, folder-fetch API and Outlook folder utilities, frontend folder selector components and hook, folderId threaded through rule/action validation, serialization, persistence, and Microsoft provider move/archive logic. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant RuleForm
participant useFolders
participant API as "/api/user/folders"
participant Server as folders route
participant Outlook as getOutlookFolders
participant Graph as Microsoft Graph
User->>RuleForm: open MOVE_FOLDER action editor
RuleForm->>useFolders: load folders
useFolders->>API: GET /api/user/folders
API->>Server: withEmailAccount -> getFolders
Server->>Outlook: getOutlookFolders()
Outlook->>Graph: fetch /me/mailFolders (nested)
Graph-->>Outlook: folders tree
Outlook-->>Server: OutlookFolder[]
Server-->>API: JSON folders
API-->>useFolders: folders
useFolders-->>RuleForm: folders, isLoading=false
User->>RuleForm: select folder
RuleForm-->>RuleForm: set action.folderId + action.folderName
sequenceDiagram
participant Runner as Action runner
participant OutlookProv as OutlookProvider
participant LabelUtil as archiveThread
participant Graph as Microsoft Graph
Runner->>OutlookProv: moveThreadToFolder(threadId, ownerEmail, folderId)
OutlookProv->>LabelUtil: archiveThread({threadId, folderId})
LabelUtil->>Graph: move messages destinationId=folderId
Graph-->>LabelUtil: Success/Failure
LabelUtil-->>OutlookProv: result
OutlookProv-->>Runner: result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
@edulelis is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Actionable comments posted: 7
🔭 Outside diff range comments (3)
apps/web/utils/scheduled-actions/scheduler.ts (1)
140-155: Remove non-null assertion and fix inferred any[] type for scheduledActions
- actionItem.delayInMinutes! violates the “don’t use non-null assertions” guideline.
- scheduledActions is inferred as any[].
Apply this diff in the loop to avoid the non-null assertion and keep types tight:
- const scheduledActions = []; + const scheduledActions: ScheduledAction[] = []; for (const actionItem of delayedActions) { - const scheduledFor = addMinutes(new Date(), actionItem.delayInMinutes!); + if (actionItem.delayInMinutes == null) { + // Should be unreachable due to the filter above, but narrows the type safely + continue; + } + const scheduledFor = addMinutes(new Date(), actionItem.delayInMinutes);And add the missing type import at the top of the file:
// near the other imports import { ScheduledActionStatus, type ScheduledAction } from "@prisma/client";apps/web/utils/ai/actions.ts (1)
270-277: Guard against missing folderId and tighten the action args typeThe provider call will receive undefined if validation upstream ever regresses. Add a lightweight guard and avoid any.
-const move_folder: ActionFunction<any> = async ({ +const move_folder: ActionFunction<{ folderId?: string | null }> = async ({ client, email, userEmail, args, }) => { - await client.moveThreadToFolder(email.threadId, userEmail, args.folderId); + if (!args.folderId) { + logger.warn("MOVE_FOLDER skipped: missing folderId", { + threadId: email.threadId, + }); + return; + } + await client.moveThreadToFolder(email.threadId, userEmail, args.folderId); };This keeps runtime safe even if upstream Zod validation changes.
apps/web/utils/actions/rule.ts (1)
225-241: Scope the action update to the current rule to prevent cross-rule updatesUsing update with where: { id: a.id } isn’t scoped to the current rule. A crafted payload could update an action not belonging to this rule/email account. Prefer updateMany with a guarded where clause including ruleId to ensure only actions of this rule are modified.
Apply this diff within the selected range to guard updates by ruleId:
- ...actionsToUpdate.map((a) => { - return prisma.action.update({ - where: { id: a.id }, - data: sanitizeActionFields({ + ...actionsToUpdate.map((a) => { + return prisma.action.updateMany({ + where: { id: a.id, ruleId: id }, + data: sanitizeActionFields({ type: a.type, label: a.label?.value, subject: a.subject?.value, content: a.content?.value, to: a.to?.value, cc: a.cc?.value, bcc: a.bcc?.value, url: a.url?.value, folderName: a.folderName?.value, - folderId: a.folderId?.value, + folderId: a.folderId?.value, delayInMinutes: a.delayInMinutes, }), }); }),Additionally, tighten actionsToUpdate to only include actions that belong to currentRule (outside selected lines):
const actionsToUpdate = actions.filter( (a) => a.id && currentActions.some((ca) => ca.id === a.id), );Also applies to: 254-262
🧹 Nitpick comments (10)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (1)
137-139: Adding folderId: null to action objects is correct; update ActionBadges prop type for consistencyPropagating folderId in these action shapes aligns with the repo-wide folderId introduction and prevents shape mismatches. One follow-up: ActionBadges' prop type doesn't include folderId, which can hurt type safety and DX (especially if getActionDisplay starts leveraging folderId for MOVE_FOLDER).
Update ActionBadges' actions type to include folderId (outside the selected lines):
export function ActionBadges({ actions, }: { actions: { id: string; type: ActionType; label?: string | null; folderName?: string | null; folderId?: string | null; // add this }[]; }) { // ... }Also applies to: 155-157, 174-176
apps/web/prisma/schema.prisma (1)
351-352: LGTM; consider adding indexes if you’ll query by folderId at scaleAdding folderId as optional on Action, ExecutedAction, and ScheduledAction matches the PR’s objectives. If any hot paths will query/filter by folderId (especially in ExecutedAction/ScheduledAction flows), consider adding composite indexes with emailAccountId to keep lookups fast.
If needed, augment the schema with indexes like:
model ExecutedAction { // ... folderId String? // ... @@index([folderId, executedRuleId]) } model ScheduledAction { // ... folderId String? // ... @@index([emailAccountId, folderId, status]) }Also applies to: 428-429, 459-460
apps/web/prisma/migrations/20250812223533_add_folder_id/migration.sql (1)
2-8: Migration is safe and minimal; add indexes in schema if neededAdding nullable TEXT columns is a fast, lock-light operation in Postgres. If you decide to index folderId (per schema suggestion), declare indexes in schema.prisma and re-migrate so Prisma manages them.
apps/web/utils/email/microsoft.ts (1)
170-181: Consider consolidating duplicate archiving logic.Both
archiveThreadandarchiveThreadWithLabelcontain identical logic except for theactionSourceparameter. Consider refactoring to reduce duplication.Apply this diff to consolidate the archiving functions:
- async archiveThread(threadId: string, ownerEmail: string): Promise<void> { - await outlookArchiveThread({ - client: this.client, - threadId, - ownerEmail, - actionSource: "automation", - folderId: "archive", - }); - } - - async archiveThreadWithLabel( - threadId: string, - ownerEmail: string, - ): Promise<void> { - await outlookArchiveThread({ - client: this.client, - threadId, - ownerEmail, - actionSource: "user", - folderId: "archive", - }); - } + async archiveThread(threadId: string, ownerEmail: string): Promise<void> { + return this._archiveWithSource(threadId, ownerEmail, "automation"); + } + + async archiveThreadWithLabel( + threadId: string, + ownerEmail: string, + ): Promise<void> { + return this._archiveWithSource(threadId, ownerEmail, "user"); + } + + private async _archiveWithSource( + threadId: string, + ownerEmail: string, + actionSource: "user" | "automation", + ): Promise<void> { + await outlookArchiveThread({ + client: this.client, + threadId, + ownerEmail, + actionSource, + folderId: "archive", + }); + }apps/web/app/api/user/folders/route.ts (1)
35-37: Consider null safety for expires_at conversion.The conversion to seconds could be simplified and made safer by handling the null case in the ternary operator itself.
Apply this diff to simplify the conversion:
- expiresAt: emailAccount.account.expires_at - ? Math.floor(emailAccount.account.expires_at.getTime() / 1000) - : null, + expiresAt: emailAccount.account.expires_at?.getTime() + ? Math.floor(emailAccount.account.expires_at.getTime() / 1000) + : null,apps/web/utils/action-item.ts (1)
147-154: Consider updating the UI label for folder selection.The label "Folder name" for the MOVE_FOLDER action might be misleading since users will now be selecting from a list rather than typing a folder name. Consider updating it to better reflect the new functionality.
Apply this diff to update the label:
[ActionType.MOVE_FOLDER]: { fields: [ { name: "folderName", - label: "Folder name", + label: "Folder", }, ], },apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
1196-1207: Consider validating folder selection immediately.When a folder is selected or cleared, consider triggering validation immediately to provide instant feedback to the user.
Apply this diff to add immediate validation:
onChangeValue={(folderData) => { if (folderData.name && folderData.id) { setValue(`actions.${index}.folderName`, { value: folderData.name, }); setValue(`actions.${index}.folderId`, { value: folderData.id, }); } else { setValue(`actions.${index}.folderName`, undefined); setValue(`actions.${index}.folderId`, undefined); } + trigger(`actions.${index}`); }}apps/web/components/FolderCombobox.tsx (1)
44-96: Consider handling edge cases and potential improvementsThe component implementation looks clean, but there are a few areas that could be improved:
- The component doesn't handle keyboard navigation properly when selecting folders
- The search filter implementation is case-insensitive but doesn't handle special characters that might cause issues
- The width of the popover content might not match the trigger width on all screen sizes
Consider these improvements:
export function FolderCombobox({ folders, isLoading, value, onChangeValue, placeholder = "Select a folder...", }: FolderComboboxProps) { const [open, setOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const selectedFolder = folders.find((folder) => folder.id === value); + // Escape special regex characters in search query + const escapeRegex = (str: string) => + str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const filteredFolders = folders.filter((folder) => - folder.displayName.toLowerCase().includes(searchQuery.toLowerCase()), + folder.displayName.toLowerCase().includes(escapeRegex(searchQuery.toLowerCase())), ); return ( <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button variant="outline" role="combobox" aria-expanded={open} + aria-haspopup="listbox" + aria-controls="folder-listbox" className="w-full justify-between" disabled={isLoading} > {selectedFolder ? selectedFolder.displayName : placeholder} <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> </Button> </PopoverTrigger> - <PopoverContent className="w-full p-0"> + <PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0"> <Command> <div className="flex items-center border-b px-3"> <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" /> <CommandInput placeholder="Search..." value={searchQuery} onValueChange={setSearchQuery} className="border-0 focus:ring-0" /> </div> - <CommandList> + <CommandList id="folder-listbox" role="listbox"> <CommandEmpty>No folder found.</CommandEmpty> <CommandGroup> {filteredFolders.map((folder) => ( <CommandItem key={folder.id} value={folder.id} + aria-selected={value === folder.id} onSelect={(currentValue) => { onChangeValue(currentValue === value ? "" : currentValue); setOpen(false); }} >apps/web/components/FolderSelector.tsx (1)
203-217: Optimize conditional rendering logicThe conditional rendering logic for the button content is complex and could be simplified. The current implementation checks
selectedFolder?.displayNametwice unnecessarily.Simplify the conditional rendering:
<div className="flex items-center gap-2 flex-1"> {isLoading ? ( <> <Loader2 className="h-4 w-4 animate-spin" /> <span>Loading folders...</span> </> - ) : selectedFolder?.displayName ? ( + ) : selectedFolder ? ( <div className="flex items-center gap-2"> <FolderIcon className="h-4 w-4" /> <span>{value.name || selectedFolder?.displayName || ""}</span> </div> ) : ( placeholder )} </div> <div className="flex items-center gap-1"> - {selectedFolder?.displayName && !isLoading && ( + {selectedFolder && !isLoading && ( <Button variant="ghost" size="sm" className="h-6 w-6 p-0 hover:bg-muted" onClick={(e) => { e.stopPropagation(); onChangeValue({ name: "", id: "" }); }} title="Clear folder selection" > <X className="h-3 w-3" /> </Button> )} <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" /> </div>apps/web/utils/outlook/label.ts (1)
319-327: Inconsistent error message formattingThe error logging on lines 320-326 has inconsistent indentation in the multi-line string literal, which could make logs harder to read.
Fix the formatting:
if (publishResult.status === "rejected") { - logger.error( - `Failed to publish action to move thread to folder ${folderId}`, - { - threadId, - error: publishResult.reason, - }, - ); + logger.error(`Failed to publish action to move thread to folder ${folderId}`, { + threadId, + error: publishResult.reason, + }); }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(8 hunks)apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx(3 hunks)apps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsx(1 hunks)apps/web/app/api/user/folders/route.ts(1 hunks)apps/web/app/api/user/rules/[id]/route.ts(1 hunks)apps/web/components/FolderCombobox.tsx(1 hunks)apps/web/components/FolderSelector.tsx(1 hunks)apps/web/hooks/useFolders.ts(1 hunks)apps/web/prisma/migrations/20250812223533_add_folder_id/migration.sql(1 hunks)apps/web/prisma/schema.prisma(3 hunks)apps/web/utils/action-item.ts(6 hunks)apps/web/utils/actions/rule.ts(4 hunks)apps/web/utils/actions/rule.validation.ts(2 hunks)apps/web/utils/ai/actions.ts(1 hunks)apps/web/utils/ai/types.ts(1 hunks)apps/web/utils/email/microsoft.ts(3 hunks)apps/web/utils/outlook/folders.ts(1 hunks)apps/web/utils/outlook/label.ts(8 hunks)apps/web/utils/outlook/message.ts(0 hunks)apps/web/utils/scheduled-actions/scheduler.ts(1 hunks)
💤 Files with no reviewable changes (1)
- apps/web/utils/outlook/message.ts
🧰 Additional context used
📓 Path-based instructions (31)
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/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/hooks/useFolders.tsapps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/components/FolderCombobox.tsxapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/components/FolderSelector.tsxapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/utils/outlook/label.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.ts
!{.cursor/rules/*.mdc}
📄 CodeRabbit Inference Engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/prisma/schema.prismaapps/web/hooks/useFolders.tsapps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/components/FolderCombobox.tsxapps/web/prisma/migrations/20250812223533_add_folder_id/migration.sqlapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/components/FolderSelector.tsxapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/utils/outlook/label.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.ts
**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)
**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod
Files:
apps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/hooks/useFolders.tsapps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.ts
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/hooks/useFolders.tsapps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/components/FolderCombobox.tsxapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/components/FolderSelector.tsxapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/utils/outlook/label.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.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/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.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/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/utils/outlook/label.tsapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/hooks/useFolders.tsapps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/components/FolderCombobox.tsxapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/components/FolderSelector.tsxapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/utils/outlook/label.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.ts
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/ai/types.tsapps/web/prisma/schema.prismaapps/web/hooks/useFolders.tsapps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/components/FolderCombobox.tsxapps/web/prisma/migrations/20250812223533_add_folder_id/migration.sqlapps/web/utils/actions/rule.tsapps/web/utils/ai/actions.tsapps/web/components/FolderSelector.tsxapps/web/utils/email/microsoft.tsapps/web/utils/outlook/folders.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/utils/outlook/label.tsapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/utils/actions/rule.validation.tsapps/web/utils/action-item.ts
apps/web/utils/{ai,llms}/**/*
📄 CodeRabbit Inference Engine (.cursor/rules/llm.mdc)
LLM-related code must be organized in the directories: apps/web/utils/ai/, apps/web/utils/llms/, and apps/web/tests/ for LLM-specific tests.
Files:
apps/web/utils/ai/types.tsapps/web/utils/ai/actions.ts
apps/web/utils/{ai,llms}/**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/llm.mdc)
apps/web/utils/{ai,llms}/**/*.ts: Keep system prompts and user prompts separate in LLM-related functions.
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 in LLM-related functions.
Make Zod schemas as specific as possible to guide the LLM output.
Use descriptive scoped loggers for each LLM feature.
Log inputs and outputs with appropriate log levels in LLM-related functions.
Include relevant context in log messages for LLM-related code.
Implement early returns for invalid inputs in LLM-related functions.
Use proper error types and logging in LLM-related code.
Implement fallbacks for AI failures in LLM-related functions.
Add retry logic for transient failures using withRetry in LLM-related functions.
Use XML-like tags to structure data in LLM prompts.
Remove excessive whitespace and truncate long inputs in LLM prompts.
Format data consistently across similar LLM-related functions.
Use TypeScript types for all parameters and return values in LLM-related functions.
Define clear interfaces for complex input/output structures in LLM-related code.
Keep related AI functions in the same file or directory.
Extract common patterns into utility functions in LLM-related code.
Document complex AI logic with clear comments in LLM-related code.
Files:
apps/web/utils/ai/types.tsapps/web/utils/ai/actions.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/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.ts
apps/web/hooks/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
Call
mutate()after successful mutations to refresh data
Files:
apps/web/hooks/useFolders.ts
apps/web/hooks/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/hooks.mdc)
Place custom hooks in the
apps/web/hooks/directory.
Files:
apps/web/hooks/useFolders.ts
apps/web/hooks/use*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/hooks.mdc)
apps/web/hooks/use*.{js,jsx,ts,tsx}: Name custom hooks with theuseprefix (e.g.,useAccounts.ts).
For fetching data from API endpoints in custom hooks, prefer usinguseSWR.
Create dedicated hooks for specific data types (e.g.,useAccounts,useLabels).
Custom hooks should encapsulate reusable stateful logic, especially for data fetching or complex UI interactions.
Keep custom hooks focused on a single responsibility.
Files:
apps/web/hooks/useFolders.ts
apps/web/app/**
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/route.tsapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
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/user/folders/route.tsapps/web/app/api/user/rules/[id]/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/user/rules/[id]/route.ts
apps/web/app/api/**/*.{ts,js}
📄 CodeRabbit Inference Engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.
Files:
apps/web/app/api/user/folders/route.tsapps/web/app/api/user/rules/[id]/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/FolderCombobox.tsxapps/web/components/FolderSelector.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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/FolderCombobox.tsxapps/web/components/FolderSelector.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/FolderCombobox.tsxapps/web/components/FolderSelector.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.
Files:
apps/web/components/FolderCombobox.tsxapps/web/components/FolderSelector.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
**/*.{html,jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...Files:
apps/web/components/FolderCombobox.tsxapps/web/components/FolderSelector.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.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/rule.validation.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/rule.validation.tsapps/web/app/(app)/*/**
📄 CodeRabbit Inference Engine (.cursor/rules/page-structure.mdc)
Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
Files:
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/*/**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/page-structure.mdc)
If you need to use onClick in a component, that component is a client component and file must start with 'use client'
Files:
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/*/**/**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/page-structure.mdc)
If we're in a deeply nested component we will use swr to fetch via API
Files:
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
Components with
onClickmust be client components withuse clientdirectiveFiles:
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/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🧠 Learnings (16)
📚 Learning: 2025-07-18T15:05:41.705Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/hooks.mdc:0-0 Timestamp: 2025-07-18T15:05:41.705Z Learning: Applies to apps/web/hooks/use*.{js,jsx,ts,tsx} : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`).Applied to files:
apps/web/hooks/useFolders.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/hooks/**/*.ts : Use SWR for efficient data fetching and cachingApplied to files:
apps/web/hooks/useFolders.ts📚 Learning: 2025-07-20T09:00:16.505Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/project-structure.mdc:0-0 Timestamp: 2025-07-20T09:00:16.505Z Learning: Use `swr` for data fetching in deeply nested componentsApplied to files:
apps/web/hooks/useFolders.ts📚 Learning: 2025-07-18T15:05:56.644Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/index.mdc:0-0 Timestamp: 2025-07-18T15:05:56.644Z Learning: Fetching data from the API using SWRApplied to files:
apps/web/hooks/useFolders.ts📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR PR: elie222/inbox-zero#0 File: apps/web/CLAUDE.md:0-0 Timestamp: 2025-07-18T15:04:30.467Z Learning: Applies to apps/web/app/api/**/route.ts : Use `withEmailAccount` for email-account-level operationsApplied to files:
apps/web/app/api/user/folders/route.ts📚 Learning: 2025-07-18T17:27:46.389Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/security.mdc:0-0 Timestamp: 2025-07-18T17:27:46.389Z Learning: Applies to **/api/**/route.ts : Use `withEmailAccount` middleware for API routes that operate on a specific email account (i.e., use or require `emailAccountId`).Applied to files:
apps/web/app/api/user/folders/route.ts📚 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/app/api/**/route.ts : Wrap all GET API route handlers with `withAuth` or `withEmailAccount` middleware for authentication and authorization.Applied to files:
apps/web/app/api/user/folders/route.ts📚 Learning: 2025-07-18T15:05:26.713Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/get-api-route.mdc:0-0 Timestamp: 2025-07-18T15:05:26.713Z Learning: Applies to app/api/**/route.ts : Always wrap the handler with `withAuth` or `withEmailAccount` for consistent error handling and authentication in GET API routes.Applied to files:
apps/web/app/api/user/folders/route.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/microsoft.tsapps/web/utils/outlook/folders.ts📚 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-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/*.validation.ts : Define input validation schemas using Zod in the corresponding `.validation.ts` file. These schemas are used by `next-safe-action` (`.schema()`) and can also be reused on the client for form 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-08-10T22:08:49.243Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/llm.mdc:0-0 Timestamp: 2025-08-10T22:08:49.243Z Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Make Zod schemas as specific as possible to guide the LLM output.Applied to files:
apps/web/utils/actions/rule.validation.ts📚 Learning: 2025-07-18T15:04:57.115Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/form-handling.mdc:0-0 Timestamp: 2025-07-18T15:04:57.115Z Learning: Applies to **/*.ts : Define validation schemas using ZodApplied to files:
apps/web/utils/actions/rule.validation.ts📚 Learning: 2025-08-10T22:08:49.243Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/llm.mdc:0-0 Timestamp: 2025-08-10T22:08:49.243Z Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Always define a Zod schema for response validation in LLM-related functions.Applied to files:
apps/web/utils/actions/rule.validation.ts📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/security-audit.mdc:0-0 Timestamp: 2025-07-20T09:00:41.968Z Learning: Applies to apps/web/app/api/**/*.{ts,js} : Request bodies in API routes should use Zod schemas for validation.Applied to files:
apps/web/utils/actions/rule.validation.ts🧬 Code Graph Analysis (7)
apps/web/hooks/useFolders.ts (1)
apps/web/utils/outlook/folders.ts (1)
OutlookFolder(9-14)apps/web/app/api/user/folders/route.ts (4)
apps/web/app/api/user/rules/[id]/route.ts (1)
GET(51-60)apps/web/utils/middleware.ts (1)
withEmailAccount(251-255)apps/web/utils/outlook/client.ts (1)
getOutlookClientWithRefresh(76-148)apps/web/utils/outlook/folders.ts (1)
getOutlookFolders(16-73)apps/web/components/FolderCombobox.tsx (3)
apps/web/utils/outlook/folders.ts (1)
OutlookFolder(9-14)apps/web/components/Button.tsx (1)
Button(60-87)apps/web/utils/index.ts (1)
cn(4-6)apps/web/components/FolderSelector.tsx (3)
apps/web/utils/outlook/folders.ts (2)
OutlookFolder(9-14)FOLDER_SEPARATOR(8-8)apps/web/utils/index.ts (1)
cn(4-6)apps/web/components/Button.tsx (1)
Button(60-87)apps/web/utils/outlook/folders.ts (3)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/logger.ts (1)
createScopedLogger(17-65)apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)apps/web/utils/outlook/label.ts (2)
apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)apps/web/utils/outlook/message.ts (1)
WELL_KNOWN_FOLDERS(14-21)apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
apps/web/hooks/useFolders.ts (1)
useFolders(4-8)apps/web/components/FolderSelector.tsx (1)
FolderSelector(88-280)🔇 Additional comments (13)
apps/web/utils/scheduled-actions/scheduler.ts (1)
67-69: Persisting folderName/folderId in scheduled actions — verify end-to-end usageStoring both fields here is consistent with the schema and broader PR. Please confirm:
- Prisma ScheduledAction model has folderName/folderId as nullable fields.
- The executor path that reads ScheduledAction (e.g., /api/scheduled-actions/execute) reads and honors folderId for MOVE_FOLDER.
If you want, I can scan the repository for the execute path and verify it reads these fields.
apps/web/app/api/user/rules/[id]/route.ts (1)
41-43: Adding folderId to action serialization — LGTMThis aligns the API shape with the new folderId-based flow. The nullability will naturally flow through as { value: null } for older rules.
apps/web/utils/ai/types.ts (1)
25-26: Extending ActionItem with folderName/folderId — LGTMTyping against ExecutedAction keeps these fields consistent with Prisma.
apps/web/utils/actions/rule.ts (2)
259-260: folderId propagation on createMany during update: LGTMThe create path correctly forwards folderId through sanitizeActionFields. This keeps create/update parity.
91-105: folderId propagation verified end-to-endAll layers now include
folderId, so creation and validation are consistent:
- apps/web/utils/actions/rule.ts: maps
folderIdintosanitizeActionFields- apps/web/utils/actions/rule.validation.ts:
folderIdis in the Zod schema and checked insuperRefine- apps/web/utils/action-item.ts: sanitizer preserves and handles
folderId- apps/web/app/api/user/rules/[id]/route.ts: request mapping includes
folderIdNo further changes needed.
apps/web/app/(app)/[emailAccountId]/assistant/settings/DraftReplies.tsx (1)
92-93: LGTM: optimistic action shape now includes folderIdAdding folderId: null keeps the optimistic DRAFT_EMAIL action shape consistent with the updated schema and UI contracts.
apps/web/utils/email/microsoft.ts (2)
160-168: LGTM! Proper implementation of folderId-based archiving.The archiving functions now correctly use
folderId: "archive"instead of folder names, aligning with the PR's broader shift to folderId-based operations. The use of the well-known folder ID "archive" is appropriate for Microsoft's Graph API.
823-835: ArchiveThread correctly handles both well-known and custom folder IDsThe Outlook
archiveThreadimplementation inapps/web/utils/outlook/label.ts(lines 233–241) already:
- Treats well-known folder names (from
WELL_KNOWN_FOLDERS) as trusted.- For other
folderIds, issues a GET to/me/mailFolders/{folderId}to verify the folder exists before proceeding.- Uses the validated
folderIdwhen moving each message in the thread.No changes needed.
apps/web/app/api/user/folders/route.ts (1)
16-44: Add error handling for missing email account.The function should handle the case where the email account is not found to prevent potential null pointer exceptions.
Apply this diff to add proper error handling:
async function getFolders({ emailAccountId }: { emailAccountId: string }) { const emailAccount = await prisma.emailAccount.findUnique({ where: { id: emailAccountId }, select: { account: { select: { provider: true, access_token: true, refresh_token: true, expires_at: true, }, }, }, }); + if (!emailAccount?.account) { + console.error(`Email account not found: ${emailAccountId}`); + return []; + } + if (emailAccount?.account?.provider === "microsoft") {Likely an incorrect or invalid review comment.
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (1)
1047-1059: LGTM! Appropriate handling of folder fields for variables.The logic correctly prevents the use of variables in folder-related fields, which makes sense since these are selected from a predefined list rather than being dynamically generated.
apps/web/utils/outlook/folders.ts (2)
1-5: LGTM! Logger initialization follows best practicesThe scoped logger is correctly initialized at the module level as per the coding guidelines.
6-8: Good choice for the folder separator constantThe U+2999 character (⦙) is indeed an uncommon character that's unlikely to appear in typical folder names, making it a solid choice for a separator. The comment clearly explains the rationale.
apps/web/utils/outlook/label.ts (1)
238-245: Good migration to folderId-based approachThe refactoring from
folderNametofolderIdaligns well with the broader PR objectives and provides a more reliable way to reference folders. The default value of "archive" maintains backward compatibility.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
apps/web/utils/outlook/folders.ts (3)
20-33: Harden Graph calls: add try/catch and validate shape with ZodUncaught errors here will bubble up and Zod-validated shapes help maintain strict typing (and avoid implicit any on response.value). This also aligns with the project’s “Define validation schemas using Zod” guidance.
Apply within this range:
- const getFolders = async (path: string) => { - const fields = "id,displayName"; - const response = await client - .getClient() - .api(path) - .select(fields) - .expand( - `childFolders($select=${fields};$expand=childFolders($select=${fields}))`, - ) - .get(); - - return response.value; - }; + const getFolders = async (path: string) => { + const fields = "id,displayName"; + try { + const response = await client + .getClient() + .api(path) + .select(fields) + .expand( + `childFolders($select=${fields};$expand=childFolders($select=${fields}))`, + ) + .get(); + const parsed = GraphResponseSchema.parse(response); + return parsed.value; + } catch (error) { + logger.error("Failed to fetch folders", { + path, + message: error instanceof Error ? error.message : String(error), + }); + throw error; + } + };And add these definitions outside the selected range (place after imports):
// Graph API folder shape and validation export type GraphApiFolder = { id: string; displayName: string; childFolders?: GraphApiFolder[]; }; const GraphApiFolderSchema: z.ZodType<GraphApiFolder> = z.lazy(() => z.object({ id: z.string(), displayName: z.string(), childFolders: z.array(z.lazy(() => GraphApiFolderSchema)).optional(), }), ); const GraphResponseSchema = z.object({ value: z.array(GraphApiFolderSchema), }); const toOutlookFolder = ( node: GraphApiFolder, parentId?: string, ): OutlookFolder => ({ id: node.id, displayName: node.displayName, parentFolderId: parentId, childFolders: node.childFolders?.map((c) => toOutlookFolder(c, node.id)), });
35-46: Fix typing in processFolders and leverage a normalizerYou’re treating raw Graph objects as OutlookFolder. Normalize explicitly from GraphApiFolder to OutlookFolder.
- const processFolders = ( - folderList: OutlookFolder[], - parentId?: string, - ): OutlookFolder[] => { - return folderList.map((folder) => ({ - ...folder, - parentFolderId: parentId, - childFolders: folder.childFolders - ? processFolders(folder.childFolders, folder.id) - : undefined, - })); - }; + const processFolders = ( + folderList: GraphApiFolder[], + parentId?: string, + ): OutlookFolder[] => folderList.map((f) => toOutlookFolder(f, parentId));
50-74: Avoid redundant re-fetches and batch deeper calls; preserve nested children returned by $expand
- Only fetch when childFolders is undefined (treat empty array as “fetched, no children”) to prevent repeated calls on every pass.
- Batch the per-level fetches with Promise.all to reduce total latency. Consider adding a concurrency cap later if rate-limits become an issue.
- When fetching, keep any nested childFolders already returned by the Graph $expand by reusing processFolders.
- const fetchNested = async (folderList: OutlookFolder[]) => { - for (const folder of folderList) { - if (!folder.childFolders || folder.childFolders.length === 0) { - try { - const childFolders = await getFolders( - `/me/mailFolders/${folder.id}/childFolders`, - ); - folder.childFolders = childFolders.map( - (childFolder: OutlookFolder) => ({ - id: childFolder.id, - displayName: childFolder.displayName, - parentFolderId: folder.id, - }), - ); - } catch (error) { - logger.warn("Failed to fetch deeper folders", { - folderId: folder.id, - error, - }); - } - } else { - await fetchNested(folder.childFolders); - } - } - }; + const fetchNested = async (folderList: OutlookFolder[]): Promise<void> => { + const tasks: Array<Promise<void>> = []; + for (const folder of folderList) { + if (folder.childFolders === undefined) { + tasks.push( + (async () => { + try { + const childFolders = await getFolders( + `/me/mailFolders/${folder.id}/childFolders`, + ); + // Preserve any nested children surfaced via $expand + folder.childFolders = processFolders(childFolders, folder.id); + } catch (error) { + logger.warn("Failed to fetch deeper folders", { + folderId: folder.id, + message: error instanceof Error ? error.message : String(error), + }); + // Mark as fetched to avoid repeated retries on subsequent passes + folder.childFolders = []; + } + })(), + ); + } else if (folder.childFolders.length > 0) { + tasks.push(fetchNested(folder.childFolders)); + } + } + await Promise.all(tasks); + };
🧹 Nitpick comments (3)
apps/web/utils/outlook/folders.ts (3)
18-18: Clarify the expandLevels commentThe current comment is misleading. It does not necessarily “fetch 6 levels deep” as written.
- expandLevels = 4, // This is fetching 6 levels deep + expandLevels = 4, // Additional fetch passes after the initial 2-level $expand
65-69: Log actionable error details instead of raw Error objectsThe logger stringifies objects; raw Error serializes to "{}". Log message (and optionally code) for usefulness.
- logger.warn("Failed to fetch deeper folders", { - folderId: folder.id, - error, - }); + logger.warn("Failed to fetch deeper folders", { + folderId: folder.id, + message: error instanceof Error ? error.message : String(error), + });
16-80: Optional: handle 429 rate limits with Retry-After and cap concurrencyMicrosoft Graph can throttle with 429. Consider reading Retry-After and backing off; also introduce a small concurrency limiter (e.g., p-limit) if you hit tenant-wide throttles.
If you want, I can propose a minimal p-limit integration with a safe default concurrency (e.g., 4–8).
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(8 hunks)apps/web/components/FolderSelector.tsx(1 hunks)apps/web/utils/outlook/folders.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/components/FolderSelector.tsx
- apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/outlook/folders.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/outlook/folders.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/outlook/folders.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/outlook/folders.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/outlook/folders.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/outlook/folders.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/outlook/folders.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/outlook/folders.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/outlook/folders.ts
🧬 Code Graph Analysis (1)
apps/web/utils/outlook/folders.ts (3)
apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/logger.ts (1)
createScopedLogger(17-65)apps/web/utils/outlook/client.ts (1)
OutlookClient(19-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: Static Code Analysis Js
- GitHub Check: Jit Security
🔇 Additional comments (1)
apps/web/utils/outlook/folders.ts (1)
6-8: LGTM: separator choice is robustGood call using a rarely-used Unicode char to avoid collisions with user-defined folder names.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/utils/outlook/label.ts (1)
246-253: Add validation for empty or malformed folderId values.The current validation only checks if the folderId exists but doesn't validate if it's empty, whitespace-only, or malformed, which could lead to unnecessary API calls.
Add validation for empty or invalid folderIds:
export async function archiveThread({ client, threadId, ownerEmail, actionSource, folderId = "archive", }: { client: OutlookClient; threadId: string; ownerEmail: string; actionSource: TinybirdEmailAction["actionSource"]; folderId?: string; }) { - if (!folderId) { + // Validate folderId + if (!folderId || typeof folderId !== 'string' || !folderId.trim()) { + logger.warn("Invalid or empty folderId provided", { folderId, threadId }); + return; + } + + if (!folderId) { logger.warn("No folderId provided, skipping archive operation", { threadId, ownerEmail, actionSource, }); return; }
🧹 Nitpick comments (1)
apps/web/utils/outlook/label.ts (1)
255-271: Consider caching folder existence checks for performance.The current implementation makes an API call to verify custom folder existence on every archive operation. For frequently used custom folders, this could be optimized with caching.
Consider implementing a simple in-memory cache for folder existence:
+// Simple cache for folder existence (could be moved to a shared location) +const folderExistenceCache = new Map<string, boolean>(); +const CACHE_TTL = 5 * 60 * 1000; // 5 minutes +const cacheTimestamps = new Map<string, number>(); // Check if the destination folder exists (only for custom folders, well-known names can be trusted and used directly) const wellKnownFolders = Object.keys(WELL_KNOWN_FOLDERS); if (!wellKnownFolders.includes(folderId.toLowerCase())) { + const now = Date.now(); + const cacheKey = `${ownerEmail}:${folderId}`; + const cached = folderExistenceCache.get(cacheKey); + const cacheTime = cacheTimestamps.get(cacheKey); + + if (cached !== undefined && cacheTime && (now - cacheTime) < CACHE_TTL) { + if (!cached) { + logger.warn( + "Custom destination folder not found (cached), skipping archive operation", + { folderId, threadId } + ); + return; + } + } else { try { await client.getClient().api(`/me/mailFolders/${folderId}`).get(); + folderExistenceCache.set(cacheKey, true); + cacheTimestamps.set(cacheKey, now); } catch (error) { + folderExistenceCache.set(cacheKey, false); + cacheTimestamps.set(cacheKey, now); logger.warn( "Custom destination folder not found, skipping archive operation", { folderId, threadId, error, }, ); return; } + } }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(8 hunks)apps/web/components/FolderSelector.tsx(1 hunks)apps/web/hooks/useFolders.ts(1 hunks)apps/web/utils/outlook/label.ts(8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/hooks/useFolders.ts
- apps/web/components/FolderSelector.tsx
- apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.ts
🧬 Code Graph Analysis (1)
apps/web/utils/outlook/label.ts (4)
apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)packages/tinybird/src/publish.ts (1)
TinybirdEmailAction(12-12)apps/web/app/api/outlook/webhook/logger.ts (1)
logger(3-3)apps/web/utils/outlook/message.ts (1)
WELL_KNOWN_FOLDERS(14-21)
⏰ 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/outlook/label.ts (5)
4-4: LGTM! Import change aligns with refactoring.The import of
WELL_KNOWN_FOLDERSfrom the message module supports the new folder validation logic and replaces the previous folder name approach.
233-244: Function signature updated correctly for folderId-based operations.The signature change from
folderNametofolderIdwith proper TypeScript typing aligns with the PR's objective to use folder IDs throughout the system.
255-271: Well-known folder validation logic is sound.The approach of checking against
WELL_KNOWN_FOLDERSfirst before making API calls for custom folders is efficient and reduces unnecessary network requests. The error handling appropriately logs warnings and skips operations for non-existent folders.
290-290: Consistent use of folderId in move operations.All move operations correctly use
destinationId: folderId, ensuring consistency across different code paths (primary, alternative filter, and direct message approaches).Also applies to: 369-369, 388-388
293-293: Updated error and log messages reflect folderId terminology.All error messages and log statements have been consistently updated to reference
folderIdinstead of the previousfolderName, maintaining clarity in debugging and monitoring.Also applies to: 321-321, 329-335, 373-373, 402-402, 413-413
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx(8 hunks)apps/web/app/api/user/folders/route.ts(1 hunks)apps/web/hooks/useFolders.ts(1 hunks)apps/web/utils/actions/rule.ts(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/utils/actions/rule.ts
- apps/web/app/api/user/folders/route.ts
- apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
🧰 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/hooks/useFolders.ts
apps/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.ts
apps/web/hooks/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
Call
mutate()after successful mutations to refresh data
Files:
apps/web/hooks/useFolders.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/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 Zod
Files:
apps/web/hooks/useFolders.ts
apps/web/hooks/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/hooks.mdc)
Place custom hooks in the
apps/web/hooks/directory.
Files:
apps/web/hooks/useFolders.ts
apps/web/hooks/use*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/hooks.mdc)
apps/web/hooks/use*.{js,jsx,ts,tsx}: Name custom hooks with theuseprefix (e.g.,useAccounts.ts).
For fetching data from API endpoints in custom hooks, prefer usinguseSWR.
Create dedicated hooks for specific data types (e.g.,useAccounts,useLabels).
Custom hooks should encapsulate reusable stateful logic, especially for data fetching or complex UI interactions.
Keep custom hooks focused on a single responsibility.
Files:
apps/web/hooks/useFolders.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/hooks/useFolders.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/hooks/useFolders.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/hooks/useFolders.ts
🧠 Learnings (10)
📚 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/hooks/**/*.ts : Use SWR for efficient data fetching and caching
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-18T15:05:41.705Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-07-18T15:05:41.705Z
Learning: Applies to apps/web/hooks/use*.{js,jsx,ts,tsx} : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`).
Applied to files:
apps/web/hooks/useFolders.ts
📚 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/hooks/**/*.ts : Use SWR for client-side data fetching and caching.
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-18T15:05:41.705Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-07-18T15:05:41.705Z
Learning: Applies to apps/web/hooks/use*.{js,jsx,ts,tsx} : For fetching data from API endpoints in custom hooks, prefer using `useSWR`.
Applied to files:
apps/web/hooks/useFolders.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 `actionClient` when both authenticated user context and a specific `emailAccountId` are needed. The `emailAccountId` must be bound when calling the action from the client.
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-18T17:27:46.389Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-07-18T17:27:46.389Z
Learning: Applies to **/api/**/route.ts : Use `withEmailAccount` middleware for API routes that operate on a specific email account (i.e., use or require `emailAccountId`).
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-18T15:04:44.818Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-07-18T15:04:44.818Z
Learning: Applies to {app,components}/**/*.{ts,tsx} : For API GET requests to server, use the `swr` package
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-19T17:50:22.078Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-19T17:50:22.078Z
Learning: Applies to {components,app}/**/*.tsx : For API get requests to server use the `swr` package
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-20T09:00:16.505Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-07-20T09:00:16.505Z
Learning: Use `swr` for data fetching in deeply nested components
Applied to files:
apps/web/hooks/useFolders.ts
📚 Learning: 2025-07-18T15:05:56.644Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/index.mdc:0-0
Timestamp: 2025-07-18T15:05:56.644Z
Learning: Fetching data from the API using SWR
Applied to files:
apps/web/hooks/useFolders.ts
🧬 Code Graph Analysis (1)
apps/web/hooks/useFolders.ts (1)
apps/web/utils/outlook/folders.ts (1)
OutlookFolder(9-14)
⏰ 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 (1)
apps/web/hooks/useFolders.ts (1)
1-1: Path alias and type-only import are correctUsing "@/..." and
import typeis aligned with project conventions and tree-shaking.
| emailAccountId, | ||
| groupId: rule.groupId, | ||
| .action( | ||
| async ({ ctx: { emailAccountId, provider }, parsedInput: { id } }) => { |
There was a problem hiding this comment.
commit hook formatting
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (4)
apps/web/utils/rule/rule.ts (2)
365-385: folderId is not persisted in mapActionFields (Microsoft)You’re gating folderName by provider, but folderId is not mapped at all. This will drop folderId when creating/updating rules through this path, leading to Outlook moves targeting by name only and breaking end-to-end folderId support introduced elsewhere in the PR.
Add folderId alongside folderName when provider === "microsoft".
Apply this diff:
function mapActionFields( actions: CreateOrUpdateRuleSchemaWithCategories["actions"], - provider: string, + provider: string, ) { return actions.map( (a): Prisma.ActionCreateManyRuleInput => ({ type: a.type, label: a.fields?.label, to: a.fields?.to, cc: a.fields?.cc, bcc: a.fields?.bcc, subject: a.fields?.subject, content: a.fields?.content, url: a.fields?.webhookUrl, - ...(provider === "microsoft" && { - folderName: a.fields?.folderName as string | null, - }), + ...(provider === "microsoft" && { + folderName: a.fields?.folderName as string | null, + folderId: (a.fields?.folderId as string | null) ?? null, + }), delayInMinutes: a.delayInMinutes, }), ); }
90-99: Throw SafeError from safeCreateRule on non-duplicate failuressafeCreateRule currently logs the error and returns implicitly (undefined), which silently fails callers (makes upstream behavior unpredictable). Throw a SafeError so next-safe-action centralizes handling.
Files to change / review:
- apps/web/utils/rule/rule.ts — catch block in safeCreateRule (currently logs then returns implicitly).
- Call sites to verify after change (found via rg):
- apps/web/utils/actions/ai-rule.ts (createAutomationAction and the "add new rules" loop)
- apps/web/utils/reply-tracker/enable.ts (createToReplyRule)
- (search command) rg -n "safeCreateRule(" -A 2
Suggested diff:
} catch (error) { if (isDuplicateError(error, "name")) { ... } logger.error("Error creating rule", { emailAccountId, error: error instanceof Error ? { message: error.message, stack: error.stack, name: error.name } : error, }); - // return { error: "Error creating rule." }; + throw new SafeError("Error creating rule."); }Also consider making safeUpdateRule consistent (it currently returns { error: "Error updating rule." } — prefer throwing SafeError as well) and update any callers that rely on returned error objects.
apps/web/utils/actions/rule.ts (1)
225-242: Harden updates: prevent cross-rule action updates via ID spoofingactionsToUpdate includes any action with an id, even if it doesn’t belong to the current rule. The subsequent prisma.action.update({ where: { id: a.id } }) can mutate another rule’s action if a spoofed ID is sent. Scope updates to current rule.
Apply this diff to filter updates and scope the write:
- const actionsToUpdate = actions.filter((a) => a.id); + const allowedActionIds = new Set(currentActions.map((a) => a.id)); + const actionsToUpdate = actions.filter( + (a) => a.id && allowedActionIds.has(a.id), + );- ...actionsToUpdate.map((a) => { - return prisma.action.update({ - where: { id: a.id }, - data: sanitizeActionFields({ + ...actionsToUpdate.map((a) => { + return prisma.action.updateMany({ + where: { id: a.id, ruleId: id }, + data: sanitizeActionFields({ type: a.type, label: a.label?.value, subject: a.subject?.value, content: a.content?.value, to: a.to?.value, cc: a.cc?.value, bcc: a.bcc?.value, url: a.url?.value, folderName: a.folderName?.value, folderId: a.folderId?.value, delayInMinutes: a.delayInMinutes, }), }); }),Note: updateMany returns a count; you’re not using the return value, so this change is non-breaking.
apps/web/utils/ai/assistant/process-user-request.ts (1)
563-586: AI-created rules drop folderId; add it to action field mappingThe tool’s create_rule path builds fields but omits folderId, so Outlook moves created via the AI assistant won’t persist the folderId. Include folderId alongside folderName.
Apply this diff:
const rule = await createRule({ result: { name, condition, actions: actions.map((action) => ({ ...action, fields: action.fields ? { ...action.fields, label: action.fields.label ?? null, to: action.fields.to ?? null, cc: action.fields.cc ?? null, bcc: action.fields.bcc ?? null, subject: action.fields.subject ?? null, content: action.fields.content ?? null, webhookUrl: action.fields.webhookUrl ?? null, folderName: action.fields.folderName ?? null, + folderId: action.fields.folderId ?? null, } : null, })), }, emailAccountId: emailAccount.id, categoryIds, provider: emailAccount.account.provider, });
🧹 Nitpick comments (2)
apps/web/utils/rule/rule.ts (1)
36-45: Tighten provider typing to the Prisma Provider enumUsing string for provider invites typos and weakens type safety. Prefer Provider from @prisma/client across these signatures (and mapActionFields) to enforce valid values.
Apply representative diffs (repeat the type change for all affected signatures):
-import { - ActionType, - type SystemType, - type Prisma, - type Rule, -} from "@prisma/client"; +import { + ActionType, + type SystemType, + type Prisma, + type Rule, + type Provider, +} from "@prisma/client";export async function safeCreateRule({ result, emailAccountId, - provider, + provider, categoryNames, systemType, triggerType = "ai_creation", shouldCreateIfDuplicate, }: { result: CreateOrUpdateRuleSchemaWithCategories; emailAccountId: string; - provider: string; + provider: Provider;Do the same for:
- safeUpdateRule
- createRule
- updateRule (internal)
- updateRuleActions
- mapActionFields
Also applies to: 44-45, 107-115, 114-115, 157-165, 164-165, 215-223, 222-223, 261-266, 365-368
apps/web/utils/actions/rule.ts (1)
399-456: Wrap after() side effects in try/catch to avoid unhandled rejectionsErrors thrown inside after() run outside the outer try/catch and can become unhandled rejections (e.g., missing emailAccount). Swallow expected errors and log others.
Apply this diff:
- after(async () => { - const emailAccount = await prisma.emailAccount.findUnique({ + after(async () => { + try { + const emailAccount = await prisma.emailAccount.findUnique({ where: { id: emailAccountId }, select: { id: true, userId: true, email: true, about: true, rulesPrompt: true, user: { select: { aiModel: true, aiProvider: true, aiApiKey: true, }, }, }, - }); - if (!emailAccount) throw new SafeError("User not found"); + }); + if (!emailAccount) return; - if (!emailAccount.rulesPrompt) return; + if (!emailAccount.rulesPrompt) return; - const updatedPrompt = await generatePromptOnDeleteRule({ - emailAccount: { ...emailAccount, account: { provider } }, - existingPrompt: emailAccount.rulesPrompt, - deletedRule: rule, - }); + const updatedPrompt = await generatePromptOnDeleteRule({ + emailAccount: { ...emailAccount, account: { provider } }, + existingPrompt: emailAccount.rulesPrompt, + deletedRule: rule, + }); - await prisma.emailAccount.update({ - where: { id: emailAccountId }, - data: { rulesPrompt: updatedPrompt }, - }); - }); + await prisma.emailAccount.update({ + where: { id: emailAccountId }, + data: { rulesPrompt: updatedPrompt }, + }); + } catch (err) { + // Best-effort post-delete maintenance; log and continue + logger.warn("Failed to update prompt after rule delete", { + emailAccountId, + error: err instanceof Error ? err.message : String(err), + }); + } + });
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/utils/actions/ai-rule.ts(3 hunks)apps/web/utils/actions/rule.ts(5 hunks)apps/web/utils/ai/assistant/chat.ts(8 hunks)apps/web/utils/ai/assistant/process-user-request.ts(1 hunks)apps/web/utils/ai/choose-rule/ai-choose-rule.ts(1 hunks)apps/web/utils/reply-tracker/enable.ts(2 hunks)apps/web/utils/rule/rule.ts(12 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/utils/ai/choose-rule/ai-choose-rule.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/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.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/ai/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.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/ai/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.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/ai/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.tsapps/web/utils/rule/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/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.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/ai/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.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/ai/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.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/ai/assistant/process-user-request.tsapps/web/utils/reply-tracker/enable.tsapps/web/utils/actions/ai-rule.tsapps/web/utils/ai/assistant/chat.tsapps/web/utils/actions/rule.tsapps/web/utils/rule/rule.ts
apps/web/utils/{ai,llms}/**/*
📄 CodeRabbit Inference Engine (.cursor/rules/llm.mdc)
LLM-related code must be organized in the directories: apps/web/utils/ai/, apps/web/utils/llms/, and apps/web/tests/ for LLM-specific tests.
Files:
apps/web/utils/ai/assistant/process-user-request.tsapps/web/utils/ai/assistant/chat.ts
apps/web/utils/{ai,llms}/**/*.ts
📄 CodeRabbit Inference Engine (.cursor/rules/llm.mdc)
apps/web/utils/{ai,llms}/**/*.ts: Keep system prompts and user prompts separate in LLM-related functions.
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 in LLM-related functions.
Make Zod schemas as specific as possible to guide the LLM output.
Use descriptive scoped loggers for each LLM feature.
Log inputs and outputs with appropriate log levels in LLM-related functions.
Include relevant context in log messages for LLM-related code.
Implement early returns for invalid inputs in LLM-related functions.
Use proper error types and logging in LLM-related code.
Implement fallbacks for AI failures in LLM-related functions.
Add retry logic for transient failures using withRetry in LLM-related functions.
Use XML-like tags to structure data in LLM prompts.
Remove excessive whitespace and truncate long inputs in LLM prompts.
Format data consistently across similar LLM-related functions.
Use TypeScript types for all parameters and return values in LLM-related functions.
Define clear interfaces for complex input/output structures in LLM-related code.
Keep related AI functions in the same file or directory.
Extract common patterns into utility functions in LLM-related code.
Document complex AI logic with clear comments in LLM-related code.
Files:
apps/web/utils/ai/assistant/process-user-request.tsapps/web/utils/ai/assistant/chat.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/ai-rule.tsapps/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/ai-rule.tsapps/web/utils/actions/rule.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/ai/assistant/chat.ts
🧬 Code Graph Analysis (3)
apps/web/utils/reply-tracker/enable.ts (1)
apps/web/utils/error.ts (1)
SafeError(86-96)
apps/web/utils/actions/rule.ts (5)
apps/web/utils/error.ts (1)
SafeError(86-96)apps/web/utils/rule/rule.ts (1)
deleteRule(278-294)apps/web/utils/path.ts (1)
prefixPath(1-4)apps/web/utils/ai/rule/generate-prompt-on-delete-rule.ts (1)
generatePromptOnDeleteRule(14-69)apps/web/utils/prisma-helpers.ts (1)
isNotFoundError(14-19)
apps/web/utils/rule/rule.ts (1)
apps/web/utils/ai/rule/create-rule-schema.ts (1)
CreateOrUpdateRuleSchemaWithCategories(147-150)
⏰ 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: test
- GitHub Check: Static Code Analysis Js
- GitHub Check: Jit Security
🔇 Additional comments (15)
apps/web/utils/actions/rule.ts (1)
81-107: LGTM: folderId is correctly persisted on createThe createRuleAction now threads folderId through sanitizeActionFields and createMany. This matches the PR’s folderId objective.
apps/web/utils/reply-tracker/enable.ts (1)
140-149: LGTM: provider is correctly fetched and threaded into safeCreateRuleFetching account.provider and passing it to safeCreateRule aligns this flow with the provider-aware rule creation across the codebase.
apps/web/utils/actions/ai-rule.ts (1)
184-191: LGTM: provider context is correctly propagated to safeCreateRule/safeUpdateRuleAll three call sites now pass emailAccount.account.provider. This keeps rule creation/update paths provider-aware and consistent with backend expectations.
Also applies to: 455-461, 486-493
apps/web/utils/ai/assistant/chat.ts (12)
179-187: LGTM! Provider parameter correctly added to createRuleTool signature.The provider parameter is properly typed and threaded through to enable provider-specific functionality.
211-214: Provider-aware folderName handling implemented correctly.The conditional logic properly restricts
folderNameto Microsoft provider only, with appropriate fallback toundefinedfor other providers.
220-220: Provider parameter correctly passed to createRule.The provider value is properly threaded through to the rule creation logic.
273-281: Provider parameter correctly added to updateRuleConditionsTool.The function signature is updated to accept the provider parameter, maintaining consistency with other tool functions.
359-367: Provider parameter correctly added to updateRuleActionsTool.The function signature properly includes the provider parameter for provider-aware action handling.
445-445: Provider-aware folderName filtering in original actions.The logic correctly includes
folderNamein the original actions only when the provider is Microsoft, maintaining consistency with the provider-specific behavior.
461-464: Provider-aware folderName handling in action updates.The conditional logic properly restricts
folderNameassignment to Microsoft provider only during action updates.
468-468: Provider parameter correctly passed to updateRuleActions.The provider value is properly threaded through to the rule action update logic.
484-492: Provider parameter added to updateLearnedPatternsTool signature.While the provider parameter is added for consistency with other tools, it's not currently used within this function's logic.
590-598: Provider parameter added to updateAboutTool signature.The provider parameter is included for consistency with other tool functions, though it's not used in the about update logic.
628-636: Provider parameter added to addToKnowledgeBaseTool signature.The provider parameter maintains consistency across all tool functions, even though it's not used in the knowledge base logic.
943-947: Provider correctly extracted and passed to tool options.The provider value is properly extracted from
user.account.providerand included intoolOptionsfor all tool functions.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/utils/outlook/label.ts (1)
246-253: Add validation for empty or malformed folderId.The current validation only checks if folderId is falsy. However, it should also validate for empty strings, whitespace-only strings, or potentially malformed values before making API calls.
Apply this diff to add proper validation:
- if (!folderId) { - logger.warn("No folderId provided, skipping archive operation", { + // Validate folderId + if (!folderId || typeof folderId !== 'string' || !folderId.trim()) { + logger.warn("Invalid or empty folderId provided, skipping archive operation", { + folderId, threadId, ownerEmail, actionSource, }); return; }
🧹 Nitpick comments (5)
apps/web/utils/outlook/label.ts (5)
238-245: Consider using a more descriptive parameter name.The parameter name
folderIdsuggests it should be a unique identifier (like a GUID), but it can also accept well-known folder names like "archive". Consider renaming tofolderIdOrNameorfolderTargetto better reflect its dual nature.
255-271: Improve error handling for folder existence check.The current implementation only logs a warning when a custom folder is not found. Consider distinguishing between different error types (404 vs other errors) to provide more specific feedback.
Apply this diff to improve error handling:
// Check if the destination folder exists (only for custom folders, well-known names can be trusted and used directly) const wellKnownFolders = Object.keys(WELL_KNOWN_FOLDERS); if (!wellKnownFolders.includes(folderId.toLowerCase())) { try { await client.getClient().api(`/me/mailFolders/${folderId}`).get(); } catch (error) { + const isNotFound = error instanceof Error && + (error.message?.includes('404') || + error.message?.includes('not found') || + error.message?.includes('ResourceNotFound')); + logger.warn( - "Custom destination folder not found, skipping archive operation", + isNotFound + ? "Custom destination folder not found, skipping archive operation" + : "Failed to verify custom destination folder, skipping archive operation", { folderId, threadId, - error, + error: error instanceof Error ? error.message : error, + errorType: isNotFound ? 'not_found' : 'verification_failed', }, ); return; } }
322-327: Inconsistent error message format.The error message on line 322 uses template literal while other similar messages use object properties for logging context.
Apply this diff for consistency:
- logger.error(`Failed to move thread to folder ${folderId}`, { + logger.error("Failed to move thread to folder", { + folderId, threadId, error, });
330-337: Remove redundant template literal in error message.Similar to the previous comment, the error message uses a template literal unnecessarily.
Apply this diff:
- logger.error( - `Failed to publish action to move thread to folder ${folderId}`, - { - threadId, - error: publishResult.reason, - }, - ); + logger.error("Failed to publish action to move thread to folder", { + folderId, + threadId, + error: publishResult.reason, + });
346-420: Consider extracting the fallback logic into a separate function.The error handling block contains substantial logic for alternative approaches to move messages. This makes the function harder to read and test. Consider extracting this into a separate helper function.
Add a new helper function before
archiveThread:async function moveThreadMessagesFallback({ client, threadId, folderId, ownerEmail, actionSource, }: { client: OutlookClient; threadId: string; folderId: string; ownerEmail: string; actionSource: TinybirdEmailAction["actionSource"]; }) { // Try to get messages by conversationId using a different endpoint const messages = await client .getClient() .api("/me/messages") .select("id") .get(); // Filter messages by conversationId manually const threadMessages = messages.value.filter( (message: { conversationId: string }) => message.conversationId === threadId, ); if (threadMessages.length > 0) { // Move each message in the thread to the destination folder const movePromises = threadMessages.map( async (message: { id: string }) => { try { return await client .getClient() .api(`/me/messages/${message.id}/move`) .post({ destinationId: folderId, }); } catch (moveError) { logger.warn("Failed to move message to folder", { folderId, messageId: message.id, threadId, error: moveError instanceof Error ? moveError.message : moveError, }); return null; } }, ); await Promise.allSettled(movePromises); } else { // If no messages found, try treating threadId as a messageId await client.getClient().api(`/me/messages/${threadId}/move`).post({ destinationId: folderId, }); } // Publish the archive action try { await publishArchive({ ownerEmail, threadId, actionSource, timestamp: Date.now(), }); } catch (publishError) { logger.error("Failed to publish action to move thread to folder", { folderId, email: ownerEmail, threadId, error: publishError, }); } }Then simplify the error handling in
archiveThread:} catch (error) { // If the filter fails, try a different approach logger.warn("Filter failed, trying alternative approach", { threadId, error, }); try { - // Try to get messages by conversationId using a different endpoint - const messages = await client - .getClient() - .api("/me/messages") - .select("id") - .get(); - - // ... rest of the fallback logic ... + await moveThreadMessagesFallback({ + client, + threadId, + folderId, + ownerEmail, + actionSource, + }); return { status: 200 }; } catch (directError) { logger.error("Failed to move thread to folder", { folderId, threadId, error: directError, }); throw directError; } }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/utils/outlook/label.ts(7 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/utils/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.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/outlook/label.ts
🧬 Code Graph Analysis (1)
apps/web/utils/outlook/label.ts (3)
apps/web/utils/outlook/client.ts (1)
OutlookClient(19-63)packages/tinybird/src/publish.ts (1)
TinybirdEmailAction(12-12)apps/web/utils/outlook/message.ts (1)
WELL_KNOWN_FOLDERS(14-21)
| emailAccountId: string, | ||
| addDigest: boolean, | ||
| ) { | ||
| const emailAccount = await prisma.emailAccount.findUnique({ |
There was a problem hiding this comment.
-
You can just pass in provider. I'd really think before making db calls unnecessarily like this. We're passing provider around all over the app. There's a very high chance the function calling this already has that info.
-
not actually sure how critical it is to pass provider down. i guess it's just to check we don't create a folder rule for gmail users?
| url: a.fields?.webhookUrl, | ||
| folderName: a.fields?.folderName, | ||
| ...(provider === "microsoft" && { | ||
| folderName: a.fields?.folderName as string | null, |
There was a problem hiding this comment.
we avoid as here?
or folderName || null might fix it?
| error: publishResult.reason, | ||
| }); | ||
| logger.error( | ||
| `Failed to publish action to move thread to folder ${folderId}`, |
There was a problem hiding this comment.
i've made this note before. if you're adding logs, then don't use variables in it. harder to analyse logs this way
| return { status: 404, message: "Thread not found" }; | ||
| } | ||
| logger.error(`Failed to move thread to ${folderName}`, { | ||
| logger.error(`Failed to move thread to folder ${folderId}`, { |
| for (const folder of folderList) { | ||
| if (!folder.childFolders || folder.childFolders.length === 0) { | ||
| try { | ||
| const childFolders = await getFolders( |
There was a problem hiding this comment.
why pass path in? wouldn't it be clearer to do getChildFolders?
| return response.value; | ||
| }; | ||
|
|
||
| const folders = await getFolders("/me/mailFolders"); |
There was a problem hiding this comment.
no need to pass in path. see comment below
| subject: action.subject, | ||
| webhookUrl: action.url, | ||
| folderName: action.folderName, | ||
| folderName: provider === "microsoft" ? action.folderName : undefined, |
There was a problem hiding this comment.
might make sense to have isMicrosoft helper instead of magic string each time.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes / Validation
Chores