Conversation
|
@jshwrnr 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. |
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds an account-scoped bulk-archive feature: redirect routes, account pages, UI components (cards, tab, progress, setup), API endpoints and actions, Redis helper, classifier + tests, provider-based batch handler, Prisma migration, and assorted UI/util updates. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser as Client UI
participant API as /api/user/categorize/senders/categorized
participant DB
participant Queue
participant Provider as Email Provider
User->>Browser: Open Bulk Archive page
Browser->>API: GET categorized senders (SWR, polled)
API->>DB: Query senders, categories, account flags
DB-->>API: senders, categories, autoCategorizeSenders
API-->>Browser: categorized data
Browser->>Browser: derive emailGroups & classify candidates
User->>Browser: Select senders & click Archive
Browser->>Queue: addToArchiveSenderQueue(selected senders)
Queue->>Provider: fetch threads / perform provider-specific actions
Provider-->>Queue: thread data / ack
Queue->>DB: update archive status
DB-->>Queue: persisted
Browser->>Browser: Poll status and show progress/completion
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Fix all issues with AI Agents 🤖
In @apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx:
- Around line 125-130: The BulkArchiveTab component's props include an unused
categories parameter; remove categories from the component signature and any
related type annotation so the props only accept emailGroups (adjust the
exported function signature BulkArchiveTab({ emailGroups }: { emailGroups:
EmailGroup[] }) and any callers/usage if they pass categories), or
alternatively, if categories are needed, use the categories variable inside
BulkArchiveTab where appropriate; pick one approach and update the function
signature and type annotations accordingly to eliminate the unused prop warning.
🧹 Nitpick comments (7)
apps/web/app/(app)/early-access/page.tsx (1)
24-24: Remove unnecessary fragment wrapper.Lines 24 and 68 wrap the Sender Categories card in a fragment (
<></>) that only contains a single conditional Card. Fragments are only needed when you have multiple sibling elements. If these commented-out card blocks are removed (per the previous comment), the fragment becomes unnecessary and should be removed.🔎 Proposed fix
{isGoogleProvider(provider) && ( - <> - <Card> + <Card> <CardHeader> <CardTitle>Sender Categories</CardTitle> <CardDescription>And at the end:
- {/* ... commented cards ... */} - </> + {/* ... commented cards removed ... */}Also applies to: 68-68
apps/web/components/BulkArchiveCards.tsx (2)
63-118: Move helper function to the bottom of the file.Per coding guidelines, helper functions should be placed at the bottom of files, not the top. Consider moving
getCategoryIconafter theExpandedEmailscomponent.
221-236: Consider parallelizing archive queue additions.Using
awaitinside aforloop processes senders sequentially, which may be slower than necessary. Per coding guidelines, avoidawaitinside loops when parallel execution is safe.🔎 Proposed fix using Promise.all
const archiveCategory = async (categoryName: string, e: React.MouseEvent) => { e.stopPropagation(); const senders = groupedEmails[categoryName] || []; const selectedToArchive = senders.filter( (s) => selectedSenders[s.address] !== false, ); - for (const sender of selectedToArchive) { - await addToArchiveSenderQueue({ + await Promise.all( + selectedToArchive.map((sender) => + addToArchiveSenderQueue({ sender: sender.address, emailAccountId, - }); - } + }), + ), + ); setArchivedCategories((prev) => ({ ...prev, [categoryName]: true })); };apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (1)
23-33: Consider extracting shared data-fetching logic.This data-fetching pattern (senders with categories + user categories with rules) is duplicated between
bulk-archive/page.tsxandquick-bulk-archive/page.tsx. Consider extracting to a shared utility function.// Example: apps/web/utils/bulk-archive.server.ts export async function getBulkArchiveData(emailAccountId: string) { const [senders, categories] = await Promise.all([ prisma.newsletter.findMany({ where: { emailAccountId, categoryId: { not: null } }, select: { id: true, email: true, category: { select: { id: true, description: true, name: true } }, }, }), getUserCategoriesWithRules({ emailAccountId }), ]); return { senders, categories }; }apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (3)
226-239: Consider parallelizing archive queue additions.Same issue as in
BulkArchiveCards.tsx- usingawaitinside aforloop processes senders sequentially. Per coding guidelines, avoidawaitinside loops when parallel execution is safe.🔎 Proposed fix using Promise.all
const archiveSelected = async () => { setIsArchiving(true); const toArchive = candidates.filter((c) => selectedSenders[c.address]); - for (const candidate of toArchive) { - await addToArchiveSenderQueue({ + await Promise.all( + toArchive.map((candidate) => + addToArchiveSenderQueue({ sender: candidate.address, emailAccountId, - }); - } + }), + ), + ); setArchiveComplete(true); setIsArchiving(false); };
48-90: Move helper function to the bottom of the file.Per coding guidelines, helper functions should be placed at the bottom of files. Consider moving
getArchiveCandidatesafter theExpandedEmailscomponent. The TODO comment suggests this is temporary, so you may want to address placement when implementing the actual AI-derived data.
483-681: Significant code duplication with BulkArchiveCards.tsx.
SenderRow,ArchiveStatus, andExpandedEmailsare nearly identical in bothBulkArchiveTab.tsxandBulkArchiveCards.tsx. Consider extracting these to shared components in a common location (e.g.,@/components/bulk-archive/) to reduce duplication and ensure consistent behavior.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/early-access/page.tsxapps/web/app/(marketing)apps/web/components/BulkArchiveCards.tsxapps/web/components/EmailCell.tsx
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/app/(app)/*/page.tsx
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/*/page.tsx: Create new pages atapps/web/app/(app)/PAGE_NAME/page.tsx
Pages are Server components so you can load data into them directlyCreate new pages at
apps/web/app/(app)/PAGE_NAME/page.tsxwith components either colocated in the same folder or inpage.tsx
Files:
apps/web/app/(app)/early-access/page.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(marketing)apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks in React
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Follow consistent naming conventions using PascalCase for components
Use LoadingContent component for async data with loading and error states
Use React Hook Form with Zod validation for form handling
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in forms
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/components/EmailCell.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
Use
LoadingContentcomponent to consistently handle loading and error states, passingloading,error, andchildrenpropsUse PascalCase for component file names (e.g.,
components/Button.tsx)
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/EmailCell.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/EmailCell.tsx
🧠 Learnings (17)
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.
Applied to files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/app/(app)/early-access/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Early access features are automatically displayed on the Early Access page (`/early-access`) through the `EarlyAccessFeatures` component, requiring no manual configuration
Applied to files:
apps/web/app/(app)/early-access/page.tsx
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Early access features are automatically displayed on the Early Access page (`/early-access`) through the `EarlyAccessFeatures` component with no manual configuration needed
Applied to files:
apps/web/app/(app)/early-access/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx` with components either colocated in the same folder or in `page.tsx`
Applied to files:
apps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : Components for the page are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : Components for pages are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(app)/(redirects)/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/app/**/*.{ts,tsx} : Follow NextJS app router structure with (app) directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/{pages,routes,components}/**/*.{ts,tsx} : Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/*/page.tsx : Pages must be Server components that load data directly
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Pages are Server components so you can load data into them directly
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
🧬 Code graph analysis (4)
apps/web/app/(app)/early-access/page.tsx (2)
apps/web/components/new-landing/common/Card.tsx (3)
Card(67-105)CardHeader(22-54)CardContent(10-12)apps/web/components/new-landing/icons/Link.tsx (1)
Link(1-18)
apps/web/app/(app)/(redirects)/bulk-archive/page.tsx (2)
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (1)
BulkArchivePage(15-65)apps/web/utils/account.ts (1)
redirectToEmailAccountPath(136-160)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (5)
apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx (1)
QuickBulkArchivePage(3-5)apps/web/utils/email-account.ts (1)
checkUserOwnsEmailAccount(5-22)apps/web/utils/category.server.ts (1)
getUserCategoriesWithRules(24-39)apps/web/app/(app)/[emailAccountId]/PermissionsCheck.tsx (1)
PermissionsCheck(12-35)apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (1)
BulkArchiveTab(125-481)
apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx (1)
apps/web/utils/account.ts (1)
redirectToEmailAccountPath(136-160)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Macroscope - Correctness Check
🔇 Additional comments (10)
apps/web/app/(app)/early-access/page.tsx (1)
40-67: The commented-out code is intentional and requires no changes.The bulk archive features are fully implemented and routable. The early access page correctly uses PostHog's
EarlyAccessFeaturescomponent, which manages features through PostHog's dedicated early access/beta enrollment system—not through feature flags. The commented cards represent features awaiting PostHog early access configuration. Once configured in PostHog, they will be automatically displayed through theEarlyAccessFeaturescomponent. No feature flag hooks or code changes are needed at this time.apps/web/app/(marketing) (1)
1-1: Incomplete review context: actual code files not provided.The file shows only a git submodule pointer update, but the AI summary references multiple new implementation files (redirect pages, bulk-archive pages, BulkArchiveCards component, BulkArchiveTab component, etc.) that are not provided in this review context.
To perform a thorough code review, please provide the actual implementation files mentioned in the summary so I can assess:
- Correctness of page logic and data validation
- Client-side component implementation and state management
- Adherence to Next.js best practices (e.g., the coding guideline about avoiding next/head in pages/_document.js)
- Security and ownership validation in the new routes
- Error handling and edge cases
apps/web/components/EmailCell.tsx (1)
17-23: LGTM!Clean implementation that avoids redundant subtitle display when the name and email are identical (e.g., plain email addresses without a display name).
apps/web/app/(app)/(redirects)/bulk-archive/page.tsx (1)
1-5: LGTM!Follows the established redirect pattern and correctly uses the
redirectToEmailAccountPathutility. Based on learnings, the page structure follows project conventions.apps/web/components/BulkArchiveCards.tsx (2)
1-61: LGTM!Correct use of
"use client"directive, lodash import pattern following coding guidelines (specific function import), and properly scoped local types.
556-660: LGTM!
ExpandedEmailscorrectly uses theuseThreadshook (SWR pattern per coding guidelines) and properly handles loading, error, and empty states. The snippet sanitization logic appropriately removes invisible characters.apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx (1)
1-5: LGTM!Follows the established redirect pattern consistently with the bulk-archive redirect page.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (1)
1-65: LGTM!Well-structured server component following Next.js app router patterns. Correctly uses parallel data fetching with
Promise.all, ownership validation viacheckUserOwnsEmailAccount, and proper data transformation before passing to the client component. Based on learnings, this follows the page structure conventions.apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (1)
1-65: LGTM on the implementation.Correctly follows the established server component patterns with ownership validation, parallel data fetching, and proper component composition.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (1)
241-288: LGTM on state handling.Good handling of empty state and archive completion state with appropriate user feedback. The reset functionality after archiving is a nice UX touch.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
4 issues found across 9 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx">
<violation number="1" location="apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx:226">
P1: Missing error handling in async function. If `addToArchiveSenderQueue` fails, the UI will be stuck in the archiving state with the button permanently disabled. Wrap in try/catch and reset state in finally block.</violation>
<violation number="2" location="apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx:627">
P2: Potential runtime error if `thread.messages` is empty. Consider adding a guard to skip threads without messages.</violation>
</file>
<file name="apps/web/components/BulkArchiveCards.tsx">
<violation number="1" location="apps/web/components/BulkArchiveCards.tsx:602">
P1: Potential crash: `thread.messages[0]` is accessed without checking if the array is empty. If `messages` is empty, this will throw a runtime error when accessing `.subject` on undefined.</violation>
<violation number="2" location="apps/web/components/BulkArchiveCards.tsx:648">
P2: Dead code: The 'more emails' section will never be shown. Since `useThreads` is called with `limit: 5`, `data.threads.length` will always be <= 5, making `totalCount > displayedCount` always false. If you want to show a 'more' indicator, you need the API to return a total count separately from the paginated results.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
Show resolved
Hide resolved
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (1)
30-63: UsetoastErrorutility for consistent error handling.The error handling implementation works but doesn't follow the established pattern. The coding guidelines specify using
toastErrorfrom@/components/Toastfor consistency across the codebase.🔎 Suggested refactor
+import { toastError, toastSuccess } from "@/components/Toast"; -import { toast } from "sonner"; try { // ... existing code ... - toast.success( + toastSuccess({ + description: categorizeResult?.data?.totalUncategorizedSenders ? `Categorizing ${categorizeResult.data.totalUncategorizedSenders} senders... This may take a few minutes.` : "No uncategorized senders found.", - ); + }); } catch (error) { - toast.error( - `Failed to enable feature: ${error instanceof Error ? error.message : "Unknown error"}`, - ); + toastError({ + description: `Failed to enable feature: ${error instanceof Error ? error.message : "Unknown error"}`, + }); setIsBulkCategorizing(false); }As per coding guidelines, use
toastErrorandtoastSuccessfrom@/components/Toastfor error handling in async operations.apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (1)
6-6: Consider moving ArchiveProgress to a shared location.The component is imported from the
bulk-unsubscribefolder while this page is inbulk-archive. IfArchiveProgressis intended to be shared across features, consider moving it to a common location like@/components/or@/app/(app)/[emailAccountId]/components/to improve discoverability and reduce cross-feature coupling.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/utils/actions/categorize.ts
🧰 Additional context used
📓 Path-based instructions (28)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks in React
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Follow consistent naming conventions using PascalCase for components
Use LoadingContent component for async data with loading and error states
Use React Hook Form with Zod validation for form handling
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in forms
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/utils/actions/*.ts
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
apps/web/utils/actions/*.ts: Usenext-safe-actionwith Zod schemas for all server actions (create/update/delete mutations), storing validation schemas inapps/web/utils/actions/*.validation.ts
Server actions should use 'use server' directive and automatically receive authentication context (emailAccountId) from theactionClient
apps/web/utils/actions/*.ts: Create corresponding server action implementation files using the naming conventionapps/web/utils/actions/NAME.tswith 'use server' directive
Use 'use server' directive at the top of server action implementation files
Implement all server actions using thenext-safe-actionlibrary with actionClient, actionClientUser, or adminActionClient for type safety and validation
UseactionClientUserwhen only authenticated user context (userId) is needed
UseactionClientwhen both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
UseadminActionClientfor actions restricted to admin users
Add metadata with a meaningful action name using.metadata({ name: "actionName" })for Sentry instrumentation and monitoring
Use.schema()method with Zod validation schemas from corresponding.validation.tsfiles in next-safe-action configuration
Access context (userId, emailAccountId, etc.) via thectxobject parameter in the.action()handler
UserevalidatePathorrevalidateTagfrom 'next/cache' within server action handlers when mutations modify data displayed elsewhere
Files:
apps/web/utils/actions/categorize.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/actions/**/*.ts: Server actions must be located inapps/web/utils/actionsfolder
Server action files must start withuse serverdirective
apps/web/utils/actions/**/*.ts: Use proper error handling with try/catch blocks
Usenext-safe-actionwith Zod schemas for server actions to handle mutations
UserevalidatePathin server actions for cache invalidation after mutations
Files:
apps/web/utils/actions/categorize.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/actions/categorize.ts
apps/web/utils/actions/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Infer types from Zod schemas using
z.infer<typeof schema>instead of duplicating as separate interfaces
Files:
apps/web/utils/actions/categorize.ts
apps/web/**/*.{example,ts,json}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Add environment variables to
.env.example,env.ts, andturbo.json
Files:
apps/web/utils/actions/categorize.tsapps/web/app/api/user/categorize/senders/categorized/route.ts
apps/web/app/api/**/route.ts
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
apps/web/app/api/**/route.ts: Create GET API routes usingwithAuthorwithEmailAccountmiddleware inapps/web/app/api/*/route.ts, export response types asGetExampleResponsetype alias for client-side type safety
Always export response types from GET routes asGet[Feature]Responseusing type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions withnext-safe-actioninstead
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
**/app/**/route.ts
📄 CodeRabbit inference engine (.cursor/rules/get-api-route.mdc)
**/app/**/route.ts: Always wrap GET API route handlers withwithAuthorwithEmailAccountmiddleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes usingAwaited<ReturnType<typeof functionName>>pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses usingNextResponse.json()in GET API routes
Do not use try/catch blocks in GET API route handlers when usingwithAuthorwithEmailAccountmiddleware, as the middleware handles error handling
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
apps/web/app/**/[!.]*/route.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Use kebab-case for route directories in Next.js App Router (e.g.,
api/hello-world/route)
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
apps/web/app/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,tsx}: API routes must usewithAuth,withEmailAccount, orwithErrormiddleware for authentication
All database queries must include user scoping withemailAccountIdoruserIdfiltering in WHERE clauses
Request parameters must be validated before use; avoid direct parameter usage without type checking
Use generic error messages instead of revealing internal details; throwSafeErrorinstead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields usingselectin database queries to prevent unintended information disclosure
Cron endpoints must usehasCronSecretorhasPostCronSecretto validate cron requests and prevent unauthorized access
Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
**/app/api/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/app/api/**/*.ts: ALL API routes that handle user data MUST use appropriate middleware: usewithEmailAccountfor email-scoped operations, usewithAuthfor user-scoped operations, or usewithErrorwith proper validation for public/custom auth endpoints
UsewithEmailAccountmiddleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation usingemailAccountId
UsewithAuthmiddleware for user-level operations such as user settings, API keys, and referrals that use onlyuserId
UsewithErrormiddleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST usehasCronSecret()orhasPostCronSecret()validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret usinghasCronSecret(request)orhasPostCronSecret(request)and capture unauthorized attempts withcaptureException()
Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations
Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
**/{app,pages}/**/{route,+page}.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/{app,pages}/**/{route,+page}.{ts,tsx}: Use middleware wrappers (withError, withAuth, withEmailAccount, withEmailProvider) that automatically create loggers with request context in API routes
Enrich logger context within route handlers using logger.with() to add request-specific fields like messageId
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
apps/web/app/api/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/app/api/**/*.ts: Create GET API routes wrapped withwithAuthorwithEmailAccountmiddleware for fetching data
Export response types from GET API routes usingexport type GetXResponse = Awaited<ReturnType<typeof getData>>
Files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
🧠 Learnings (16)
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`) that wrap `useSWR`, handle the API endpoint URL, and return data, loading state, error state, and the `mutate` function
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Create GET API routes using `withAuth` or `withEmailAccount` middleware in `apps/web/app/api/*/route.ts`, export response types as `GetExampleResponse` type alias for client-side type safety
Applied to files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/app/api/**/*.ts : Create GET API routes wrapped with `withAuth` or `withEmailAccount` middleware for fetching data
Applied to files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Always wrap GET API route handlers with `withAuth` or `withEmailAccount` middleware for consistent error handling and authentication in Next.js App Router
Applied to files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
📚 Learning: 2025-11-25T14:37:11.434Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:11.434Z
Learning: Applies to **/app/**/route.ts : Infer and export the response type for GET API routes using `export type GetResponse = Awaited<ReturnType<typeof getData>>` pattern in Next.js
Applied to files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
📚 Learning: 2025-11-25T14:37:22.822Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/get-api-route.mdc:0-0
Timestamp: 2025-11-25T14:37:22.822Z
Learning: Applies to **/app/**/route.ts : Infer and export response type for GET API routes using `Awaited<ReturnType<typeof functionName>>` pattern in Next.js
Applied to files:
apps/web/app/api/user/categorize/senders/categorized/route.ts
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx` with components either colocated in the same folder or in `page.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : Components for the page are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : Components for pages are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/app/**/*.{ts,tsx} : Follow NextJS app router structure with (app) directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/{pages,routes,components}/**/*.{ts,tsx} : Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
🧬 Code graph analysis (5)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx (2)
apps/web/app/api/user/categorize/senders/progress/route.ts (1)
CategorizeProgress(5-7)apps/web/components/ProgressPanel.tsx (1)
ProgressPanel(8-64)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx (5)
apps/web/utils/category.server.ts (1)
CategoryWithRules(4-11)apps/web/app/api/user/categorize/senders/categorized/route.ts (1)
CategorizedSendersResponse(6-8)apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx (1)
BulkArchiveProgress(10-84)apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (1)
AutoCategorizationSetup(20-87)apps/web/components/BulkArchiveCards.tsx (1)
BulkArchiveCards(120-457)
apps/web/utils/actions/categorize.ts (3)
apps/web/utils/actions/safe-action.ts (1)
actionClient(74-129)apps/web/utils/categories.ts (1)
defaultCategory(1-105)apps/web/utils/path.ts (1)
prefixPath(1-4)
apps/web/app/api/user/categorize/senders/categorized/route.ts (2)
apps/web/utils/category.server.ts (1)
getUserCategoriesWithRules(24-39)apps/web/utils/middleware.ts (1)
withEmailAccount(408-426)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (3)
apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(79-89)apps/web/utils/actions/categorize.ts (2)
createAllDefaultCategoriesAction(291-310)bulkCategorizeSendersAction(29-101)apps/web/components/new-landing/common/Card.tsx (3)
Card(67-105)CardHeader(22-54)CardContent(10-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: cubic · AI code reviewer
🔇 Additional comments (7)
apps/web/utils/actions/categorize.ts (1)
291-310: LGTM! Well-structured server action.The implementation correctly follows the server action patterns:
- Uses
actionClientwith proper metadata- Includes
emailAccountIdin database operations for proper scoping- Uses
skipDuplicatesto handle idempotent category creation- Revalidates the correct path after mutation
apps/web/app/api/user/categorize/senders/categorized/route.ts (1)
1-37: LGTM! Excellent adherence to API route patterns.This GET route follows all the coding guidelines:
- Wrapped with
withEmailAccountmiddleware for authentication and error handling- Response type properly exported using
Awaited<ReturnType<...>>pattern- Database queries correctly scoped to
emailAccountIdto prevent IDOR- Parallel data fetching with
Promise.allfor performanceBased on learnings, GET API routes should use
withAuthorwithEmailAccountmiddleware and export response types for client-side type safety.apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx (1)
10-84: LGTM! Well-implemented progress tracking with smooth UX.The implementation demonstrates good practices:
- SWR polling controlled by categorization state
- Fake progress animation to provide responsive feedback
- Proper effect cleanup to prevent memory leaks
- Graceful completion handling with delay for user feedback
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx (1)
19-62: LGTM! Clean data orchestration with proper SWR patterns.The component correctly orchestrates the bulk archive flow:
- Uses SWR with
fallbackDatafor initial server-side data and conditional polling- Memoizes expensive computations appropriately
- Delegates to specialized child components for clean separation of concerns
- Properly handles data refresh on completion
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (3)
11-12: LGTM!The route configuration is appropriate for a dynamic, potentially long-running bulk archive operation.
14-20: LGTM!Correctly implements Next.js 15's async params API and performs ownership validation before data fetching.
22-55: LGTM!The parallel data fetching with
Promise.allis efficient, and the component composition correctly separates server and client boundaries usingClientOnlywrappers. Initial data is properly passed to the client component for hydration.
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/utils/actions/categorize.ts">
<violation number="1" location="apps/web/utils/actions/categorize.ts:309">
P2: Return value `created` doesn't reflect actual records created when duplicates are skipped. Use the count returned by `createMany` for accurate reporting.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Fix all issues with AI Agents 🤖
In @apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:
- Line 10: The double cast "metadata as unknown as AlternativeComparisonProps"
bypasses TS safety; replace it by giving the MDX metadata a proper type or
validating it at runtime: import or declare a typed metadata (e.g.,
export/import a Metadata type from content.mdx) and use "satisfies
AlternativeComparisonProps" when assigning md in the Content function, or create
a runtime validator (e.g., Zod schema) and call schema.parse(metadata) to
produce md; update references to md, metadata, and AlternativeComparisonProps
accordingly.
In @apps/web/app/(marketing)/(landing)/creator/page.tsx:
- Around line 53-55: Replace the plain <a> wrapper with Next.js Link and avoid
nesting a native <button> inside an anchor: either (a) use Next.js Link (import
Link from "next/link") and render the interactive element as the anchor by
making the Button render as an anchor (e.g., if your Button supports an asChild
prop, pass asChild and wrap Button with Link) or (b) if Button cannot render as
an anchor, style the Link to look/behave like the button (give Link the same
classes/props) and render <Link
href="/case-studies/study/clicks-talent-saves-60-hours-weekly">…</Link> instead
of <a><Button/></a>, ensuring no <button> is nested inside an anchor and
preserving client-side navigation.
In @apps/web/app/(marketing)/(landing)/error.tsx:
- Line 9: The ErrorBoundary component currently types its props as any and omits
the reset function; update the signature of ErrorBoundary to accept a properly
typed props object (replace any with an interface or the Next.js
ErrorBoundaryProps type) that includes both error and reset (e.g., { error:
Error; reset: () => void } or import Next.js ErrorBoundaryProps), call or wire
the provided reset function from a retry UI control (e.g., a "Retry" button) so
clicking it invokes reset, and ensure the component uses the typed error for
rendering instead of any.
In @apps/web/app/(marketing)/blog/components/TableOfContents.tsx:
- Around line 18-38: The effect is re-running because the headings array is
re-created on every render; memoize the computed headings before the useEffect
(e.g., replace the ad-hoc filtered/derived headings with a useMemo that returns
the array of heading blocks and depends only on the stable inputs), then have
the useEffect depend on that memoized variable (the same name `headings` or
rename to `memoizedHeadings`) so IntersectionObserver is only recreated when
actual heading content changes; locate the code that builds `headings` and wrap
it in useMemo and update the useEffect dependency accordingly.
In @apps/web/app/(marketing)/blog/components/TryInboxZero.tsx:
- Around line 8-29: The Link currently wraps a Button, causing nested
interactive elements; remove the Button component inside TryInboxZero.tsx and
instead style the Link itself (or the anchor it renders) to look and behave like
the call-to-action: move the "w-full" and the Button's blue variant styles onto
the Link/anchor (and add an appropriate aria-label if needed), ensure the Link
is a block-level element that contains the image and copy, and remove the nested
focusable Button so there are no nested interactive controls.
In @apps/web/app/(marketing)/blog/page.tsx:
- Around line 261-268: The Image usage is using deprecated props layout and
objectFit; update the <Image> component (the instance rendering post.imageUrl
and alt={post.title}) to use the Next.js 13+ API by removing layout and
objectFit, adding the fill boolean prop, and apply object-fit via CSS or inline
style (e.g., className="object-cover" on the Image or style={{objectFit:
'cover'}}) while keeping the surrounding wrapper div with position/size so the
fill behavior works.
In @apps/web/app/(marketing)/blog/post/[slug]/Post.tsx:
- Around line 169-175: The external Link element rendering the author Twitter
profile (the <Link ... href={`https://x.com/${post.authorTwitter}`}
target="_blank`}> block) lacks rel attributes; add rel="noopener noreferrer" to
that Link so external links opened with target="_blank" cannot access
window.opener and follow security/accessibility best practices.
- Around line 119-131: The custom Portable Text mark renderer marks.link reads
value?.href which may be undefined and can cause broken links; update the
marks.link handler to derive href with a safe fallback (e.g., const href =
value?.href || '#') and pass a non-null string into the Link component, and
optionally add rel/target handling or skip rendering as a link when href is
falsy; change the link renderer in marks.link to compute the fallback href
before returning the Link so the component always receives a valid href.
In @apps/web/app/(marketing)/blog/post/alternatives-to-skiff-mail/content.mdx:
- Line 3: Fix the typo in the MDX frontmatter description: replace "aqcuired"
with "acquired" in the description string (the metadata key "description" in
content.mdx) so it reads "Notion recently acquired Skiff Mail and is sunsetting
it in six months. Here are some good alternatives to consider for your email
needs."
In @apps/web/app/(marketing)/blog/post/alternatives-to-skiff-mail/page.tsx:
- Around line 7-8: Fix the typo in the page metadata by updating the description
field string in page.tsx: change "aqcuired" to "acquired" in the description
property so it reads "Notion recently acquired Skiff Mail and is sunsetting it
in six months. Here are some good alternatives to consider for your email
needs."
In @apps/web/app/(marketing)/blog/post/bulk-unsubscribe-from-emails/content.mdx:
- Line 55: Replace the outdated "Gsuite" branding with "Google Workspace"
wherever it appears in the MDX content (specifically update the string "Gsuite"
found in the sentence beginning with "Support and Compatibility:" and the other
occurrence noted at line 71), ensuring both instances are changed to "Google
Workspace" and preserving the surrounding sentence wording and punctuation.
In
@apps/web/app/(marketing)/blog/post/how-my-open-source-saas-hit-first-on-product-hunt/content.mdx:
- Line 124: Fix the typo in the blog post sentence that currently reads
"monthtly" — replace "monthtly" with "monthly" so the sentence reads "...pay a
$50,000 monthly subscription for a SaaS product..." (locate the sentence in the
post content where the misspelling appears).
- Line 50: Replace the misspelled word "contributers" with the correct spelling
"contributors" in the blog post sentence and also change "its" to "it's" on the
same line as noted; update the MDX content string that contains the sentence
"Being open source also means anyone can contribute..." so the line reads
"...developers from all over the world contributing to making it better. Top
open source projects have hundreds of contributors." (and ensure "it's" is
corrected where indicated).
In
@apps/web/app/(marketing)/blog/post/inbox-zero-benefits-for-mental-health/page.tsx:
- Around line 17-18: The StructuredData component's headline prop contains a
typo ("Inbox Zero Benefitsfor Mental Health"); update the headline string passed
to StructuredData (the headline prop in the page file where StructuredData is
used) to "Inbox Zero Benefits for Mental Health" so the SEO structured data
contains the correct spacing.
In
@apps/web/app/(marketing)/blog/post/why-build-an-open-source-saas/content.mdx:
- Line 126: Fix the typo "monthtly" to "monthly" in the blog content string (the
sentence starting "But if a company is being asked to pay a $50,000 monthtly
subscription..."); update the MDX text to read "monthly" so the sentence becomes
"...pay a $50,000 monthly subscription for a SaaS product...".
- Line 52: Fix the typo in the blog paragraph: replace "contributers" with the
correct spelling "contributors" in the sentence "Top open source projects have
hundreds of contributers." in the content of the MDX file
(apps/web/app/(marketing)/blog/post/why-build-an-open-source-saas/content.mdx).
In @apps/web/app/(marketing)/case-studies/components/CaseStudy.tsx:
- Around line 73-82: The AvatarFallback logic assumes interviewee is a
non-empty, space-separated string and uses n[0], which can throw for empty or
undefined names; update the AvatarFallback expression in the CaseStudy component
to first guard for falsy interviewee, then split into words, filter out empty
strings (.filter(Boolean)), derive initials using safe indexing (e.g.,
word.charAt(0)), and limit the result to at most two characters (fallback to the
first two characters of the full name if no spaces) so initials generation is
robust.
In @apps/web/app/(marketing)/case-studies/components/CaseStudyCard.tsx:
- Around line 49-57: Replace the unstable array index key in CaseStudyCard's
keyResults mapping with a stable unique key: change key={index} to key={result}
in the JSX where keyResults.map((result, index) => ...) so each <div> uses the
result string as the key; if result may not be globally unique, combine it with
a stable identifier (e.g., `${result}-${someId}`) but prefer key={result} per
the review.
In @apps/web/app/(marketing)/case-studies/components/CaseStudyContent.tsx:
- Around line 51-57: The external Link component rendering the company link uses
target="_blank" without rel="noopener", which is a security risk; update the
Link element (the component rendering href={companyUrl}, target="_blank"} that
displays {company}) to include rel="noopener" (or rel="noopener noreferrer")
alongside target="_blank" so the opened page cannot access window.opener.
In @apps/web/app/(marketing)/sanity/lib/queries.ts:
- Around line 49-52: The GROQ query exported as postSlugsQuery is using the
wrong field name "date"; update the query inside postSlugsQuery to request
"publishedAt" instead of "date" so it matches the schema (modify the groq
template string in postSlugsQuery to include publishedAt).
- Around line 40-47: The GROQ queries reference a non-existent field named
"date"; update both recentPostsQuery and postSlugsQuery to use the schema's
datetime field "publishedAt" instead—replace order(date desc) with
order(publishedAt desc) and change any projected "date" entry to use publishedAt
(e.g., "date": publishedAt or rename the projection key to "publishedAt") so the
queries return the correct values.
In @apps/web/app/(marketing)/sanity/sanity.config.ts:
- Around line 1-31: The Sanity config references NEXT_PUBLIC_SANITY_PROJECT_ID /
NEXT_PUBLIC_SANITY_DATASET / NEXT_PUBLIC_SANITY_API_VERSION via the exported
symbols projectId, dataset, apiVersion; move the validation and exports for
these three vars out of the local sanity/env.ts into the main apps/web/env.ts
using a Zod schema under the client section (export client.projectId,
client.dataset, client.apiVersion), update imports in sanity.config.ts to use
those exports, delete or stop using the local sanity/env.ts, and then uncomment
and document these keys in .env.example and add them to turbo.json build
environment settings so the variables are centralized, validated, documented,
and included in the pipeline.
In @apps/web/app/(marketing)/sitemap.ts:
- Around line 103-119: The sitemap currently builds entries from hardcoded
staticUrls and from getBlogPosts(), which can produce duplicate URLs; update the
sitemap generation (the code that combines staticUrls with getBlogPosts()
results—look for the staticUrls variable and the function that calls
getBlogPosts() in apps/web/app/(marketing)/sitemap.ts) to deduplicate entries by
URL before returning: normalize URLs (trim/ensure consistent trailing
slash/lowercase if needed) and filter the combined array to keep only the first
occurrence of each url (e.g., using a Set or Map keyed by url) so duplicates are
removed regardless of source.
🟡 Minor comments (15)
apps/web/app/(marketing)/case-studies/components/CaseStudyCard.tsx-49-57 (1)
49-57: Replace array index with stable key.Using array indices as keys can cause rendering issues if the
keyResultsarray order changes. Since result strings should be unique within a case study, use the result text itself as the key.🔎 Proposed fix
- {keyResults.map((result, index) => ( + {keyResults.map((result) => ( <div - key={index} + key={result} className="flex items-center text-sm text-gray-700" >apps/web/app/(marketing)/case-studies/components/CaseStudy.tsx-73-82 (1)
73-82: Add defensive handling for initials generation.The fallback initials generation assumes
intervieweecontains space-separated words. If the name has no spaces or contains empty strings,n[0]could cause runtime errors.🔎 Proposed fix
<AvatarFallback> {interviewee .split(" ") + .filter((n) => n.length > 0) .map((n) => n[0]) .join("") - .toUpperCase()} + .toUpperCase() + .slice(0, 2)} </AvatarFallback>This filters empty strings and limits initials to 2 characters for better display.
apps/web/app/(marketing)/blog/post/how-my-open-source-saas-hit-first-on-product-hunt/content.mdx-124-124 (1)
124-124: Fix spelling error: "monthtly" → "monthly".🔎 Proposed fix
-But if a company is being asked to pay a $50,000 monthtly subscription for a SaaS product, the company may decide it's cheaper to self-host and not pay that fee. +But if a company is being asked to pay a $50,000 monthly subscription for a SaaS product, the company may decide it's cheaper to self-host and not pay that fee.apps/web/app/(marketing)/blog/post/how-my-open-source-saas-hit-first-on-product-hunt/content.mdx-50-50 (1)
50-50: Fix spelling error: "contributers" → "contributors".🔎 Proposed fix
-Open source projects also have strong communities built around them. More so than closed source products. As developers can meaningfully contribute to the project and impact its direction. And its important for contributors to have a place to collaborate. +Open source projects also have strong communities built around them. More so than closed source products. As developers can meaningfully contribute to the project and impact its direction. And it's important for contributors to have a place to collaborate.Note: Also fixed "its" → "it's" on the same line.
Committable suggestion skipped: line range outside the PR's diff.
apps/web/app/(marketing)/sitemap.ts-103-119 (1)
103-119: Potential duplicate blog URLs in sitemap.These hardcoded blog post URLs may be duplicated if the same posts are also returned by
getBlogPosts()from Sanity. Consider either:
- Removing these from
staticUrlsif they exist in Sanity, or- Deduplicating the final sitemap array by URL before returning.
🔎 Suggested deduplication
- return [...staticUrls, ...blogPosts]; + const allUrls = [...staticUrls, ...blogPosts]; + const seen = new Set<string>(); + return allUrls.filter((item) => { + if (seen.has(item.url)) return false; + seen.add(item.url); + return true; + });Committable suggestion skipped: line range outside the PR's diff.
apps/web/app/(marketing)/blog/post/alternatives-to-skiff-mail/page.tsx-7-8 (1)
7-8: Fix typo in description."aqcuired" should be "acquired".
🔎 Proposed fix
description: - "Notion recently aqcuired Skiff Mail and is sunsetting it in six months. Here are some good alternatives to consider for your email needs.", + "Notion recently acquired Skiff Mail and is sunsetting it in six months. Here are some good alternatives to consider for your email needs.",apps/web/app/(marketing)/blog/post/alternatives-to-skiff-mail/content.mdx-3-3 (1)
3-3: Fix typo in metadata description.The word "aqcuired" should be "acquired".
🔎 Proposed fix
- description: "Notion recently aqcuired Skiff Mail and is sunsetting it in six months. Here are some good alternatives to consider for your email needs.", + description: "Notion recently acquired Skiff Mail and is sunsetting it in six months. Here are some good alternatives to consider for your email needs.",apps/web/app/(marketing)/blog/post/why-build-an-open-source-saas/content.mdx-126-126 (1)
126-126: Typo: "monthtly" should be "monthly".-But if a company is being asked to pay a $50,000 monthtly subscription for a SaaS product, the company may decide it's cheaper to self-host and not pay that fee. +But if a company is being asked to pay a $50,000 monthly subscription for a SaaS product, the company may decide it's cheaper to self-host and not pay that fee.apps/web/app/(marketing)/blog/post/why-build-an-open-source-saas/content.mdx-52-52 (1)
52-52: Typo: "contributers" should be "contributors".-Being open source also means anyone can contribute to the project to improve it or fix bugs. Inbox Zero already has developers from all over the world contributing to making it better. Top open source projects have hundreds of contributers. +Being open source also means anyone can contribute to the project to improve it or fix bugs. Inbox Zero already has developers from all over the world contributing to making it better. Top open source projects have hundreds of contributors.apps/web/app/(marketing)/blog/post/inbox-zero-benefits-for-mental-health/page.tsx-17-18 (1)
17-18: Typo in headline: missing space between "Benefits" and "for".The StructuredData headline has a typo that will affect SEO structured data.
<StructuredData - headline="Inbox Zero Benefitsfor Mental Health" + headline="Inbox Zero Benefits for Mental Health" datePublished="2024-06-27T23:00:00+00:00"apps/web/app/(marketing)/blog/post/bulk-unsubscribe-from-emails/content.mdx-55-55 (1)
55-55: Consider updating "Gsuite" to current branding.Google rebranded G Suite to "Google Workspace" in 2020. Consider updating for accuracy:
-- **Support and Compatibility:** Currently, Inbox Zero supports Gmail and Gsuite email accounts, with plans to add other providers like Outlook in the future. +- **Support and Compatibility:** Currently, Inbox Zero supports Gmail and Google Workspace email accounts, with plans to add other providers like Outlook in the future.The same applies to line 71.
apps/web/app/(marketing)/blog/post/[slug]/Post.tsx-169-175 (1)
169-175: Addrel="noopener noreferrer"to external link.Per accessibility and security guidelines, external links with
target="_blank"should includerel="noopener noreferrer"to prevent the new page from accessingwindow.opener.🔎 Proposed fix
<Link href={`https://x.com/${post.authorTwitter}`} className="text-sm text-gray-500" target="_blank" + rel="noopener noreferrer" >apps/web/app/(marketing)/blog/post/[slug]/Post.tsx-119-131 (1)
119-131: Add fallback for potentially undefinedhref.
value?.hrefcould be undefined. Consider adding a fallback similar to the ReactMarkdown link handler.🔎 Proposed fix
marks: { link: ({ children, value }) => { - const href = value?.href; + const href = value?.href || "#"; return ( <Link href={href}apps/web/app/(marketing)/sanity/lib/queries.ts-49-52 (1)
49-52: Same field name inconsistency.The
postSlugsQueryalso referencesdatewhich should likely bepublishedAtto match the schema.🔎 Proposed fix
export const postSlugsQuery = groq`*[_type == "post"] { "slug": slug.current, - date + "date": publishedAt }`;apps/web/app/(marketing)/sanity/lib/queries.ts-40-47 (1)
40-47: Field name mismatch in GROQ queries:dateshould bepublishedAt.The
recentPostsQueryandpostSlugsQueryreference adatefield that doesn't exist in the postType schema. The schema definespublishedAtas the datetime field. This will cause these queries to return undefined values.🔎 Proposed fix
// Get 4 most recent posts -export const recentPostsQuery = groq`*[_type == "post"] | order(date desc) [0...4] { +export const recentPostsQuery = groq`*[_type == "post"] | order(publishedAt desc) [0...4] { "slug": slug.current, title, description, - date, + "date": publishedAt, "image": mainImage.asset->url }`; -export const postSlugsQuery = groq`*[_type == "post"] { +export const postSlugsQuery = groq`*[_type == "post"] { "slug": slug.current, - date + "date": publishedAt }`;Committable suggestion skipped: line range outside the PR's diff.
🧹 Nitpick comments (31)
apps/web/app/(marketing)/(landing)/block-cold-emails/page.tsx (1)
44-62: Consider using a semantic<section>element for the wrapper.The helper component is well-structured and correctly placed at the bottom of the file. The plain
<div>wrapper could optionally be a<section>element for improved HTML semantics, but this is a minor suggestion.Optional: Use semantic HTML
function ColdEmailBlockerFeatures() { return ( - <div> + <section> <OrganizedInbox title="Never read a cold email again" subtitle="Say goodbye to unsolicited outreach. Automatically filter sales pitches and cold emails so you only see messages that matter. Block out the noise and keep your inbox clean and focused on what matters." /> <Banner title="Customize what counts as a cold email"> Tell Inbox Zero what constitutes a cold email for you. It will block them based on your instructions. Automatically label cold emails so you can review them later if needed. </Banner> <StartedInMinutes title="Clean inbox, zero effort" subtitle="Set up your cold email blocker once and it works automatically. Keep your inbox clean and focused on what matters without manually filtering through spam and sales pitches." /> - </div> + </section> ); }apps/web/app/(marketing)/(landing)/layout.tsx (1)
3-7: Consider removingasyncif no asynchronous operations are performed.The function is marked
asyncbut doesn't useawaitor perform any asynchronous operations. Unless you're planning to add async data fetching later, marking this as a regular function would be more accurate and avoid unnecessary overhead.🔎 Proposed fix
-export default async function MarketingLandingLayout({ +export default function MarketingLandingLayout({ children, }: { children: React.ReactNode; }) {apps/web/app/(marketing)/(landing)/reply-zero-ai/page.tsx (1)
52-56: Consider tightening the Banner copy to eliminate redundancy.The Banner content repeats the title phrase "Never lose track of conversations" and contains similar phrasing about labeling emails. This could be more concise for better reader engagement.
💡 Suggested refinement
<Banner title="Never lose track of conversations"> - We label every email that needs a reply, so it's easy to focus on the - ones that matter. Never lose track of conversations. We label emails - awaiting replies and help you filter for overdue ones. + We label every email that needs a reply and help you filter for overdue + ones, so you can focus on what matters. </Banner>apps/web/app/(marketing)/(landing)/small-business/page.tsx (1)
54-80: Consider a semantic wrapper for better HTML structure.The outer
<div>wrapper could be replaced with a<section>element for better semantic HTML, or use a Fragment (<></>) if no wrapper styling is needed.🔎 Proposed semantic improvement
function SmallBusinessFeatures() { return ( - <div> + <section> <OrganizedInbox title="Never miss a customer or opportunity" subtitle="Every missed customer email is lost revenue. Our AI monitors your inbox 24/7, instantly identifying and prioritizing customer inquiries, sales opportunities, and important vendor communications so you can respond when it matters most. Automatically categorizes and prioritizes customer questions, complaints, and requests for faster response times." /> <Banner title="Work smarter, not harder"> Inbox Zero integrates seamlessly with your existing Gmail, so there's no learning curve. Just open your email and focus on growing your business while AI handles the routine communications. </Banner> <StartedInMinutes title="Deliver exceptional service at small business speed" subtitle="Inbox Zero learns your business style and suggests quick responses for customer inquiries, vendor communications, and partnership requests. Maintain professional relationships while focusing on what you do best - running your business." /> <Banner title="Scale your business without scaling your workload"> Focus on growth, not email management <br /> As your business grows, so does your email volume. Transform communication chaos into organized efficiency so you can serve more customers and capture more opportunities without working longer hours. </Banner> - </div> + </section> ); }apps/web/app/(marketing)/(landing)/enterprise/page.tsx (1)
19-48: LGTM! Well-structured page component.The component follows a clean composition pattern with proper mobile-first responsive design. The structure aligns well with other marketing landing pages in the codebase.
Optional: Consider content management for marketing copy
If marketing copy (subtitle, badge text) requires frequent updates, consider extracting these strings to constants or a headless CMS for easier content management without code changes. However, the current inline approach is acceptable and common for Next.js marketing pages.
apps/web/app/(marketing)/(landing)/bulk-email-unsubscriber/page.tsx (1)
53-71: Consider using semantic HTML for the wrapper.The wrapper
divcould be replaced with a<section>element or React Fragment for better semantic HTML structure.🔎 Proposed semantic improvement
function UnsubscribeFeatures() { return ( - <div> + <section> <OrganizedInbox title="Take back control of your inbox" subtitle="Effortlessly manage the newsletters in your inbox. One click unsubscribe, auto archive, or approve. No more drowning in marketing emails and newsletters you never signed up for." /> <Banner title="Bulk unsubscribe in seconds"> Review and unsubscribe from hundreds of newsletters at once. Inbox Zero identifies all your subscriptions and lets you clean them up with a single click. </Banner> <StartedInMinutes title="Clean inbox, minimal effort" subtitle="Set up automatic archiving for newsletters you want to keep but don't need cluttering your inbox. Or approve the ones you actually want to read. Your inbox, your rules." /> - </div> + </section> ); }apps/web/app/(marketing)/contact/page.tsx (1)
15-51: Overall implementation looks good, with a mobile responsiveness suggestion.The contact page is well-structured and follows Next.js patterns correctly. The layout, components, and email integration are all implemented properly.
However, consider making the padding responsive per coding guidelines:
The fixed padding
pb-40 pt-60(160px/240px) may be excessive on mobile devices. Consider using responsive variants to improve mobile UX:🔎 Proposed responsive padding adjustment
- <div className="pb-40 pt-60"> + <div className="pb-20 pt-32 md:pb-40 md:pt-60">As per coding guidelines: "Implement responsive design with Tailwind CSS using a mobile-first approach"
apps/web/app/(marketing)/case-studies/study/clicks-talent-saves-60-hours-weekly/content.mdx (1)
32-32: Consider polishing the marketing copy for professional tone.Static analysis identified several style opportunities in the testimonial quotes:
- Line 32: Consider replacing "stuff" with "content" or "emails" for a more professional tone
- Line 40: "really big budgets" could be "substantial budgets"
- Line 46: Remove filler phrases like "kind of" for more confident messaging
- Lines 92, 100: Replace "very easy/easily" with stronger alternatives like "straightforward" or "simple"
- Lines 106, 126: Use hyphens in compound modifiers ("full-time job", "time-saving")
These are direct quotes from the interviewee, so maintaining authenticity vs. polish is a judgment call.
Also applies to: 40-40, 46-46, 92-92, 100-100, 106-106, 126-126
apps/web/app/(marketing)/case-studies/components/CaseStudyCard.tsx (1)
66-66: Consider making read time dynamic.The "5 min read" text is hardcoded. If read times vary between case studies, consider adding a
readTimeprop toCaseStudyCardPropsor calculating it based on content length.apps/web/app/(marketing)/case-studies/page.tsx (1)
16-32: Consider data source scalability.The case studies are currently hardcoded. As you add more case studies, consider moving this data to a CMS (like Sanity, which is used elsewhere in the codebase) or a database to simplify content management.
apps/web/app/(marketing)/(alternatives)/best-perplexity-email-assistant-alternative/content.tsx (1)
9-20: Consider adding runtime validation for the metadata type assertion.The type assertion on Line 10 (
as unknown as AlternativeComparisonProps) is unsafe and could cause runtime errors if the MDX metadata doesn't match the expected shape. Consider using a Zod schema to validate the metadata before casting.🔎 Recommended approach using Zod validation
Define a Zod schema for AlternativeComparisonProps and validate the metadata:
+import { z } from "zod"; + +const alternativeComparisonPropsSchema = z.object({ + // Define the expected shape based on AlternativeComparisonProps + // Example fields: + // title: z.string(), + // description: z.string(), + // ... other fields +}); export function Content() { - const md = metadata as unknown as AlternativeComparisonProps; + const md = alternativeComparisonPropsSchema.parse(metadata); return ( <BlogLayout>This ensures type safety at runtime and provides clear error messages if the metadata shape is incorrect.
As per coding guidelines on Zod schema validation.
apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.mdx (1)
273-273: Inconsistent spelling: British vs American English.The text uses "customisation" (British spelling) in the feature comparison table, but the rest of the document appears to use American English. For consistency, consider using "customization" throughout.
🔎 Proposed fix
-| Full customisation/automation (e.g. forward / reply) | ✅ | ❌ | +| Full customization/automation (e.g. forward / reply) | ✅ | ❌ |apps/web/app/(marketing)/(alternatives)/components/AlternativeComparison.tsx (2)
88-102: Consider using stable keys instead of array indices.The pros and cons lists currently use array indices as keys, which goes against the coding guideline. While the impact is minimal for static lists like these, using content-based keys would be more robust.
🔎 Suggested approach for stable keys
<ul className="space-y-1"> - {pros.map((pro, index) => ( - <li key={index} className="text-sm text-gray-600"> + {pros.map((pro) => ( + <li key={pro} className="text-sm text-gray-600"> • {pro} </li> ))} </ul>Apply the same pattern to the cons list (lines 111-115). This assumes each pro/con string is unique, which is reasonable for marketing content.
Also applies to: 104-118
73-77: Consider making the savings message configurable.The "Save up to 50% with Inbox Zero" text is currently hardcoded. If different alternatives have different actual savings percentages, you might want to make this configurable via the props interface.
💡 Optional enhancement
Add an optional prop:
export interface AlternativeComparisonProps { // ... existing props inboxZeroPricing: string; + savingsMessage?: string; content: ReactNode;Then use it:
<div className="border-t pt-4"> <p className="text-sm text-gray-600"> - Save up to 50% with Inbox Zero + {savingsMessage ?? "Save up to 50% with Inbox Zero"} </p> </div>apps/web/app/(marketing)/blog/components/BlogPost.tsx (1)
5-11: Remove unused props or implement the commented date/author display.The
date,title, andauthorprops are declared but never used. The commented code on lines 16-21 suggests these were intended to be displayed but the implementation is incomplete.🔎 Suggested fixes
Option 1: Remove unused props
export function BlogPost(props: { - date: string; - title: string; - author: string; content: React.ReactNode; }) { const { content } = props;Option 2: Implement the date/author display
If you want to display the metadata, you'll need to:
- Import
formatandparseISOfromdate-fns- Uncomment the display code
+import { format, parseISO } from "date-fns"; import { Prose } from "@/app/(marketing)/blog/components/Prose"; import { BasicLayout } from "@/components/layouts/BasicLayout"; import { Card, CardContent } from "@/components/ui/card"; export function BlogPost(props: { date: string; title: string; author: string; content: React.ReactNode; }) { - const { content } = props; + const { date, author, content } = props; return ( <BasicLayout> <article className="mx-auto max-w-3xl px-6 py-20"> - {/* <div className="text-center"> + <div className="text-center"> <time dateTime={date} className="mb-1 text-xs text-gray-600"> {format(parseISO(date), "LLLL d, yyyy")} </time> <p className="text-sm font-semibold">by {author}</p> - </div> */} + </div>apps/web/app/(marketing)/sitemap.ts (1)
5-6: Top-level await for dynamic imports.Top-level await is supported in Next.js App Router, but these imports will execute at module load time regardless of whether Sanity credentials are configured. Consider moving these inside
getBlogPosts()to defer the import until needed and avoid potential initialization errors when Sanity is not configured.🔎 Suggested refactor
-const { sanityFetch } = await import("./sanity/lib/fetch"); -const { postSlugsQuery } = await import("./sanity/lib/queries"); - const logger = createScopedLogger("sitemap"); async function getBlogPosts() { // Skip Sanity fetch during build with dummy credentials or if marketing submodule is not available if ( !process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || process.env.NEXT_PUBLIC_SANITY_PROJECT_ID === "project123" ) { return []; // Return empty array directly } try { + const { sanityFetch } = await import("./sanity/lib/fetch"); + const { postSlugsQuery } = await import("./sanity/lib/queries"); + const posts = await sanityFetch<{ slug: string; date: string }[]>({apps/web/app/(marketing)/blog/post/alternatives-to-skiff-mail/content.mdx (1)
10-17: Consider minor style improvements for readability.A few optional style refinements:
- Line 10: Consider "until" instead of "till" for more formal tone
- Line 15: When "open source" is used as a compound adjective (modifying "email app"), it should be hyphenated: "open-source email app"
These are stylistic suggestions that can enhance the professional tone of the blog post.
apps/web/app/(marketing)/blog/components/ReadMore.tsx (1)
29-35: Consider adding lazy loading for improved performance.Since these images are likely below the fold in a "Read More" section, adding
loading="lazy"would improve initial page load performance by deferring image loading until they're needed.🔎 Proposed optimization
<Image src={post.image} alt={post.title} width={400} height={200} className="w-full object-cover" + loading="lazy" />apps/web/app/(marketing)/sanity/schemaTypes/authorType.ts (1)
10-20: Add required validation for critical author fields.The
nameandslugfields are essential for author documents but currently have no required validation. This could allow authors to be created without names or slugs, potentially breaking routing and display logic.🔎 Recommended validation
defineField({ name: "name", type: "string", + validation: (Rule) => Rule.required(), }), defineField({ name: "slug", type: "slug", options: { source: "name", }, + validation: (Rule) => Rule.required(), }),apps/web/app/(marketing)/blog/post/best-email-unsubscribe-app/content.mdx (1)
22-22: Optional: Consider hyphenating compound adjective.When "Open Source" is used as a compound adjective modifying "Option", it's conventionally hyphenated: "Open-Source Option". This is a minor style point and entirely optional.
apps/web/app/(marketing)/blog/post/inbox-zero-benefits-for-mental-health/content.mdx (1)
1-168: LGTM! Well-structured MDX content with proper metadata.The MDX file correctly exports metadata and provides comprehensive blog content. The static analysis tool flagged a minor prose style suggestion at line 162 ("regular routine" could be simplified to "routine"), but this is purely stylistic and optional.
apps/web/app/(marketing)/sanity/schemaTypes/blockContentType.ts (1)
34-34: Consider adding numbered list support.Currently only bullet lists are defined. If the blog content requires ordered lists, you may want to add support:
- lists: [{ title: "Bullet", value: "bullet" }], + lists: [ + { title: "Bullet", value: "bullet" }, + { title: "Numbered", value: "number" }, + ],apps/web/app/(marketing)/blog/components/TableOfContents.tsx (1)
13-16: Remove commented-out code or complete h3 support.The commented-out h3 filter (line 15) and the h3 conditional styling (line 48) are inconsistent. Since h3 headings are currently filtered out, the
heading.style === "h3"check on line 48 will never be true.Either remove the dead code or uncomment h3 support if needed.
apps/web/app/(marketing)/sanity/lib/image.ts (1)
1-11: LGTM! Clean image URL builder helper.This properly centralizes Sanity image URL generation. However, note that
Post.tsxcreates its own builder instance viaimageUrlBuilder(client)instead of using thisurlForhelper, resulting in duplicated instantiation.Consider using this
urlForhelper inPost.tsxto consolidate image URL generation:// In Post.tsx, replace: import imageUrlBuilder from "@sanity/image-url"; const builder = imageUrlBuilder(client); // With: import { urlFor } from "@/app/(marketing)/sanity/lib/image";apps/web/app/(marketing)/blog/post/[slug]/Post.tsx (2)
22-22: Consider using the centralizedurlForhelper.This creates a duplicate image URL builder instance. The
urlForhelper in@/app/(marketing)/sanity/lib/imagealready provides this functionality.🔎 Proposed refactor
-import imageUrlBuilder from "@sanity/image-url"; +import { urlFor } from "@/app/(marketing)/sanity/lib/image"; import { BlogLayout } from "@/components/layouts/BlogLayout"; ... -const builder = imageUrlBuilder(client);Then update usages like
builder.image(post.mainImage)tourlFor(post.mainImage).
87-103: Avoidconsole.errorin production code.Per coding guidelines, avoid using
console. Consider removing this logging or using the project's logger utility if error tracking is needed.🔎 Proposed fix
const decodeAssetId = (id: string) => { const match = pattern.exec(id); if (!match) { - console.error(`Invalid asset ID: ${id}`); return null; }apps/web/app/(marketing)/blog/post/email-management-best-practices/page.tsx (1)
1-28: LGTM! Standard blog post page structure.The page follows the established pattern for marketing blog posts with proper metadata exports and structured data.
Consider adding an image to the
StructuredDatacomponent for better SEO and social sharing. The empty arrayimage={[]}may result in missing image metadata in search results and social previews.apps/web/app/(marketing)/sanity/schemaTypes/postType.ts (1)
10-20: Consider adding validation for required fields.The
titleandslugfields are likely required for post functionality. Adding validation would prevent incomplete posts from being created in the CMS.🔎 Suggested validation
defineField({ name: "title", type: "string", + validation: (rule) => rule.required(), }), defineField({ name: "slug", type: "slug", options: { source: "title", }, + validation: (rule) => rule.required(), }),apps/web/app/(marketing)/blog/page.tsx (1)
248-250: Use a unique key instead of title.Using
post.titleas the key may cause issues if two posts have the same title. Consider using thefile(slug) field which should be unique.🔎 Proposed fix
{allPosts.map((post) => ( - <PostCard key={post.title} post={post} /> + <PostCard key={post.file} post={post} /> ))}apps/web/app/(marketing)/blog/post/[slug]/page.tsx (2)
77-79: UsenotFound()for proper 404 handling.Returning a simple
<div>when a post isn't found results in a 200 status code. Use Next.js'snotFound()function to properly trigger a 404 response and render the not-found page.🔎 Proposed fix
+import { notFound } from "next/navigation"; // ... other imports export default async function Page(props: Props) { const params = await props.params; const post = await sanityFetch<PostType>({ query: postQuery, params }); if (!post) { - return <div>Blog post content unavailable.</div>; + notFound(); } return <Post post={post} />; }
54-55: Biome suppression comment appears intentional.The
biome-ignorecomment for.fit("max")is flagged asnoFocusedTests, which seems like a miscategorized lint rule. This is a method chain on the image builder, not a test. The suppression works but the reason comment "blog" is unclear.Consider removing the biome-ignore if it's not actually triggering a lint error, or updating the comment to clarify why it's needed:
- // biome-ignore lint/suspicious/noFocusedTests: blog .fit("max")
apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(marketing)/case-studies/components/CaseStudyContent.tsx
Outdated
Show resolved
Hide resolved
apps/web/app/(marketing)/case-studies/study/clicks-talent-saves-60-hours-weekly/content.tsx
Outdated
Show resolved
Hide resolved
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- Remove unused `categories` prop from BulkArchiveTab - Add guards for empty thread.messages arrays - Add error handling with try/catch in archiveSelected - Remove dead code for 'more emails' section - Fix createMany return value to use result.count - Use Next.js Link instead of anchor tag in creator page - Type error boundary props properly with reset function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Fix all issues with AI Agents 🤖
In @apps/web/app/(marketing)/(landing)/creator/page.tsx:
- Around line 14-19: The meta description in export const metadata: Metadata
(the description field) is too long (~225 chars); shorten it to ~150–160
characters to avoid truncation—edit the description value in page.tsx (export
const metadata) to a concise version that preserves the core value (AI assistant
managing brand emails, collaboration requests, fan inquiries) and target benefit
(save time/focus on creating content).
In @apps/web/components/BulkArchiveCards.tsx:
- Around line 221-236: archiveCategory currently marks a category archived even
if addToArchiveSenderQueue fails partway; wrap the senders loop in a try/catch
(or catch individual addToArchiveSenderQueue calls) so failures are detected,
only call setArchivedCategories({ ... , [categoryName]: true }) after all
addToArchiveSenderQueue calls succeed, and on error log/report the failure and
avoid updating the archived state (consider setting an error/toast state to
surface the failure); refer to archiveCategory, addToArchiveSenderQueue, and
setArchivedCategories when making the change.
🧹 Nitpick comments (3)
apps/web/app/(marketing)/(landing)/error.tsx (1)
24-25: Optional: Consider consistent onClick handler style.For consistency with the
resetbutton handler, you could simplify line 25:🔎 Optional refactor for consistency
<Button onClick={reset}>Try again</Button> - <Button onClick={() => logOut()}>Log out</Button> + <Button onClick={logOut}>Log out</Button>Both forms work correctly, but passing the function directly is slightly more concise when no arguments are needed.
apps/web/components/BulkArchiveCards.tsx (1)
64-118: Consider moving helper function to bottom of file.Per coding guidelines, helper functions should be placed at the bottom of files. Consider moving
getCategoryIconafter the main exported component and child components.apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (1)
23-33: Consider whether category rules are needed.The code fetches user categories including their rules (line 32), and then uses those categories in the emailGroups mapping (lines 54-56). However, the
BulkArchiveTabcomponent only accessescategory.nameand never uses therulesfield.If the rules are intended for future use, this is fine. Otherwise, you could simplify by using the category data already fetched with the newsletters (lines 29) instead of looking them up again, or modify
getUserCategoriesWithRulesto skip fetching rules for this use case.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/utils/actions/categorize.ts
🧰 Additional context used
📓 Path-based instructions (23)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks in React
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Follow consistent naming conventions using PascalCase for components
Use LoadingContent component for async data with loading and error states
Use React Hook Form with Zod validation for form handling
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in forms
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/utils/actions/categorize.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
Use
LoadingContentcomponent to consistently handle loading and error states, passingloading,error, andchildrenpropsUse PascalCase for component file names (e.g.,
components/Button.tsx)
Files:
apps/web/components/BulkArchiveCards.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/BulkArchiveCards.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/utils/actions/*.ts
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
apps/web/utils/actions/*.ts: Usenext-safe-actionwith Zod schemas for all server actions (create/update/delete mutations), storing validation schemas inapps/web/utils/actions/*.validation.ts
Server actions should use 'use server' directive and automatically receive authentication context (emailAccountId) from theactionClient
apps/web/utils/actions/*.ts: Create corresponding server action implementation files using the naming conventionapps/web/utils/actions/NAME.tswith 'use server' directive
Use 'use server' directive at the top of server action implementation files
Implement all server actions using thenext-safe-actionlibrary with actionClient, actionClientUser, or adminActionClient for type safety and validation
UseactionClientUserwhen only authenticated user context (userId) is needed
UseactionClientwhen both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
UseadminActionClientfor actions restricted to admin users
Add metadata with a meaningful action name using.metadata({ name: "actionName" })for Sentry instrumentation and monitoring
Use.schema()method with Zod validation schemas from corresponding.validation.tsfiles in next-safe-action configuration
Access context (userId, emailAccountId, etc.) via thectxobject parameter in the.action()handler
UserevalidatePathorrevalidateTagfrom 'next/cache' within server action handlers when mutations modify data displayed elsewhere
Files:
apps/web/utils/actions/categorize.ts
apps/web/utils/actions/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
apps/web/utils/actions/**/*.ts: Server actions must be located inapps/web/utils/actionsfolder
Server action files must start withuse serverdirective
apps/web/utils/actions/**/*.ts: Use proper error handling with try/catch blocks
Usenext-safe-actionwith Zod schemas for server actions to handle mutations
UserevalidatePathin server actions for cache invalidation after mutations
Files:
apps/web/utils/actions/categorize.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/actions/categorize.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/actions/categorize.ts
apps/web/utils/actions/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Infer types from Zod schemas using
z.infer<typeof schema>instead of duplicating as separate interfaces
Files:
apps/web/utils/actions/categorize.ts
apps/web/**/*.{example,ts,json}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Add environment variables to
.env.example,env.ts, andturbo.json
Files:
apps/web/utils/actions/categorize.ts
🧠 Learnings (48)
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx` with components either colocated in the same folder or in `page.tsx`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2026-01-05T14:59:55.884Z
Learnt from: jshwrnr
Repo: elie222/inbox-zero PR: 1198
File: apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:10-10
Timestamp: 2026-01-05T14:59:55.884Z
Learning: In apps/web/app/(marketing)/**/content.tsx files, the pattern `metadata as unknown as AlternativeComparisonProps` (or similar double casts for MDX metadata) is used consistently across marketing pages (alternatives, case-studies). This is an intentional pattern for marketing content where runtime validation overhead is not justified, and should be kept for consistency.
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : Components for the page are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : Components for pages are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure all anchors are valid and navigable
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure anchors have content that's accessible to screen readers
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `target="_blank"` without `rel="noopener"`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't assign non-interactive ARIA roles to interactive HTML elements
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't assign interactive ARIA roles to non-interactive HTML elements
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make static elements with click handlers use a valid role attribute
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use semantic elements instead of role attributes in JSX
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`)
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use valid, non-abstract ARIA roles for elements with ARIA roles
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Components with `onClick` handlers must be client components marked with the `use client` directive
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If you need to use `onClick` in a component, that component must be a client component and file must start with `use client` directive
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*{.action,.server}.{ts,tsx} : For mutating data, use Next.js server actions
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : If you need to use `onClick` in a component, that component is a client component and file must start with `use client`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in forms
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : Use generic error messages instead of revealing internal details; throw `SafeError` instead of exposing user IDs, resource IDs, or system information
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*.tsx : Use `LoadingContent` component to consistently handle loading and error states, passing `loading`, `error`, and `children` props
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Use LoadingContent component for async data with loading and error states
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use proper error handling with try/catch blocks
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/components/**/*Form*.tsx : Handle form submission results using `result?.serverError` to show error toasts and `toastSuccess` to show success messages after server action completion
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : Import error and success toast utilities from '@/components/Toast' for displaying notifications
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-07-08T13:14:03.250Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/api/user/stats/newsletters/route.ts:235-239
Timestamp: 2025-07-08T13:14:03.250Z
Learning: The user prefers proper TypeScript error type checking (using `error instanceof Error`) over type assertions with `as any` for better type safety and code quality.
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure to pass a message value when creating a built-in error
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For text inputs, use the `Input` component with `registerProps` for form integration and error handling
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-06-05T09:49:12.168Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 485
File: apps/web/app/(landing)/login/page.tsx:41-43
Timestamp: 2025-06-05T09:49:12.168Z
Learning: In Next.js App Router, components that use the `useSearchParams` hook require a Suspense boundary to handle the asynchronous nature of search parameter access. The Suspense wrapper is necessary and should not be removed when a component uses useSearchParams.
Applied to files:
apps/web/app/(marketing)/(landing)/error.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use event handlers on non-interactive elements
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/app/**/*.{ts,tsx} : Follow NextJS app router structure with (app) directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/{pages,routes,components}/**/*.{ts,tsx} : Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/**/[!.]*/route.{ts,tsx} : Use kebab-case for route directories in Next.js App Router (e.g., `api/hello-world/route`)
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Create corresponding server action implementation files using the naming convention `apps/web/utils/actions/NAME.ts` with 'use server' directive
Applied to files:
apps/web/utils/actions/categorize.ts
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused private class members
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused labels
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused variables
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
🧬 Code graph analysis (6)
apps/web/app/(marketing)/(landing)/creator/page.tsx (8)
apps/web/components/layouts/BasicLayout.tsx (1)
BasicLayout(7-15)apps/web/app/(landing)/home/Hero.tsx (2)
Hero(40-81)HeroVideoPlayer(83-126)apps/web/components/new-landing/common/WordReveal.tsx (1)
WordReveal(12-38)apps/web/components/new-landing/BrandScroller.tsx (1)
BrandScroller(15-49)apps/web/components/Banner.tsx (1)
Banner(8-24)apps/web/app/(landing)/home/FinalCTA.tsx (1)
FinalCTA(11-36)apps/web/components/new-landing/sections/OrganizedInbox.tsx (1)
OrganizedInbox(16-39)apps/web/components/new-landing/sections/PreWrittenDrafts.tsx (1)
PreWrittenDrafts(16-39)
apps/web/app/(marketing)/(landing)/error.tsx (2)
apps/web/components/ErrorDisplay.tsx (1)
ErrorDisplay(17-59)apps/web/utils/user.ts (1)
logOut(6-19)
apps/web/components/BulkArchiveCards.tsx (10)
apps/web/utils/category.server.ts (1)
CategoryWithRules(4-11)apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(79-89)apps/web/store/archive-sender-queue.ts (2)
addToArchiveSenderQueue(19-111)useArchiveSenderStatus(118-121)apps/web/utils/actions/categorize.ts (1)
removeAllFromCategoryAction(274-289)apps/web/components/Toast.tsx (1)
toastError(14-19)apps/web/components/ui/dropdown-menu.tsx (4)
DropdownMenu(185-185)DropdownMenuTrigger(186-186)DropdownMenuContent(187-187)DropdownMenuItem(188-188)apps/web/app/(app)/[emailAccountId]/smart-categories/CreateCategoryButton.tsx (1)
CreateCategoryDialog(115-137)apps/web/components/EmailCell.tsx (1)
EmailCell(3-26)apps/web/hooks/useThreads.ts (1)
useThreads(8-28)apps/web/utils/date.ts (1)
formatShortDate(25-56)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (3)
apps/web/utils/email-account.ts (1)
checkUserOwnsEmailAccount(5-22)apps/web/utils/category.server.ts (1)
getUserCategoriesWithRules(24-39)apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (1)
BulkArchiveTab(126-481)
apps/web/utils/actions/categorize.ts (3)
apps/web/utils/actions/safe-action.ts (1)
actionClient(74-129)apps/web/utils/categories.ts (1)
defaultCategory(1-105)apps/web/utils/path.ts (1)
prefixPath(1-4)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (6)
apps/web/utils/category.server.ts (1)
CategoryWithRules(4-11)apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(79-89)apps/web/store/archive-sender-queue.ts (2)
addToArchiveSenderQueue(19-111)useArchiveSenderStatus(118-121)apps/web/components/EmailCell.tsx (1)
EmailCell(3-26)apps/web/hooks/useThreads.ts (1)
useThreads(8-28)apps/web/utils/date.ts (1)
formatShortDate(25-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (15)
apps/web/app/(marketing)/(landing)/error.tsx (1)
9-29: LGTM! Error boundary properly implemented.The error boundary now correctly:
- Uses properly typed props with
Error & { digest?: string }andresetfunction- Integrates Sentry error tracking via
useEffect- Provides user recovery with "Try again" button (calls
reset)- Offers escape hatch with "Log out" button
- Displays user-friendly error messages via
ErrorDisplayThis implementation follows Next.js error boundary conventions and all project guidelines.
apps/web/utils/actions/categorize.ts (1)
291-310: LGTM!The action correctly:
- Uses
actionClientwith proper metadata for Sentry instrumentation- Filters only enabled default categories
- Scopes all created categories to the authenticated
emailAccountId- Uses
skipDuplicates: truefor idempotent bulk creation- Returns the actual
result.countfrom Prisma'screateMany(addressing the previous review feedback)- Revalidates the correct path
apps/web/components/BulkArchiveCards.tsx (4)
459-525: LGTM!The
SenderRowcomponent properly implements:
- Keyboard accessibility with
role="button",tabIndex={0}, andonKeyDownhandler- Visual feedback for selection state with opacity changes
- Archive status display via
useArchiveSenderStatushook
527-554: LGTM!Clean status display component with appropriate visual indicators for each archive state.
556-648: LGTM!The
ExpandedEmailscomponent properly:
- Uses
useThreadshook for SWR-based data fetching (per coding guidelines)- Handles loading, error, and empty states appropriately
- Guards against empty
messagesarray (line 600) before accessing properties- Cleans email snippets by removing invisible characters and normalizing whitespace
1-56: LGTM!Imports follow coding guidelines:
- Specific lodash import (
import groupBy from 'lodash/groupBy') to minimize bundle size- Uses
@/path aliases consistently- All imports are at the top of the file
apps/web/app/(marketing)/(landing)/creator/page.tsx (3)
1-12: LGTM!Imports are well-organized, use proper
@/path aliases, and follow Next.js App Router conventions.
21-63: LGTM!The page component follows Next.js App Router conventions correctly. The composition is clean, with proper use of BasicLayout, Hero section with WordReveal animation, testimonial Banner, and CTA sections. All component props are properly typed and passed.
65-89: LGTM!The helper function is correctly positioned at the bottom of the file per coding guidelines. Component composition is clean and logical, with feature sections properly organized using OrganizedInbox, PreWrittenDrafts, and Banner components.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (1)
35-64: LGTM! Well-structured server component.The component correctly:
- Validates account ownership before rendering
- Fetches data in parallel for performance
- Uses ClientOnly boundaries for client-side components
- Transforms data appropriately for the BulkArchiveTab component
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (5)
1-48: LGTM! Clean imports and type definitions.The imports follow project guidelines correctly:
- Specific imports from libraries
- Uses
@/path aliases- All imports at the top of the file
- "use client" directive properly placed
- Type definitions are clear and well-structured
50-124: LGTM! Clear placeholder logic with good documentation.The
getArchiveCandidatesfunction is well-documented as a placeholder for future AI-driven analysis. The keyword-based categorization provides reasonable fallback behavior, and theconfidenceConfigobject provides clean, consistent styling across confidence levels.
126-239: LGTM! Robust state management and error handling.The component properly:
- Pre-selects high/medium confidence senders by default
- Uses
useMemofor derived state to optimize re-renders- Includes comprehensive error handling in
archiveSelectedwith try/catch/finally- Displays user-friendly error messages via toast notifications
- Prevents UI state issues by resetting
isArchivingin the finally block
241-481: LGTM! Well-structured UI with proper accessibility.The rendering logic is excellent:
- Clear conditional states (success, empty, main)
- Proper keyboard navigation with
role="button",tabIndex={0}, andonKeyDownhandlers- Progress visualization with percentage calculation
- Responsive layout with Tailwind classes
- Clean component composition
483-672: LGTM! Robust sub-components with proper data fetching.The sub-components are well-implemented:
- SenderRow: Proper keyboard navigation and interaction handling
- ArchiveStatus: Clear status display for pending/processing/completed states
- ExpandedEmails:
- Uses
useThreadshook with SWR correctly (per coding guidelines)- Handles all states: loading, error, empty, and success
- Safely guards against empty messages array (line 625)
- Clean snippet formatting with zero-width character removal
All previously identified issues have been addressed.
- Memoize headings in TableOfContents to prevent IntersectionObserver recreation - Remove nested Button in TryInboxZero, style Link directly - Update deprecated Image props (layout/objectFit) to Next.js 13+ syntax - Add rel="noopener" to external link in CaseStudyContent 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Fix all issues with AI Agents 🤖
In @apps/web/app/(marketing)/blog/page.tsx:
- Line 82: Correct the typo in the blog description string in
apps/web/app/(marketing)/blog/page.tsx by replacing "aqcuired" with "acquired"
in the literal that reads "Notion recently aqcuired Skiff Mail and is sunsetting
it in six months. Here are some good alternatives to consider for your email
needs."
- Around line 259-272: The group-hover utility on the h3 (className
"group-hover:text-gray-600") is inert because no ancestor has the "group" class;
add "group" to the parent element (e.g., the Card component's className on the
Card element that wraps Link and CardContent) so the hover variant becomes
active, or alternatively add "group" to the Link element if you prefer the Link
to be the hover target; ensure the chosen parent includes "group" alongside its
existing classes (e.g., "overflow-hidden transition-transform duration-300
hover:scale-105 group").
🧹 Nitpick comments (2)
apps/web/app/(marketing)/blog/page.tsx (2)
208-214: Consider adding error handling for Sanity fetch.The
sanityFetchcall lacks error handling. If the fetch fails, the page will crash. Consider wrapping in try-catch with a fallback to an empty array.Additionally, the magic string
"project123"should be extracted to a constant or environment variable for clarity.🔎 Proposed fix
+const DUMMY_SANITY_PROJECT_ID = "project123"; + export default async function BlogContentsPage() { // Skip Sanity fetch during build with dummy credentials let posts: SanityPost[] = []; - if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID !== "project123") { - posts = await sanityFetch<SanityPost[]>({ query: postsQuery }); + if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID !== DUMMY_SANITY_PROJECT_ID) { + try { + posts = await sanityFetch<SanityPost[]>({ query: postsQuery }); + } catch (error) { + console.error("Failed to fetch Sanity posts:", error); + } }
248-250: Use a more stable identifier for the React key.Using
post.titleas the key could cause issues if titles are duplicated. Thepost.file(slug) is a more reliable unique identifier.- <PostCard key={post.title} post={post} /> + <PostCard key={post.file} post={post} />
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(marketing)/blog/components/TableOfContents.tsxapps/web/app/(marketing)/blog/components/TryInboxZero.tsxapps/web/app/(marketing)/blog/page.tsxapps/web/app/(marketing)/case-studies/components/CaseStudyContent.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/app/(marketing)/blog/components/TryInboxZero.tsx
- apps/web/app/(marketing)/case-studies/components/CaseStudyContent.tsx
- apps/web/app/(marketing)/blog/components/TableOfContents.tsx
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(marketing)/blog/page.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(marketing)/blog/page.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/app/(marketing)/blog/page.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks in React
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Follow consistent naming conventions using PascalCase for components
Use LoadingContent component for async data with loading and error states
Use React Hook Form with Zod validation for form handling
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in forms
Files:
apps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/app/(marketing)/blog/page.tsx
🧠 Learnings (8)
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx` with components either colocated in the same folder or in `page.tsx`
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2026-01-05T14:59:55.884Z
Learnt from: jshwrnr
Repo: elie222/inbox-zero PR: 1198
File: apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:10-10
Timestamp: 2026-01-05T14:59:55.884Z
Learning: In apps/web/app/(marketing)/**/content.tsx files, the pattern `metadata as unknown as AlternativeComparisonProps` (or similar double casts for MDX metadata) is used consistently across marketing pages (alternatives, case-studies). This is an intentional pattern for marketing content where runtime validation overhead is not justified, and should be kept for consistency.
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to pages/_document.{jsx,tsx} : Don't import next/document outside of pages/_document.jsx in Next.js projects
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use `next/image` package for images
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `<img>` elements in Next.js projects
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : Use `next/image` package for images
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
🧬 Code graph analysis (1)
apps/web/app/(marketing)/blog/page.tsx (3)
apps/web/components/layouts/BlogLayout.tsx (1)
BlogLayout(14-27)apps/web/components/new-landing/common/Card.tsx (2)
Card(67-105)CardContent(10-12)apps/web/components/new-landing/icons/Link.tsx (1)
Link(1-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (2)
apps/web/app/(marketing)/blog/page.tsx (2)
1-7: LGTM!Imports follow project conventions with
@/path aliases and usenext/imageandnext/linkas required.
9-27: LGTM!Type definitions are appropriately scoped to this file and not exported, following the coding guidelines.
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/components/BulkArchiveCards.tsx">
<violation number="1" location="apps/web/components/BulkArchiveCards.tsx:237">
P2: The caught `error` is unused. Consider logging it or including error details for debugging. If intentionally unused, prefix with underscore (`_error`) to satisfy linters.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI Agents
In @apps/web/app/(marketing)/blog/page.tsx:
- Around line 248-250: Replace the non-unique key usage in the allPosts.map
render to avoid React key collisions: change the key on the PostCard component
from post.title to the unique slug field post.file (i.e., in the
allPosts.map(...) where you render <PostCard key={post.title} post={post} />
swap to use key={post.file}); ensure post.file is present for all items or
fallback to a guaranteed-unique value before committing.
In @apps/web/components/BulkArchiveCards.tsx:
- Around line 617-621: The Link that opens external emails (the Link wrapping
getEmailUrl(thread.id, userEmail, provider) with target="_blank") must
explicitly include rel="noopener noreferrer"; update the Link component props to
add rel="noopener noreferrer" to prevent the opened page from accessing
window.opener while keeping the href generated by getEmailUrl and preserving
existing classes and behavior.
🧹 Nitpick comments (7)
apps/web/components/BulkArchiveCards.tsx (3)
63-118: Helper function placement.Per coding guidelines, helper functions should be placed at the bottom of files rather than at the top. Consider moving
getCategoryIconbelow the component definitions.
221-242: Sequential await in loop may be intentional but worth noting.The
for...ofloop with sequentialawaitcalls processes senders one at a time. This is likely intentional for rate limiting, but if the queue can handle concurrent additions, consider usingPromise.allfor better performance with larger sender lists.🔎 Alternative with Promise.all (if concurrency is acceptable)
try { - for (const sender of selectedToArchive) { - await addToArchiveSenderQueue({ + await Promise.all( + selectedToArchive.map((sender) => + addToArchiveSenderQueue({ - sender: sender.address, - emailAccountId, - }); - } + sender: sender.address, + emailAccountId, + }), + ), + ); setArchivedCategories((prev) => ({ ...prev, [categoryName]: true }));
497-504: Type cast workaround.The double cast
e as unknown as React.MouseEventon line 501 is a workaround for the Checkbox component's event type. This works but is a code smell. Consider refactoring the handler to avoid the cast:🔎 Cleaner approach
<Checkbox checked={isSelected} - onClick={(e) => { - e.stopPropagation(); - onToggleSelection(e as unknown as React.MouseEvent); - }} + onCheckedChange={() => { + // Selection toggle doesn't need the event object + }} + onClick={(e) => e.stopPropagation()} className="size-5" />Or update
onToggleSelectionprop type to not require the event if it's only used forstopPropagation.apps/web/app/(marketing)/blog/page.tsx (4)
208-213: Consider a more semantic environment check.The hardcoded
"project123"string is used to detect dummy credentials during build. While functional, extracting this to a named constant or checking for a dedicated build-time flag would improve maintainability.🔎 Suggested improvements
Option 1: Extract to constant
+const DUMMY_SANITY_PROJECT_ID = "project123"; + export default async function BlogContentsPage() { // Skip Sanity fetch during build with dummy credentials let posts: SanityPost[] = []; - if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID !== "project123") { + if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID !== DUMMY_SANITY_PROJECT_ID) { posts = await sanityFetch<SanityPost[]>({ query: postsQuery }); }Option 2: Use a dedicated build flag
export default async function BlogContentsPage() { // Skip Sanity fetch during build with dummy credentials let posts: SanityPost[] = []; - if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID !== "project123") { + if (process.env.NEXT_PUBLIC_SANITY_PROJECT_ID && process.env.NODE_ENV !== "test") { posts = await sanityFetch<SanityPost[]>({ query: postsQuery }); }
228-228: Specify locale for consistent date formatting.Calling
toLocaleDateString()without a locale parameter uses the user's system locale, which can produce inconsistent date formats across different regions. Consider specifying a locale (e.g.,'en-US') for predictable formatting.🔎 Proposed fix
- date: new Date(post._createdAt).toLocaleDateString(), + date: new Date(post._createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }),This would produce dates like "Jan 5, 2024" consistently, matching the format used in the
mdxPostsarray.
277-283: Consider descriptive alt text for author image.While
alt=""is technically acceptable here since the author's name appears adjacent to the image (making it decorative/redundant), providing descriptive alt text likealt={post.author.name}can enhance the screen reader experience by explicitly identifying the image subject.🔎 Proposed enhancement
<Image src={post.author.imageUrl} - alt="" + alt={post.author.name} className="h-8 w-8 rounded-full bg-gray-50" width={32} height={32} />
29-204: Consider extracting static blog data to a separate file.The
mdxPostsarray spans 175 lines. Moving it to a dedicated data file (e.g.,apps/web/app/(marketing)/blog/data/mdx-posts.ts) would improve file organization and make the component logic easier to scan.This is purely organizational—no functional changes needed.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/app/(marketing)/blog/page.tsxapps/web/components/BulkArchiveCards.tsx
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks in React
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Follow consistent naming conventions using PascalCase for components
Use LoadingContent component for async data with loading and error states
Use React Hook Form with Zod validation for form handling
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in forms
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/components/BulkArchiveCards.tsxapps/web/app/(marketing)/blog/page.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
Use
LoadingContentcomponent to consistently handle loading and error states, passingloading,error, andchildrenpropsUse PascalCase for component file names (e.g.,
components/Button.tsx)
Files:
apps/web/components/BulkArchiveCards.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/BulkArchiveCards.tsx
🧠 Learnings (27)
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx` with components either colocated in the same folder or in `page.tsx`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2026-01-05T14:59:55.884Z
Learnt from: jshwrnr
Repo: elie222/inbox-zero PR: 1198
File: apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:10-10
Timestamp: 2026-01-05T14:59:55.884Z
Learning: In apps/web/app/(marketing)/**/content.tsx files, the pattern `metadata as unknown as AlternativeComparisonProps` (or similar double casts for MDX metadata) is used consistently across marketing pages (alternatives, case-studies). This is an intentional pattern for marketing content where runtime validation overhead is not justified, and should be kept for consistency.
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsxapps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : Components for the page are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : Components for pages are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/app/(marketing)/(landing)/enterprise/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure all anchors are valid and navigable
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure anchors have content that's accessible to screen readers
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `target="_blank"` without `rel="noopener"`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't assign non-interactive ARIA roles to interactive HTML elements
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't assign interactive ARIA roles to non-interactive HTML elements
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make static elements with click handlers use a valid role attribute
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use semantic elements instead of role attributes in JSX
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`)
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use valid, non-abstract ARIA roles for elements with ARIA roles
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Components with `onClick` handlers must be client components marked with the `use client` directive
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If you need to use `onClick` in a component, that component must be a client component and file must start with `use client` directive
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*{.action,.server}.{ts,tsx} : For mutating data, use Next.js server actions
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : If you need to use `onClick` in a component, that component is a client component and file must start with `use client`
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/app/(marketing)/(landing)/creator/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use event handlers on non-interactive elements
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use proper error handling with try/catch blocks
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use `next/image` package for images
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `<img>` elements in Next.js projects
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : Use `next/image` package for images
Applied to files:
apps/web/app/(marketing)/blog/page.tsx
🧬 Code graph analysis (3)
apps/web/app/(marketing)/(landing)/creator/page.tsx (7)
apps/web/app/(marketing)/(landing)/enterprise/page.tsx (1)
metadata(11-16)apps/web/app/(landing)/home/Hero.tsx (2)
Hero(40-81)HeroVideoPlayer(83-126)apps/web/components/new-landing/BrandScroller.tsx (1)
BrandScroller(15-49)apps/web/components/Banner.tsx (1)
Banner(8-24)apps/web/app/(landing)/home/FinalCTA.tsx (1)
FinalCTA(11-36)apps/web/components/new-landing/sections/OrganizedInbox.tsx (1)
OrganizedInbox(16-39)apps/web/components/new-landing/sections/PreWrittenDrafts.tsx (1)
PreWrittenDrafts(16-39)
apps/web/app/(marketing)/(landing)/enterprise/page.tsx (9)
apps/web/app/(marketing)/(landing)/creator/page.tsx (1)
metadata(14-19)apps/web/components/layouts/BasicLayout.tsx (1)
BasicLayout(7-15)apps/web/app/(landing)/home/Hero.tsx (2)
Hero(40-81)HeroVideoPlayer(83-126)apps/web/components/new-landing/common/WordReveal.tsx (1)
WordReveal(12-38)apps/web/components/new-landing/BrandScroller.tsx (1)
BrandScroller(15-49)apps/web/app/(landing)/home/FAQs.tsx (1)
FAQs(66-98)apps/web/components/new-landing/sections/OrganizedInbox.tsx (1)
OrganizedInbox(16-39)apps/web/components/Banner.tsx (1)
Banner(8-24)apps/web/components/new-landing/sections/StartedInMinutes.tsx (1)
StartedInMinutes(34-139)
apps/web/app/(marketing)/blog/page.tsx (2)
apps/web/components/layouts/BlogLayout.tsx (1)
BlogLayout(14-27)apps/web/components/new-landing/icons/Link.tsx (1)
Link(1-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (12)
apps/web/app/(marketing)/(landing)/enterprise/page.tsx (4)
1-9: LGTM! Clean import structure.All imports follow project conventions with
@/path aliases and are organized logically.
11-16: LGTM! Proper SEO metadata.The metadata follows the established pattern from other marketing pages with appropriate title, description, and canonical URL.
18-47: LGTM! Well-structured page component.The component follows React best practices with clean composition of imported components. The Hero section is properly configured with WordReveal animation, and the layout structure matches established patterns in the codebase.
49-79: LGTM! Properly structured helper component.The
EnterpriseFeaturesfunction is correctly placed at the bottom of the file and appropriately kept private to the module. The component composition is clean, and all props passed to child components match their expected signatures.apps/web/components/BulkArchiveCards.tsx (4)
1-61: LGTM!Imports follow project conventions: specific lodash import (
groupBy),@/path aliases, and the internalEmailGrouptype is correctly not exported since it's only used within this file.
305-377: LGTM!The category card rendering is well-implemented with proper accessibility attributes (
role="button",tabIndex={0}, keyboard event handlers), correct button types, and appropriate event propagation handling.
533-560: LGTM!The
ArchiveStatuscomponent cleanly handles all status cases with appropriate visual feedback.
604-606: Null check properly added.Good defensive coding with the
if (!firstMessage) return null;guard. This prevents the potential crash whenthread.messagesis empty.apps/web/app/(marketing)/(landing)/creator/page.tsx (4)
1-12: LGTM! Imports are properly structured.All imports are at the top of the file, use path aliases correctly, and the type-only import for
Metadatauses the properimport typesyntax.
14-19: LGTM! Metadata is well-structured and optimized.The metadata follows Next.js conventions with a descriptive title, properly sized description (~146 characters—within the optimal SEO range), and canonical URL. The previous meta description length concern has been appropriately addressed.
21-63: LGTM! Component structure is well-organized.The
CreatorPagecomponent properly uses Next.js conventions as a server component, follows the established marketing page composition pattern, and correctly implements client-side navigation with Next.jsLink(lines 54-56). The previous navigation concern has been appropriately resolved.
65-89: LGTM! Helper component follows established patterns.The
CreatorFeaturescomponent is properly positioned at the bottom of the file, not exported (internal-only use), and correctly composes the marketing sections using the imported UI components.
The marketing directory was accidentally converted from a submodule to a regular directory during a merge, causing 100+ spurious file changes in the PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move category icon mapping logic to bulk-archive/categoryIcons.ts to reduce BulkArchiveCards.tsx file size. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI Agents
In @apps/web/components/BulkArchiveCards.tsx:
- Line 164: The catch (error) in the BulkArchiveCards.tsx component is currently
ignoring the captured error; update the catch to either log the error and
include its message in the user toast or rename it to _error if intentionally
unused: call console.error(error) (or your app logger) and change the
toast/error notification to include error.message (or String(error)) for clearer
debugging, or simply rename the parameter to _error to indicate it is
intentionally unused.
- Around line 544-547: The Link element rendering the email link in
BulkArchiveCards.tsx (the Link that uses getEmailUrl(thread.id, userEmail,
provider) and target="_blank") must include rel="noopener noreferrer"; update
that Link JSX to add the rel attribute so external pages opened with
target="_blank" cannot access window.opener and satisfy the project's security
guideline.
🧹 Nitpick comments (1)
apps/web/components/BulkArchiveCards.tsx (1)
156-161: Consider parallel execution for better performance (optional).The current implementation uses sequential
awaitin a loop, which processes senders one at a time. If rate limiting isn't a concern foraddToArchiveSenderQueue, usingPromise.all()could significantly improve performance for categories with many senders.🔎 Alternative implementation with Promise.all
try { - for (const sender of selectedToArchive) { - await addToArchiveSenderQueue({ - sender: sender.address, - emailAccountId, - }); - } + await Promise.all( + selectedToArchive.map((sender) => + addToArchiveSenderQueue({ + sender: sender.address, + emailAccountId, + }) + ) + ); setArchivedCategories((prev) => ({ ...prev, [categoryName]: true }));Note: Only apply this if the archive queue can handle concurrent requests. Sequential processing may be intentional to prevent rate limiting or resource exhaustion.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
Use
LoadingContentcomponent to consistently handle loading and error states, passingloading,error, andchildrenpropsUse PascalCase for component file names (e.g.,
components/Button.tsx)
Files:
apps/web/components/BulkArchiveCards.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/components/BulkArchiveCards.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/components/BulkArchiveCards.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root
Prefer self-documenting code over comments; use descriptive variable and function names instead of explaining intent with comments
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for class sorting
Prefer functional components with hooks in React
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Follow consistent naming conventions using PascalCase for components
Use LoadingContent component for async data with loading and error states
Use React Hook Form with Zod validation for form handling
Useresult?.serverErrorwithtoastErrorandtoastSuccessfor error handling in forms
Files:
apps/web/components/BulkArchiveCards.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Format code with Prettier
Files:
apps/web/components/BulkArchiveCards.tsxapps/web/components/bulk-archive/categoryIcons.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/components/bulk-archive/categoryIcons.ts
apps/web/**/*.{example,ts,json}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Add environment variables to
.env.example,env.ts, andturbo.json
Files:
apps/web/components/bulk-archive/categoryIcons.ts
🧠 Learnings (6)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make static elements with click handlers use a valid role attribute
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use event handlers on non-interactive elements
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-12-21T12:21:37.794Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-12-21T12:21:37.794Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use proper error handling with try/catch blocks
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `target="_blank"` without `rel="noopener"`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (8)
apps/web/components/bulk-archive/categoryIcons.ts (1)
22-76: LGTM! Clean utility implementation.The icon mapping logic is clear, maintainable, and covers a comprehensive set of categories. The cascading if-return pattern with case-insensitive matching is appropriate for this use case, and the default fallback ensures robustness.
apps/web/components/BulkArchiveCards.tsx (7)
1-45: LGTM! Imports and type definitions are well-organized.The imports follow project guidelines, including the specific lodash function import to minimize bundle size. The
EmailGrouptype is appropriately scoped for this component.
47-100: LGTM! State management and memoization are well-structured.The component properly uses
useMemowith correct dependencies, and the state organization is logical for managing the complex UI interactions (expansion, selection, archival status). ThegroupedEmailsmemoization ensures empty categories are still displayed, which provides good user feedback.
193-389: LGTM! Render logic with excellent accessibility support.The component properly implements keyboard navigation with
role="button",tabIndex, andonKeyDownhandlers for interactive elements. The UI gracefully handles both archived and active states, and the expanded/collapsed pattern provides good progressive disclosure. Event propagation is correctly managed withstopPropagationwhere needed.
392-458: LGTM! SenderRow properly handles interaction states.The component correctly implements keyboard accessibility and manages selection state. The opacity change for unselected senders provides clear visual feedback. The type casting on line 428 is not ideal but is a pragmatic solution for the Checkbox component's event handler type requirements.
460-487: LGTM! Clear status feedback.The
ArchiveStatuscomponent provides appropriate visual feedback for each stage of the archive process. The switch statement handles all expected states with sensible defaults.
504-533: LGTM! Proper guards and state handling.The component correctly handles loading, error, and empty states with appropriate UI feedback. The guard check for
firstMessageon line 533 prevents potential crashes when threads have empty message arrays.
558-567: LGTM! Robust snippet sanitization.The snippet cleaning logic properly handles invisible characters, zero-width spaces, and whitespace normalization before truncation. This prevents rendering issues and provides consistent UI display.
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
Show resolved
Hide resolved
- Keep only 5 primary categories (OTHER, NEWSLETTER, MARKETING, RECEIPT, NOTIFICATION) - Add TODO for future secondary categories implementation - Refactor bulk-archive/page.tsx to use SWR instead of server component fetching - Refactor quick-bulk-archive/page.tsx to use SWR pattern - BulkArchiveTab now fetches its own data via SWR - Remove illustrations.popsy.co from next.config.ts remotePatterns - Update categorized API to include autoCategorizeSenders flag
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx">
<violation number="1" location="apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx:116">
P1: `useMemo` is being used to perform side effects (`setSelectedSenders`, `setHasInitializedSelection`). This should be `useEffect` instead. `useMemo` is for memoizing computed values and may be called multiple times or skipped by React, making it unreliable for side effects.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/categories.ts (1)
49-51: RenameSenderCategorytoSenderCategoryNamefor clarity
SenderCategoryrepresents the display name union ("Other" | "Newsletter" | ...), not the key or full value object. The current naming is easy to confuse at call sites. Since this type is imported and used in other files (e.g.,apps/web/utils/categorize/senders/categorize.ts), renaming toSenderCategoryNamewould improve clarity across the codebase.
🤖 Fix all issues with AI agents
In @apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx:
- Around line 115-126: The code currently uses useMemo to perform stateful side
effects when initializing selection; replace that useMemo block with a useEffect
that depends on [candidates, hasInitializedSelection], import useEffect at the
top, and inside the effect run the same initialization logic (build initial
Record from candidates using candidate.address and confidence check) then call
setSelectedSenders(initial) and setHasInitializedSelection(true); keep the
existing guards (candidates.length > 0 && !hasInitializedSelection) and remove
the useMemo import/usages.
🧹 Nitpick comments (3)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (2)
539-545: Clarify archived count message.The text
"Archived {status.threadsTotal}!"renders as "Archived 5!" which is unclear. Consider adding "threads" for better user comprehension.💡 Suggested improvement
return ( <span className="text-sm text-green-600"> - Archived {status.threadsTotal}! + Archived {status.threadsTotal} thread{status.threadsTotal !== 1 ? "s" : ""}! </span> );
571-575: Redundant slice operation.The
useThreadshook is called withlimit: 5, sodata.threads.slice(0, 5)on line 604 is redundant.💡 Suggested cleanup
- {data.threads.slice(0, 5).map((thread) => { + {data.threads.map((thread) => {Also applies to: 604-604
apps/web/utils/categories.ts (1)
30-46: Avoid the large commented-out “future categories” block driftingThis is a lot of commented code to keep correct over time. Consider moving it to an issue/ADR/docs, or encode it as data (e.g., a separate
secondaryCategoriesconst exported for future use) so it can be validated/typed/tested when referenced.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/utils/categories.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/web/app/api/user/categorize/senders/categorized/route.ts
- apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/utils/categories.tsapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for formatting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Use LoadingContent component for async data with pattern:<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>
UseuseActionhook fromnext-safe-action/hookswithonSuccessandonErrorcallbacks in forms
UsegetActionErrorMessage(error.error)from@/utils/errorto extract user-friendly error messages from server actions
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/categories.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/categories.ts
🧠 Learnings (32)
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx` with components either colocated in the same folder or in `page.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/*/page.tsx : Create new pages at `apps/web/app/(app)/PAGE_NAME/page.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:23.265Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:23.265Z
Learning: Applies to apps/web/app/(app)/**/*.{ts,tsx} : Components for the page are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : Components for pages are either put in `page.tsx`, or in the `apps/web/app/(app)/PAGE_NAME` folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:42:11.919Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-11-25T14:42:11.919Z
Learning: Applies to utils/**/*.{js,ts,jsx,tsx} : The `utils` folder contains core app logic such as Next.js Server Actions and Gmail API requests
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/{pages,routes,components}/**/*.{ts,tsx} : Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: If in a server file, you can fetch the data inline instead of using SWR
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Default to SWR for data fetching as it makes it easier to refetch data we need
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*{.server,.action}.{ts,tsx} : If in a server file, you can fetch data inline instead of using SWR
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Default to SWR for data fetching as it makes it easier to refetch data
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: For mutating data, use Next.js server actions instead of SWR
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching, prefer using `useSWR` and follow the data-fetching guidelines
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Use `swr` for data fetching in deeply nested components
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.{tsx,ts} : For API GET requests to server, use the `swr` package with hooks like `useSWR` to fetch data
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For API get requests to server, use the `swr` package with `useSWR` hook
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: For data fetching, use dedicated GET API Routes combined with SWR Hooks instead of Server Actions
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
📚 Learning: 2026-01-07T21:07:06.691Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1230
File: apps/web/app/(app)/[emailAccountId]/drive/page.tsx:47-70
Timestamp: 2026-01-07T21:07:06.691Z
Learning: In TSX files across the codebase, prefer direct server action calls with manual error handling (e.g., check result?.serverError and handle errors explicitly). The useAction hook pattern is not required; you can call server actions directly and use try/finally to ensure cleanup (such as resetting loading states) regardless of the hook. Apply consistently for components that perform server interactions.
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsxapps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/app/**/*.{ts,tsx} : Use NextJS app router structure with (app) directory
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Main Next.js application is located in `apps/web`
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2026-01-08T15:09:06.726Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2026-01-08T15:09:06.726Z
Learning: Organize fullstack feature code with GET routes at `apps/web/app/api/user/{feature}/route.ts`, validation at `apps/web/utils/actions/{feature}.validation.ts`, actions at `apps/web/utils/actions/{feature}.ts`, hooks at `apps/web/hooks/use{Feature}.ts`, and components at `apps/web/components/{Feature}.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If nested deeply in components, use `swr` to fetch via API instead of loading data directly
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Use SWR for client-side data fetching with pattern `useSWR<GetExampleResponse>("/api/user/example")`
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused private class members
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused labels
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused variables
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : Use proper error handling with try/catch blocks
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
🧬 Code graph analysis (2)
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (2)
apps/web/app/(app)/[emailAccountId]/PermissionsCheck.tsx (1)
PermissionsCheck(12-35)apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx (1)
BulkArchive(16-66)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (4)
apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx (1)
QuickBulkArchivePage(3-5)apps/web/app/(app)/[emailAccountId]/PermissionsCheck.tsx (1)
PermissionsCheck(12-35)apps/web/components/PageHeader.tsx (1)
PageHeader(21-45)apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (1)
BulkArchiveTab(76-462)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (7)
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (1)
1-11: LGTM! Clean page structure following Next.js patterns.The page correctly composes
PermissionsCheckfor authorization andBulkArchivefor the main UI, appropriately delegating complexity to specialized components. The server component pattern is used correctly, and the data fetching is properly handled in theBulkArchivecomponent using SWR with polling support as shown in the related snippets.apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx (1)
1-26: LGTM!The page structure correctly follows the app router pattern with proper use of
ClientOnlywrappers for client components. Components are appropriately colocated in the page folder per project guidelines.apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (4)
1-74: LGTM!Imports follow project guidelines: specific lodash import (
sortBy), SWR for data fetching, and propertypeimports. TheconfidenceConfigobject is well-structured withas constassertions for type safety.
192-209: LGTM!The error handling follows project conventions with
toast.errorfor user feedback, and thetry/catch/finallypattern ensuresisArchivingis reset regardless of outcome. Sequential queue additions are likely intentional to avoid overwhelming the archive queue.
276-461: LGTM!The main render section demonstrates good practices: comprehensive UI states (loading, error, empty, complete), proper accessibility for clickable divs (
role="button",tabIndex={0}, keyboard handlers), and appropriate use ofLoadingContentfor error display.
464-531: LGTM!The
SenderRowcomponent is well-structured with proper accessibility attributes on the clickable container and correct event handling for the checkbox (stopPropagationto prevent toggling expand when selecting).apps/web/utils/categories.ts (1)
2-7: No database migration or legacy key normalization is required;defaultCategorykeys are never persisted as identifiers. Only category names (stored as database Category records) and their IDs are used at runtime. The commented secondary categories are intentional design documentation for future two-round categorization.
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
Show resolved
Hide resolved
useMemo should not be used for triggering side effects like setState. Changed to useEffect for initializing selection when candidates load.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx:
- Around line 617-621: The Link in BulkArchiveTab (the anchor created around
getEmailUrl(thread.id, userEmail, provider)) uses target="_blank" but lacks the
security attributes; update that Link to include rel="noopener noreferrer" so
the opened page cannot access window.opener (i.e., add rel="noopener noreferrer"
alongside target="_blank" on the Link element).
- Around line 192-209: In archiveSelected, the catch block currently swallows
errors; update it to capture the thrown error (e.g., catch (err)) and log the
error details (console.error or your app logger) including context (caller:
archiveSelected, emailAccountId, and candidate info), and then use toast.error
with a more informative message (optionally include err.message or a
user-friendly summary) before finally resetting isArchiving; ensure references
to addToArchiveSenderQueue, setArchiveComplete, and toast.error remain intact.
🧹 Nitpick comments (1)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (1)
629-641: Consider extracting snippet cleaning logic.The inline IIFE for cleaning and truncating snippets could be extracted to a helper function for better testability and potential reuse.
♻️ Optional refactor
Add helper function at the bottom of the file:
function cleanAndTruncateSnippet(snippet: string, maxLength: number): string { const cleaned = snippet .replace(/[\u034F\u200B-\u200D\uFEFF\u00A0]/g, "") .trim() .replace(/\s+/g, " "); return cleaned.length > maxLength ? `${cleaned.slice(0, maxLength).trimEnd()}...` : cleaned; }Then use it:
{snippet && ( <span className="ml-2 text-muted-foreground"> - {(() => { - const cleaned = snippet - .replace(/[\u034F\u200B-\u200D\uFEFF\u00A0]/g, "") - .trim() - .replace(/\s+/g, " "); - return cleaned.length > 80 - ? `${cleaned.slice(0, 80).trimEnd()}...` - : cleaned; - })()} + {cleanAndTruncateSnippet(snippet, 80)} </span> )}As per coding guidelines, helper functions should be added to the bottom of files.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for formatting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Use LoadingContent component for async data with pattern:<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>
UseuseActionhook fromnext-safe-action/hookswithonSuccessandonErrorcallbacks in forms
UsegetActionErrorMessage(error.error)from@/utils/errorto extract user-friendly error messages from server actions
Files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
🧠 Learnings (11)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused private class members
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused labels
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have unused variables
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : Use proper error handling with try/catch blocks
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-09T19:50:05.292Z
Learnt from: ppranay20
Repo: elie222/inbox-zero PR: 935
File: apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx:367-369
Timestamp: 2025-11-09T19:50:05.292Z
Learning: In React Hook Form, when using `useMemo` or `useEffect` with form errors, always use `formState` as the dependency rather than `errors` or `formState.errors`. This is because React Hook Form uses a Proxy pattern where the errors object is mutable and won't trigger dependency arrays correctly. The formState dependency ensures proper reactivity when form validation state changes.
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Make sure all dependencies are correctly specified in React hooks
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
📚 Learning: 2026-01-07T21:07:06.691Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1230
File: apps/web/app/(app)/[emailAccountId]/drive/page.tsx:47-70
Timestamp: 2026-01-07T21:07:06.691Z
Learning: In TSX files across the codebase, prefer direct server action calls with manual error handling (e.g., check result?.serverError and handle errors explicitly). The useAction hook pattern is not required; you can call server actions directly and use try/finally to ensure cleanup (such as resetting loading states) regardless of the hook. Apply consistently for components that perform server interactions.
Applied to files:
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (3)
apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx (3)
1-74: LGTM: Imports and configuration.The imports follow best practices with specific lodash imports, proper path aliases, and the configuration object is well-structured with appropriate TypeScript const assertions for badge variants.
116-126: Correct fix: useEffect for selection initialization.Good use of
useEffectfor the side effect of initializing selection state. The dependencies are correct, and thehasInitializedSelectionguard prevents re-initialization. This properly addresses the issue mentioned in the commit message.
548-554: Verify type safety forstatus.threadIds.Line 551 accesses
status.threadIds.lengthwithout checking ifthreadIdsexists. If the type fromuseArchiveSenderStatusdoesn't guarantee this property exists for "processing" status, this could throw a runtime error.Verify that the return type of
useArchiveSenderStatusguaranteesthreadIdsis defined whenstatus === "processing". If not, add a defensive check:case "processing": return ( <span className="text-sm text-blue-600"> {status.threadIds ? `${status.threadsTotal - status.threadIds.length} / ${status.threadsTotal}` : "Processing..." } </span> );Based on coding guidelines, ensure proper error handling with type safety.
| const archiveSelected = async () => { | ||
| setIsArchiving(true); | ||
| const toArchive = candidates.filter((c) => selectedSenders[c.address]); | ||
|
|
||
| try { | ||
| for (const candidate of toArchive) { | ||
| await addToArchiveSenderQueue({ | ||
| sender: candidate.address, | ||
| emailAccountId, | ||
| }); | ||
| } | ||
| setArchiveComplete(true); | ||
| } catch { | ||
| toast.error("Failed to archive some senders. Please try again."); | ||
| } finally { | ||
| setIsArchiving(false); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Handle and log caught errors.
The empty catch block swallows error details, making debugging difficult. Capture the error to log it and potentially provide more specific feedback to users.
🔍 Proposed fix
try {
for (const candidate of toArchive) {
await addToArchiveSenderQueue({
sender: candidate.address,
emailAccountId,
});
}
setArchiveComplete(true);
- } catch {
- toast.error("Failed to archive some senders. Please try again.");
+ } catch (error) {
+ console.error("Failed to archive senders:", error);
+ toast.error("Failed to archive some senders. Please try again.");
} finally {
setIsArchiving(false);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const archiveSelected = async () => { | |
| setIsArchiving(true); | |
| const toArchive = candidates.filter((c) => selectedSenders[c.address]); | |
| try { | |
| for (const candidate of toArchive) { | |
| await addToArchiveSenderQueue({ | |
| sender: candidate.address, | |
| emailAccountId, | |
| }); | |
| } | |
| setArchiveComplete(true); | |
| } catch { | |
| toast.error("Failed to archive some senders. Please try again."); | |
| } finally { | |
| setIsArchiving(false); | |
| } | |
| }; | |
| const archiveSelected = async () => { | |
| setIsArchiving(true); | |
| const toArchive = candidates.filter((c) => selectedSenders[c.address]); | |
| try { | |
| for (const candidate of toArchive) { | |
| await addToArchiveSenderQueue({ | |
| sender: candidate.address, | |
| emailAccountId, | |
| }); | |
| } | |
| setArchiveComplete(true); | |
| } catch (error) { | |
| toast.error("Failed to archive some senders. Please try again."); | |
| } finally { | |
| setIsArchiving(false); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In @apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
around lines 192 - 209, In archiveSelected, the catch block currently swallows
errors; update it to capture the thrown error (e.g., catch (err)) and log the
error details (console.error or your app logger) including context (caller:
archiveSelected, emailAccountId, and candidate info), and then use toast.error
with a more informative message (optionally include err.message or a
user-friendly summary) before finally resetting isArchiving; ensure references
to addToArchiveSenderQueue, setArchiveComplete, and toast.error remain intact.
| <Link | ||
| href={getEmailUrl(thread.id, userEmail, provider)} | ||
| target="_blank" | ||
| className="mr-2 flex flex-1 items-center gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/50" | ||
| > |
There was a problem hiding this comment.
Add security attributes to external link.
The Link with target="_blank" is missing rel="noopener noreferrer", which creates a security vulnerability allowing the opened page to access window.opener.
🔒 Proposed security fix
<Link
href={getEmailUrl(thread.id, userEmail, provider)}
target="_blank"
+ rel="noopener noreferrer"
className="mr-2 flex flex-1 items-center gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/50"
>As per coding guidelines, always include rel="noopener noreferrer" with target="_blank" to prevent security issues.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Link | |
| href={getEmailUrl(thread.id, userEmail, provider)} | |
| target="_blank" | |
| className="mr-2 flex flex-1 items-center gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/50" | |
| > | |
| <Link | |
| href={getEmailUrl(thread.id, userEmail, provider)} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="mr-2 flex flex-1 items-center gap-3 rounded-md px-2 py-2 transition-colors hover:bg-muted/50" | |
| > |
🤖 Prompt for AI Agents
In @apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
around lines 617 - 621, The Link in BulkArchiveTab (the anchor created around
getEmailUrl(thread.id, userEmail, provider)) uses target="_blank" but lacks the
security attributes; update that Link to include rel="noopener noreferrer" so
the opened page cannot access window.opener (i.e., add rel="noopener noreferrer"
alongside target="_blank" on the Link element).
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx:
- Around line 28-30: AutoCategorizationSetup is hidden by LoadingContent because
autoCategorizeSenders is defaulted to false and the setup component is rendered
inside the isLoading/error gated block; move the <AutoCategorizationSetup ... />
render outside the LoadingContent/error gating so it always renders (so users
can complete onboarding even when the fetch is loading/errored) and stop
defaulting autoCategorizeSenders to false—use const autoCategorizeSenders =
data?.autoCategorizeSenders; (and similarly avoid forcing senders/categories to
silent defaults that hide intent) so the setup UI can detect absence vs explicit
false.
🧹 Nitpick comments (4)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx (4)
10-10: Avoid importing API-route types into a client component; move to a shared types module.Even as a
typeimport, pulling types fromapp/api/**is a brittle coupling; prefer a sharedtypes/(or similar) module forCategorizedSendersResponse.
32-39: Avoid O(n·m) category lookup; pre-index categories by id.
categories.find(...)insidesenders.map(...)(Line 34-37) is a nested scan. Build aRecord<string, Category>once and do O(1) lookups.
41-43: Handlemutate()promise to avoid unhandled rejections.
mutate()returns a promise; calling it without await/catch in an event callback can still surface unhandled rejections in some setups.Proposed fix
const handleProgressComplete = useCallback(() => { - mutate(); + void mutate().catch(() => { + // SWR will set `error` for LoadingContent; avoid unhandled rejection noise. + }); }, [mutate]);
20-26: SWR cache key should include account ID for multi-account robustness.The endpoint correctly scopes results to the current
emailAccountIdviawithEmailAccountmiddleware and filters all database queries accordingly. However, the SWR cache key is a fixed string without account scoping. While cache pollution is unlikely in practice since the component resides in[emailAccountId]route segment (causing remount on account switch), including the account ID in the cache key ("/api/user/categorize/senders/categorized?emailAccountId=<id>") would make the pattern more explicit and robust.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsxapps/web/components/SetupCard.tsxapps/web/components/ui/dialog.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Use PascalCase for component file names (e.g.,
components/Button.tsx)
apps/web/components/**/*.tsx: Use React Hook Form withzodResolverfor form validation, combining it withuseActionhook fromnext-safe-action/hooksfor server action execution
UseuseActionhook withonSuccessandonErrorcallbacks for handling server action responses in forms
UsegetActionErrorMessage(error.error)utility to extract user-friendly error messages from server actions, supporting optionalprefixparameter
UseLoadingContentcomponent to handle loading and error states, passingloading,error, andchildrenprops
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsx
apps/web/components/ui/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Shadcn UI components are located in
components/uidirectory
Files:
apps/web/components/ui/dialog.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for formatting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Use LoadingContent component for async data with pattern:<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>
UseuseActionhook fromnext-safe-action/hookswithonSuccessandonErrorcallbacks in forms
UsegetActionErrorMessage(error.error)from@/utils/errorto extract user-friendly error messages from server actions
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
apps/web/**/components/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/components/**/*.{tsx,jsx}: Follow consistent naming conventions: use PascalCase for component names
Use React Hook Form withzodResolverfor form validation and handling
Files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
apps/web/**/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
🧠 Learnings (16)
📚 Learning: 2026-01-07T21:07:06.691Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1230
File: apps/web/app/(app)/[emailAccountId]/drive/page.tsx:47-70
Timestamp: 2026-01-07T21:07:06.691Z
Learning: In TSX files across the codebase, prefer direct server action calls with manual error handling (e.g., check result?.serverError and handle errors explicitly). The useAction hook pattern is not required; you can call server actions directly and use try/finally to ensure cleanup (such as resetting loading states) regardless of the hook. Apply consistently for components that perform server interactions.
Applied to files:
apps/web/components/ui/dialog.tsxapps/web/components/SetupCard.tsxapps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/components/ui/**/*.tsx : Shadcn UI components are located in `components/ui` directory
Applied to files:
apps/web/components/SetupCard.tsx
📚 Learning: 2026-01-05T14:59:55.884Z
Learnt from: jshwrnr
Repo: elie222/inbox-zero PR: 1198
File: apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:10-10
Timestamp: 2026-01-05T14:59:55.884Z
Learning: In apps/web/app/(marketing)/**/content.tsx files, the pattern `metadata as unknown as AlternativeComparisonProps` (or similar double casts for MDX metadata) is used consistently across marketing pages (alternatives, case-studies). This is an intentional pattern for marketing content where runtime validation overhead is not justified, and should be kept for consistency.
Applied to files:
apps/web/components/SetupCard.tsx
📚 Learning: 2026-01-08T15:09:06.726Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2026-01-08T15:09:06.726Z
Learning: Applies to apps/web/components/**/*.tsx : Use `LoadingContent` component to handle loading and error states, passing `loading`, `error`, and `children` props
Applied to files:
apps/web/components/SetupCard.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Use shadcn/ui components when available
Applied to files:
apps/web/components/SetupCard.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in server actions
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : Use proper error handling with try/catch blocks
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching, prefer using `useSWR` and follow the data-fetching guidelines
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : Import error and success toast utilities from '@/components/Toast' for displaying notifications
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:37:35.343Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:35.343Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching in custom hooks, prefer using `useSWR` and wrap it to handle API endpoint URL, returning data, loading state, error state, and potentially the `mutate` function
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If nested deeply in components, use `swr` to fetch via API instead of loading data directly
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : Use generic error messages instead of revealing internal details; throw `SafeError` instead of exposing user IDs, resource IDs, or system information
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`) that wrap `useSWR`, handle the API endpoint URL, and return data, loading state, error state, and the `mutate` function
Applied to files:
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
🧬 Code graph analysis (2)
apps/web/components/SetupCard.tsx (5)
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx (1)
SetupContent(314-334)apps/web/components/ui/dialog.tsx (5)
Dialog(116-116)DialogContent(121-121)DialogHeader(122-122)DialogTitle(124-124)DialogDescription(125-125)apps/web/components/Typography.tsx (2)
TypographyH3(125-125)SectionDescription(127-127)apps/web/components/ui/item.tsx (5)
ItemGroup(187-187)Item(183-183)ItemContent(185-185)ItemTitle(189-189)ItemDescription(190-190)apps/web/components/ui/card.tsx (1)
CardFooter(201-201)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx (6)
apps/web/app/api/user/categorize/senders/categorized/route.ts (1)
CategorizedSendersResponse(6-8)apps/web/components/LoadingContent.tsx (1)
LoadingContent(13-31)apps/web/components/PageHeader.tsx (1)
PageHeader(21-45)apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx (1)
BulkArchiveProgress(10-97)apps/web/components/BulkArchiveCards.tsx (1)
BulkArchiveCards(33-318)apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (1)
AutoCategorizationSetup(32-81)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (4)
apps/web/components/ui/dialog.tsx (1)
34-54: LGTM! Clean implementation of optional close button suppression.The
hideCloseButtonprop is properly typed as optional and the conditional rendering logic is correct. The close button remains visible by default (good UX), and can be hidden when needed for modal flows that require completion.The accessibility is well-handled: the close button includes sr-only text, and consumers using this feature (like SetupDialog) appropriately prevent ESC/outside-click as well to create a consistent modal experience.
apps/web/components/SetupCard.tsx (3)
37-43: LGTM! Clean card wrapper with responsive layout.The component correctly wraps SetupContent in a Card with appropriate responsive margins and padding.
45-65: Modal dialog pattern prevents all user-initiated closing.The dialog implementation disables all close mechanisms (ESC key, outside clicks, close button), creating a truly modal experience where only programmatic control via the
openprop can dismiss it. This is appropriate for required setup/onboarding flows but ensure the parent component has proper logic to updateopenwhen the flow completes.The sr-only
DialogHeaderproperly maintains accessibility by providing context to screen readers while keeping the visual design clean.Verify that parent components consuming SetupDialog have proper state management to set
open={false}when the setup completes, preventing users from being trapped in the modal.
77-84: Verify theunoptimizedprop is intentional.The
unoptimizedprop bypasses Next.js image optimization, which impacts performance and bandwidth. This is typically only appropriate for SVG images or when image optimization causes issues.Confirm whether the images used with SetupCard/SetupDialog are SVGs or have specific reasons to skip optimization. If these are raster images (PNG, JPG, etc.), consider removing the
unoptimizedprop to benefit from Next.js automatic image optimization.
| const senders = data?.senders ?? []; | ||
| const categories = data?.categories ?? []; | ||
| const autoCategorizeSenders = data?.autoCategorizeSenders ?? false; |
There was a problem hiding this comment.
Setup dialog is blocked by LoadingContent on loading/error (and can prevent enabling the feature).
Because AutoCategorizationSetup is rendered inside LoadingContent (Line 49-61), it won’t appear while isLoading is true or when error is set—exactly when you may still want to show onboarding/setup (especially if the categorized endpoint errors).
Proposed fix (render setup outside LoadingContent + avoid defaulting autoCategorizeSenders to false)
- const autoCategorizeSenders = data?.autoCategorizeSenders ?? false;
+ const autoCategorizeSenders = data?.autoCategorizeSenders;
const handleProgressComplete = useCallback(() => {
mutate();
}, [mutate]);
const shouldShowSetup =
- onboarding || (!autoCategorizeSenders && !isBulkCategorizing);
+ onboarding || (autoCategorizeSenders === false && !isBulkCategorizing);
return (
- <LoadingContent loading={isLoading} error={error}>
- <PageWrapper>
- <PageHeader
- title="Bulk Archive"
- rightElement={
- <TooltipExplanation text="Archive emails in bulk by category to quickly clean up your inbox." />
- }
- />
- <BulkArchiveProgress onComplete={handleProgressComplete} />
- <BulkArchiveCards emailGroups={emailGroups} categories={categories} />
- </PageWrapper>
- <AutoCategorizationSetup open={shouldShowSetup} />
- </LoadingContent>
+ <>
+ <LoadingContent loading={isLoading} error={error}>
+ <PageWrapper>
+ <PageHeader
+ title="Bulk Archive"
+ rightElement={
+ <TooltipExplanation text="Archive emails in bulk by category to quickly clean up your inbox." />
+ }
+ />
+ <BulkArchiveProgress onComplete={handleProgressComplete} />
+ <BulkArchiveCards emailGroups={emailGroups} categories={categories} />
+ </PageWrapper>
+ </LoadingContent>
+ <AutoCategorizationSetup open={shouldShowSetup} />
+ </>
);Also applies to: 45-47, 49-61
🤖 Prompt for AI Agents
In @apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx around
lines 28 - 30, AutoCategorizationSetup is hidden by LoadingContent because
autoCategorizeSenders is defaulted to false and the setup component is rendered
inside the isLoading/error gated block; move the <AutoCategorizationSetup ... />
render outside the LoadingContent/error gating so it always renders (so users
can complete onboarding even when the fetch is loading/errored) and stop
defaulting autoCategorizeSenders to false—use const autoCategorizeSenders =
data?.autoCategorizeSenders; (and similarly avoid forcing senders/categories to
silent defaults that hide intent) so the setup UI can detect absence vs explicit
false.
…utton - Removed PageHeader component in favor of a simplified layout with PageHeading. - Added CategorizeWithAiButton for enhanced user interaction. - Updated CategorizeWithAiButton text for clarity. - Cleaned up unused imports and adjusted component structure for better readability.
- Wrapped the queue publishing logic in a try-catch block to log errors when publishing fails. - Enhanced error logging to include relevant details such as URL and queue name for better debugging.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/utils/upstash/index.ts">
<violation number="1" location="apps/web/utils/upstash/index.ts:77">
P1: Missing `await` on `queue.upsert()`. According to the Upstash QStash documentation, this is an async operation that should be awaited. Without `await`, errors from this call won't be caught by the try/catch, and the queue configuration may not complete before `enqueueJSON` is called.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @apps/web/utils/upstash/index.ts:
- Around line 74-86: The call to queue.upsert({ parallelism }) is not awaited,
which can cause unhandled rejections and a race where enqueueJSON runs before
upsert completes; update the logic in the block that creates const queue =
client.queue({ queueName }) (the code around queue.upsert and queue.enqueueJSON)
to await the upsert Promise (e.g., await queue.upsert({ parallelism })) before
calling or returning await queue.enqueueJSON(...), so any errors from upsert are
caught by the surrounding try/catch and the parallelism setting is applied
before enqueueing.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/utils/upstash/index.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/utils/upstash/index.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/utils/upstash/index.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/utils/upstash/index.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/upstash/index.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/utils/upstash/index.ts
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/utils/upstash/index.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/utils/upstash/index.ts
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/utils/upstash/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/utils/upstash/index.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/upstash/index.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/utils/upstash/index.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: cubic · AI code reviewer
- Updated getInternalApiUrl function to ensure the returned URL starts with "http://" or "https://". - Added logic to prepend "https://" if the URL does not already include a protocol, improving URL validity.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/web/utils/internal-api.ts (1)
9-11: Consider using URL constructor for more robust URL handling.The current string-based protocol check works for the common cases but doesn't handle edge cases like protocol-relative URLs (
//example.com), which would result inhttps:////example.com. While this may not be an issue in your current environment configuration, using the URL constructor would provide more robust URL parsing and validation.♻️ Alternative implementation using URL constructor
export function getInternalApiUrl(): string { - const url = env.INTERNAL_API_URL || env.NEXT_PUBLIC_BASE_URL; + const rawUrl = (env.INTERNAL_API_URL || env.NEXT_PUBLIC_BASE_URL).trim(); + + if (!rawUrl) { + throw new Error("Internal API URL is not configured"); + } - if (!url.startsWith("http://") && !url.startsWith("https://")) { - return `https://${url}`; + // Handle protocol-relative and non-protocol URLs + let url = rawUrl; + if (!url.match(/^https?:\/\//)) { + // Remove leading slashes for protocol-relative URLs + url = url.replace(/^\/+/, ""); + url = `https://${url}`; } + + // Validate the URL + try { + const parsed = new URL(url); + return parsed.toString(); + } catch (error) { + throw new Error(`Invalid internal API URL: ${rawUrl}`); + } - - return url; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/utils/internal-api.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/utils/internal-api.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/utils/internal-api.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/utils/internal-api.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/internal-api.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/utils/internal-api.ts
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/utils/internal-api.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/utils/internal-api.ts
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/utils/internal-api.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/utils/internal-api.ts
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/internal-api.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/utils/internal-api.ts
🧬 Code graph analysis (1)
apps/web/utils/internal-api.ts (1)
apps/web/env.ts (1)
env(17-260)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (1)
apps/web/utils/internal-api.ts (1)
7-13: Add validation to prevent invalid URL construction from empty strings.If
env.INTERNAL_API_URLis set to an empty string (which is truthy in JavaScript), the fallback won't trigger and line 10 would return"https://", which is an invalid URL. This would cause failures when the URL is subsequently used for API calls.🛡️ Proposed fix with validation
export function getInternalApiUrl(): string { - const url = env.INTERNAL_API_URL || env.NEXT_PUBLIC_BASE_URL; + const url = (env.INTERNAL_API_URL?.trim() || env.NEXT_PUBLIC_BASE_URL).trim(); + + if (!url) { + throw new Error("Internal API URL is not configured"); + } if (!url.startsWith("http://") && !url.startsWith("https://")) { return `https://${url}`; } return url; }Alternatively, for more robust validation:
export function getInternalApiUrl(): string { - const url = env.INTERNAL_API_URL || env.NEXT_PUBLIC_BASE_URL; + const rawUrl = (env.INTERNAL_API_URL?.trim() || env.NEXT_PUBLIC_BASE_URL).trim(); + + if (!rawUrl) { + throw new Error("Internal API URL is not configured"); + } + + let url = rawUrl; if (!url.startsWith("http://") && !url.startsWith("https://")) { - return `https://${url}`; + url = `https://${url}`; } + + // Validate the constructed URL + try { + new URL(url); + } catch (error) { + throw new Error(`Invalid internal API URL: ${url}`); + } return url; }⛔ Skipped due to learnings
Learnt from: CR Repo: elie222/inbox-zero PR: 0 File: .cursor/rules/environment-variables.mdc:0-0 Timestamp: 2025-11-25T14:36:45.807Z Learning: Applies to {.env.example,apps/web/env.ts} : Client-side environment variables must be prefixed with `NEXT_PUBLIC_`
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
apps/web/components/BulkArchiveCards.tsx (3)
150-155: Consider concurrent archiving for better performance.The current implementation archives senders sequentially, which could be slow for categories with many senders. Using
Promise.allwould process them concurrently.⚡ Proposed performance optimization
- for (const sender of selectedToArchive) { - await addToArchiveSenderQueue({ - sender: sender.address, - emailAccountId, - }); - } + await Promise.all( + selectedToArchive.map((sender) => + addToArchiveSenderQueue({ + sender: sender.address, + emailAccountId, + }) + ) + );Note: If sequential processing is intentional for rate-limiting, consider adding a comment explaining the reasoning.
436-436: Remove redundant slice operation.The
useThreadshook already limits results to 5 (line 405), making the.slice(0, 5)redundant.🧹 Proposed cleanup
- {data.threads.slice(0, 5).map((thread) => { + {data.threads.map((thread) => {
464-473: Extract snippet cleaning logic to a utility function.The snippet cleaning and truncation logic is complex and would benefit from extraction to a reusable utility function for better testability and potential reuse across the codebase.
♻️ Suggested refactor
Create a new utility function (e.g., in
@/utils/email.ts):export function cleanAndTruncateSnippet( snippet: string, maxLength = 80 ): string { const cleaned = snippet .replace(/[\u034F\u200B-\u200D\uFEFF\u00A0]/g, "") .trim() .replace(/\s+/g, " "); return cleaned.length > maxLength ? `${cleaned.slice(0, maxLength).trimEnd()}...` : cleaned; }Then use it in the component:
{snippet && ( <span className="ml-2 text-muted-foreground"> - {(() => { - const cleaned = snippet - .replace(/[\u034F\u200B-\u200D\uFEFF\u00A0]/g, "") - .trim() - .replace(/\s+/g, " "); - return cleaned.length > 80 - ? `${cleaned.slice(0, 80).trimEnd()}...` - : cleaned; - })()} + {cleanAndTruncateSnippet(snippet)} </span> )}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/components/BulkArchiveCards.tsxapps/web/components/ProgressPanel.tsx
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Use PascalCase for component file names (e.g.,
components/Button.tsx)
apps/web/components/**/*.tsx: Use React Hook Form withzodResolverfor form validation, combining it withuseActionhook fromnext-safe-action/hooksfor server action execution
UseuseActionhook withonSuccessandonErrorcallbacks for handling server action responses in forms
UsegetActionErrorMessage(error.error)utility to extract user-friendly error messages from server actions, supporting optionalprefixparameter
UseLoadingContentcomponent to handle loading and error states, passingloading,error, andchildrenprops
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for formatting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Use LoadingContent component for async data with pattern:<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>
UseuseActionhook fromnext-safe-action/hookswithonSuccessandonErrorcallbacks in forms
UsegetActionErrorMessage(error.error)from@/utils/errorto extract user-friendly error messages from server actions
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/components/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/components/**/*.{tsx,jsx}: Follow consistent naming conventions: use PascalCase for component names
Use React Hook Form withzodResolverfor form validation and handling
Files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
🧠 Learnings (29)
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use semantic elements instead of role attributes in JSX
Applied to files:
apps/web/components/ProgressPanel.tsx
📚 Learning: 2026-01-07T21:07:06.691Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1230
File: apps/web/app/(app)/[emailAccountId]/drive/page.tsx:47-70
Timestamp: 2026-01-07T21:07:06.691Z
Learning: In TSX files across the codebase, prefer direct server action calls with manual error handling (e.g., check result?.serverError and handle errors explicitly). The useAction hook pattern is not required; you can call server actions directly and use try/finally to ensure cleanup (such as resetting loading states) regardless of the hook. Apply consistently for components that perform server interactions.
Applied to files:
apps/web/components/ProgressPanel.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make static elements with click handlers use a valid role attribute
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use event handlers on non-interactive elements
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : Use proper error handling with try/catch blocks
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use unnecessary catch clauses
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `target="_blank"` without `rel="noopener"`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure all anchors are valid and navigable
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in server actions
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't reassign exceptions in catch clauses
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use unnecessary boolean casts
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-05T14:59:55.884Z
Learnt from: jshwrnr
Repo: elie222/inbox-zero PR: 1198
File: apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:10-10
Timestamp: 2026-01-05T14:59:55.884Z
Learning: In apps/web/app/(marketing)/**/content.tsx files, the pattern `metadata as unknown as AlternativeComparisonProps` (or similar double casts for MDX metadata) is used consistently across marketing pages (alternatives, case-studies). This is an intentional pattern for marketing content where runtime validation overhead is not justified, and should be kept for consistency.
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to **/*.{ts,tsx} : Always define types for A/B test variant flags (e.g., `type PricingVariant = "control" | "variant-a" | "variant-b"`) and provide type safety through type casting
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-08T15:09:06.726Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2026-01-08T15:09:06.726Z
Learning: Applies to apps/web/components/**/*.tsx : Use `LoadingContent` component to handle loading and error states, passing `loading`, `error`, and `children` props
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.tsx : Use the `LoadingContent` component to handle loading states instead of manual loading state management
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Use LoadingContent component for async data with pattern: `<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use the `LoadingContent` component to handle loading states
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If nested deeply in components, use `swr` to fetch via API instead of loading data directly
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching, prefer using `useSWR` and follow the data-fetching guidelines
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (4)
apps/web/components/ProgressPanel.tsx (1)
28-28: LGTM! Layout and semantic improvements.The changes appropriately adjust the component's presentation:
- Padding update (line 28) removes horizontal padding while maintaining vertical spacing, likely for better integration with the new bulk archive UI.
- Element change from
<p>to<div>(lines 40, 59) is semantically more correct—the content is a flex container with status displays, not a paragraph. Thearia-live="polite"attribute properly maintains accessibility for dynamic status updates.Also applies to: 40-59
apps/web/components/BulkArchiveCards.tsx (3)
1-31: LGTM! Clean import organization.The imports follow best practices: specific lodash imports, proper use of Next.js components, and all imports at the top of the file.
187-198: Excellent accessibility implementation!Both the category header and sender row properly implement keyboard navigation with
onKeyDown,role="button", andtabIndex={0}. This ensures the UI is fully accessible to keyboard and screen reader users.Also applies to: 317-328
449-453: Proper external link security.The Link component correctly includes
rel="noopener noreferrer"withtarget="_blank", preventing security vulnerabilities.
…r name support - Keep archived categories expandable so users can still see sender details - Add name field to Newsletter model for storing sender display names - Update EmailCell to accept separate name prop and use email.ts helpers - Update categorization functions to accept optional senderName parameter - Clean up EmailCell parsing logic using extractNameFromEmail/extractEmailAddress
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/categorize/senders/categorize.ts (1)
121-132: Add validation to guard against empty email extraction.The
extractEmailAddressfunction can legitimately return an empty string when no valid email is found (lines 32–65 inapps/web/utils/email.tsconfirm this behavior). However, there is no validation after the extraction on line 121 before theupsertcall, which would attempt to proceed with an empty string as part of the composite unique keyemail_emailAccountId. This will either cause a database constraint violation or create data integrity issues. Add a guard to either throw an error or handle the case whenextractEmailAddressreturns an empty string.
🤖 Fix all issues with AI agents
In @apps/web/components/BulkArchiveCards.tsx:
- Around line 244-252: The Button rendered for archiving currently lacks an
explicit type, which can cause unintended form submits; update the Button
component instance that calls archiveCategory (the one using onClick={(e) =>
archiveCategory(categoryName, e)} and rendering ArchiveIcon + conditional label
using isExpanded, getSelectedCount(categoryName), and senders.length) to include
type="button" as a prop so it will not act as a submit button.
🧹 Nitpick comments (6)
apps/web/utils/categorize/senders/categorize.ts (3)
92-95: Consider consistent null handling between update and create.The conditional spread
...(senderName && { name: senderName })in the update clause won't include thenamefield ifsenderNameis null, undefined, or an empty string. However, the create clause on line 98 directly assignsname: senderName, which will set it to null ifsenderNameis null or undefined.This asymmetry means:
- On update: If
senderNameis explicitlynull, the existing name won't be cleared- On create:
senderNameofnullwill storenullin the databaseIf the intent is to never clear existing names (preserve them), this is correct. However, if callers expect to clear names by passing
null, the update logic should be:♻️ Alternative for explicit null handling
update: { categoryId: category.id, - ...(senderName && { name: senderName }), + ...(senderName !== undefined && { name: senderName }), },This change allows
nullto explicitly clear the name whileundefinedmeans "don't update the name field."
125-125: Same null-handling pattern as updateSenderCategory.The same asymmetric null handling exists here. See the comment on
updateSenderCategoryfor details and suggested refinement.
122-122: Minor: Remove unnecessary empty line.This empty line appears to be an unintentional formatting change.
🧹 Cleanup
const email = extractEmailAddress(sender); - await prisma.newsletter.upsert({apps/web/components/BulkArchiveCards.tsx (3)
329-335: Consider fixing the event type mismatch.The
onToggleSelectionprop expectsReact.MouseEvent<HTMLButtonElement>, but the CheckboxonClickevent may have a different type. Since onlye.stopPropagation()is used, this works at runtime, but the type definition at line 309 could be relaxed toReact.MouseEventfor accuracy.Proposed fix
- onToggleSelection: (e: React.MouseEvent<HTMLButtonElement>) => void; + onToggleSelection: (e: React.MouseEvent) => void;
404-408: Redundant slice operation.The
useThreadshook is already called withlimit: 5, sodata.threads.slice(0, 5)is redundant.Proposed fix
- {data.threads.slice(0, 5).map((thread) => { + {data.threads.map((thread) => {Also applies to: 437-437
463-476: Consider extracting snippet cleaning to a helper function.The IIFE inside JSX reduces readability. Per coding guidelines, helper functions should be added at the bottom of files.
Proposed refactor
Add this helper at the bottom of the file:
function cleanSnippet(snippet: string, maxLength = 80): string { const cleaned = snippet .replace(/[\u034F\u200B-\u200D\uFEFF\u00A0]/g, "") .trim() .replace(/\s+/g, " "); return cleaned.length > maxLength ? `${cleaned.slice(0, maxLength).trimEnd()}...` : cleaned; }Then use it in JSX:
{snippet && ( <span className="ml-2 text-muted-foreground"> - {(() => { - // Remove invisible/zero-width chars and normalize whitespace - const cleaned = snippet - .replace(/[\u034F\u200B-\u200D\uFEFF\u00A0]/g, "") - .trim() - .replace(/\s+/g, " "); - return cleaned.length > 80 - ? `${cleaned.slice(0, 80).trimEnd()}...` - : cleaned; - })()} + {cleanSnippet(snippet)} </span> )}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsxapps/web/app/api/user/categorize/senders/categorized/route.tsapps/web/components/BulkArchiveCards.tsxapps/web/components/EmailCell.tsxapps/web/prisma/migrations/20260109163518_newsletter_sender_name/migration.sqlapps/web/prisma/schema.prismaapps/web/utils/bulk-archive/get-archive-candidates.tsapps/web/utils/categorize/senders/categorize.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
- apps/web/app/api/user/categorize/senders/categorized/route.ts
- apps/web/utils/bulk-archive/get-archive-candidates.ts
🧰 Additional context used
📓 Path-based instructions (18)
**/prisma/schema.prisma
📄 CodeRabbit inference engine (.cursor/rules/prisma.mdc)
Use PostgreSQL as the database system with Prisma
Files:
apps/web/prisma/schema.prisma
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/prisma/schema.prismaapps/web/utils/categorize/senders/categorize.tsapps/web/prisma/migrations/20260109163518_newsletter_sender_name/migration.sqlapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/web/utils/categorize/senders/categorize.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/{utils,helpers,lib}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
Logger should be passed as a parameter to helper functions instead of creating their own logger instances
Files:
apps/web/utils/categorize/senders/categorize.ts
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/utils/categorize/senders/categorize.tsapps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Use PascalCase for component file names (e.g.,
components/Button.tsx)
apps/web/components/**/*.tsx: Use React Hook Form withzodResolverfor form validation, combining it withuseActionhook fromnext-safe-action/hooksfor server action execution
UseuseActionhook withonSuccessandonErrorcallbacks for handling server action responses in forms
UsegetActionErrorMessage(error.error)utility to extract user-friendly error messages from server actions, supporting optionalprefixparameter
UseLoadingContentcomponent to handle loading and error states, passingloading,error, andchildrenprops
Files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for formatting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Use LoadingContent component for async data with pattern:<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>
UseuseActionhook fromnext-safe-action/hookswithonSuccessandonErrorcallbacks in forms
UsegetActionErrorMessage(error.error)from@/utils/errorto extract user-friendly error messages from server actions
Files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
apps/web/**/components/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/components/**/*.{tsx,jsx}: Follow consistent naming conventions: use PascalCase for component names
Use React Hook Form withzodResolverfor form validation and handling
Files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
🧠 Learnings (30)
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory
Applied to files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Applied to files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
Applied to files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to apps/web/utils/gmail/**/*.{ts,tsx} : Always use wrapper functions from @/utils/gmail/ for Gmail API operations instead of direct provider API calls
Applied to files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:22.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-11-25T14:37:22.660Z
Learning: Applies to **/*.{ts,tsx} : Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Applied to files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : For text inputs in forms, use the `Input` component with `type='email'`, `name`, `label`, `registerProps` from react-hook-form, and `error` props
Applied to files:
apps/web/components/EmailCell.tsx
📚 Learning: 2026-01-01T10:42:29.775Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2026-01-01T10:42:29.775Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helper functions `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for creating mock data
Applied to files:
apps/web/components/EmailCell.tsx
📚 Learning: 2026-01-07T21:07:06.691Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1230
File: apps/web/app/(app)/[emailAccountId]/drive/page.tsx:47-70
Timestamp: 2026-01-07T21:07:06.691Z
Learning: In TSX files across the codebase, prefer direct server action calls with manual error handling (e.g., check result?.serverError and handle errors explicitly). The useAction hook pattern is not required; you can call server actions directly and use try/finally to ensure cleanup (such as resetting loading states) regardless of the hook. Apply consistently for components that perform server interactions.
Applied to files:
apps/web/components/EmailCell.tsxapps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make static elements with click handlers use a valid role attribute
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use event handlers on non-interactive elements
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : Use proper error handling with try/catch blocks
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:36:40.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:40.146Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` from `@/components/Toast` for error handling in async operations
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use unnecessary catch clauses
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{jsx,tsx} : Don't use `target="_blank"` without `rel="noopener"`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure all anchors are valid and navigable
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't reassign exceptions in catch clauses
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in server actions
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use unnecessary boolean casts
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-05T14:59:55.884Z
Learnt from: jshwrnr
Repo: elie222/inbox-zero PR: 1198
File: apps/web/app/(marketing)/(alternatives)/best-fyxer-alternative/content.tsx:10-10
Timestamp: 2026-01-05T14:59:55.884Z
Learning: In apps/web/app/(marketing)/**/content.tsx files, the pattern `metadata as unknown as AlternativeComparisonProps` (or similar double casts for MDX metadata) is used consistently across marketing pages (alternatives, case-studies). This is an intentional pattern for marketing content where runtime validation overhead is not justified, and should be kept for consistency.
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to **/*.{ts,tsx} : Always define types for A/B test variant flags (e.g., `type PricingVariant = "control" | "variant-a" | "variant-b"`) and provide type safety through type casting
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-08T15:09:06.726Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2026-01-08T15:09:06.726Z
Learning: Applies to apps/web/components/**/*.tsx : Use `LoadingContent` component to handle loading and error states, passing `loading`, `error`, and `children` props
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:40:15.063Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:15.063Z
Learning: Applies to **/*.tsx : Use the `LoadingContent` component to handle loading states instead of manual loading state management
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2026-01-08T15:09:16.739Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2026-01-08T15:09:16.739Z
Learning: Applies to apps/web/**/*.{tsx,jsx} : Use LoadingContent component for async data with pattern: `<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:40:13.649Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-11-25T14:40:13.649Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Use the `LoadingContent` component to handle loading states
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:18.874Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:18.874Z
Learning: Applies to apps/web/app/(app)/**/*.tsx : If nested deeply in components, use `swr` to fetch via API instead of loading data directly
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:30.660Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/hooks.mdc:0-0
Timestamp: 2025-11-25T14:37:30.660Z
Learning: Applies to apps/web/hooks/use*.ts : For data fetching, prefer using `useSWR` and follow the data-fetching guidelines
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.
Applied to files:
apps/web/components/BulkArchiveCards.tsx
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`
Applied to files:
apps/web/components/BulkArchiveCards.tsx
🧬 Code graph analysis (2)
apps/web/utils/categorize/senders/categorize.ts (1)
apps/web/utils/email.ts (1)
extractEmailAddress(32-65)
apps/web/components/BulkArchiveCards.tsx (9)
apps/web/utils/bulk-archive/get-archive-candidates.ts (1)
EmailGroup(3-7)apps/web/utils/category.server.ts (1)
CategoryWithRules(4-11)apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(79-89)apps/web/utils/categories.ts (1)
defaultCategory(1-47)apps/web/store/archive-sender-queue.ts (2)
addToArchiveSenderQueue(19-111)useArchiveSenderStatus(118-121)apps/web/components/Toast.tsx (1)
toastError(14-19)apps/web/components/bulk-archive/categoryIcons.ts (1)
getCategoryStyle(21-71)apps/web/components/EmailCell.tsx (1)
EmailCell(4-23)apps/web/utils/date.ts (1)
formatShortDate(25-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
🔇 Additional comments (9)
apps/web/prisma/schema.prisma (1)
656-656: LGTM! Schema change properly adds sender name support.The optional
namefield on the Newsletter model is correctly typed and positioned. The corresponding migration is present and properly structured.apps/web/prisma/migrations/20260109163518_newsletter_sender_name/migration.sql (1)
1-2: LGTM! Migration is safe and correct.The ALTER TABLE statement properly adds the nullable TEXT column for storing sender names. Since it's nullable, no data migration or backfill is required.
apps/web/components/EmailCell.tsx (1)
2-20: LGTM!Clean refactoring that consolidates email parsing logic into reusable utilities. The conditional rendering with
showEmailcorrectly avoids redundant display when the extracted name equals the email address. The optionalnameprop allows callers (likeBulkArchiveCards) to pass pre-extracted sender names when available.apps/web/components/BulkArchiveCards.tsx (6)
1-31: LGTM!Imports follow project conventions: specific lodash import to minimize bundle size,
import typefor type-only imports, and consistent use of@/path aliases.
142-163: LGTM!Sequential processing via
awaitin the loop is appropriate here sinceaddToArchiveSenderQueueupdates jotai store state per sender. Parallel execution could cause race conditions in the queue state management.
187-198: Good accessibility implementation.The interactive div correctly includes
role="button",tabIndex={0}, and keyboard event handling for Enter and Space keys.
450-454: LGTM!External link correctly includes
rel="noopener noreferrer"withtarget="_blank"per security guidelines.
314-364: LGTM!
SenderRowcomponent is well-structured with proper accessibility attributes and clean separation of concerns. TheuseArchiveSenderStatushook provides reactive status updates.
366-393: LGTM!Clean status rendering with appropriate visual feedback for each archive queue state. The
defaultcase correctly returnsnullfor senders not yet in the queue.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx">
<violation number="1" location="apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx:111">
P2: Server action called without error handling. If `saveOnboardingFeaturesAction` fails, the user won't know their preferences weren't saved and will proceed to the next step. Consider adding a `.catch()` handler to show an error toast, even if you want to keep the non-blocking navigation.
(Based on your team's feedback about checking serverError and showing toast on failures.) [FEEDBACK_USED]</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| .map(([label]) => label); | ||
|
|
||
| // Fire and forget - don't block navigation | ||
| saveOnboardingFeaturesAction({ features }); |
There was a problem hiding this comment.
P2: Server action called without error handling. If saveOnboardingFeaturesAction fails, the user won't know their preferences weren't saved and will proceed to the next step. Consider adding a .catch() handler to show an error toast, even if you want to keep the non-blocking navigation.
(Based on your team's feedback about checking serverError and showing toast on failures.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx, line 111:
<comment>Server action called without error handling. If `saveOnboardingFeaturesAction` fails, the user won't know their preferences weren't saved and will proceed to the next step. Consider adding a `.catch()` handler to show an error toast, even if you want to keep the non-blocking navigation.
(Based on your team's feedback about checking serverError and showing toast on failures.) </comment>
<file context>
@@ -103,26 +101,16 @@ export function StepFeatures({ onNext }: { onNext: () => void }) {
+ .map(([label]) => label);
+
+ // Fire and forget - don't block navigation
+ saveOnboardingFeaturesAction({ features });
- try {
</file context>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx:
- Around line 104-114: The onClick currently fire-and-forgets
saveOnboardingFeaturesAction so failures are silent; change it to await the
promise, capture the result, and if result?.serverError is truthy call
toastError with that error (mirror the pattern used in StepDraft.tsx), then only
call onNext() when there is no serverError; also add the import for toastError
from "@/components/Toast" and handle any thrown exceptions by showing toastError
as well.
🧹 Nitpick comments (1)
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx (1)
108-108: Clarify variable name in destructuring.The destructured variable
labelis misleading—it actually contains thechoice.valuefield (the Map key set on line 84). Rename tovalueorfeatureValuefor clarity.♻️ Proposed refactor
const features = Array.from(selectedChoices.entries()) .filter(([_, isSelected]) => isSelected) - .map(([label]) => label); + .map(([value]) => value);
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
apps/web/app/(app)/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
apps/web/app/(app)/**/*.{ts,tsx}: Components for the page are either put inpage.tsx, or in theapps/web/app/(app)/PAGE_NAMEfolder
If we're in a deeply nested component we will useswrto fetch via API
If you need to useonClickin a component, that component is a client component and file must start withuse client
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Import specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Do not export types/interfaces that are only used within the same file. Export later if needed
Infer types from Zod schemas usingz.infer<typeof schema>instead of duplicating as separate interfaces
Centralize types in dedicated type files when shared across multiple files
Use SWR for client-side data fetching with patternuseSWR<GetExampleResponse>("/api/user/example")
Callmutate()after successful mutations or userevalidatePathin server actions for cache invalidation
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.tsx: Use theLoadingContentcomponent to handle loading states instead of manual loading state management
For text areas, use theInputcomponent withtype='text',autosizeTextareaprop set to true, andregisterPropsfor form integration
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>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 assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
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 atitleelement 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
AssigntabIndexto non-interactive HTML elements witharia-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 atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
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 AR...
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't use unnecessary fragments
Don't pass children as props
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 forget key props in iterators and collection literals
Don't define React components inside other components
Don't use event handlers on non-interactive elements
Don't assign to React component props
Don't use bothchildrenanddangerouslySetInnerHTMLprops on the same element
Don't use dangerous JSX props
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Don't add extra closing tags for components without children
Use<>...</>instead of<Fragment>...</Fragment>
Watch out for possible "wrong" semicolons inside JSX elements
Make sure void (self-closing) elements don't have children
Don't usetarget="_blank"withoutrel="noopener"
Don't use<img>elements in Next.js projects
Don't use<head>elements in Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
apps/web/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx,js,jsx}: Use@/path aliases for imports from project root instead of relative paths
Use proper error handling with try/catch blocks
Add helper functions to the bottom of files, not the top
All imports go at the top of files, no mid-file dynamic imports
UseNEXT_PUBLIC_prefix for client-side environment variables
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
apps/web/**/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Use NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
apps/web/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{tsx,jsx}: Follow tailwindcss patterns with prettier-plugin-tailwindcss for formatting
Prefer functional components with hooks over class components
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach in components
Use LoadingContent component for async data with pattern:<LoadingContent loading={isLoading} error={error}>{data && <YourComponent data={data} />}</LoadingContent>
UseuseActionhook fromnext-safe-action/hookswithonSuccessandonErrorcallbacks in forms
UsegetActionErrorMessage(error.error)from@/utils/errorto extract user-friendly error messages from server actions
Files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
🧠 Learnings (6)
📚 Learning: 2026-01-08T15:09:06.726Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2026-01-08T15:09:06.726Z
Learning: Organize fullstack feature code with GET routes at `apps/web/app/api/user/{feature}/route.ts`, validation at `apps/web/utils/actions/{feature}.validation.ts`, actions at `apps/web/utils/actions/{feature}.ts`, hooks at `apps/web/hooks/use{Feature}.ts`, and components at `apps/web/components/{Feature}.tsx`
Applied to files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
📚 Learning: 2025-11-25T14:38:27.988Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:27.988Z
Learning: Applies to apps/web/hooks/useFeatureFlags.ts : Use kebab-case for feature flag keys (e.g., `inbox-cleaner`, `pricing-options-2`)
Applied to files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
📚 Learning: 2025-11-25T14:36:36.276Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/data-fetching.mdc:0-0
Timestamp: 2025-11-25T14:36:36.276Z
Learning: Applies to **/*.{ts,tsx} : Import error and success toast utilities from '@/components/Toast' for displaying notifications
Applied to files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Centralize all feature flag hooks in `apps/web/hooks/useFeatureFlags.ts` rather than scattering them across multiple files
Applied to files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.
Applied to files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
📚 Learning: 2026-01-07T21:07:06.691Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1230
File: apps/web/app/(app)/[emailAccountId]/drive/page.tsx:47-70
Timestamp: 2026-01-07T21:07:06.691Z
Learning: In TSX files across the codebase, prefer direct server action calls with manual error handling (e.g., check result?.serverError and handle errors explicitly). The useAction hook pattern is not required; you can call server actions directly and use try/finally to ensure cleanup (such as resetting loading states) regardless of the hook. Apply consistently for components that perform server interactions.
Applied to files:
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx
🧬 Code graph analysis (1)
apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx (1)
apps/web/utils/actions/onboarding.ts (1)
saveOnboardingFeaturesAction(146-156)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: test
| onClick={() => { | ||
| // Get all selected features (only the ones that are true) | ||
| const features = Array.from(selectedChoices.entries()) | ||
| .filter(([_, isSelected]) => isSelected) | ||
| .map(([label, _]) => label); | ||
| .map(([label]) => label); | ||
|
|
||
| // Fire and forget - don't block navigation | ||
| saveOnboardingFeaturesAction({ features }); | ||
|
|
||
| try { | ||
| await saveOnboardingFeaturesAction({ features }); | ||
| onNext(); | ||
| } catch (error) { | ||
| console.error("Failed to save features:", error); | ||
| toastError({ | ||
| title: "Failed to save your preferences", | ||
| description: "Please try again.", | ||
| }); | ||
| setIsSaving(false); | ||
| } | ||
| onNext(); | ||
| }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and read the relevant section
head -n 150 apps/web/app/\(app\)/\[emailAccountId\]/onboarding/StepFeatures.tsx | tail -n 60Repository: elie222/inbox-zero
Length of output: 2018
🏁 Script executed:
# Also check the full file structure to understand context
wc -l "apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx"Repository: elie222/inbox-zero
Length of output: 129
🏁 Script executed:
# Look for saveOnboardingFeaturesAction definition
rg -n "saveOnboardingFeaturesAction" --type ts --type tsxRepository: elie222/inbox-zero
Length of output: 89
🏁 Script executed:
# Check imports and overall file structure
head -n 50 "apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx"Repository: elie222/inbox-zero
Length of output: 1643
🏁 Script executed:
# Find the saveOnboardingFeaturesAction definition
fd -t f "onboarding" apps/web/utils/actions/Repository: elie222/inbox-zero
Length of output: 146
🏁 Script executed:
# Search for the action definition
cat "apps/web/utils/actions/onboarding.ts" 2>/dev/null || cat "apps/web/utils/actions/onboarding.tsx" 2>/dev/null || echo "File not found, searching..."Repository: elie222/inbox-zero
Length of output: 5345
🏁 Script executed:
# Search more broadly for the action
rg "saveOnboardingFeaturesAction" -A 10 --max-count 5Repository: elie222/inbox-zero
Length of output: 2826
🏁 Script executed:
# Look for similar onClick patterns with server actions in the codebase
rg "onClick.*Action.*{" apps/web/app -A 3 --max-count 3Repository: elie222/inbox-zero
Length of output: 161
🏁 Script executed:
# Search for similar onClick patterns with server actions in forms/components
rg "onClick.*async.*Action" apps/web -A 5 --max-count 5Repository: elie222/inbox-zero
Length of output: 44
🏁 Script executed:
# Check for patterns using toastError with server actions
rg "toastError.*Action\|saveOnboarding\|serverError" apps/web -B 3 -A 3 --max-count 8Repository: elie222/inbox-zero
Length of output: 44
🏁 Script executed:
# Look at how other onboarding steps handle this
fd -t f "Step.*\.tsx" apps/web/app/\(app\)/\[emailAccountId\]/onboarding/Repository: elie222/inbox-zero
Length of output: 763
🏁 Script executed:
# Check one other step to see pattern
cat "apps/web/app/(app)/[emailAccountId]/onboarding/StepAnswers.tsx" | head -n 200Repository: elie222/inbox-zero
Length of output: 158
🏁 Script executed:
# Check another step file to see the pattern
cat "apps/web/app/(app)/[emailAccountId]/onboarding/StepDraft.tsx"Repository: elie222/inbox-zero
Length of output: 2776
🏁 Script executed:
# Check the validation schema to understand the action signature
cat "apps/web/utils/actions/onboarding.validation.ts"Repository: elie222/inbox-zero
Length of output: 567
🏁 Script executed:
# Search for examples of await with next-safe-action in the codebase
rg "const result = await.*Action\|result\?.serverError" apps/web -B 2 -A 2 --max-count 10Repository: elie222/inbox-zero
Length of output: 44
🏁 Script executed:
# Look at the actual return type/behavior - check if result needs to be checked
rg "actionClientUser" apps/web/utils/actions -A 3 | head -n 40Repository: elie222/inbox-zero
Length of output: 2810
🏁 Script executed:
# Check if there are any loading states or other patterns in StepFeatures
cat "apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx"Repository: elie222/inbox-zero
Length of output: 3908
🏁 Script executed:
# Look at safe-action to understand result type
cat "apps/web/utils/actions/safe-action.ts" | head -n 80Repository: elie222/inbox-zero
Length of output: 2442
🏁 Script executed:
# Verify that StepDraft actually handles the result correctly
cat "apps/web/app/(app)/[emailAccountId]/onboarding/StepDraft.tsx" | grep -A 10 "const result"Repository: elie222/inbox-zero
Length of output: 340
🏁 Script executed:
# Double check the map destructuring - is 'label' the right term?
# The choices array has 'value' property, let's confirm the destructuring
rg "selectedChoices.entries" -A 5Repository: elie222/inbox-zero
Length of output: 720
Add error handling for the save action.
The fire-and-forget pattern means users receive no feedback if their feature preferences fail to save. This violates the coding guideline to use result?.serverError with toastError for async operations. See StepDraft.tsx in the same directory for the correct pattern.
Await the action result and check for errors:
Proposed fix
- onClick={() => {
+ onClick={async () => {
// Get all selected features (only the ones that are true)
const features = Array.from(selectedChoices.entries())
.filter(([_, isSelected]) => isSelected)
.map(([label]) => label);
- // Fire and forget - don't block navigation
- saveOnboardingFeaturesAction({ features });
+ const result = await saveOnboardingFeaturesAction({ features });
+
+ if (result?.serverError) {
+ toastError({
+ description: "Failed to save your preferences. Please try again.",
+ });
+ return;
+ }
onNext();
}}Requires importing toastError from @/components/Toast.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @apps/web/app/(app)/[emailAccountId]/onboarding/StepFeatures.tsx around lines
104 - 114, The onClick currently fire-and-forgets saveOnboardingFeaturesAction
so failures are silent; change it to await the promise, capture the result, and
if result?.serverError is truthy call toastError with that error (mirror the
pattern used in StepDraft.tsx), then only call onNext() when there is no
serverError; also add the import for toastError from "@/components/Toast" and
handle any thrown exceptions by showing toastError as well.
Add Bulk Archive and Quick Bulk Archive flows with account-scoped redirects and in-page categorization progress in apps/web
Introduce account-scoped routes for bulk archive and quick bulk archive, add client pages for categorization progress and queueing archives, provide a provider-agnostic categorization batch handler, and expose a categorized senders API. Core entry points are implemented in apps/web/app/(app)/(redirects)/bulk-archive/page.tsx, apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx, apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx, and apps/web/app/api/user/categorize/senders/categorized/route.ts.
📍Where to Start
Start with the server page that wires data and permissions for Bulk Archive in apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx, then follow the flow into
BulkArchiveContentin apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx and the progress/polling logic.Macroscope summarized 823d56f.
Summary by CodeRabbit
New Features
Improvements
Refactor
Schema
Tests
✏️ Tip: You can customize this high-level summary in your review settings.
Note
Enables bulk archiving by sender categories with real-time categorization status and streamlined data fetching.
bulk-archiveandquick-bulk-archivewith UI: category cards, per-sender previews, selection, and archiving queueingBulkArchiveProgress+ProgressPanel; SWR polling andAutoCategorizationSetupto kick off AI categorizationGET /api/user/categorize/senders/categorizedendpoint for refreshed datacreateEmailProvider(provider-agnostic), removing Gmail-specific client/token usagebulkCategorizeSendersActionnow upserts default categories before queuing workEmailCellsubtitle visibility,ProgressPanelmarkup)Written by Cursor Bugbot for commit 823d56f. Configure here.