Skip to content

Comments

Upgrade packages#735

Merged
elie222 merged 5 commits intomainfrom
chore/upgrade-packages
Aug 31, 2025
Merged

Upgrade packages#735
elie222 merged 5 commits intomainfrom
chore/upgrade-packages

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Aug 31, 2025

Summary by CodeRabbit

  • Chores
    • Broad dependency and tooling upgrades, package manager and submodule/version bumps; Docker pnpm update; updated lint/config rules.
  • Tests
    • Standardized test timeouts with a shared TIMEOUT constant, unified test invocation style, and added/expanded AI-related tests.
  • Documentation
    • LLM test/template updated to use a shared timeout constant.
  • Refactor
    • Tighter immutability/typing, explicit error for unknown provider, small AI draft option added (emailHistoryContext), chat/completion API now requires messages, Tinybird export added.

@vercel
Copy link

vercel bot commented Aug 31, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Aug 31, 2025 0:13am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 31, 2025

Walkthrough

Centralized test timeouts into TIMEOUT constants and standardized test timeouts; bumped many dependency versions; made several private fields readonly; tightened LLM selection with an explicit error for unknown providers; adjusted date-filter branching; added optional emailHistoryContext to draft API; minor barrel/export and lint changes.

Changes

Cohort / File(s) Summary
Test timeout unification
apps/web/__tests__/*ai-*.test.ts, apps/web/utils/ai/digest/summarize-email-for-digest.test.ts, .cursor/rules/llm-test.mdc
Introduced TIMEOUT constants and replaced hard-coded timeouts; tests updated to three-argument test/it(..., timeout) forms; minor formatting and one added unknown-sender test.
AI args & model selection
apps/web/__tests__/ai-choose-args.test.ts, apps/web/src/utils/ai/choose-rule/choose-args.ts, apps/unsubscriber/src/llm.ts
getActionItemsWithAiArgs now accepts client and modelType; getModel adds a default branch that throws Error("Unknown provider: ${provider}").
Draft-with-knowledge API
apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts, apps/web/src/utils/ai/reply/draft-with-knowledge.ts
aiDraftWithKnowledge accepts optional emailHistoryContext; tests pass emailHistoryContext: null and align message date math to use TIMEOUT.
Readonly immutability
apps/web/utils/braintrust.ts, apps/web/utils/email/google.ts, apps/web/utils/email/microsoft.ts, apps/web/utils/outlook/client.ts
Converted several private fields to private readonly (dataset, client, accessToken); constructors unchanged.
LLM/chat API signature
apps/web/utils/llms/index.ts, apps/web/utils/assistant/chat.ts, apps/web/app/api/ai/compose-autocomplete/route.ts, apps/web/app/api/ai/summarise/controller.ts
Chat calls now require a messages array; previous optional system/prompt parameters removed and replaced by messages containing a system message.
Date filter refactor
apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
Moved all early-return into the switch as case "all": return undefined;; behavior preserved.
Async handler typing
apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
onAction callback type changed to return Promise<void> (async); call sites remain non-awaited.
Tool input descriptor rename
apps/web/utils/ai/example-matches/find-example-matches.ts, apps/web/utils/ai/group/create-group.ts
Renamed tool input field from parameters to inputSchema (shape unchanged).
Lint config & ignores
biome.json, apps/web/utils/usage.ts, packages/*/src/index.ts, packages/*/src/*.ts
Biome schema bumped and many lint rules added/changed; added file-level biome-ignore comments for barrel files and magic numbers.
Barrel/export updates
packages/tinybird/src/index.ts, packages/resend/src/index.ts, packages/loops/src/index.ts, packages/tinybird-ai-analytics/src/index.ts
Added/expanded exports (export * from "./delete") and inserted top-of-file biome-ignore comments for barrel files.
Dependency upgrades (manifests)
package.json, apps/*/package.json, packages/*/package.json, apps/mcp-server/package.json, apps/unsubscriber/package.json, apps/web/package.json
Wide version bumps across SDKs, tooling, TypeScript, Node types, UI and backend deps; no script or runtime control-flow changes.
Docker & version bump
docker/Dockerfile.prod, version.txt
Updated pinned pnpm version in Dockerfile and bumped version.txt from v2.6.20v2.6.21.
Misc tests/data adjustments
apps/web/__tests__/ai-choose-args.test.ts, other test files
Test helpers and mock shapes updated (e.g., Action and RuleWithActions fields, ParsedMessage shape) in tests; console.debug outputs updated.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller as Web UI / Tests
  participant AI as choose-args / getActionItemsWithAiArgs
  participant LLM as getModel / Provider

  Caller->>AI: getActionItemsWithAiArgs({ client, modelType, ... })
  AI->>LLM: getModel(modelType)
  alt provider unknown
    LLM-->>AI: throw Error("Unknown provider: ${provider}")
    AI-->>Caller: propagate error
  else provider found
    LLM-->>AI: generate args
    AI-->>Caller: return action items
  end
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant Draft as aiDraftWithKnowledge
  participant LLM

  Caller->>Draft: requestDraft(email, { emailHistoryContext? })
  Draft->>LLM: chatCompletionStream(messages: [{role:"system",...}, {role:"user",...}], ...)
  LLM-->>Draft: draft reply
  Draft-->>Caller: return reply
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • johnlowe399-blip

Poem

I hop where TIMEOUTs line the trail,
No magic numbers make me pale.
Readonly fields stand tidy, neat,
ModelType hops in, email context sweet.
I nibble tests and stamp my paw—merge done, hooray! 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/upgrade-packages

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@claude
Copy link

claude bot commented Aug 31, 2025

Claude finished @elie222's task —— View job


PR Review: Package Upgrades

I've conducted a comprehensive review of this package upgrade PR. Here are my findings:

Review Checklist

  • Read repository guidelines (CLAUDE.md)
  • Analyze package.json changes and upgrade scope
  • Review test file modifications for correctness
  • Check configuration changes (biome.json)
  • Assess potential breaking changes
  • Evaluate security implications
  • Check performance considerations
  • Provide final recommendations

What Looks Good

Systematic Upgrade Approach:

  • Comprehensive upgrade across the entire monorepo (37 files)
  • Consistent React 19.1.1 upgrade across all packages
  • Next.js 15.5.2 properly aligned with React 19
  • Node.js engine requirement appropriately bumped to >=22.0.0

Security & Configuration:

  • Type overrides properly configured in root package.json for React 19 compatibility
  • Biome configuration updated to 2.2.2 with appropriate rule adjustments
  • Testing framework properly migrated to use describe.runIf() pattern for conditional AI tests
  • Better-auth patch applied (security fix for user image handling)

Development Workflow:

  • AI SDK packages consistently updated across all providers
  • Test files properly adapted to new Vitest patterns
  • Proper use of environment variables for test conditionals (RUN_AI_TESTS)

⚠️ Potential Issues & Recommendations

1. React 19 Breaking Changes

  • Issue: React 19 introduces breaking changes in concurrent features, Suspense behavior, and deprecated APIs
  • Recommendation: Thoroughly test components using Suspense, error boundaries, and any custom hooks
  • Action Required: Verify that all React components still render correctly, especially around async data fetching

2. Next.js 15 Migration Concerns

  • Issue: Next.js 15 has changes in App Router behavior, middleware, and API routes
  • Recommendation: Test all API routes and server actions thoroughly
  • Action Required: Verify that authentication middleware (withAuth, withEmailAccount) still works correctly

3. Node.js 22 Compatibility

  • Issue: Some npm packages might not be fully compatible with Node.js 22
  • Recommendation: Run a full build and test suite to catch any runtime issues
  • Action Required: Test on production-like environment before deployment

4. AI SDK Breaking Changes

  • Issue: Multiple AI SDK packages upgraded simultaneously could introduce API changes
  • Files to check: apps/unsubscriber/src/llm.ts, apps/web/utils/ai/*
  • Recommendation: Verify all AI provider integrations still work correctly

🔍 Code Quality Assessment

Testing Framework Migration:

// ✅ Good: Proper conditional test execution
describe.runIf(isAiTest)("AI Sender Categorization", () => {
  // Tests only run when RUN_AI_TESTS=true
});

Configuration Management:

// ✅ Good: Type overrides for React 19
"pnpm": {
  "overrides": {
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4"
  }
}

🚀 Performance Considerations

Positive Impacts:

  • React 19 includes performance improvements in concurrent rendering
  • Next.js 15 has better build optimization
  • Updated Radix UI components likely have performance enhancements

Watch Out For:

  • Larger bundle sizes due to new dependencies
  • Potential memory usage changes with React 19's new features

🔐 Security Assessment

Positive:

  • All packages upgraded to latest versions (security patches included)
  • Better-auth patch addresses user data handling
  • No obvious security vulnerabilities in the changes

Recommendations:

  • Run pnpm audit after installation
  • Monitor for any new security advisories on upgraded packages

📋 Final Recommendations

Before Merging:

  1. Critical: Run full test suite (pnpm test and pnpm test-ai)
  2. Critical: Verify build succeeds (pnpm build)
  3. Critical: Test authentication flows end-to-end
  4. Important: Deploy to staging environment first
  5. Important: Check that all AI integrations still function

After Merging:

  1. Monitor error logs for any runtime issues
  2. Watch for performance regressions
  3. Be prepared to rollback if critical issues arise

Overall Assessment: This is a well-structured upgrade PR that follows best practices. The main risk is the scale of changes (React 19 + Next.js 15) happening simultaneously. I recommend thorough testing in a staging environment before production deployment.


@socket-security
Copy link

socket-security bot commented Aug 31, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​tanstack/​react-virtual@​3.13.9 ⏵ 3.13.121001006487 -2100
Updated@​radix-ui/​react-toggle@​1.1.9 ⏵ 1.1.10991006598100
Updated@​radix-ui/​react-collapsible@​1.1.11 ⏵ 1.1.12991006798100
Updated@​radix-ui/​react-tabs@​1.1.12 ⏵ 1.1.13991006898100
Updated@​radix-ui/​react-hover-card@​1.1.14 ⏵ 1.1.15991006998100
Updated@​radix-ui/​react-checkbox@​1.3.2 ⏵ 1.3.3991006998100
Updated@​radix-ui/​react-radio-group@​1.3.7 ⏵ 1.3.8991006998100
Updated@​radix-ui/​react-dropdown-menu@​2.1.15 ⏵ 2.1.16991006998100
Updated@​radix-ui/​react-alert-dialog@​1.1.14 ⏵ 1.1.15991006998100
Updated@​radix-ui/​react-popover@​1.1.14 ⏵ 1.1.15991006998100
Updated@​radix-ui/​react-dialog@​1.1.14 ⏵ 1.1.15991006998100
Updated@​radix-ui/​react-tooltip@​1.2.7 ⏵ 1.2.8991007098100
Updated@​serwist/​next@​9.0.14 ⏵ 9.2.099 +11007094 +6100
Updated@​radix-ui/​react-scroll-area@​1.2.9 ⏵ 1.2.10991007198100
Updated@​radix-ui/​react-navigation-menu@​1.2.13 ⏵ 1.2.14991007198100
Updated@​ai-sdk/​react@​2.0.0 ⏵ 2.0.28991007197100
Updated@​radix-ui/​react-select@​2.2.5 ⏵ 2.2.6981007298100
Updated@​types/​lodash@​4.17.17 ⏵ 4.17.2010010078 +186 -1100
Updated@​types/​node@​22.15.29 ⏵ 24.3.0100 +110080 +196100
Updated@​next/​third-parties@​15.3.3 ⏵ 15.5.21001008099100
Updated@​headlessui/​react@​2.2.4 ⏵ 2.2.7100 +110081 -199 +3100
Updated@​googleapis/​people@​3.0.9 ⏵ 5.0.197 +210081 +186100
Updated@​ai-sdk/​gateway@​1.0.5 ⏵ 1.0.1510010081 -1997 -3100
Updated@​react-email/​components@​0.0.41 ⏵ 0.5.1981008196100
Updated@​dub/​analytics@​0.0.27 ⏵ 0.0.3089 +210097 +190 +670
Updated@​googleapis/​gmail@​12.0.1 ⏵ 14.0.1100 +110082 +187100
Updated@​turbo/​gen@​2.5.4 ⏵ 2.5.6991008297 +2100
Updated@​ai-sdk/​google@​2.0.0 ⏵ 2.0.11100 +110082 +197100
Updated@​react-email/​render@​1.1.2 ⏵ 1.2.1100 +110083 +194100
Updated@​ai-sdk/​anthropic@​2.0.0 ⏵ 2.0.910010083 +197100
Updated@​ai-sdk/​openai@​2.0.0 ⏵ 2.0.2310010084 +197 +1100
Updated@​formkit/​auto-animate@​0.8.2 ⏵ 0.8.4100 +110085 +191 +10100
Updatedbetter-auth@​1.3.4 ⏵ 1.3.7981008596 +1100
See 20 more rows in the dashboard

View full report

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: 8

Caution

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

⚠️ Outside diff range comments (6)
packages/tinybird/src/index.ts (1)

5-5: Export missing in delete.ts
The barrel export * from "./delete"; exports nothing because delete.ts defines deleteFromDatasource and _deleteFromDatasourceWithRetry without any export modifiers. Add exports, for example:

export async function deleteFromDatasource(...) {  }
export async function _deleteFromDatasourceWithRetry(...) {  }
apps/web/utils/usage.ts (1)

24-41: try/catch is ineffective without awaiting Promise.all.

Errors from publishAiCall/saveUsage won’t be caught because the promise isn’t awaited. Either await and return the result, or intentionally let rejections bubble (and remove try/catch).

   try {
-    return Promise.all([
+    const result = await Promise.all([
       publishAiCall({
         userId: email,
         provider,
         model,
         totalTokens: usage.totalTokens ?? 0,
         completionTokens: usage.outputTokens ?? 0,
         promptTokens: usage.inputTokens ?? 0,
         cost,
         timestamp: Date.now(),
         label,
       }),
       saveUsage({ email, cost, usage }),
     ]);
+    return result;
   } catch (error) {
     logger.error("Failed to save usage", { error });
   }
apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx (2)

482-493: Avoid unhandled Promise rejections from onAction

onAction now returns a Promise, but handleKeyAction neither awaits nor catches it. Wrap calls to prevent unhandled rejections triggered by key events.

-  const handleKeyAction = useCallback(
-    (index: number, key: string) => {
-      if (key === "r") onAction(index, "reply");
-      else if (key === "d") onAction(index, "resolve");
-      else if (key === "n") onAction(index, "unresolve");
-    },
+  const handleKeyAction = useCallback(
+    (index: number, key: string) => {
+      const run = (action: "reply" | "resolve" | "unresolve") =>
+        onAction(index, action).catch(() => {
+          /* noop – errors are surfaced via toasts in handlers */
+        });
+      if (key === "r") run("reply");
+      else if (key === "d") run("resolve");
+      else if (key === "n") run("unresolve");
+    },

118-131: Guard against missing last message

thread.messages.at(-1)! can be undefined. Add a defensive check to avoid runtime errors with empty threads.

-      const message = thread.messages.at(-1)!;
+      const message = thread.messages.at(-1);
+      if (!message) return;
apps/web/__tests__/ai-extract-from-email-history.test.ts (1)

66-77: Fix mismatch with implementation: expect null for empty history

aiExtractFromEmailHistory returns null when historicalMessages is empty. The test expects a string.

-    expect(result).toBeDefined();
-    expect(result).toBe("No relevant historical context available.");
+    expect(result).toBeNull();
apps/web/utils/outlook/client.ts (1)

90-93: Token expiry unit mismatch (seconds vs ms) causes unnecessary refreshes

expires_at is saved in seconds, but compared to Date.now() (ms). Convert before comparing.

-  const expiryDate = expiresAt ? expiresAt : null;
-  if (accessToken && expiryDate && expiryDate > Date.now()) {
+  const expiryDate = expiresAt ?? null; // seconds
+  if (accessToken && expiryDate && expiryDate * 1000 > Date.now()) {
     return createOutlookClient(accessToken);
   }
🧹 Nitpick comments (29)
packages/tinybird-ai-analytics/package.json (1)

10-13: Add a Node engine constraint at the repo root
Add the following to the workspace-root package.json to lock the Node runtime to match @types/node@24.3.0:

"engines": {
  "node": ">=24.3.0"
}

This ensures your CI and local runtime align with the types you’re using.

packages/loops/src/index.ts (1)

1-1: Inline lint-disable for barrel files — consider centralizing.

Looks good for now; optionally move this rule to biome config overrides for barrel files to avoid per-file comments.

packages/tinybird/src/index.ts (1)

1-1: Prefer targeted lint ignore over file-wide suppression

Use a single-line directive above the export(s) instead of ignore-all to reduce scope.

-/** biome-ignore-all lint/performance/noBarrelFile: fix later */
+/** biome-ignore lint/performance/noBarrelFile: Barrel file intentional */
apps/web/__tests__/writing-style.test.ts (1)

21-33: Optional: add debug logs of AI outputs per test rules

Add console.debug of salient generated fields to aid flakiness triage.

console.debug("writing-style result", {
  typicalLength: result?.typicalLength,
  formality: result?.formality,
  commonGreeting: result?.commonGreeting,
  notableTraitsCount: result?.notableTraits?.length,
  examplesCount: result?.examples?.length,
});
packages/resend/src/index.ts (1)

1-1: Barrel lint suppression acknowledged

Index remains a purposeful barrel; suppression is fine. Consider narrowing to single-line ignore as in tinybird for consistency.

-/** biome-ignore-all lint/performance/noBarrelFile: fix later */
+/** biome-ignore lint/performance/noBarrelFile: Barrel file intentional */
apps/unsubscriber/src/llm.ts (1)

8-20: Make switch exhaustive at compile time (avoid default)

Use a never-check helper to catch future provider additions during CI, while still throwing at runtime.

function assertUnreachable(x: never): never {
  throw new Error(`Unknown provider: ${x as string}`);
}

export function getModel(provider: LLMProvider) {
  switch (provider) {
    case "google":
      return google("gemini-1.5-flash");
    case "openai":
      return openai("gpt-4o-mini");
    case "anthropic":
      return anthropic("claude-3-7-sonnet-20250219");
    case "bedrock":
      return bedrock("anthropic.claude-3-7-sonnet-20250219-v1:0");
  }
  return assertUnreachable(provider);
}
apps/web/utils/usage.ts (3)

1-1: Scope the noMagicNumbers suppression more narrowly.

File-wide suppression can hide unrelated issues. Consider limiting it to the cost table blocks or replacing with targeted // biome-ignore lint/style/noMagicNumbers comments.


22-22: Typo: rename calcuateCost → calculateCost.

Prevents future confusion and improves searchability.

-  const cost = calcuateCost(model, usage);
+  const cost = calculateCost(model, usage);
@@
-function calcuateCost(model: string, usage: LanguageModelUsage): number {
+function calculateCost(model: string, usage: LanguageModelUsage): number {

Also applies to: 140-140


141-145: Log unknown model rates to catch gaps in the cost table.

Returning 0 silently can hide usage costs.

-  if (!costs[model]) return 0;
+  if (!costs[model]) {
+    logger.warn("Missing cost mapping for model", { model });
+    return 0;
+  }
packages/tinybird-ai-analytics/src/index.ts (1)

1-1: Barrel-file lint suppression acknowledged.

Ok as a temporary measure. Consider replacing with explicit imports/exports or module-level re-exports later to satisfy the rule.

apps/web/__tests__/ai-detect-recurring-pattern.test.ts (1)

110-110: Avoid randomness in tests for determinism.

Math.random() can cause flaky AI prompts and snapshots. Use a deterministic value derived from i.

-        Total: $${(Math.random() * 100).toFixed(2)}
+        Total: $${(10 + i).toFixed(2)}
apps/unsubscriber/package.json (1)

1-32: Consider declaring engines to prevent Node/type mismatches at runtime.

Locks CI/prod to versions compatible with TS 5.9, @types/node 24, fastify 5, and dotenv 17.

 {
   "name": "inbox-zero-unsubscriber",
   "version": "1.0.0",
   "private": true,
   "type": "module",
   "main": "index.js",
+  "engines": {
+    "node": ">=20.11"
+  },
   "scripts": {
apps/web/__tests__/ai-extract-knowledge.test.ts (1)

6-7: Centralized TIMEOUT looks good

Using a per-file TIMEOUT constant improves readability and maintenance. Consider later centralizing common AI test constants in a shared test helper to DRY.

apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts (1)

79-80: EmailForLLM.date is correctly defined as Date—no changes needed.

  • Optional: replace TIMEOUT with a dedicated SPACING_MS constant when calculating message spacing for clarity.
apps/web/__tests__/ai-create-group.test.ts (1)

20-71: Deterministic mocking + real LLM path looks good; add a couple of assertions/logs

  • Nice: mocking queryBatchMessages while still exercising the real LLM.
  • Suggest adding visibility into the generated output and ensuring external senders aren’t included.
       const result = await aiGenerateGroupItems(emailAccount, gmail, group);

       expect(result).toEqual({
         senders: expect.arrayContaining(["@mycompany.com"]),
         subjects: expect.arrayContaining(["Project Update:", "Team Meeting:"]),
       });

-      expect(queryBatchMessages).toHaveBeenCalled();
+      // Sanity: AI shouldn’t include unrelated external senders
+      expect(result.senders.some((s) => s.includes("@external.com"))).toBe(false);
+
+      // Helpful when RUN_AI_TESTS=true to inspect output
+      console.debug("Generated group items:", result);
+
+      expect(queryBatchMessages).toHaveBeenCalled();
apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx (3)

139-162: Remove unnecessary async or await something

onSendSuccess is marked async but doesn’t await anything. Drop async to avoid returning an unused Promise.

-  const onSendSuccess = useCallback(
-    async (_messageId: string, threadId: string) => {
+  const onSendSuccess = useCallback(
+    (_messageId: string, threadId: string) => {

150-159: Cleanup timeout on unmount to avoid leaks

The 3-minute setTimeout isn’t cleared on unmount. Track and clear it in a cleanup effect.

-        const timeout = 3 * 60 * 1000;
-        setTimeout(() => {
+        const timeout = 3 * 60 * 1000;
+        const id = setTimeout(() => {
           setRecentlySentThreads((prev) => {
             const next = new Set(prev);
             next.delete(threadId);
             return next;
           });
-        }, timeout);
+        }, timeout);
+        return () => clearTimeout(id);

Note: If you keep this inside the callback, you can store ids in a ref and clear them in a useEffect cleanup.


382-384: Remove stray Tailwind class “dark”

dark is a variant prefix, not a standalone class. Replace with dark:… utility or drop it.

-      <div className="dark ml-2">
+      <div className="ml-2">
apps/web/utils/outlook/client.ts (2)

124-131: Clarify stored expiry to seconds and document intent

Make the seconds semantics explicit to avoid future regressions.

-    await saveTokens({
+    // Store as UNIX seconds
+    await saveTokens({
       tokens: {
         access_token: tokens.access_token,
-        expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in),
+        // tokens.expires_in is seconds
+        expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in),
       },

Add a comment where expiresAt is passed into getOutlookClientWithRefresh to indicate it should be seconds.


11-16: Remove duplicate/unused option name

AuthOptions lists both expiryDate and expiresAt; only expiresAt is used. Drop the extra to reduce confusion.

 type AuthOptions = {
   accessToken?: string | null;
   refreshToken?: string | null;
-  expiryDate?: number | null;
   expiresAt?: number | null;
 };
apps/web/utils/email/microsoft.ts (1)

808-816: Avoid atob in Node runtime; use Buffer for base64 decoding

atob isn’t guaranteed in Node. Prefer Buffer to avoid SSR/runtime errors while preserving the existing try/catch fallback.

-  return atob(message.conversationIndex || "").length > 22;
+  const idx = message.conversationIndex ?? "";
+  const bytes =
+    typeof atob === "function"
+      ? Uint8Array.from(atob(idx), (c) => c.charCodeAt(0))
+      : Buffer.from(idx, "base64");
+  return bytes.length > 22;
apps/web/__tests__/ai-example-matches.test.ts (1)

69-74: Add debug log for generated AI result (per test guidelines)

Helps diagnose flakiness when RUN_AI_TESTS=true.

-      const result = await aiFindExampleMatches(
+      const result = await aiFindExampleMatches(
         emailAccount,
         gmail,
         rulesPrompt,
       );
+      console.debug("aiFindExampleMatches result:", JSON.stringify(result));
apps/web/__tests__/ai-categorize-senders.test.ts (1)

54-84: Minor test ergonomics: reduce duplication and speed up failures

  • Cache categories in a const to avoid recomputing getEnabledCategories().
  • Consider table-driven tests (describe.each) for sender cases to get per-row reporting.
-        const result = await aiCategorizeSenders({
+        const categories = getEnabledCategories();
+        const result = await aiCategorizeSenders({
           emailAccount,
-          senders: testSenders,
-          categories: getEnabledCategories(),
+          senders: testSenders,
+          categories,
         });

Also applies to: 96-122, 126-147, 149-169, 173-221

biome.json (1)

33-37: Consider enabling noTsIgnore

If feasible, turning on noTsIgnore helps prevent hidden type issues, especially after dependency upgrades.

apps/web/package.json (1)

150-150: Move TypeScript to devDependencies to avoid shipping build tooling in prod.

typescript is only needed for build/test; keeping it in dependencies increases install size and attack surface.

Apply this diff:

@@
-    "typescript": "5.9.2",
+    
@@
   "devDependencies": {
@@
-    "vitest": "3.2.4",
+    "vitest": "3.2.4",
     "vitest-mock-extended": "3.1.0",
+    "typescript": "5.9.2"
   },
apps/web/__tests__/ai-choose-args.test.ts (2)

27-29: Avoid as any; use a typed stub without leaking any.

For stronger type-safety in tests, derive the client type from the SUT instead of as any.

Apply this diff in each call site:

-      client: {} as any,
+      client: {} as Parameters<typeof getActionItemsWithAiArgs>[0]["client"],

Optionally, extract a getMockClient() helper in __tests__/helpers.ts returning the same typed stub to reduce repetition.

Also applies to: 50-52, 80-82, 110-112, 148-150


84-88: Reduce flakiness in LLM assertion by relaxing exact string match.

Exact equality is brittle for real LLM output. Prefer contains + prefix match.

Apply this diff:

-      expect(result[0]).toMatchObject({
-        ...actions[0],
-        content: "The price of pears is: $1.99",
-      });
+      expect(result[0].content).toMatch(/^The price of pears is:?/);
+      expect(result[0].content).toContain("$1.99");
apps/web/__tests__/ai-prompt-to-rules.test.ts (2)

36-36: Prefer console.debug in AI tests.

Project rules allow console.debug for AI outputs; switch from console.log.

Apply this diff:

-      console.log(JSON.stringify(result, null, 2));
+      console.debug(JSON.stringify(result, null, 2));

319-321: Normalize how reply content is asserted (content vs fields.content).

Tests mix action.content and action.fields.content. Standardize with a fallback to avoid coupling to internal shape.

Apply this diff:

-          {
-            type: ActionType.REPLY,
-            content: expect.stringMatching(/Hi {{firstName}}/),
-          },
+          {
+            type: ActionType.REPLY,
+          },

And replace the follow-up assertion with:

-      expect(replyAction?.fields?.content).toContain("{{firstName}}");
+      const content = (replyAction as any)?.fields?.content ?? (replyAction as any)?.content;
+      expect(content).toContain("{{firstName}}");

Also applies to: 325-330

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6bbebee and ced414f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • .cursor/rules/llm-test.mdc (3 hunks)
  • apps/mcp-server/package.json (1 hunks)
  • apps/unsubscriber/package.json (2 hunks)
  • apps/unsubscriber/src/llm.ts (1 hunks)
  • apps/web/__tests__/ai-categorize-senders.test.ts (3 hunks)
  • apps/web/__tests__/ai-choose-args.test.ts (8 hunks)
  • apps/web/__tests__/ai-create-group.test.ts (1 hunks)
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts (3 hunks)
  • apps/web/__tests__/ai-diff-rules.test.ts (1 hunks)
  • apps/web/__tests__/ai-example-matches.test.ts (2 hunks)
  • apps/web/__tests__/ai-extract-from-email-history.test.ts (4 hunks)
  • apps/web/__tests__/ai-extract-knowledge.test.ts (3 hunks)
  • apps/web/__tests__/ai-prompt-to-rules.test.ts (4 hunks)
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts (4 hunks)
  • apps/web/__tests__/writing-style.test.ts (2 hunks)
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts (1 hunks)
  • apps/web/app/(marketing) (1 hunks)
  • apps/web/package.json (3 hunks)
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (2 hunks)
  • apps/web/utils/braintrust.ts (1 hunks)
  • apps/web/utils/email/google.ts (1 hunks)
  • apps/web/utils/email/microsoft.ts (1 hunks)
  • apps/web/utils/outlook/client.ts (1 hunks)
  • apps/web/utils/usage.ts (1 hunks)
  • biome.json (5 hunks)
  • package.json (2 hunks)
  • packages/loops/package.json (1 hunks)
  • packages/loops/src/index.ts (1 hunks)
  • packages/resend/package.json (1 hunks)
  • packages/resend/src/index.ts (1 hunks)
  • packages/tinybird-ai-analytics/package.json (1 hunks)
  • packages/tinybird-ai-analytics/src/index.ts (1 hunks)
  • packages/tinybird/package.json (1 hunks)
  • packages/tinybird/src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (27)
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(marketing)
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/(marketing)
  • apps/unsubscriber/src/llm.ts
  • package.json
  • apps/web/__tests__/writing-style.test.ts
  • apps/web/utils/usage.ts
  • packages/resend/src/index.ts
  • packages/loops/src/index.ts
  • packages/tinybird/package.json
  • apps/mcp-server/package.json
  • packages/tinybird-ai-analytics/src/index.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/utils/email/google.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/unsubscriber/package.json
  • .cursor/rules/llm-test.mdc
  • apps/web/utils/outlook/client.ts
  • packages/tinybird/src/index.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • packages/resend/package.json
  • apps/web/utils/email/microsoft.ts
  • packages/tinybird-ai-analytics/package.json
  • apps/web/utils/braintrust.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • packages/loops/package.json
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • biome.json
  • apps/web/package.json
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/(marketing)
  • apps/unsubscriber/src/llm.ts
  • package.json
  • apps/web/__tests__/writing-style.test.ts
  • apps/web/utils/usage.ts
  • packages/resend/src/index.ts
  • packages/loops/src/index.ts
  • packages/tinybird/package.json
  • apps/mcp-server/package.json
  • packages/tinybird-ai-analytics/src/index.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/utils/email/google.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/unsubscriber/package.json
  • .cursor/rules/llm-test.mdc
  • apps/web/utils/outlook/client.ts
  • packages/tinybird/src/index.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • packages/resend/package.json
  • apps/web/utils/email/microsoft.ts
  • packages/tinybird-ai-analytics/package.json
  • apps/web/utils/braintrust.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • packages/loops/package.json
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • biome.json
  • apps/web/package.json
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/unsubscriber/src/llm.ts
  • apps/web/__tests__/writing-style.test.ts
  • apps/web/utils/usage.ts
  • packages/resend/src/index.ts
  • packages/loops/src/index.ts
  • packages/tinybird-ai-analytics/src/index.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/utils/email/google.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/utils/outlook/client.ts
  • packages/tinybird/src/index.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/braintrust.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/unsubscriber/src/llm.ts
  • apps/web/__tests__/writing-style.test.ts
  • apps/web/utils/usage.ts
  • packages/resend/src/index.ts
  • packages/loops/src/index.ts
  • packages/tinybird-ai-analytics/src/index.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/utils/email/google.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/utils/outlook/client.ts
  • packages/tinybird/src/index.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/braintrust.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/unsubscriber/src/llm.ts
  • apps/web/__tests__/writing-style.test.ts
  • apps/web/utils/usage.ts
  • packages/resend/src/index.ts
  • packages/loops/src/index.ts
  • packages/tinybird-ai-analytics/src/index.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/utils/email/google.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/utils/outlook/client.ts
  • packages/tinybird/src/index.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/braintrust.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/utils/usage.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/utils/email/google.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/utils/outlook/client.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/braintrust.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
apps/web/__tests__/**/*

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

Place all LLM-related tests in apps/web/tests/

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
apps/web/__tests__/**/*.test.ts

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

apps/web/__tests__/**/*.test.ts: Always create helper functions for common test data in LLM-related tests
Include standard test cases: happy path, error handling, edge cases (empty input, null values), different user configurations, and various input formats in LLM-related tests
Set appropriate timeouts for LLM calls in tests (e.g., 15,000ms for long-running operations)
Use descriptive console.debug statements for generated content in LLM-related tests
Do not mock the LLM call in LLM-related tests; call the actual LLM
Test both AI and non-AI paths in LLM-related tests

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/*.test.{ts,js}

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

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/*.test.{ts,js,tsx,jsx}

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

**/*.test.{ts,js,tsx,jsx}: Tests are colocated next to the tested file (e.g., dir/format.ts and dir/format.test.ts)
Use vi.mock("server-only", () => ({})); to mock the server-only module in tests
Mock @/utils/prisma in tests using vi.mock("@/utils/prisma") and use the provided prisma mock
Mock external dependencies in tests
Clean up mocks between tests
Do not mock the Logger

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/__tests__/**/*.{ts,js,tsx,jsx}

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

AI tests are placed in the __tests__ directory and are not run by default (they use a real LLM)

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

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

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
apps/web/__tests__/**/*.ts

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

Place LLM-specific tests under apps/web/tests

Files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/usage.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/outlook/client.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/braintrust.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/usage.ts
  • apps/web/utils/email/google.ts
  • apps/web/utils/outlook/client.ts
  • apps/web/utils/email/microsoft.ts
  • apps/web/utils/braintrust.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
apps/web/utils/usage.ts

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

Implement usage tracking and monitoring in apps/web/utils/usage.ts

Files:

  • apps/web/utils/usage.ts
.cursor/rules/*.mdc

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

.cursor/rules/*.mdc: Always place rule files in PROJECT_ROOT/.cursor/rules/
Follow the naming convention: use kebab-case for filenames, always use .mdc extension, and make names descriptive of the rule's purpose
Cursor rules must have a frontmatter section with description, globs, and alwaysApply fields, followed by markdown content explaining the rule

Files:

  • .cursor/rules/llm-test.mdc
apps/web/**/*.tsx

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

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
apps/web/app/(app)/*/**

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
  • apps/web/app/(app)/[emailAccountId]/reply-zero/date-filter.ts
apps/web/app/(app)/*/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
apps/web/app/(app)/*/**/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)

If we're in a deeply nested component we will use swr to fetch via API

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
**/*.{jsx,tsx}

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

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
**/*.{html,jsx,tsx}

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

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/app/(app)/[emailAccountId]/reply-zero/ReplyTrackerEmails.tsx
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
🧠 Learnings (20)
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms

Applied to files:

  • apps/unsubscriber/src/llm.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Set appropriate timeouts for LLM calls in tests (e.g., 15,000ms for long-running operations)

Applied to files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • .cursor/rules/llm-test.mdc
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/utils/ai/digest/summarize-email-for-digest.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Run AI tests with: pnpm test-ai your-feature

Applied to files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths in LLM-related tests

Applied to files:

  • apps/web/__tests__/writing-style.test.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • .cursor/rules/llm-test.mdc
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-categorize-senders.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/usage.ts : Implement usage tracking and monitoring in apps/web/utils/usage.ts

Applied to files:

  • apps/web/utils/usage.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/usage.ts
  • apps/web/__tests__/ai-create-group.test.ts
  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-diff-rules.test.ts
  • apps/web/__tests__/ai-extract-from-email-history.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't export empty modules that don't change anything.

Applied to files:

  • packages/resend/src/index.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use while loops instead of for loops when you don't need initializer and update expressions.

Applied to files:

  • packages/loops/src/index.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/utils/email/google.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Add fallbacks for AI failures and include retry logic for transient errors using withRetry

Applied to files:

  • apps/web/__tests__/ai-extract-knowledge.test.ts
  • apps/web/__tests__/ai-example-matches.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
  • apps/web/__tests__/ai-prompt-to-rules.test.ts
  • apps/web/__tests__/ai-choose-args.test.ts
📚 Learning: 2025-06-23T12:27:30.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-06-23T12:27:30.570Z
Learning: When mocking Prisma in Vitest, import the Prisma mock from '@/utils/__mocks__/prisma', mock '@/utils/prisma', and clear all mocks in a beforeEach hook to ensure test isolation.

Applied to files:

  • apps/web/__tests__/ai-extract-knowledge.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use descriptive console.debug statements for generated content in LLM-related tests

Applied to files:

  • .cursor/rules/llm-test.mdc
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Do not mock the LLM call in LLM-related tests; call the actual LLM

Applied to files:

  • .cursor/rules/llm-test.mdc
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Always create helper functions for common test data in LLM-related tests

Applied to files:

  • .cursor/rules/llm-test.mdc
  • apps/web/__tests__/ai/reply/draft-with-knowledge.test.ts
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
📚 Learning: 2025-07-18T15:06:10.570Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard test cases: happy path, error handling, edge cases (empty input, null values), different user configurations, and various input formats in LLM-related tests

Applied to files:

  • .cursor/rules/llm-test.mdc
  • apps/web/__tests__/ai-detect-recurring-pattern.test.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use empty block statements and static blocks.

Applied to files:

  • biome.json
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use constant expressions in conditions.

Applied to files:

  • biome.json
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't have redundant "use strict".

Applied to files:

  • biome.json
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent

Applied to files:

  • apps/web/__tests__/ai-prompt-to-rules.test.ts
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `actionClient` when both authenticated user context and a specific `emailAccountId` are needed. The `emailAccountId` must be bound when calling the action from the client.

Applied to files:

  • apps/web/__tests__/ai-choose-args.test.ts
🧬 Code graph analysis (10)
apps/web/__tests__/ai-create-group.test.ts (4)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-18)
apps/web/utils/gmail/message.ts (1)
  • queryBatchMessages (272-304)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/ai/group/create-group.ts (1)
  • aiGenerateGroupItems (56-124)
apps/web/__tests__/ai-extract-knowledge.test.ts (2)
apps/web/utils/ai/knowledge/extract.ts (1)
  • aiExtractRelevantKnowledge (85-119)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-18)
apps/web/__tests__/ai-diff-rules.test.ts (2)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-18)
apps/web/utils/ai/rule/diff-rules.ts (1)
  • aiDiffRules (7-90)
apps/web/utils/email/microsoft.ts (1)
apps/web/utils/outlook/client.ts (1)
  • OutlookClient (19-63)
apps/web/__tests__/ai-extract-from-email-history.test.ts (2)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-18)
apps/web/utils/ai/knowledge/extract-from-email-history.ts (1)
  • aiExtractFromEmailHistory (76-119)
apps/web/__tests__/ai-categorize-senders.test.ts (2)
apps/web/utils/ai/categorize-sender/ai-categorize-senders.ts (2)
  • aiCategorizeSenders (23-122)
  • REQUEST_MORE_INFORMATION_CATEGORY (10-10)
apps/web/utils/ai/categorize-sender/ai-categorize-single-sender.ts (1)
  • aiCategorizeSender (8-71)
apps/web/__tests__/ai-example-matches.test.ts (4)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-18)
apps/web/utils/gmail/message.ts (1)
  • queryBatchMessages (272-304)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/ai/example-matches/find-example-matches.ts (2)
  • aiFindExampleMatches (30-136)
  • findExampleMatchesSchema (11-28)
apps/web/utils/ai/digest/summarize-email-for-digest.test.ts (2)
apps/web/utils/ai/digest/summarize-email-for-digest.ts (1)
  • aiSummarizeEmailForDigest (17-83)
apps/web/app/api/resend/digest/validation.ts (1)
  • DigestEmailSummarySchema (4-4)
apps/web/__tests__/ai-prompt-to-rules.test.ts (3)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-18)
apps/web/utils/ai/rule/prompt-to-rules.ts (1)
  • aiPromptToRules (15-70)
apps/web/utils/ai/rule/create-rule-schema.ts (1)
  • createRuleSchema (100-107)
apps/web/__tests__/ai-choose-args.test.ts (2)
apps/web/utils/ai/choose-rule/choose-args.ts (1)
  • getActionItemsWithAiArgs (26-92)
apps/web/__tests__/helpers.ts (1)
  • getEmailAccount (4-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). (3)
  • GitHub Check: Jit Security
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: claude-review

Comment on lines +15 to 19
"@types/node": "24.3.0",
"playwright": "1.55.0",
"tsx": "4.20.5",
"typescript": "5.9.2"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove @types/dotenv (dotenv ships its own types and v17 is ESM).

The legacy typings can conflict with v17 and ESM usage.

   "devDependencies": {
-    "@types/dotenv": "8.2.3",
     "@types/node": "24.3.0",
     "playwright": "1.55.0",
     "tsx": "4.20.5",
     "typescript": "5.9.2"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@types/node": "24.3.0",
"playwright": "1.55.0",
"tsx": "4.20.5",
"typescript": "5.9.2"
},
"devDependencies": {
"@types/node": "24.3.0",
"playwright": "1.55.0",
"tsx": "4.20.5",
"typescript": "5.9.2"
},
🤖 Prompt for AI Agents
In apps/unsubscriber/package.json around lines 15 to 19, remove the legacy
"@types/dotenv" entry from dependencies/devDependencies (dotenv v17 includes its
own types and the @types package can conflict with ESM). After removing the
entry, run your package manager (npm/yarn/pnpm) to update node_modules and the
lockfile, and verify no code imports reference "@types/dotenv"; if CI uses a
cached lockfile update that as well.

Comment on lines +234 to 236
date: new Date().toISOString(),
subject,
// ...message,
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

ParsedMessage shape changed — scan for stale call sites.

You added top-level date and subject. Verify code no longer relies on removed/old locations (e.g., headers.subject or sizeEstimate).

Run:


🏁 Script executed:

#!/bin/bash
# Find legacy access patterns
rg -nP -C2 '\bsizeEstimate\b|headers\.(subject|date)|\bmessage\.headers\.(subject|date)\b'

Length of output: 24708


Replace legacy headers and sizeEstimate references with new top-level fields
Multiple components, utilities, and tests still reference message.headers.subject / .date and the now-removed sizeEstimate. Refactor all occurrences to use the top-level subject and date props on ParsedMessage, and remove or update any sizeEstimate usages in datasources, types, and tests.

🤖 Prompt for AI Agents
In apps/web/__tests__/ai-choose-args.test.ts around lines 234 to 236, the test
still constructs or asserts against legacy nested fields like
message.headers.subject and message.headers.date and uses the removed
sizeEstimate; update the test to set and assert top-level ParsedMessage
properties subject and date (e.g., subject: ..., date: ...) instead of
message.headers.*, remove any references to sizeEstimate (or replace with the
new field if applicable), and search/replace other occurrences in this test file
(and related fixtures/mocks) to ensure all code and assertions use the new
top-level props and no longer reference sizeEstimate.

Comment on lines +96 to +102
expect(queryBatchMessages).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({
maxResults: expect.any(Number),
}),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect mock assertion: queryBatchMessages has 2 args, not 3

The implementation calls queryBatchMessages(gmail, options). This 3-arg expectation will fail.

-      expect(queryBatchMessages).toHaveBeenCalledWith(
-        expect.anything(),
-        expect.anything(),
-        expect.objectContaining({
-          maxResults: expect.any(Number),
-        }),
-      );
+      expect(queryBatchMessages).toHaveBeenCalledWith(
+        expect.anything(),
+        expect.objectContaining({
+          query: expect.stringContaining("-label:sent"),
+          maxResults: expect.any(Number),
+        }),
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(queryBatchMessages).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({
maxResults: expect.any(Number),
}),
);
expect(queryBatchMessages).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
query: expect.stringContaining("-label:sent"),
maxResults: expect.any(Number),
}),
);
🤖 Prompt for AI Agents
In apps/web/__tests__/ai-example-matches.test.ts around lines 96 to 102, the
test incorrectly asserts queryBatchMessages was called with 3 arguments but the
implementation calls queryBatchMessages(gmail, options) with 2; update the
expectation to assert two args only (first argument can remain expect.anything()
or a more specific matcher for gmail, second should be expect.objectContaining({
maxResults: expect.any(Number) })), removing the extra expect.anything() so the
toHaveBeenCalledWith matches the actual call signature.

Comment on lines +30 to +35
const result = await aiPromptToRules({
emailAccount,
promptFile,
isEditing: false,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Confirm intent to test the “old” implementation.

Calls here target aiPromptToRulesOld (aliased as aiPromptToRules). If the new implementation is the intended target post-upgrade, update the import to avoid stale coverage.

Run:

If the old impl is intentionally retained, consider renaming this test to ai-prompt-to-rules-old.test.ts for clarity.


🏁 Script executed:

#!/bin/bash
# Check current exports/usage to decide which impl should be tested
rg -nP -C2 'prompt-to-rules-old|prompt-to-rules\.ts' apps/web

Length of output: 943


Align test with intended implementation
This test currently imports aiPromptToRulesOld. Choose one:

  • If you intend to cover the new logic, update the import to aiPromptToRules from
    @/utils/ai/rule/prompt-to-rules.ts.
  • If you’re deliberately testing the legacy version, rename the file to
    ai-prompt-to-rules-old.test.ts for clarity.
🤖 Prompt for AI Agents
In apps/web/__tests__/ai-prompt-to-rules.test.ts around lines 30 to 35, the test
imports the legacy aiPromptToRulesOld but the assertion calls aiPromptToRules;
either update the import to import aiPromptToRules from
"@/utils/ai/rule/prompt-to-rules.ts" so the test exercises the new logic, or if
you intend to test the legacy implementation, rename this test file to
ai-prompt-to-rules-old.test.ts and keep the import as aiPromptToRulesOld for
clarity and correct intent.

@claude
Copy link

claude bot commented Aug 31, 2025

Claude finished @elie222's task —— View job


Claude finished @elie222's task —— View job


PR Review: Package Upgrades

I've conducted a comprehensive review of this package upgrade PR. Here are my findings:

Review Checklist

  • Read repository guidelines (CLAUDE.md)
  • Analyze package.json changes and upgrade scope
  • Review test file modifications for correctness
  • Check configuration changes (biome.json)
  • Assess potential breaking changes
  • Evaluate security implications
  • Check performance considerations
  • Provide final recommendations

What Looks Good

Systematic Upgrade Approach:

  • Comprehensive upgrade across the entire monorepo (37 files)
  • Consistent React 19.1.1 upgrade across all packages
  • Next.js 15.5.2 properly aligned with React 19
  • Node.js engine requirement appropriately bumped to >=22.0.0

Security & Configuration:

  • Type overrides properly configured in root package.json for React 19 compatibility
  • Biome configuration updated to 2.2.2 with appropriate rule adjustments
  • Testing framework properly migrated to use describe.runIf() pattern for conditional AI tests
  • Better-auth patch applied (security fix for user image handling)

Development Workflow:

  • AI SDK packages consistently updated across all providers
  • Test files properly adapted to new Vitest patterns
  • Proper use of environment variables for test conditionals (RUN_AI_TESTS)

⚠️ Potential Issues & Recommendations

1. React 19 Breaking Changes

  • Issue: React 19 introduces breaking changes in concurrent features, Suspense behavior, and deprecated APIs
  • Recommendation: Thoroughly test components using Suspense, error boundaries, and any custom hooks
  • Action Required: Verify that all React components still render correctly, especially around async data fetching

2. Next.js 15 Migration Concerns

  • Issue: Next.js 15 has changes in App Router behavior, middleware, and API routes
  • Recommendation: Test all API routes and server actions thoroughly
  • Action Required: Verify that authentication middleware (withAuth, withEmailAccount) still works correctly

3. Node.js 22 Compatibility

  • Issue: Some npm packages might not be fully compatible with Node.js 22
  • Recommendation: Run a full build and test suite to catch any runtime issues
  • Action Required: Test on production-like environment before deployment

4. AI SDK Breaking Changes

  • Issue: Multiple AI SDK packages upgraded simultaneously could introduce API changes
  • Files to check: apps/unsubscriber/src/llm.ts, apps/web/utils/ai/*
  • Recommendation: Verify all AI provider integrations still work correctly

🔍 Code Quality Assessment

Testing Framework Migration:

// ✅ Good: Proper conditional test execution
describe.runIf(isAiTest)("AI Sender Categorization", () => {
  // Tests only run when RUN_AI_TESTS=true
});

Configuration Management:

// ✅ Good: Type overrides for React 19
"pnpm": {
  "overrides": {
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4"
  }
}

🚀 Performance Considerations

Positive Impacts:

  • React 19 includes performance improvements in concurrent rendering
  • Next.js 15 has better build optimization
  • Updated Radix UI components likely have performance enhancements

Watch Out For:

  • Larger bundle sizes due to new dependencies
  • Potential memory usage changes with React 19's new features

🔐 Security Assessment

Positive:

  • All packages upgraded to latest versions (security patches included)
  • Better-auth patch addresses user data handling
  • No obvious security vulnerabilities in the changes

Recommendations:

  • Run pnpm audit after installation
  • Monitor for any new security advisories on upgraded packages

📋 Final Recommendations

Before Merging:

  1. Critical: Run full test suite (pnpm test and pnpm test-ai)
  2. Critical: Verify build succeeds (pnpm build)
  3. Critical: Test authentication flows end-to-end
  4. Important: Deploy to staging environment first
  5. Important: Check that all AI integrations still function

After Merging:

  1. Monitor error logs for any runtime issues
  2. Watch for performance regressions
  3. Be prepared to rollback if critical issues arise

Overall Assessment: This is a well-structured upgrade PR that follows best practices. The main risk is the scale of changes (React 19 + Next.js 15) happening simultaneously. I recommend thorough testing in a staging environment before production deployment.


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

Caution

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

⚠️ Outside diff range comments (2)
apps/web/ee/billing/stripe/index.ts (2)

5-23: Fix return type to satisfy strict null checks and avoid nullable client.

getStripe currently infers Stripe | null; tighten to return Stripe and avoid nullable state.

Apply:

-let stripe: Stripe | null = null;
+let stripeClient: Stripe | undefined;

-export const getStripe = () => {
+export const getStripe = (): Stripe => {
   if (!env.STRIPE_SECRET_KEY) throw new Error("STRIPE_SECRET_KEY is not set");
-  if (!stripe) {
-    stripe = new Stripe(env.STRIPE_SECRET_KEY, {
+  if (!stripeClient) {
+    stripeClient = new Stripe(env.STRIPE_SECRET_KEY, {
       apiVersion: "2025-08-27.basil",
       appInfo: {
         name: "Inbox Zero",
         version: "1.0.0",
         url: "https://www.getinboxzero.com",
       },
       typescript: true,
     });
   }
-  return stripe;
+  return stripeClient;
 }

66-71: Explicitly set proration_behavior and idempotency key; handle nullable quantity

  • Pass a proration_behavior (“create_prorations” / “none” / “always_invoice”) in the update params to control prorations.
  • Supply an idempotency key (e.g. { idempotencyKey: subItem:${subscriptionItemId}:qty:${quantityToSet} }) for safe retries.
  • Since SubscriptionItem.quantity is nullable (always null for metered plans, nullable for tiered plans), adjust your change check to handle null (for example if ((current.quantity ?? 0) !== quantityToSet) or skip updates on metered pricing).
     const updatedItem = await stripe.subscriptionItems.update(
       subscriptionItemId,
-      { quantity: quantityToSet },
+      {
+        quantity: quantityToSet,
+        proration_behavior: "create_prorations", // or "none"/"always_invoice"
+      },
+    { idempotencyKey: `subItem:${subscriptionItemId}:qty:${quantityToSet}` },
     );
🧹 Nitpick comments (9)
docker/Dockerfile.prod (6)

32-32: Lock installs for reproducible builds.

--no-frozen-lockfile allows lockfile drift at build time. If patches don’t need lockfile edits (common with patch-package), prefer a frozen install.

-RUN pnpm install --no-frozen-lockfile --prefer-offline
+RUN pnpm install --frozen-lockfile --prefer-offline

If you rely on pnpm patch changing the lockfile at build time, keep current behavior; otherwise, freezing is safer.


41-43: Disable Next telemetry in CI/builds.

Small hardening/privacy win during image builds.

 ENV NODE_ENV=production
+ENV NEXT_TELEMETRY_DISABLED=1

34-36: Run the container as non-root.

Reduce blast radius by switching to the precreated node user in the official image and ensuring permissions.

 COPY . .
+RUN chown -R node:node /app
@@
-# Use the simpler pnpm command, should work now as pnpm & next are installed
-CMD pnpm --filter inbox-zero-ai start 
+# Use the simpler pnpm command, should work now as pnpm & next are installed
+USER node
+CMD pnpm --filter inbox-zero-ai start

Also applies to: 75-77


26-27: Potential duplicate Prisma generate.

Comment implies a postinstall runs Prisma generate (hence copying schema before install), and there’s also an explicit prisma generate. Running both wastes time and cache.

  • If postinstall already runs prisma generate, drop the explicit RUN pnpm --filter inbox-zero-ai exec -- prisma generate.
  • Otherwise, keep the explicit step and disable the postinstall hook for cleaner caching.

Also applies to: 39-39


29-33: Consider multi-stage + pnpm deploy to ship only prod deps.

You currently install all dev deps in the final image. A multi-stage build with pnpm fetch + pnpm install --frozen-lockfile in the builder, then pnpm deploy --filter inbox-zero-ai --prod /app in the runner can materially shrink the image and speed cold starts.

If helpful, I can sketch a multi-stage variant tailored to this workspace.


8-8: Switch to Corepack in Dockerfile.prod for deterministic pnpm@10.15.0 activation

package.json is already pinned to pnpm@10.15.0. In docker/Dockerfile.prod (line 8), replace:

-RUN npm install -g pnpm@10.15.0
+RUN corepack enable && corepack prepare pnpm@10.15.0 --activate
apps/web/ee/billing/stripe/index.ts (3)

13-20: Consider adding explicit network resiliency (timeouts/retries).

To harden external calls, set conservative retries and a request timeout.

Apply:

       typescript: true,
+      maxNetworkRetries: 2,
+      timeout: 60000,

45-57: Avoid shadowing the module-scoped client variable.

Rename the local const for clarity and to prevent confusion with the cached singleton.

Apply:

-    const stripe = getStripe();
+    const stripeClient = getStripe();

-    const currentItem =
-      await stripe.subscriptionItems.retrieve(subscriptionItemId);
+    const currentItem =
+      await stripeClient.subscriptionItems.retrieve(subscriptionItemId);
@@
-    const updatedItem = await stripe.subscriptionItems.update(
+    const updatedItem = await stripeClient.subscriptionItems.update(
       subscriptionItemId,
       {
         quantity: quantityToSet,
       },
     );

Also applies to: 66-74


32-33: Validate and normalize quantity to a safe integer ≥ 1.

Prevents NaN/Infinity and fractional inputs from leaking into the Stripe call.

Apply:

-  const quantityToSet = Math.max(1, quantity);
+  const requested = Number.isFinite(quantity) ? Math.floor(quantity) : 1;
+  const quantityToSet = Math.max(1, requested);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ced414f and 365c25e.

📒 Files selected for processing (3)
  • apps/web/ee/billing/stripe/index.ts (1 hunks)
  • docker/Dockerfile.prod (1 hunks)
  • version.txt (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • version.txt
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/ee/billing/stripe/index.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/ee/billing/stripe/index.ts
  • docker/Dockerfile.prod
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/ee/billing/stripe/index.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/ee/billing/stripe/index.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/ee/billing/stripe/index.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/ee/billing/stripe/index.ts
  • docker/Dockerfile.prod
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Set appropriate timeouts for LLM calls in tests (e.g., 15,000ms for long-running operations)
📚 Learning: 2025-07-18T15:06:02.201Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/installing-packages.mdc:0-0
Timestamp: 2025-07-18T15:06:02.201Z
Learning: Use `pnpm` to install packages

Applied to files:

  • docker/Dockerfile.prod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Jit Security
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
apps/web/ee/billing/stripe/index.ts (1)

13-13: Confirm Stripe API version bump consistency.

apps/web/package.json pins stripe@18.5.0; no other apiVersion arguments remain. Verify stripe@18.5.0 supports "2025-08-27.basil".

@claude
Copy link

claude bot commented Aug 31, 2025

Claude finished @elie222's task —— View job


PR Review: Package Upgrades

I've conducted a comprehensive review of this package upgrade PR. Here are my findings:

Review Checklist

  • Read repository guidelines (CLAUDE.md)
  • Analyze package.json changes and upgrade scope
  • Review test file modifications for correctness
  • Check configuration changes (biome.json)
  • Assess potential breaking changes
  • Evaluate security implications
  • Check performance considerations
  • Provide final recommendations

What Looks Good

Systematic Upgrade Approach:

  • Comprehensive upgrade across the entire monorepo (37 files)
  • Consistent React 19.1.1 upgrade across all packages
  • Next.js 15.5.2 properly aligned with React 19
  • Node.js engine requirement appropriately bumped to >=22.0.0

Security & Configuration:

  • Type overrides properly configured in root package.json for React 19 compatibility
  • Biome configuration updated to 2.2.2 with appropriate rule adjustments
  • Testing framework properly migrated to use describe.runIf() pattern for conditional AI tests
  • Better-auth patch applied (security fix for user image handling)

Development Workflow:

  • AI SDK packages consistently updated across all providers
  • Test files properly adapted to new Vitest patterns
  • Proper use of environment variables for test conditionals (RUN_AI_TESTS)

⚠️ Potential Issues & Recommendations

1. React 19 Breaking Changes

  • Issue: React 19 introduces breaking changes in concurrent features, Suspense behavior, and deprecated APIs
  • Recommendation: Thoroughly test components using Suspense, error boundaries, and any custom hooks
  • Action Required: Verify that all React components still render correctly, especially around async data fetching

2. Next.js 15 Migration Concerns

  • Issue: Next.js 15 has changes in App Router behavior, middleware, and API routes
  • Recommendation: Test all API routes and server actions thoroughly
  • Action Required: Verify that authentication middleware (withAuth, withEmailAccount) still works correctly

3. Node.js 22 Compatibility

  • Issue: Some npm packages might not be fully compatible with Node.js 22
  • Recommendation: Run a full build and test suite to catch any runtime issues
  • Action Required: Test on production-like environment before deployment

4. AI SDK Breaking Changes

  • Issue: Multiple AI SDK packages upgraded simultaneously could introduce API changes
  • Files to check: apps/unsubscriber/src/llm.ts, apps/web/utils/ai/*
  • Recommendation: Verify all AI provider integrations still work correctly

🔍 Code Quality Assessment

Testing Framework Migration:

// ✅ Good: Proper conditional test execution
describe.runIf(isAiTest)("AI Sender Categorization", () => {
  // Tests only run when RUN_AI_TESTS=true
});

Configuration Management:

// ✅ Good: Type overrides for React 19
"pnpm": {
  "overrides": {
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4"
  }
}

🚀 Performance Considerations

Positive Impacts:

  • React 19 includes performance improvements in concurrent rendering
  • Next.js 15 has better build optimization
  • Updated Radix UI components likely have performance enhancements

Watch Out For:

  • Larger bundle sizes due to new dependencies
  • Potential memory usage changes with React 19's new features

🔐 Security Assessment

Positive:

  • All packages upgraded to latest versions (security patches included)
  • Better-auth patch addresses user data handling
  • No obvious security vulnerabilities in the changes

Recommendations:

  • Run pnpm audit after installation
  • Monitor for any new security advisories on upgraded packages

📋 Final Recommendations

Before Merging:

  1. Critical: Run full test suite (pnpm test and pnpm test-ai)
  2. Critical: Verify build succeeds (pnpm build)
  3. Critical: Test authentication flows end-to-end
  4. Important: Deploy to staging environment first
  5. Important: Check that all AI integrations still function

After Merging:

  1. Monitor error logs for any runtime issues
  2. Watch for performance regressions
  3. Be prepared to rollback if critical issues arise

Overall Assessment: This is a well-structured upgrade PR that follows best practices. The main risk is the scale of changes (React 19 + Next.js 15) happening simultaneously. I recommend thorough testing in a staging environment before production deployment.


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

Caution

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

⚠️ Outside diff range comments (3)
apps/web/utils/ai/example-matches/find-example-matches.ts (3)

74-79: Harden listEmails execute with try/catch

External Gmail calls can throw; add minimal error handling to avoid unhandled rejections and surface context.

Apply:

-    execute: async ({ query }: { query: string | undefined }) => {
-      const { messages } = await queryBatchMessages(gmail, {
-        query: `${query || ""} -label:sent`.trim(),
-        maxResults: 20,
-      });
+    execute: async ({ query }: { query: string | undefined }) => {
+      try {
+        const { messages } = await queryBatchMessages(gmail, {
+          query: `${query || ""} -label:sent`.trim(),
+          maxResults: 20,
+        });
+        const results = messages.map((message) => ({
+          emailId: message.id,
+          from: message.headers.from,
+          subject: message.headers.subject,
+          snippet: message.snippet,
+        }));
+        for (const result of results) {
+          listedEmails[result.emailId] = result;
+        }
+        return results;
+      } catch (err) {
+        throw new Error(`listEmails failed: ${(err as Error).message}`);
+      }
-      const results = messages.map((message) => ({
-        emailId: message.id,
-        from: message.headers.from,
-        subject: message.headers.subject,
-        snippet: message.snippet,
-      }));
-
-      for (const result of results) {
-        listedEmails[result.emailId] = result;
-      }
-
-      return results;
     },

103-116: Handle AI call failures and add retry

Wrap generateText to catch/annotate failures; consider a small retry for transient errors per project AI guidelines.

Apply:

-  const aiResponse = await generateText({
-    ...modelOptions,
-    system,
-    prompt,
-    stopWhen: stepCountIs(10),
-    tools: {
-      listEmails: listEmailsTool(gmail),
-      [FIND_EXAMPLE_MATCHES]: tool({
-        description: "Find example matches",
-        inputSchema: findExampleMatchesSchema,
-      }),
-    },
-  });
+  let aiResponse;
+  try {
+    aiResponse = await generateText({
+      ...modelOptions,
+      system,
+      prompt,
+      stopWhen: stepCountIs(10),
+      tools: {
+        listEmails: listEmailsTool(gmail),
+        [FIND_EXAMPLE_MATCHES]: tool({
+          description: "Find example matches",
+          inputSchema: findExampleMatchesSchema,
+        }),
+      },
+    });
+  } catch (err) {
+    throw new Error(`AI findExampleMatches failed: ${(err as Error).message}`);
+  }

Optionally wrap the call with your withRetry(...) helper if available.


121-126: Use schema.parse instead of type assertion for runtime safety

Avoid trusting the cast; leverage the Zod schema you already defined.

Apply:

-  >((acc, { input }) => {
-    const typedArgs = input as z.infer<typeof findExampleMatchesSchema>;
-    return acc.concat(typedArgs.matches);
-  }, []);
+  >((acc, { input }) => {
+    const { matches } = findExampleMatchesSchema.parse(input);
+    return acc.concat(matches);
+  }, []);
🧹 Nitpick comments (2)
apps/web/utils/ai/example-matches/find-example-matches.ts (2)

129-135: Preserve reason and (optionally) dedupe results

Including reason helps explain matches. Also consider deduping by emailId+rule.

Apply minimal change to keep reason:

   return {
     matches: matches
       .filter((match) => listedEmails[match.emailId])
       .map((match) => ({
         ...listedEmails[match.emailId],
         rule: match.rule,
+        reason: match.reason,
       })),
   };

Optional dedupe (outside this hunk):

const uniq = new Map<string, typeof listedEmails[string] & { rule: string; reason?: string }>();
for (const m of matches) {
  if (!listedEmails[m.emailId]) continue;
  const key = `${m.emailId}::${m.rule}`;
  if (!uniq.has(key)) uniq.set(key, { ...listedEmails[m.emailId], rule: m.rule, reason: m.reason });
}
return { matches: [...uniq.values()] };

30-36: Add explicit return type (clarity) and optionally a scoped logger

Make the API contract explicit and prepare for logging per backend guidelines.

Example:

-export async function aiFindExampleMatches(
+type FindExampleMatchesResult = {
+  matches: Array<{ emailId: string; from: string; subject: string; snippet: string; rule: string; reason?: string }>;
+};
+export async function aiFindExampleMatches(
   emailAccount: EmailAccountWithAI,
   gmail: gmail_v1.Gmail,
   rulesPrompt: string,
-) {
+): Promise<FindExampleMatchesResult> {

Optionally initialize a scoped logger at the top (adjust import/path as in your project):

// import { createScopedLogger } from "@/utils/logger";
// const log = createScopedLogger("ai.findExampleMatches");
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 365c25e and 4016b62.

📒 Files selected for processing (1)
  • apps/web/utils/ai/example-matches/find-example-matches.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/example-matches/find-example-matches.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Set appropriate timeouts for LLM calls in tests (e.g., 15,000ms for long-running operations)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Jit Security
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
apps/web/utils/ai/example-matches/find-example-matches.ts (1)

71-73: Update remaining parameters: schemas to inputSchema:
Found lingering parameters: definitions in packages/tinybird/src/query.ts (line 9), apps/web/utils/ai/group/create-group.ts (line 37), and several choose-rule utilities—replace these with inputSchema: to align with the upgraded SDK.

@claude
Copy link

claude bot commented Aug 31, 2025

Claude finished @elie222's task —— View job


PR Review: Package Upgrades

I've conducted a comprehensive review of this package upgrade PR. Here are my findings:

Review Checklist

  • Read repository guidelines (CLAUDE.md)
  • Analyze package.json changes and upgrade scope
  • Review test file modifications for correctness
  • Check configuration changes (biome.json)
  • Assess potential breaking changes
  • Evaluate security implications
  • Check performance considerations
  • Provide final recommendations

What Looks Good

Systematic Upgrade Approach:

  • Comprehensive upgrade across the entire monorepo (37 files)
  • Consistent React 19.1.1 upgrade across all packages
  • Next.js 15.5.2 properly aligned with React 19
  • Node.js engine requirement appropriately bumped to >=22.0.0

Security & Configuration:

  • Type overrides properly configured in root package.json for React 19 compatibility
  • Biome configuration updated to 2.2.2 with appropriate rule adjustments
  • Testing framework properly migrated to use describe.runIf() pattern for conditional AI tests
  • Better-auth patch applied (security fix for user image handling)

Development Workflow:

  • AI SDK packages consistently updated across all providers
  • Test files properly adapted to new Vitest patterns
  • Proper use of environment variables for test conditionals (RUN_AI_TESTS)

⚠️ Potential Issues & Recommendations

1. React 19 Breaking Changes

  • Issue: React 19 introduces breaking changes in concurrent features, Suspense behavior, and deprecated APIs
  • Recommendation: Thoroughly test components using Suspense, error boundaries, and any custom hooks
  • Action Required: Verify that all React components still render correctly, especially around async data fetching

2. Next.js 15 Migration Concerns

  • Issue: Next.js 15 has changes in App Router behavior, middleware, and API routes
  • Recommendation: Test all API routes and server actions thoroughly
  • Action Required: Verify that authentication middleware (withAuth, withEmailAccount) still works correctly

3. Node.js 22 Compatibility

  • Issue: Some npm packages might not be fully compatible with Node.js 22
  • Recommendation: Run a full build and test suite to catch any runtime issues
  • Action Required: Test on production-like environment before deployment

4. AI SDK Breaking Changes

  • Issue: Multiple AI SDK packages upgraded simultaneously could introduce API changes
  • Files to check: apps/unsubscriber/src/llm.ts, apps/web/utils/ai/*
  • Recommendation: Verify all AI provider integrations still work correctly

🔍 Code Quality Assessment

Testing Framework Migration:

// ✅ Good: Proper conditional test execution
describe.runIf(isAiTest)("AI Sender Categorization", () => {
  // Tests only run when RUN_AI_TESTS=true
});

Configuration Management:

// ✅ Good: Type overrides for React 19
"pnpm": {
  "overrides": {
    "@types/react": "19.0.10",
    "@types/react-dom": "19.0.4"
  }
}

🚀 Performance Considerations

Positive Impacts:

  • React 19 includes performance improvements in concurrent rendering
  • Next.js 15 has better build optimization
  • Updated Radix UI components likely have performance enhancements

Watch Out For:

  • Larger bundle sizes due to new dependencies
  • Potential memory usage changes with React 19's new features

🔐 Security Assessment

Positive:

  • All packages upgraded to latest versions (security patches included)
  • Better-auth patch addresses user data handling
  • No obvious security vulnerabilities in the changes

Recommendations:

  • Run pnpm audit after installation
  • Monitor for any new security advisories on upgraded packages

📋 Final Recommendations

Before Merging:

  1. Critical: Run full test suite (pnpm test and pnpm test-ai)
  2. Critical: Verify build succeeds (pnpm build)
  3. Critical: Test authentication flows end-to-end
  4. Important: Deploy to staging environment first
  5. Important: Check that all AI integrations still function

After Merging:

  1. Monitor error logs for any runtime issues
  2. Watch for performance regressions
  3. Be prepared to rollback if critical issues arise

Overall Assessment: This is a well-structured upgrade PR that follows best practices. The main risk is the scale of changes (React 19 + Next.js 15) happening simultaneously. I recommend thorough testing in a staging environment before production deployment.


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

Caution

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

⚠️ Outside diff range comments (2)
apps/web/utils/llms/index.ts (1)

118-166: createGenerateObject doesn’t pass a model and misattributes usage; add model + fallback (parity with createGenerateText).

Currently generateObject is called without model, and usage is saved against modelOptions regardless of the actual model used. This can break calls and/or skew analytics.

Apply:

 export function createGenerateObject({
   userEmail,
   label,
   modelOptions,
 }: {
   userEmail: string;
   label: string;
   modelOptions: ReturnType<typeof getModel>;
 }): typeof generateObject {
   return async (...args) => {
-    try {
-      const [options, ...restArgs] = args;
-
-      logger.trace("Generating object", {
-        label,
-        system: options.system?.slice(0, MAX_LOG_LENGTH),
-        prompt: options.prompt?.slice(0, MAX_LOG_LENGTH),
-      });
-
-      const result = await generateObject(
-        {
-          ...options,
-          ...commonOptions,
-        },
-        ...restArgs,
-      );
-
-      if (result.usage) {
-        await saveAiUsage({
-          email: userEmail,
-          usage: result.usage,
-          provider: modelOptions.provider,
-          model: modelOptions.modelName,
-          label,
-        });
-      }
-
-      logger.trace("Generated object", {
-        label,
-        result: result.object,
-      });
-
-      return result;
-    } catch (error) {
-      await handleError(error, userEmail);
-      throw error;
-    }
+    const [options, ...restArgs] = args;
+
+    const generate = async (model: LanguageModelV2) => {
+      logger.trace("Generating object", {
+        label,
+        system: options.system?.slice(0, MAX_LOG_LENGTH),
+        prompt: options.prompt?.slice(0, MAX_LOG_LENGTH),
+      });
+
+      const result = await generateObject(
+        {
+          ...options,
+          ...commonOptions,
+          model,
+        },
+        ...restArgs,
+      );
+
+      if (result.usage) {
+        await saveAiUsage({
+          email: userEmail,
+          usage: result.usage,
+          provider: modelOptions.provider,
+          model: modelOptions.modelName,
+          label,
+        });
+      }
+
+      logger.trace("Generated object", { label, result: result.object });
+      return result;
+    };
+
+    try {
+      return await generate(modelOptions.model);
+    } catch (error) {
+      if (
+        modelOptions.backupModel &&
+        (isServiceUnavailableError(error) || isAWSThrottlingError(error))
+      ) {
+        logger.warn("Using backup model (generateObject)", {
+          error,
+          model: modelOptions.backupModel,
+        });
+        try {
+          return await generate(modelOptions.backupModel);
+        } catch (e) {
+          await handleError(e, userEmail);
+          throw e;
+        }
+      }
+      await handleError(error, userEmail);
+      throw error;
+    }
   };
 }
apps/web/utils/ai/assistant/chat.ts (1)

735-737: Action type naming mismatch (“DRAFT_REPLY” vs enum “DRAFT_EMAIL”) will confuse the model and fail Zod validation.

Align prompt text and examples with actual enum values (or add a server-side synonym map).

Apply:

- - The knowledge base is used to draft reply content.
- - It is only used when an action of type DRAFT_REPLY is used AND the rule has no preset draft content.
+ - The knowledge base is used to draft reply content.
+ - It is only used when an action of type DRAFT_EMAIL is used AND the rule has no preset draft content.
-            {
-              "type": "draft",
-              "fields": {
-                "content": "{{draft a reply}}"
-              }
-            }
+            {
+              "type": "DRAFT_EMAIL",
+              "fields": {
+                "content": "{{draft a reply}}"
+              }
+            }

If you prefer keeping “draft” in examples, implement a mapping layer to coerce “draft”/“DRAFT_REPLY” to ActionType.DRAFT_EMAIL before validation.

Also applies to: 798-802

🧹 Nitpick comments (8)
apps/web/utils/ai/group/create-group.ts (2)

35-54: Wrap listEmails in tool(...) for consistency and schema enforcement

Using the SDK’s tool(...) wrapper here (like the other tools) keeps things uniform and ensures consistent validation/telemetry.

Apply:

-const listEmailsTool = (gmail: gmail_v1.Gmail) => ({
-  description: "List email messages. Returns max 20 results.",
-  inputSchema: z.object({
-    query: z.string().optional().describe("Optional Gmail search query."),
-  }),
-  execute: async ({ query }: { query: string | undefined }) => {
-    const { messages } = await queryBatchMessages(gmail, {
-      query: `${query || ""} -label:sent`.trim(),
-      maxResults: 20,
-    });
-
-    const results = messages.map((message) => ({
-      from: message.headers.from,
-      subject: message.headers.subject,
-      snippet: message.snippet,
-    }));
-
-    return results;
-  },
-});
+const listEmailsTool = (gmail: gmail_v1.Gmail) =>
+  tool({
+    description: "List email messages. Returns max 20 results.",
+    inputSchema: z.object({
+      query: z.string().optional().describe("Optional Gmail search query."),
+    }),
+    execute: async ({ query }: { query?: string }) => {
+      const log = logger.with({ tool: "listEmails" });
+      try {
+        const { messages } = await queryBatchMessages(gmail, {
+          query: `${query ?? ""} -label:sent`.trim(),
+          maxResults: 20,
+        });
+        const results = messages.map((message) => ({
+          from: message.headers.from,
+          subject: message.headers.subject,
+          snippet: message.snippet,
+        }));
+        log.debug({ count: results.length }, "Listed emails");
+        return results;
+      } catch (err) {
+        log.warn({ err }, "Failed to list emails");
+        return [];
+      }
+    },
+  });

41-44: Prefer nullish coalescing and add lightweight guardrails

Use query ?? "" instead of || to avoid treating empty strings as falsy; also consider a simple length cap to prevent overly long queries from the model.

-      query: `${query || ""} -label:sent`.trim(),
+      query: `${(query ?? "").slice(0, 256)} -label:sent`.trim(),
packages/loops/src/loops.ts (2)

6-8: Replace console.warn with createScopedLogger per codebase guidelines

Use the project’s logger instead of console.* in backend TS files. Initialize a scoped logger once at the top and call logger.warn here.

+import { createScopedLogger } from "<update-to-your-logger-path>";
+const logger = createScopedLogger("packages/loops/loops");
   if (!process.env.LOOPS_API_SECRET) {
-    console.warn("LOOPS_API_SECRET is not set");
+    logger.warn("LOOPS_API_SECRET is not set");
     return;
   }

Please confirm the correct import path for createScopedLogger in this repo (guideline: use createScopedLogger for backend TS).


11-13: Environment var name: double-check against Loops docs

Docs reference LOOPS_API_KEY for the SDK. If LOOPS_API_SECRET is intentional in your infra, ignore; otherwise align to KEY to prevent misconfig at runtime. (loops.so)

apps/web/app/api/ai/compose-autocomplete/route.ts (1)

12-12: Return proper HTTP status for unauthenticated case.

Respond with 401 to aid clients and monitoring.

Apply:

-  if (!user) return NextResponse.json({ error: "Not authenticated" });
+  if (!user) {
+    return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
+  }
apps/web/utils/ai/assistant/chat.ts (2)

719-721: Fix typos/grammar in the system prompt.

Small polish improves instruction clarity.

Apply:

-You can set general infomation about the user too that will be passed as context when the AI is processing emails.
-Reply Zero is a feature that labels emails that need a reply "To Reply". And labels emails that are awaiting a response "Awaiting". The also is also able to see these in a minimalist UI within Inbox Zero which only shows which emails the user needs to reply to or is awaiting a response on.
+You can set general information about the user that will be passed as context when the AI processes emails.
+Reply Zero labels emails that need a reply "To Reply" and emails awaiting a response "Awaiting". The user can also view these in a minimalist UI within Inbox Zero that shows only emails to reply to or awaiting a response.

899-906: Example contradicts “don’t use placeholders”; avoid @company.com.

Either remove the placeholder or state a concrete domain to reduce ambiguity.

Apply one:

-          "condition": { "static": { "from": "@company.com" } },
+          "condition": { "static": { "from": "@inboxzero.com" } },

or rephrase to avoid static placeholder:

-          "condition": { "static": { "from": "@company.com" } },
+          "condition": { "aiInstructions": "Emails from my company domain (ask the user if unknown)" },
apps/web/app/api/ai/summarise/controller.ts (1)

35-38: Use a hashed Redis key instead of the raw prompt to reduce PII exposure and key length.

Hashing the key avoids very long keys and leaking input content in keyspace.

Apply:

+import { createHash } from "node:crypto";
...
-    onFinish: async (result) => {
-      await saveSummary(prompt, result.text);
-      await expire(prompt, 60 * 60 * 24);
-    },
+    onFinish: async (result) => {
+      const key =
+        "summary:" +
+        createHash("sha256").update(prompt).digest("hex").slice(0, 32);
+      await saveSummary(key, result.text);
+      await expire(key, 60 * 60 * 24);
+    },
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4016b62 and e3aca65.

📒 Files selected for processing (7)
  • apps/web/app/api/ai/compose-autocomplete/route.ts (1 hunks)
  • apps/web/app/api/ai/summarise/controller.ts (1 hunks)
  • apps/web/utils/ai/assistant/chat.ts (1 hunks)
  • apps/web/utils/ai/group/create-group.ts (1 hunks)
  • apps/web/utils/auth.ts (1 hunks)
  • apps/web/utils/llms/index.ts (1 hunks)
  • packages/loops/src/loops.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/utils/auth.ts
🧰 Additional context used
📓 Path-based instructions (14)
apps/web/**/*.{ts,tsx}

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

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
apps/web/app/**

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

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/app/api/ai/summarise/controller.ts
apps/web/app/api/**/route.ts

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

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • packages/loops/src/loops.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • packages/loops/src/loops.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • packages/loops/src/loops.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/app/api/ai/summarise/controller.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • packages/loops/src/loops.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • packages/loops/src/loops.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • apps/web/utils/llms/index.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • apps/web/utils/llms/index.ts
apps/web/utils/{ai,llms}/**/*.ts

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

apps/web/utils/{ai,llms}/**/*.ts: Place LLM-related implementation code under apps/web/utils/ai or apps/web/utils/llms
Keep system and user prompts separate; system defines role/task, user contains data/context
Always validate LLM responses with a specific Zod schema
Use descriptive scoped loggers per feature and log inputs/outputs with appropriate levels and context
Implement early returns for invalid inputs and use proper error types with logging
Add fallbacks for AI failures and include retry logic for transient errors using withRetry
Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent
Use TypeScript types for all parameters/returns and define interfaces for complex IO structures
Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments
Call LLMs via createGenerateObject; pass system, prompt, and a Zod schema; return the validated result.object
Derive model options using getModel(...) and pass them to createGenerateObject and the generate call

Files:

  • apps/web/utils/ai/group/create-group.ts
  • apps/web/utils/ai/assistant/chat.ts
  • apps/web/utils/llms/index.ts
apps/web/utils/llms/index.ts

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

Keep core LLM functionality centralized in apps/web/utils/llms/index.ts

Files:

  • apps/web/utils/llms/index.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-07-18T15:06:10.570Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Set appropriate timeouts for LLM calls in tests (e.g., 15,000ms for long-running operations)
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Keep system and user prompts separate; system defines role/task, user contains data/context

Applied to files:

  • apps/web/app/api/ai/compose-autocomplete/route.ts
  • apps/web/utils/ai/assistant/chat.ts
  • apps/web/app/api/ai/summarise/controller.ts
  • apps/web/utils/llms/index.ts
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent

Applied to files:

  • apps/web/utils/llms/index.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Docker Scan
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
  • GitHub Check: claude-review
🔇 Additional comments (7)
apps/web/utils/ai/group/create-group.ts (2)

37-40: Switch to inputSchema looks correct

This aligns the tool with the SDK’s current API and the pattern used elsewhere. No issues.


98-104: Confirm mixed tool definitions interoperate
No legacy parameters: usage remains in any tool() calls, but listEmailsTool (and similar helpers) still return plain objects with inputSchema. Run an integration smoke test to ensure plain-object tools and tool()-wrapped tools interoperate as expected—or refactor listEmailsTool to use tool() for consistency.

packages/loops/src/loops.ts (1)

23-31: createContact signature correct for v6.0.0 The Loops JS SDK v6.0.0+ switched to a single-object parameter for createContact, so the existing call is valid.

Likely an incorrect or invalid review comment.

apps/web/app/api/ai/compose-autocomplete/route.ts (1)

23-32: Good migration to chat-style messages.

System first, then user — correct ordering for most providers.

apps/web/utils/llms/index.ts (1)

181-181: API change to require messages: ModelMessage[] looks good.

Matches upstream usage and related call sites.

apps/web/utils/ai/assistant/chat.ts (1)

947-953: Correct adoption of messages array with system preface.

This maintains deterministic behavior across providers.

apps/web/app/api/ai/summarise/controller.ts (1)

23-32: Messages array migration LGTM.

Structure and order look correct.

@elie222 elie222 merged commit 650c9ee into main Aug 31, 2025
16 checks passed
@elie222 elie222 deleted the chore/upgrade-packages branch August 31, 2025 12:16
This was referenced Oct 21, 2025
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.

1 participant