Skip to content

Conversation

@jshwrnr
Copy link
Collaborator

@jshwrnr jshwrnr commented Jan 5, 2026

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 BulkArchiveContent in apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx and the progress/polling logic.


Macroscope summarized 823d56f.

Summary by CodeRabbit

  • New Features

    • Complete bulk-archive workflow: redirect entry points, quick bulk-archive UI, categorized archive cards/tab, progress panel, and auto-categorization setup.
  • Improvements

    • Category system consolidated and renamed ("Unknown" → "Other"); many defaults merged into a unified Notification category.
    • UI tweaks: email subtitle hides when redundant; header supports an optional right-side element; remote image host added; layout nesting fixed.
  • Tests

    • Added tests for archive-candidate classification logic.

✏️ 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.

  • New account-scoped routes and pages for bulk-archive and quick-bulk-archive with UI: category cards, per-sender previews, selection, and archiving queueing
  • Live progress via BulkArchiveProgress + ProgressPanel; SWR polling and AutoCategorizationSetup to kick off AI categorization
  • Server pages load categorized senders and categories; new GET /api/user/categorize/senders/categorized endpoint for refreshed data
  • Categorization batch handler now uses createEmailProvider (provider-agnostic), removing Gmail-specific client/token usage
  • bulkCategorizeSendersAction now upserts default categories before queuing work
  • Utilities/tests: category-to-icon mapping and archive-candidate classifier; minor UI fixes (EmailCell subtitle visibility, ProgressPanel markup)

Written by Cursor Bugbot for commit 823d56f. Configure here.

@vercel
Copy link

vercel bot commented Jan 5, 2026

@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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

Adds a bulk-email archival feature: redirect routes, account-scoped pages, client UIs for selecting/archiving senders and progress, server APIs and batch handling via a pluggable email provider, classifier utilities + tests, and category/AI-constant renames.

Changes

Cohort / File(s) Summary
Redirect Pages
apps/web/app/(app)/(redirects)/bulk-archive/page.tsx, apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
New lightweight redirect pages forwarding to account-scoped bulk-archive routes using redirectToEmailAccountPath.
Account-Scoped Pages
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx, apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
New dynamic server pages (dynamic = "force-dynamic", maxDuration = 300) that validate ownership and fetch senders + categories in parallel to render bulk-archive scaffolding.
Bulk-Archive Client UI
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx, apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx, apps/web/components/BulkArchiveCards.tsx, apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx, apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
New client components: main container, quick-tab UI grouping candidates by confidence, categorized cards with selection/expansion, progress panel with polling/animation, and setup flow to enable bulk categorization.
Archive Candidate Logic & Tests
apps/web/utils/bulk-archive/get-archive-candidates.ts, apps/web/utils/bulk-archive/get-archive-candidates.test.ts
New classifier mapping email groups → archive candidates (high/medium/low) by category-name keywords; unit tests covering classification cases and ordering.
API Routes & Batch Handler
apps/web/app/api/user/categorize/senders/categorized/route.ts, apps/web/app/api/user/categorize/senders/batch/handle-batch.ts
New GET endpoint returning categorized senders+categories; batch handler refactored to use a pluggable email provider (removed Gmail-specific token handling) and updated return typing.
Categorization Actions & Redis
apps/web/utils/actions/categorize.ts, apps/web/utils/redis/categorization-progress.ts
bulkCategorizeSendersAction ensures default categories exist (createMany skipDuplicates) and enables emailAccount.autoCategorizeSenders; added deleteCategorizationProgress to clear Redis progress.
Category / AI Constant Changes
apps/web/utils/categories.ts, apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts, apps/web/__tests__/ai-categorize-senders.test.ts
Replaced UNKNOWNOTHER ("Other"); removed many default categories and introduced NOTIFICATION; AI constants and tests updated accordingly.
UI Helpers & Small Fixes
apps/web/components/SetupCard.tsx, apps/web/components/bulk-archive/categoryIcons.ts, apps/web/components/EmailCell.tsx, apps/web/components/ProgressPanel.tsx, apps/web/components/PageHeader.tsx
Added SetupCard and category icon/style mapper; conditional subtitle rendering in EmailCell; fixed markup in ProgressPanel; PageHeader accepts optional rightElement.
Onboarding / Config / Misc
apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx, apps/web/app/(app)/early-access/page.tsx, apps/web/next.config.ts
Onboarding switched to SetupCard; early-access JSX wrapper adjusted; added illustrations.popsy.co to Next image remote patterns.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Browser as Client (Browser)
    participant Server as App Server / Page
    participant DB as Database (Prisma)
    participant Provider as Email Provider
    participant Queue as Archive Queue

    User->>Browser: Open /bulk-archive (redirect)
    Browser->>Server: Request account-scoped BulkArchive page
    Server->>DB: validate ownership & fetch senders + categories
    DB-->>Server: senders + categories
    Server-->>Browser: render BulkArchive UI with initial data

    User->>Browser: Enable auto-categorize
    Browser->>Server: POST bulkCategorizeSendersAction
    Server->>DB: ensure default categories & set autoCategorizeSenders
    DB-->>Server: success
    Server-->>Browser: start progress polling

    User->>Browser: Select senders → Archive
    Browser->>Queue: enqueue archive jobs
    loop per job
        Queue->>Provider: fetch threads for sender
        Provider-->>Queue: return threads
        Queue->>DB: update archive state
        DB-->>Browser: progress updates (via API/SWR)
        Browser->>Browser: update UI
    end
    Queue-->>Browser: completion → show final state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Update digest to support Outlook #612 — refactors Gmail-specific logic to a pluggable email-provider abstraction; directly related to batch handler provider changes.
  • Smart Categories #252 — implements smart categories / bulk-categorization feature set; overlaps on categorization models, AI actions, and bulk-archive UI.
  • Longer bulk categorize #257 — modifies bulkCategorizeSendersAction and related categorization flows; related to default-category creation and auto-categorize toggling.

Poem

🐰 I hopped through folders, nibbling code,

I sorted senders high, medium, low,
I queued the jobs and fetched the threads,
I watched the progress bars glow,
🥕 inbox light — a rabbit's show!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Bulk Archive' directly describes the main feature added in this PR—a comprehensive bulk archive system with multiple pages, components, and flows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 getCategoryIcon after the ExpandedEmails component.


221-236: Consider parallelizing archive queue additions.

Using await inside a for loop processes senders sequentially, which may be slower than necessary. Per coding guidelines, avoid await inside 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.tsx and quick-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 - using await inside a for loop processes senders sequentially. Per coding guidelines, avoid await inside 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 getArchiveCandidates after the ExpandedEmails component. 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, and ExpandedEmails are nearly identical in both BulkArchiveTab.tsx and BulkArchiveCards.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

📥 Commits

Reviewing files that changed from the base of the PR and between c8adcf8 and 547d5a3.

📒 Files selected for processing (9)
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(marketing)
  • apps/web/components/BulkArchiveCards.tsx
  • apps/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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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 at apps/web/app/(app)/PAGE_NAME/page.tsx
Pages are Server components so you can load data into them directly

Create new pages at apps/web/app/(app)/PAGE_NAME/page.tsx with components either colocated in the same folder or in page.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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(marketing)
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(app)/early-access/page.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • apps/web/components/EmailCell.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use PascalCase for component file names (e.g., components/Button.tsx)

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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 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.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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)/**/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.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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)/(redirects)/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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)/(redirects)/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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)/(redirects)/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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]/quick-bulk-archive/page.tsx
  • apps/web/app/(app)/(redirects)/quick-bulk-archive/page.tsx
  • 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/*/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.tsx
  • 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 : 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 EarlyAccessFeatures component, 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 the EarlyAccessFeatures component. 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 redirectToEmailAccountPath utility. 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!

ExpandedEmails correctly uses the useThreads hook (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 via checkUserOwnsEmailAccount, 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.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

4 issues found across 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 &#39;more emails&#39; section will never be shown. Since `useThreads` is called with `limit: 5`, `data.threads.length` will always be &lt;= 5, making `totalCount &gt; displayedCount` always false. If you want to show a &#39;more&#39; 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (1)

30-63: Use toastError utility for consistent error handling.

The error handling implementation works but doesn't follow the established pattern. The coding guidelines specify using toastError from @/components/Toast for 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 toastError and toastSuccess from @/components/Toast for 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-unsubscribe folder while this page is in bulk-archive. If ArchiveProgress is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 547d5a3 and 3b4b8cf.

📒 Files selected for processing (6)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/api/user/categorize/senders/categorized/route.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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: Use next-safe-action with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in apps/web/utils/actions/*.validation.ts
Server actions should use 'use server' directive and automatically receive authentication context (emailAccountId) from the actionClient

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
Use 'use server' directive at the top of server action implementation files
Implement all server actions using the next-safe-action library with actionClient, actionClientUser, or adminActionClient for type safety and validation
Use actionClientUser when only authenticated user context (userId) is needed
Use actionClient when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
Use adminActionClient for 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.ts files in next-safe-action configuration
Access context (userId, emailAccountId, etc.) via the ctx object parameter in the .action() handler
Use revalidatePath or revalidateTag from '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 in apps/web/utils/actions folder
Server action files must start with use server directive

apps/web/utils/actions/**/*.ts: Use proper error handling with try/catch blocks
Use next-safe-action with Zod schemas for server actions to handle mutations
Use revalidatePath in 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's select option. 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. All findUnique/findFirst calls 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
All findMany queries 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
  • apps/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, and turbo.json

Files:

  • apps/web/utils/actions/categorize.ts
  • apps/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 using withAuth or withEmailAccount middleware in apps/web/app/api/*/route.ts, export response types as GetExampleResponse type alias for client-side type safety
Always export response types from GET routes as Get[Feature]Response using type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions with next-safe-action instead

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 with withAuth or withEmailAccount middleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes using Awaited<ReturnType<typeof functionName>> pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses using NextResponse.json() in GET API routes
Do not use try/catch blocks in GET API route handlers when using withAuth or withEmailAccount middleware, 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 use withAuth, withEmailAccount, or withError middleware for authentication
All database queries must include user scoping with emailAccountId or userId filtering 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; throw SafeError instead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields using select in database queries to prevent unintended information disclosure
Cron endpoints must use hasCronSecret or hasPostCronSecret to 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: use withEmailAccount for email-scoped operations, use withAuth for user-scoped operations, or use withError with proper validation for public/custom auth endpoints
Use withEmailAccount middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using emailAccountId
Use withAuth middleware for user-level operations such as user settings, API keys, and referrals that use only userId
Use withError middleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST use hasCronSecret() or hasPostCronSecret() validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret using hasCronSecret(request) or hasPostCronSecret(request) and capture unauthorized attempts with captureException()
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 with withAuth or withEmailAccount middleware for fetching data
Export response types from GET API routes using export 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.tsx
  • apps/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 actionClient with proper metadata
  • Includes emailAccountId in database operations for proper scoping
  • Uses skipDuplicates to 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 withEmailAccount middleware for authentication and error handling
  • Response type properly exported using Awaited<ReturnType<...>> pattern
  • Database queries correctly scoped to emailAccountId to prevent IDOR
  • Parallel data fetching with Promise.all for performance

Based on learnings, GET API routes should use withAuth or withEmailAccount middleware 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 fallbackData for 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.all is efficient, and the component composition correctly separates server and client boundaries using ClientOnly wrappers. Initial data is properly passed to the client component for hydration.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

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&#39;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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 keyResults array 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 interviewee contains 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:

  1. Removing these from staticUrls if they exist in Sanity, or
  2. 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: Add rel="noopener noreferrer" to external link.

Per accessibility and security guidelines, external links with target="_blank" should include rel="noopener noreferrer" to prevent the new page from accessing window.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 undefined href.

value?.href could 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 postSlugsQuery also references date which should likely be publishedAt to 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: date should be publishedAt.

The recentPostsQuery and postSlugsQuery reference a date field that doesn't exist in the postType schema. The schema defines publishedAt as 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 removing async if no asynchronous operations are performed.

The function is marked async but doesn't use await or 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 div could 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 readTime prop to CaseStudyCardProps or 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, and author props 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:

  1. Import format and parseISO from date-fns
  2. 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 name and slug fields 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.tsx creates its own builder instance via imageUrlBuilder(client) instead of using this urlFor helper, resulting in duplicated instantiation.

Consider using this urlFor helper in Post.tsx to 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 centralized urlFor helper.

This creates a duplicate image URL builder instance. The urlFor helper in @/app/(marketing)/sanity/lib/image already 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) to urlFor(post.mainImage).


87-103: Avoid console.error in 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 StructuredData component for better SEO and social sharing. The empty array image={[]} 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 title and slug fields 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.title as the key may cause issues if two posts have the same title. Consider using the file (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: Use notFound() for proper 404 handling.

Returning a simple <div> when a post isn't found results in a 200 status code. Use Next.js's notFound() 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-ignore comment for .fit("max") is flagged as noFocusedTests, 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")

@vercel
Copy link

vercel bot commented Jan 5, 2026

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

Project Deployment Review Updated (UTC)
inbox-zero Ready Ready Preview Jan 8, 2026 6:55am

- 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 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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 reset button 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 getCategoryIcon after 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 BulkArchiveTab component only accesses category.name and never uses the rules field.

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 getUserCategoriesWithRules to skip fetching rules for this use case.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3791f29 and 486e797.

📒 Files selected for processing (6)
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • 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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • 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/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • 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
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.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/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.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • 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 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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/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.tsx
  • apps/web/app/(marketing)/(landing)/error.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/web/utils/actions/categorize.ts
  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/BulkArchiveTab.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use 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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/quick-bulk-archive/page.tsx
  • apps/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: Use next-safe-action with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in apps/web/utils/actions/*.validation.ts
Server actions should use 'use server' directive and automatically receive authentication context (emailAccountId) from the actionClient

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
Use 'use server' directive at the top of server action implementation files
Implement all server actions using the next-safe-action library with actionClient, actionClientUser, or adminActionClient for type safety and validation
Use actionClientUser when only authenticated user context (userId) is needed
Use actionClient when both authenticated user context and a specific emailAccountId are needed, with emailAccountId bound when calling from the client
Use adminActionClient for 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.ts files in next-safe-action configuration
Access context (userId, emailAccountId, etc.) via the ctx object parameter in the .action() handler
Use revalidatePath or revalidateTag from '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 in apps/web/utils/actions folder
Server action files must start with use server directive

apps/web/utils/actions/**/*.ts: Use proper error handling with try/catch blocks
Use next-safe-action with Zod schemas for server actions to handle mutations
Use revalidatePath in 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's select option. 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. All findUnique/findFirst calls 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
All findMany queries 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, and turbo.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.tsx
  • apps/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.tsx
  • 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/(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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • 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 : 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.tsx
  • apps/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.tsx
  • 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} : 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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/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 } and reset function
  • 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 ErrorDisplay

This 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 actionClient with proper metadata for Sentry instrumentation
  • Filters only enabled default categories
  • Scopes all created categories to the authenticated emailAccountId
  • Uses skipDuplicates: true for idempotent bulk creation
  • Returns the actual result.count from Prisma's createMany (addressing the previous review feedback)
  • Revalidates the correct path
apps/web/components/BulkArchiveCards.tsx (4)

459-525: LGTM!

The SenderRow component properly implements:

  • Keyboard accessibility with role="button", tabIndex={0}, and onKeyDown handler
  • Visual feedback for selection state with opacity changes
  • Archive status display via useArchiveSenderStatus hook

527-554: LGTM!

Clean status display component with appropriate visual indicators for each archive state.


556-648: LGTM!

The ExpandedEmails component properly:

  • Uses useThreads hook for SWR-based data fetching (per coding guidelines)
  • Handles loading, error, and empty states appropriately
  • Guards against empty messages array (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 getArchiveCandidates function is well-documented as a placeholder for future AI-driven analysis. The keyword-based categorization provides reasonable fallback behavior, and the confidenceConfig object 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 useMemo for derived state to optimize re-renders
  • Includes comprehensive error handling in archiveSelected with try/catch/finally
  • Displays user-friendly error messages via toast notifications
  • Prevents UI state issues by resetting isArchiving in 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}, and onKeyDown handlers
  • 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 useThreads hook 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 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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 sanityFetch call 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.title as the key could cause issues if titles are duplicated. The post.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

📥 Commits

Reviewing files that changed from the base of the PR and between 486e797 and d93dc93.

📒 Files selected for processing (4)
  • apps/web/app/(marketing)/blog/components/TableOfContents.tsx
  • apps/web/app/(marketing)/blog/components/TryInboxZero.tsx
  • apps/web/app/(marketing)/blog/page.tsx
  • apps/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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for 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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import 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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for 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 the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for 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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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
Use result?.serverError with toastError and toastSuccess for 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 use next/image and next/link as required.


9-27: LGTM!

Type definitions are appropriately scoped to this file and not exported, following the coding guidelines.

@jshwrnr jshwrnr changed the title Feat/bulk-archive Bulk Archive Jan 6, 2026
Copy link
Contributor

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

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 getCategoryIcon below the component definitions.


221-242: Sequential await in loop may be intentional but worth noting.

The for...of loop with sequential await calls processes senders one at a time. This is likely intentional for rate limiting, but if the queue can handle concurrent additions, consider using Promise.all for 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.MouseEvent on 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 onToggleSelection prop type to not require the event if it's only used for stopPropagation.

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 mdxPosts array.


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 like alt={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 mdxPosts array 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

📥 Commits

Reviewing files that changed from the base of the PR and between d93dc93 and 22e00b0.

📒 Files selected for processing (4)
  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/app/(marketing)/blog/page.tsx
  • apps/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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(marketing)/blog/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(marketing)/blog/page.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • 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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(marketing)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(marketing)/blog/page.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use 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.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • 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)/(landing)/creator/page.tsx
  • apps/web/app/(marketing)/(landing)/enterprise/page.tsx
  • 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)/**/*.{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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • 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} : 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 EnterpriseFeatures function 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 internal EmailGroup type 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 ArchiveStatus component 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 when thread.messages is 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 Metadata uses the proper import type syntax.


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 CreatorPage component properly uses Next.js conventions as a server component, follows the established marketing page composition pattern, and correctly implements client-side navigation with Next.js Link (lines 54-56). The previous navigation concern has been appropriately resolved.


65-89: LGTM! Helper component follows established patterns.

The CreatorFeatures component 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.

jshwrnr and others added 2 commits January 6, 2026 09:54
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 <[email protected]>
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 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 await in a loop, which processes senders one at a time. If rate limiting isn't a concern for addToArchiveSenderQueue, using Promise.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

📥 Commits

Reviewing files that changed from the base of the PR and between 22e00b0 and 03bb4cc.

📒 Files selected for processing (2)
  • apps/web/components/BulkArchiveCards.tsx
  • apps/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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use 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/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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/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.tsx
  • apps/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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/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.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for 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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.tsx
  • apps/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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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.tsx
  • apps/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.tsx
  • apps/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.tsx
  • apps/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
Use result?.serverError with toastError and toastSuccess for 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.tsx
  • apps/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's select option. 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. All findUnique/findFirst calls 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
All findMany queries 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, and turbo.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 EmailGroup type is appropriately scoped for this component.


47-100: LGTM! State management and memoization are well-structured.

The component properly uses useMemo with correct dependencies, and the state organization is logical for managing the complex UI interactions (expansion, selection, archival status). The groupedEmails memoization 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, and onKeyDown handlers 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 with stopPropagation where 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 ArchiveStatus component 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 firstMessage on 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/components/SetupCard.tsx (1)

36-43: Consider optimizing the image.

The unoptimized prop disables Next.js automatic image optimization. Unless there's a specific reason (e.g., SVG animations or external URL issues), consider removing it to benefit from Next.js image optimization features like lazy loading and automatic format selection.

💡 Suggested change
       <Image
         src={imageSrc}
         alt={imageAlt}
         width={200}
         height={200}
         className="mx-auto dark:brightness-90 dark:invert"
-        unoptimized
       />
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81d570b and 72ff54a.

⛔ Files ignored due to path filters (1)
  • apps/web/public/images/illustrations/working-vacation.svg is excluded by !**/*.svg
📒 Files selected for processing (7)
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/components/SetupCard.tsx
  • apps/web/next.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use PascalCase for component file names (e.g., components/Button.tsx)

Files:

  • apps/web/components/SetupCard.tsx
  • 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/SetupCard.tsx
  • apps/web/components/BulkArchiveCards.tsx
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/components/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/components/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}

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

Format code with Prettier

Files:

  • apps/web/components/SetupCard.tsx
  • apps/web/next.config.ts
  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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's select option. 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. All findUnique/findFirst calls 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
All findMany queries 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/next.config.ts
apps/web/**/*.{example,ts,json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/next.config.ts
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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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]/briefs/Onboarding.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
🧠 Learnings (29)
📚 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: 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} : Ensure responsive design with mobile-first approach in components

Applied to files:

  • apps/web/components/SetupCard.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} : Prefer functional components with hooks in React

Applied to files:

  • apps/web/components/SetupCard.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 shadcn/ui components when available

Applied to files:

  • apps/web/components/SetupCard.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/next.config.ts
📚 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/next.config.ts
📚 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/next.config.ts
📚 Learning: 2026-01-05T13:44:53.648Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 1199
File: apps/web/utils/meeting-briefs/send-briefing.ts:0-0
Timestamp: 2026-01-05T13:44:53.648Z
Learning: In apps/web/utils/meeting-briefs/, the Zod schema in generate-briefing.ts intentionally only includes `{guests: GuestBriefing[]}` because the AI only generates guest briefings. The `internalTeamMembers` field comes from domain-based filtering in gather-context.ts (not AI-researched) and is merged into the BriefingContent in send-briefing.ts before sending the email. This separation keeps the Zod schema focused on what the AI actually generates.
<!-- [add_learning]
In the meeting briefing feature (apps/web/utils/meeting-briefs/), there's an intentional type separation: the Zod schema BriefingContent type from generate-briefing.ts differs from the email package BriefingContent type in packages/resend/emails/meeting-briefing.tsx. The AI-generated content uses a narrower type while the email content includes additional domain-filtered data merged in send-briefing.ts.

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.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]/briefs/Onboarding.tsx
  • 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: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)/**/*.{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: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]/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: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-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]/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: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: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-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/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
🧬 Code graph analysis (1)
apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx (2)
apps/web/components/SetupCard.tsx (1)
  • SetupCard (19-69)
apps/web/app/(app)/[emailAccountId]/calendars/ConnectCalendar.tsx (1)
  • ConnectCalendar (13-124)
⏰ 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 (16)
apps/web/next.config.ts (1)

61-64: Configuration change is correct and properly configured.

The domain illustrations.popsy.co is actively used in the codebase via celebration.ts, which exports getCelebrationImage(). This utility is imported by the Celebration component, which uses next/image to load the illustrations and is integrated into the EmailList component. The remote pattern configuration is properly formatted, follows the existing pattern structure, and uses HTTPS for security.

apps/web/app/(app)/[emailAccountId]/briefs/Onboarding.tsx (2)

3-25: LGTM! Clean refactoring to use shared SetupCard component.

The extraction of features into a static array is a good pattern for maintainability. The component now uses the shared SetupCard component, reducing code duplication.


27-63: LGTM! Proper use of SetupCard with conditional content.

The component correctly uses SetupCard's children prop to render different content based on whether the calendar is connected, maintaining the original behavior while simplifying the UI structure.

apps/web/components/SetupCard.tsx (1)

1-17: LGTM! Clean type definitions and imports.

The FeatureItem type is well-structured, and all necessary dependencies are properly imported. Using ReactNode for the icon provides good flexibility for different icon types.

apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (3)

1-29: LGTM! Well-structured setup with clear feature descriptions.

The static features array clearly communicates the value proposition of the bulk archive feature to users.


37-61: LGTM! Proper error handling and state management.

The enableFeature callback correctly handles the async operation with proper loading states and error handling. The past review concern about redundant server actions has been addressed—this now makes a single call to bulkCategorizeSendersAction.


63-76: LGTM! Clean setup UI using shared component.

The component properly uses SetupCard with appropriate props and includes a loading state on the action button for good UX.

apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (3)

1-12: LGTM! Appropriate route configuration.

The force-dynamic setting is correct for user-specific data, and the 5-minute maxDuration provides sufficient time for bulk operations.


14-38: LGTM! Efficient server-side data fetching with proper authorization.

The page correctly:

  • Uses Next.js 15's async params/searchParams API
  • Performs ownership validation before data access
  • Fetches data in parallel for optimal performance
  • Determines onboarding state based on both query params and data availability

40-70: LGTM! Clean conditional rendering with proper client/server boundaries.

The page correctly wraps client components in ClientOnly and provides appropriate layouts for both onboarding and normal states. Initial server-fetched data is properly passed down to client components.

apps/web/components/BulkArchiveCards.tsx (6)

1-34: LGTM! Clean imports following project guidelines.

Good use of specific lodash imports (import groupBy from 'lodash/groupBy') to minimize bundle size, as per coding guidelines.


36-90: LGTM! Well-structured state management and memoization.

The component properly uses useMemo for derived data (categoryMap, groupedEmails) and maintains clean separation of concerns with multiple focused state hooks. The sorting logic appropriately handles the "Uncategorized" category.


134-155: LGTM! Error handling properly implemented.

The past review concerns have been addressed:

  • Try/catch block ensures errors are caught
  • Category is only marked as archived after all senders are successfully queued
  • Error variable properly prefixed with underscore to indicate intentional non-use
  • Toast notification provides user feedback on failure

Note: The sequential await in the loop may impact performance for large sender lists, but this could be intentional for rate limiting.


195-241: LGTM! Excellent accessibility implementation.

The category header properly implements:

  • Keyboard navigation with onKeyDown handling Enter/Space
  • Semantic role="button" with tabIndex={0}
  • Visual hover states
  • Event propagation control

This follows accessibility best practices per coding guidelines.


276-371: LGTM! Clean component implementations with proper state handling.

Both SenderRow and ArchiveStatus are well-implemented:

  • Proper accessibility with keyboard navigation
  • Clear visual feedback for different archive states
  • Correct event handling with stopPropagation

373-466: LGTM! All past review issues have been addressed.

The ExpandedEmails component now correctly handles:

  • Null check for firstMessage preventing crashes (line 417)
  • Security with rel="noopener noreferrer" on external links (line 431)
  • Proper loading, error, and empty states
  • Thorough snippet text cleaning for display

The dead code issue ("+N more emails" section) mentioned in past reviews has also been removed.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

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/ai/categorize-sender/ai-categorize-senders.ts">

<violation number="1" location="apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts:11">
P2: The prompt still instructs the AI to use "Unknown" (lines 78 and 85), but the `UNKNOWN_CATEGORY` constant was changed to "Other". This creates an inconsistency - either the prompt should be updated to use "Other" (or interpolate the constant), or keep the constant as "Unknown".</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx:
- Around line 30-36: Destructure the error from the useSWR call in
BulkArchiveContent (e.g., const { data, mutate, error } = useSWR(...)) and add
an effect/handler that, when error is truthy, calls toastError with the server
error payload (use data?.serverError or result?.serverError per guideline) to
surface fetch failures; ensure you import toastError from "@/components/Toast"
and avoid duplicate toasts by guarding so the toast only runs when the error
first appears.

In @apps/web/components/BulkArchiveCards.tsx:
- Around line 32-35: You duplicated the EmailGroup type in BulkArchiveCards.tsx;
instead remove the local type and import the exported EmailGroup type from the
module that defines archive candidate utilities (the module that exports
EmailGroup from get-archive-candidates), updating the top-level imports in
BulkArchiveCards.tsx and replacing the local declaration; ensure you import it
as a type-only import (e.g., import type { EmailGroup } from '...') and adjust
any usages to reference the imported EmailGroup.
- Around line 385-412: Replace the manual loading/error conditional block in
BulkArchiveCards (the useThreads call and subsequent isLoading/error checks)
with the LoadingContent component: call useThreads(...) as before, then render
<LoadingContent loading={isLoading} error={error}> around the normal UI; move
the "No emails found" / thread list rendering into LoadingContent's children so
LoadingContent handles the skeleton and error state consistently. Ensure you
import LoadingContent and preserve the existing props passed to useThreads
(sender, limit, type) and the existing border-t/bg-muted container styling when
migrating the UI into LoadingContent.
- Around line 311-318: The Checkbox click handler is using a double cast (as
unknown as React.MouseEvent) which bypasses TypeScript safety; update the types
so you pass the real event type instead of force-casting: adjust the Checkbox
onClick signature or change the onToggleSelection parameter type in the
SenderRow props to accept the event type emitted by Checkbox (e.g.,
React.MouseEvent<HTMLButtonElement> or the correct element/event generic), and
then call onToggleSelection(e) without casting and remove e.stopPropagation from
the cast workaround if not needed.

In @apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts:
- Line 11: The prompt text still tells the LLM to return "Unknown" while the
constant UNKNOWN_CATEGORY was changed to "Other"; update the prompt occurrences
that instruct the model to use "Unknown" so they instead reference the
UNKNOWN_CATEGORY value or the string "Other" (the two prompt spots in
ai-categorize-senders.ts where the LLM is explicitly told to return "Unknown"),
ensuring the returned label matches the UNKNOWN_CATEGORY constant used later in
the code.
🧹 Nitpick comments (3)
apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx (1)

33-33: Consider whether 2-second polling interval is appropriate.

The component polls every 2 seconds (refreshInterval: 2000) while categorization is in progress. Depending on the categorization workload and expected duration, this could be aggressive. Consider whether a longer interval (e.g., 5000ms) would be sufficient while reducing server load.

apps/web/components/BulkArchiveCards.tsx (2)

145-150: Consider parallel processing for better performance.

The sequential await in the loop processes senders one at a time, which may be slow for categories with many senders. Unless sequential processing is required for rate limiting, consider using Promise.all() for parallel execution.

⚡ Proposed optimization
     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 }));

446-456: Extract snippet cleaning logic to a helper function.

The inline snippet cleaning logic using an IIFE makes the render logic harder to read and test. Consider extracting it to a named helper function at the bottom of the file for better code organization.

♻️ Suggested refactor

Add at the bottom of the file:

function cleanSnippet(snippet: string, maxLength: number = 80): string {
  // 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 > maxLength
    ? `${cleaned.slice(0, maxLength).trimEnd()}...`
    : cleaned;
}

Then simplify the render:

                   {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

📥 Commits

Reviewing files that changed from the base of the PR and between 72ff54a and 3868dfe.

📒 Files selected for processing (6)
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/utils/categories.ts
🧰 Additional context used
📓 Path-based instructions (29)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.ts
apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts

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

LLM-related code must be organized in specific directories: apps/web/utils/ai/ for main implementations, apps/web/utils/llms/ for core utilities and configurations, and apps/web/__tests__/ for LLM-specific tests

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
apps/web/utils/ai/**/*.ts

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

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
LLM feature functions must follow a standard structure: accept options with inputData and emailAccount parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use createGenerateObject to execute the LLM call
System prompts must define the LLM's role and task specifications
User prompts must contain the actual data and context, and should be kept separate from system prompts
Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output
Use descriptive scoped loggers for each LLM feature, log inputs and outputs with appropriate log levels, and include relevant context in log messages
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
Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.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/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.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's select option. 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. All findUnique/findFirst calls 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
All findMany queries 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/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/categories.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.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/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.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/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.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/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • 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/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/utils/categories.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/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.ts
apps/web/**/*.{ts,tsx,js,jsx,json,css}

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

Format code with Prettier

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/categories.ts
apps/web/**/*.{example,ts,json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/categories.ts
apps/web/__tests__/**/*.test.ts

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

apps/web/__tests__/**/*.test.ts: Place all LLM-related tests in apps/web/__tests__/ directory
Use vitest imports (describe, expect, test, vi, beforeEach) in LLM test files
Mock 'server-only' module with empty object in LLM test files: vi.mock("server-only", () => ({}))
Set timeout constant const TIMEOUT = 15_000; for LLM tests
Use describe.runIf(isAiTest) with environment variable RUN_AI_TESTS === "true" to conditionally run LLM tests
Use console.debug() for outputting generated LLM content in tests, e.g., console.debug("Generated content:\n", result.content);
Prefer using existing helpers from @/__tests__/helpers.ts (getEmailAccount, getEmail, getRule, getMockMessage, getMockExecutedRule) instead of creating custom test data helpers

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Make sure the assertion function, like expect, is placed inside an it() function call
Don't use disabled tests

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
**/{scripts,tests,__tests__}/**/*.{ts,tsx}

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

Use createScopedLogger only for code that doesn't run within a middleware chain, such as standalone scripts or tests

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
apps/web/**/*.test.{ts,tsx}

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

Co-locate test files next to source files (e.g., utils/example.test.ts). Only E2E and AI tests go in __tests__/

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use vitest as the testing framework
Colocate test files next to the tested file with .test.ts or .test.tsx naming convention (e.g., dir/format.ts and dir/format.test.ts)
Mock server-only using vi.mock("server-only", () => ({}))
Mock Prisma using vi.mock("@/utils/prisma") and the provided mock from @/utils/__mocks__/prisma
Use test helper functions getEmail, getEmailAccount, and getRule from @/__tests__/helpers for creating mock data
Clear all mocks between tests using beforeEach(() => { vi.clearAllMocks(); })
Use descriptive test names that clearly indicate what is being tested
Do not mock the Logger in tests

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
**/__tests__/**/*.{ts,tsx}

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

Place AI tests in the __tests__ directory and do not run them by default as they use a real LLM

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
**/*.test.{js,jsx,ts,tsx}

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

Co-locate test files next to source files (e.g., utils/example.test.ts). Only E2E and AI tests go in __tests__/

Files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • apps/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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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/BulkArchiveContent.tsx
  • apps/web/components/BulkArchiveCards.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/BulkArchiveContent.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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.tsx
  • 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/bulk-archive/categoryIcons.ts
  • apps/web/components/BulkArchiveCards.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use PascalCase for component file names (e.g., components/Button.tsx)

Files:

  • apps/web/components/BulkArchiveCards.tsx
🧠 Learnings (21)
📚 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 **/__tests__/**/*.{ts,tsx} : Place AI tests in the `__tests__` directory and do not run them by default as they use a real LLM

Applied to files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
📚 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 : Use `describe.runIf(isAiTest)` with environment variable `RUN_AI_TESTS === "true"` to conditionally run LLM tests

Applied to files:

  • apps/web/__tests__/ai-categorize-senders.test.ts
📚 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/__tests__/ai-categorize-senders.test.ts
📚 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/__tests__/ai-categorize-senders.test.ts
📚 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/__tests__/ai-categorize-senders.test.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 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.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/BulkArchiveContent.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 : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`) to wrap `useSWR` for individual API endpoints

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveContent.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} : Use kebab-case for PostHog feature flag keys (e.g., `inbox-cleaner`, `pricing-options-2`)

Applied to files:

  • apps/web/components/bulk-archive/categoryIcons.ts
📚 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: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-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/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
🧬 Code graph analysis (1)
apps/web/components/BulkArchiveCards.tsx (11)
apps/web/utils/bulk-archive/get-archive-candidates.ts (1)
  • EmailGroup (3-6)
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-24)
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)
  • getCategoryIcon (8-17)
apps/web/components/EmailCell.tsx (1)
  • EmailCell (3-26)
apps/web/hooks/useThreads.ts (1)
  • useThreads (8-28)
apps/web/components/new-landing/icons/Link.tsx (1)
  • Link (1-18)
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 (3)
apps/web/components/bulk-archive/categoryIcons.ts (1)

8-17: LGTM! Clean icon mapping implementation.

The function correctly maps category names to appropriate lucide-react icons with case-insensitive matching and a sensible default fallback.

apps/web/__tests__/ai-categorize-senders.test.ts (1)

100-100: LGTM! Test correctly updated for category rename.

The filter now excludes "Other" instead of "Unknown", properly aligning with the UNKNOWN_CATEGORY constant change.

apps/web/utils/categories.ts (1)

1-24: No breaking change risk — existing categorizations are preserved.

The categories are fully database-driven and user-scoped (linked to emailAccountId). Existing senders with any category assignment will remain unchanged; the Newsletter.categoryId field supports any category stored in the database. The bulkCategorizeSendersAction uses skipDuplicates: true when creating default categories, so it safely handles both new and existing users. No data migration is needed, and automation rules/filters are independent of the defaultCategory constant since they reference actual database categories.

Likely an incorrect or invalid review comment.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

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/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx">

<violation number="1" location="apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx:64">
P1: The `onComplete` callback is commented out and will never be called, even though the parent passes `handleSetupComplete`. This appears to be incomplete code - either uncomment the line and add `onComplete` to the dependency array, or remove the prop if it's not needed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @apps/web/components/BulkArchiveCards.tsx:
- Around line 401-428: The component BulkArchiveCards is manually handling
loading/error states with isLoading, error, and data?.threads; replace those
manual conditional blocks with the standardized LoadingContent component. Import
LoadingContent, pass isLoading and error (or map error to a message) and render
LoadingContent as the wrapper around the threads UI so that when isLoading or
error is true the LoadingContent handles display; after LoadingContent renders
its children, keep existing rendering for the empty state by checking
data?.threads.length and showing "No emails found" only when not loading and not
error. Ensure you update references to useThreads (fromEmail, limit, type) and
remove the manual JSX blocks that returned Skeleton or the error div.
- Around line 327-334: The inline double-cast bypasses type safety; remove "as
unknown as React.MouseEvent" and instead make the event parameter types align:
update the onClick handler to call onToggleSelection(e) without casting and
adjust the onToggleSelection prop signature in SenderRow (and its callers) to
accept the actual event type emitted by Checkbox (e.g.,
React.MouseEvent<HTMLElement> or the specific HTML element type Checkbox uses).
Ensure you import the correct React event type and update all implementations of
onToggleSelection to match.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3868dfe and 16bc1b3.

📒 Files selected for processing (6)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/utils/actions/categorize.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchiveProgress.tsx
  • apps/web/utils/actions/categorize.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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • 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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/BulkArchiveCards.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/BulkArchiveCards.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/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
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/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/BulkArchiveCards.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/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/BulkArchiveCards.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use 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 (45)
📚 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]/bulk-archive/BulkArchive.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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} : Keep Gmail provider-specific implementation details isolated within the apps/web/utils/gmail/ directory

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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/**/*.{tsx,jsx} : Use `result?.serverError` with `toastError` and `toastSuccess` for error handling in forms

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/components/BulkArchiveCards.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/(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-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/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/components/BulkArchiveCards.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/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • 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} : 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: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: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
📚 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 : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`) to wrap `useSWR` for individual API endpoints

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)/**/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-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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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-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/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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: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: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
📚 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: 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/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/**/*.{ts,tsx} : Do not export types/interfaces that are only used within the same file. Export later if needed

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: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: 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: 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 **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files

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} : Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`

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/**/*.{tsx,jsx} : Use LoadingContent component for async data with loading and error states

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: 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-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} : Prefer functional components with hooks in React

Applied to files:

  • apps/web/components/BulkArchiveCards.tsx
🧬 Code graph analysis (2)
apps/web/app/(app)/[emailAccountId]/bulk-archive/page.tsx (4)
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]/bulk-archive/BulkArchive.tsx (1)
  • BulkArchive (20-78)
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 (1)
  • bulkCategorizeSendersAction (29-121)
apps/web/components/SetupCard.tsx (1)
  • SetupCard (19-69)
⏰ 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/components/BulkArchiveCards.tsx (1)

32-35: Import EmailGroup type instead of duplicating it.

The EmailGroup type is already defined in @/utils/bulk-archive/get-archive-candidates.ts. Import it rather than duplicating the definition to maintain consistency.

♻️ Proposed fix
+import type { EmailGroup } from "@/utils/bulk-archive/get-archive-candidates";
 import type { CategoryWithRules } from "@/utils/category.server";
 import { useAccount } from "@/providers/EmailAccountProvider";
 import { getCategoryIcon } from "@/components/bulk-archive/categoryIcons";
 import { defaultCategory } from "@/utils/categories";

-type EmailGroup = {
-  address: string;
-  category: CategoryWithRules | null;
-};
-
 export function BulkArchiveCards({
⛔ Skipped due to learnings
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
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
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
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.
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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx:
- Around line 32-38: The SWR call in BulkArchive.tsx doesn't handle the error
state; destructure error from the useSWR call
(useSWR<CategorizedSendersResponse>(..., ) => const { data, error, mutate } =
useSWR(...)) and add a small effect that calls toastError(error) when error is
truthy (e.g., useEffect(() => { if (error) toastError(error); }, [error])); keep
existing refresh/fallback logic and ensure the toast import from
'@/components/Toast' is present and only invoked for real errors to provide user
feedback on failed API fetches.

In @apps/web/components/BulkArchiveCards.tsx:
- Around line 32-35: Remove the duplicated EmailGroup type declaration in
BulkArchiveCards.tsx and import the existing EmailGroup type from the module
that defines it (the module that exports the original EmailGroup type), then
update the file to use the imported EmailGroup type wherever it’s referenced
(e.g., in component props/state). If the original module does not currently
export EmailGroup, add an exported type alias there first; otherwise simply add
an import for EmailGroup and delete the local type definition.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16bc1b3 and d2fc9d6.

📒 Files selected for processing (3)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/components/BulkArchiveCards.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 (17)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use 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
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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/components/BulkArchiveCards.tsx
  • apps/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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/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/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.tsx
  • apps/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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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
  • apps/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/BulkArchiveCards.tsx
  • apps/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/BulkArchiveCards.tsx
  • apps/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
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.tsx
  • apps/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 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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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/BulkArchive.tsx
🧠 Learnings (37)
📚 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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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-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/components/BulkArchiveCards.tsx
  • 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/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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: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: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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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
📚 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: 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/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/**/*.{ts,tsx} : Do not export types/interfaces that are only used within the same file. Export later if needed

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: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} : Accompany `onClick` with at least one of: `onKeyUp`, `onKeyDown`, or `onKeyPress`

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: 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/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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/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: 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-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} : Prefer functional components with hooks in React

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
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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/(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: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: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
📚 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 : Create dedicated hooks for specific data types (e.g., `useAccounts`, `useLabels`) to wrap `useSWR` for individual API endpoints

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @apps/web/components/BulkArchiveCards.tsx:
- Around line 407-429: The component currently uses manual conditional rendering
for loading, error, and empty states (checking isLoading, error, and
!data?.threads.length) which should be replaced with the shared LoadingContent
component for consistent UI; update BulkArchiveCards to wrap or replace the
existing if-blocks so that when isLoading or error are true you render
LoadingContent (passing appropriate props/message or children) and handle the
empty state via LoadingContent or its designated empty-state slot, keeping the
data rendering logic for threads unchanged and referencing the same isLoading,
error, and data?.threads.length checks to drive LoadingContent.
- Around line 32-35: Replace the local duplicated EmailGroup type in
BulkArchiveCards.tsx with an import from the existing definition in
@/utils/bulk-archive/get-archive-candidates.ts; update the top-level imports to
include { EmailGroup } (or the exported name used there) and remove the local
type declaration so the component uses the single source of truth for
EmailGroup.
🧹 Nitpick comments (2)
apps/web/components/BulkArchiveCards.tsx (2)

154-159: Consider parallel execution for better performance.

The sequential await in the loop archives senders one at a time, which could be slow for many senders. Consider using Promise.all() to archive in parallel, unless this sequential approach is intentional for rate-limiting purposes.

♻️ Proposed parallel implementation
     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 }));

462-472: Consider extracting snippet cleaning logic to a helper function.

The inline IIFE for cleaning the snippet is complex and could be extracted to a named helper function for better readability and reusability.

♻️ Proposed helper function

Add to the bottom of the file:

function cleanSnippet(snippet: string, maxLength: number = 80): string {
  // 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 > maxLength
    ? `${cleaned.slice(0, maxLength).trimEnd()}...`
    : cleaned;
}

Then use it:

                   {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

📥 Commits

Reviewing files that changed from the base of the PR and between d2fc9d6 and f5939e0.

📒 Files selected for processing (1)
  • apps/web/components/BulkArchiveCards.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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/components/BulkArchiveCards.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use 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
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/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

Files:

  • apps/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
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/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/BulkArchiveCards.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for 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 use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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.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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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.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/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
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.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
Use result?.serverError with toastError and toastSuccess for 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.tsx
🧠 Learnings (28)
📚 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: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-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/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: 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/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/**/*.{tsx,jsx} : Use LoadingContent component for async data with loading and error states

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: 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-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} : Prefer functional components with hooks in React

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: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: 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/BulkArchiveCards.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
apps/web/components/PageHeader.tsx (1)

29-36: Fix layout bug: description renders horizontally instead of below title.

The description is currently inside the flex items-center gap-2 container (line 30), causing it to render horizontally alongside the title and rightElement. A subtitle (PageSubHeading) should appear below the title (PageHeading), not beside it.

🔧 Proposed fix

Move the description outside the inner flex container:

     <div className="flex flex-col sm:flex-row items-start sm:items-center mt-1 gap-3">
-        <div className="flex items-center gap-2">
+        <div>
+          <div className="flex items-center gap-2">
             <PageHeading>{title}</PageHeading>
             {rightElement}
-          {description && (
-            <PageSubHeading className="mt-1">{description}</PageSubHeading>
-          )}
+          </div>
+          {description && (
+            <PageSubHeading className="mt-1">{description}</PageSubHeading>
+          )}
         </div>
apps/web/utils/categories.ts (1)

1-33: Fix test expectations for non-existent categories.

The test file apps/web/__tests__/ai-categorize-senders.test.ts has hardcoded expectations for categories that no longer exist in defaultCategory. Lines 26-48 define testSenders with expectedCategory values of "Support" and "Social", but these categories were removed from the new taxonomy. This will cause test failures:

  • Line 31: expectedCategory: "Support" — does not exist (removed)
  • Line 48: expectedCategory: "Social" — does not exist (removed)

The assertions at lines 75 and 81 (expect(supportResult?.category).toBe("Support") and similar) will fail because these categories are not in getEnabledCategories().

Update the test data to expect only categories that exist in the current defaultCategory: OTHER, NEWSLETTER, MARKETING, RECEIPT, or NOTIFICATION.

🤖 Fix all issues with AI agents
In
@apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx:
- Line 4: Replace the direct sonner import and usages: remove "import { toast }
from 'sonner'" and instead import the wrapper functions "toastSuccess" and
"toastError" from "@/components/Toast"; then update all calls using
toast.success(...) to toastSuccess(...) and toast.error(...) to toastError(...),
including any occurrences inside the AutoCategorizationSetup server action
handlers or helper functions so server-action error/success handling uses the
project's toast wrappers.
🧹 Nitpick comments (1)
apps/web/components/bulk-archive/categoryIcons.ts (1)

9-19: Consider adding explicit "uncategorized" case for consistency.

The getCategoryStyle function (line 56) has an explicit check for "uncategorized", while getCategoryIcon relies on the default case. Although both return MailIcon, adding an explicit case here would improve consistency and make the special handling of "uncategorized" clearer throughout the codebase.

♻️ Suggested enhancement
 export function getCategoryIcon(categoryName: string) {
   const name = categoryName.toLowerCase();
 
   if (name.includes("newsletter")) return NewspaperIcon;
   if (name.includes("marketing")) return MegaphoneIcon;
   if (name.includes("receipt")) return ReceiptIcon;
   if (name.includes("notification")) return BellIcon;
+  if (name === "uncategorized") return MailIcon;
 
   // Default icon for "Other" and any other category
   return MailIcon;
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5939e0 and b86508f.

📒 Files selected for processing (8)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/components/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/utils/categories.ts
  • apps/web/utils/redis/categorization-progress.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/components/BulkArchiveCards.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/BulkArchive.tsx
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)

**/*.{ts,tsx}: For API GET requests to server, use the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.ts
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use PascalCase for component file names (e.g., components/Button.tsx)

Files:

  • apps/web/components/PageHeader.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/PageHeader.tsx
  • apps/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/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="noopener"
Don't use <img> elements in Next.js projects
Don't use <head> elements in Next.js projects

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
apps/web/**/*.{ts,tsx,js,jsx,json,css}

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

Format code with Prettier

Files:

  • apps/web/components/PageHeader.tsx
  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/utils/categories.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's select option. 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. All findUnique/findFirst calls 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
All findMany queries 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/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/utils/categories.ts
apps/web/**/*.{example,ts,json}

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

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/components/bulk-archive/categoryIcons.ts
  • apps/web/utils/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • 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/redis/categorization-progress.ts
  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
  • apps/web/utils/categories.ts
apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts

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

LLM-related code must be organized in specific directories: apps/web/utils/ai/ for main implementations, apps/web/utils/llms/ for core utilities and configurations, and apps/web/__tests__/ for LLM-specific tests

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
apps/web/utils/ai/**/*.ts

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

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
LLM feature functions must follow a standard structure: accept options with inputData and emailAccount parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use createGenerateObject to execute the LLM call
System prompts must define the LLM's role and task specifications
User prompts must contain the actual data and context, and should be kept separate from system prompts
Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output
Use descriptive scoped loggers for each LLM feature, log inputs and outputs with appropriate log levels, and include relevant context in log messages
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
Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/AutoCategorizationSetup.tsx
🧠 Learnings (7)
📚 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 : User prompts must contain the actual data and context, and should be kept separate from system prompts

Applied to files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
📚 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 : System prompts must define the LLM's role and task specifications

Applied to files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
📚 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/utils/ai/categorize-sender/ai-categorize-senders.ts
📚 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 follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call

Applied to files:

  • apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts
📚 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/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/AutoCategorizationSetup.tsx
📚 Learning: 2025-12-31T23:49:09.597Z
Learnt from: rsnodgrass
Repo: elie222/inbox-zero PR: 1154
File: apps/web/app/api/user/setup-progress/route.ts:0-0
Timestamp: 2025-12-31T23:49:09.597Z
Learning: In apps/web/app/api/user/setup-progress/route.ts, Reply Zero enabled status should be determined solely by checking if the TO_REPLY rule is enabled, as it is the critical/canonical rule that Reply Zero is based on. The other conversation status types (FYI, AWAITING_REPLY, ACTIONED) should not be checked for determining Reply Zero setup progress.
<!-- </add_learning>

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
🧬 Code graph analysis (1)
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 (1)
  • bulkCategorizeSendersAction (29-121)
apps/web/components/SetupCard.tsx (1)
  • SetupCard (19-69)
⏰ 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/components/PageHeader.tsx (2)

14-19: LGTM!

The new optional rightElement prop is correctly typed as React.ReactNode and follows React best practices.


21-26: LGTM!

The function signature correctly destructures the new rightElement prop alongside existing props.

apps/web/utils/redis/categorization-progress.ts (1)

65-72: LGTM!

The implementation is clean and consistent with the existing functions in the file. It properly uses the shared getKey helper and follows the same parameter destructuring pattern.

apps/web/components/bulk-archive/categoryIcons.ts (2)

1-7: LGTM!

The imports are specific and all used within the file, following the guideline to minimize bundle size.


21-71: All custom Tailwind color classes are properly defined in the configuration.

The colors used in getCategoryStyle() (new-purple, new-orange, new-green, new-pink, new-indigo, new-blue with shades 50, 100, 150, 200, 300, 500, 600) are all defined in apps/web/tailwind.config.js under the extend.colors.new object. The function will render correctly without styling issues.

apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (4)

12-29: LGTM!

The features array is well-structured with clear, user-friendly descriptions that effectively communicate the value proposition of the bulk archive feature.


31-35: LGTM!

Component initialization and hook usage follow React best practices.


37-64: Polling issue has been resolved.

The previous concern about indefinite polling when no senders need categorization has been properly addressed. The else block (lines 52-55) correctly calls setIsBulkCategorizing(false) when there are no uncategorized senders, preventing unnecessary polling.

The overall error handling logic is sound:

  • Success with senders → continues polling for progress updates
  • Success without senders → stops polling immediately
  • Error → stops polling and shows user feedback

66-78: LGTM!

The component rendering uses SetupCard appropriately with all required props, and the button correctly reflects the loading state for good UX.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

2 issues 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/components/PageHeader.tsx">

<violation number="1" location="apps/web/components/PageHeader.tsx:30">
P2: Layout bug: `flex items-center` causes `PageSubHeading` (description) to appear horizontally next to the title instead of below it. The flex container should only wrap `PageHeading` and `rightElement`, with the description outside.</violation>
</file>

<file name="apps/web/components/bulk-archive/categoryIcons.ts">

<violation number="1" location="apps/web/components/bulk-archive/categoryIcons.ts:56">
P2: Inconsistent pattern matching: `uncategorized` uses strict equality (`===`) while all other categories use `.includes()`. Category names like "Uncategorized Emails" won't match. Consider using `name.includes("uncategorized")` for consistency.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (1)

37-64: Simplify error handling by removing unnecessary throw/catch.

The current pattern throws an error on Line 45 just to immediately catch it on Line 56. Per the project's preferred patterns for server action error handling, you can handle serverError directly without the throw/catch:

♻️ Proposed simplification
 const enableFeature = useCallback(async () => {
   setIsEnabling(true);
   setIsBulkCategorizing(true);

   try {
     const result = await bulkCategorizeSendersAction(emailAccountId);

     if (result?.serverError) {
-      throw new Error(result.serverError);
+      toastError({ description: result.serverError });
+      setIsBulkCategorizing(false);
+      return;
     }

     if (result?.data?.totalUncategorizedSenders) {
       toastSuccess({
         description: `Categorizing ${result.data.totalUncategorizedSenders} senders... This may take a few minutes.`,
       });
     } else {
       toastSuccess({ description: "No uncategorized senders found." });
       setIsBulkCategorizing(false);
     }
   } catch (error) {
-    toastError({
-      description: `Failed to enable feature: ${error instanceof Error ? error.message : "Unknown error"}`,
-    });
-    setIsBulkCategorizing(false);
+    // Handle unexpected errors
+    toastError({
+      description: `Failed to enable feature: ${error instanceof Error ? error.message : "Unknown error"}`,
+    });
+    setIsBulkCategorizing(false);
   } finally {
     setIsEnabling(false);
   }
 }, [emailAccountId, setIsBulkCategorizing]);

Based on learnings, prefer direct error handling with result?.serverError checks rather than throw/catch patterns for server actions.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91168da and 661ca1b.

📒 Files selected for processing (2)
  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.tsx
🧰 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 the swr package
Use result?.serverError with toastError from @/components/Toast for 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 convention use[FeatureName]Enabled that return a boolean from useFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming convention use[FeatureName]Variant that define variant types, use useFeatureFlagVariantKey() 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
Use as const instead of literal types and type annotations
Use either T[] or Array<T> consistently
Initialize each enum member value explicitly
Use export type for types
Use `impo...

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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 in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
If we're in a deeply nested component we will use swr to fetch via API
If you need to use onClick in a component, that component is a client component and file must start with use client

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
**/*.{ts,tsx,js,jsx}

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

Always import Prisma enums from @/generated/prisma/enums instead of @/generated/prisma/client to avoid Next.js bundling errors in client components

Import Prisma using the project's centralized utility: import prisma from '@/utils/prisma'

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Use next/image package for images
For API GET requests to server, use the swr package with hooks like useSWR to fetch data
For text inputs, use the Input component with registerProps for form integration and error handling

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)

**/*.tsx: Use the LoadingContent component to handle loading states instead of manual loading state management
For text areas, use the Input component with type='text', autosizeTextarea prop set to true, and registerProps for form integration

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like <marquee> or <blink>
Only use the scope prop 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 assign tabIndex to non-interactive HTML elements
Don't use positive integers for tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include a title element for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a lang attribute on the html element
Always include a title attribute for iframe elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Include caption tracks for audio and video elements
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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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 both children and dangerouslySetInnerHTML props 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 use target="_blank" without rel="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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.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
Use result?.serverError with toastError and toastSuccess for error handling in forms

Files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)

Use LoadingContent component to consistently handle loading and error states, passing loading, error, and children props

Use PascalCase for component file names (e.g., components/Button.tsx)

Files:

  • apps/web/components/PageHeader.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/PageHeader.tsx
🧠 Learnings (14)
📚 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/AutoCategorizationSetup.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/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx
📚 Learning: 2025-12-31T23:49:09.597Z
Learnt from: rsnodgrass
Repo: elie222/inbox-zero PR: 1154
File: apps/web/app/api/user/setup-progress/route.ts:0-0
Timestamp: 2025-12-31T23:49:09.597Z
Learning: In apps/web/app/api/user/setup-progress/route.ts, Reply Zero enabled status should be determined solely by checking if the TO_REPLY rule is enabled, as it is the critical/canonical rule that Reply Zero is based on. The other conversation status types (FYI, AWAITING_REPLY, ACTIONED) should not be checked for determining Reply Zero setup progress.
<!-- </add_learning>

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/AutoCategorizationSetup.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/AutoCategorizationSetup.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/AutoCategorizationSetup.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/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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]/bulk-archive/AutoCategorizationSetup.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]/bulk-archive/AutoCategorizationSetup.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 : Implement all server actions using the `next-safe-action` library with actionClient, actionClientUser, or adminActionClient for type safety and validation

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.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]/bulk-archive/AutoCategorizationSetup.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/AutoCategorizationSetup.tsx
  • apps/web/components/PageHeader.tsx
🧬 Code graph analysis (1)
apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (4)
apps/web/providers/EmailAccountProvider.tsx (1)
  • useAccount (79-89)
apps/web/utils/actions/categorize.ts (1)
  • bulkCategorizeSendersAction (29-121)
apps/web/components/Toast.tsx (2)
  • toastSuccess (3-12)
  • toastError (14-19)
apps/web/components/SetupCard.tsx (1)
  • SetupCard (19-69)
⏰ 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 (2)
apps/web/components/PageHeader.tsx (1)

18-18: LGTM! Clean implementation of the optional right element.

The addition of the rightElement prop is well-implemented and follows React best practices. The flex layout with items-center gap-2 appropriately positions the element next to the title, and the optional typing ensures backward compatibility with existing usage.

Also applies to: 21-34

apps/web/app/(app)/[emailAccountId]/bulk-archive/AutoCategorizationSetup.tsx (1)

42-42: No changes needed. The action is correctly called with emailAccountId as an argument, which aligns with the project's pattern of binding emailAccountId when calling actions from the client. This is consistent with how the action is used elsewhere in the codebase (CategorizeWithAiButton.tsx).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants