Skip to content

Comments

feat: server-action/SWR to trpc/tanstack-query#783

Merged
MrgSub merged 2 commits intoMail-0:stagingfrom
BlankParticle:feat/tanstack-query-trpc
May 3, 2025
Merged

feat: server-action/SWR to trpc/tanstack-query#783
MrgSub merged 2 commits intoMail-0:stagingfrom
BlankParticle:feat/tanstack-query-trpc

Conversation

@BlankParticle
Copy link
Contributor

@BlankParticle BlankParticle commented Apr 27, 2025

Current Status: Unstable, needs testing

Known Issues

  • File uploads in send emails dont work yet

Tested basic stuff locally, but need a lot more testing, not suitable for merging to main until everything has been to tested for feature parity.

App might feel slow because the indexed db cache used in SWR is not ported to this version yet. We might need to think of a better caching mechanism as caching every request is too aggressive and cause way too many issues while using multiple connections due to the cache not being keyd properly.

These changes might be hard for everyone to adopt to as a contributor, but the experience using server actions and SWR without proper typesafety became frustrating to me and others, motivations and wins due to these changes has been already discussed on discord.

Due to these changes the backend is now decoupled from NextJS specific features like headers() and in future the backend can be seperated out if needed.

If you have any questions, please feel free to ask on discord.


Type of Change

Please delete options that are not relevant.

  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature with breaking changes)

By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.

Summary by CodeRabbit

  • Refactor
    • Replaced legacy data fetching and mutation logic throughout the app with a new architecture based on React Query and tRPC, improving consistency and performance.
    • Updated components and hooks to use the new query and mutation patterns, removing older REST API endpoints and SWR-based data flows.
  • New Features
    • Introduced advanced AI-powered email generation, reply, and search features accessible via the new tRPC API.
    • Added robust email template and invitation handling, including a new golden ticket email template.
  • Chores
    • Removed unused files, legacy API routes, and deprecated hooks.
    • Updated dependencies, adding React Query, tRPC, and related libraries, while removing obsolete packages.
  • Bug Fixes
    • Improved error handling and user feedback for mutations and queries.
  • Style
    • Standardized code formatting, import order, and string literal usage across the codebase.

@vercel
Copy link

vercel bot commented Apr 27, 2025

@BlankParticle is attempting to deploy a commit to the Zero Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 27, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This change migrates core backend and frontend data-fetching, mutation, and state management logic in the mail application from legacy REST API endpoints and SWR-based hooks to a unified tRPC and React Query architecture. Numerous server-side action files and API route handlers are deleted, with their functionality refactored into tRPC routers and procedures. Corresponding frontend hooks and components are updated to use React Query and tRPC hooks for data retrieval and mutations. Supporting modules are added or modified to provide new query providers, email templates, and utility functions. The update also removes obsolete or redundant code, such as SWR cache providers and direct REST fetchers, consolidating all data operations under the new architecture.

Changes

File(s) / Path(s) Change Summary
apps/mail/actions/ai-reply.ts, apps/mail/actions/ai.ts, apps/mail/actions/brain.ts, ... Deleted legacy server-side action files for AI, brain, connections, cookies, drafts, extractText, getSummary, mail, notes, send, settings, and user management. Their logic is refactored into tRPC routers and procedures.
apps/mail/app/api/[...all]/route.ts Introduced new Hono-based API route handler integrating authentication, tRPC, and custom endpoints, replacing many individual REST API routes.
apps/mail/app/api/ai-search/route.ts, apps/mail/app/api/auth/[...all]/route.ts, ... Deleted numerous REST API route handlers for AI search, authentication, early access, chat, driver, golden ticket, mailto-handler, notes, v1 endpoints, and others, as their logic is now handled by tRPC.
apps/mail/app/(full-width)/contributors/page.tsx, ... Refactored frontend data fetching from SWR to React Query with tRPC hooks in contributors, settings, connections, labels, and other pages.
apps/mail/components/context/thread-context.tsx, ... Updated components to use React Query and tRPC mutations for mail, note, and settings actions, replacing direct imports of server actions and SWR mutate calls.
apps/mail/hooks/use-ai-search.ts, apps/mail/hooks/use-contacts.ts, ... Removed or refactored hooks to use React Query and tRPC queries/mutations instead of SWR and REST fetchers.
apps/mail/lib/brain.ts, apps/mail/lib/email-templates.ts, apps/mail/lib/resend.ts Added new modules for brain feature enablement, email templates (e.g., golden ticket), and email sending client abstraction.
apps/mail/providers/query-provider.tsx Added a new provider integrating React Query and tRPC, with custom error handling, query key hashing, and singleton management for client/server environments.
apps/mail/trpc/index.ts, apps/mail/trpc/routes/ai/, ... Introduced new tRPC routers and procedures for AI, brain, connections, cookies, drafts, early access, labels, mail, notes, settings, and user management, centralizing backend logic.
apps/mail/package.json Updated dependencies: removed axios and swr, added @tanstack/react-query, @trpc/client, @trpc/server, @trpc/tanstack-react-query, dedent, hono, and updated zod.
apps/mail/lib/idb.ts Commented out and removed the IndexedDB SWR cache provider code, with a note to reintroduce it later with more granular control.
Many files Refactored imports, replaced SWR with React Query, removed direct REST fetchers, updated data access patterns, and improved code style and consistency.

Sequence Diagram(s)

sequenceDiagram
    participant UI
    participant QueryProvider
    participant tRPCClient
    participant tRPCServer
    participant DB

    UI->>QueryProvider: Render component
    UI->>tRPCClient: useQuery/useMutation (e.g., fetch threads, send email)
    tRPCClient->>tRPCServer: tRPC procedure call (e.g., mail.get, mail.send)
    tRPCServer->>DB: Query/update database (if needed)
    tRPCServer-->>tRPCClient: Return result/error
    tRPCClient-->>UI: Update UI state via React Query cache
Loading
sequenceDiagram
    participant UI
    participant tRPCClient
    participant AIService

    UI->>tRPCClient: useMutation (e.g., generate AI email reply)
    tRPCClient->>AIService: Call AI completion API with prompt and context
    AIService-->>tRPCClient: Return AI-generated reply
    tRPCClient-->>UI: Display generated reply in UI
Loading

Possibly related PRs

  • Mail-0/Zero#771: Refactors and relocates the AI reply generation function to a tRPC route, directly related to the migration in this PR.
  • Mail-0/Zero#702: Modifies the same AI reply action file, focusing on error handling and formatting, which is now removed in this PR.
  • Mail-0/Zero#534: Improves the same AI email reply function that is deleted in this PR, indicating direct overlap in functionality.

Suggested labels

enhancement

Suggested reviewers

  • MrgSub
  • ahmetskilinc

Poem

Hoppity hop, the code did grow,
From REST and SWR, we let them go!
tRPC and Query, now lead the way,
With hooks and routers, hooray hooray!
Old actions retired, the future is clear,
A faster, smarter mailbox—let’s all cheer!
🐇✨


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.
    • Generate unit testing code for this file.
    • 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. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai 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.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

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

@BlankParticle
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 27, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🔭 Outside diff range comments (6)
apps/mail/lib/driver/microsoft.ts (2)

379-389: 🛠️ Refactor suggestion

Fix typing issues in getUserInfo method

The getUserInfo method has typing issues that are being suppressed with @ts-expect-error. This should be properly addressed before merging.

Consider properly typing the tokens parameter and return value:

- // @ts-expect-error, fix types
- getUserInfo: async (tokens: IConfig['auth']) => {
+ getUserInfo: async (tokens: Required<IConfig['auth']>) => {
  if (!tokens?.access_token) throw new Error('No access token provided');
  const client = getClient(tokens.access_token);
  const user = await client.api('/me').get();
  return {
    address: user.mail || user.userPrincipalName,
    name: user.displayName,
    photo: null,
  };
},

178-232: 💡 Verification agent

🧩 Analysis chain

Address incomplete implementation in Microsoft driver's get method

The get method is marked as incomplete with a @ts-expect-error comment. This may cause issues when this driver is used in production.

Prior to finalizing this PR, ensure the implementation is completed or add a more detailed TODO comment explaining what specifically needs to be fixed. Consider adding proper error handling for production use.


🏁 Script executed:

#!/bin/bash
# Find where this method is being used in the codebase
rg "microsoft.*\.get\(" --type=ts --type=tsx

Length of output: 74


I’ll re-run a broader search without the invalid --type=tsx flag to locate any references to the Microsoft driver’s get method:


🏁 Script executed:

#!/bin/bash
# Search for usage of Microsoft driver’s get() across TS and JS files
rg "microsoft.*\.get\(" -g '*.ts' -g '*.js'

# Look for imports of the Microsoft driver module
rg "import .*microsoft" -g '*.ts' -g '*.js'

Length of output: 470


Implement or Properly Annotate the Incomplete Microsoft Driver get Method

I didn’t find any calls to microsoftDriver.get in the repo, but the method is still marked with @ts-expect-error and only returns stubbed data. Before merging:

• Remove the @ts-expect-error and complete the API call logic (e.g., fetch full conversation, map all message fields)
• Add proper error handling around client.api(...).get() so production failures bubble up meaningfully
• If this endpoint isn’t used yet, add a clear TODO explaining what remains to be implemented and gate its use behind a feature flag or integration test
• (Optional) Write unit or integration tests to exercise this method once implemented

apps/mail/components/create/email-composer.tsx (1)

531-538: 🛠️ Refactor suggestion

Avoid creating a DOM node on each keystroke

Editor.onChange is invoked for every edit, yet it constructs a <div> and performs innerHTML parsing each time.
This is (a) expensive for large messages and (b) blocks the main thread.

-const tempDiv = document.createElement('div');
-tempDiv.innerHTML = content;
-const plainText = tempDiv.textContent || tempDiv.innerText || '';
+const plainText = content.replace(/<[^>]+>/g, ''); // O-regex ≥ 10 × faster

Or, if content is already JSON, ask the editor for a text export to avoid any HTML altogether.

[performance]

apps/mail/components/create/ai-chat.tsx (1)

240-245: 🛠️ Refactor suggestion

TODO: switch file-upload to a tRPC mutation

The comment flags an unimplemented tRPC endpoint while the component still calls the legacy /api/upload-files REST route.
Mixing paradigms undermines the goal of the migration and prevents cache-aware progress / error handling.

Recommend:

  1. Add a uploadFiles mutation in trpc.file.
  2. Replace the fetch call with await uploadFiles.mutateAsync(formData).
  3. Leverage useMutation’s status flags instead of the ad-hoc isLoading state.

Would you like assistance drafting the new router and hook?

apps/mail/components/create/voice.tsx (1)

110-126: 🛠️ Refactor suggestion

⚠️ Potential issue

Add cleanup to close the ElevenLabs session when the component unmounts

conversation.startSession keeps an open WebSocket connection. If the user navigates away without pressing “End Conversation”, the session remains active and the microphone stays reserved, leaking resources.

   useEffect(() => {
     // Request microphone permission on component mount
@@
     requestMicPermission();

+    // Cleanup: ensure the session is closed and volume restored
+    return () => {
+      if (conversation.status === 'connected') {
+        conversation.endSession().catch(() => {
+          /* swallow – we’re unmounting */
+        });
+      }
+    };
   }, []);
apps/mail/components/context/thread-context.tsx (1)

354-363: ⚠️ Potential issue

favorite action is always disabled – probably unintended

disabled: true blocks the user from starring messages even though the mutation & toast logic are fully implemented above.

-      disabled: true,
+      disabled: false,

If starring is unfinished, delete the mutation code to avoid dead paths; otherwise enable the button.

🧹 Nitpick comments (41)
apps/mail/components/ui/input-otp.tsx (1)

38-38: Ensure runtime safety for slot access
The non-null assertion (!) on inputOTPContext.slots[index]! bypasses TypeScript checks but will throw at runtime if the slot is undefined. Confirm that index is always within bounds of slots—otherwise, add a runtime guard to handle missing slots gracefully, for example:

const slot = inputOTPContext.slots[index];
if (!slot) {
  // handle missing slot case (e.g., return null or render placeholder)
}
const { char, hasFakeCaret, isActive } = slot;
apps/mail/components/ui/chart.tsx (1)

134-134: Validate payload item non-null assertion
The item! non-null assertions assume item is always defined. Although you guard against empty payload earlier, consider narrowing the type explicitly or adding a runtime check to avoid potential crashes, e.g.:

const [item] = payload!;
if (!item) return null;
const key = `${labelKey ?? item.dataKey ?? item.name ?? 'value'}`;

This makes the safety intent clear and removes the need for !.

apps/mail/components/create/selectors/text-buttons.tsx (3)

52-54: Consider fully removing the commented-out code.

The change disables the icon rendering by returning null instead of rendering an icon component when item.useImage is false. Since the code is commented out rather than removed, it suggests this might be a temporary change or something to revisit. If this is intentional as part of the transition from server actions to trpc, consider fully removing the commented code for cleaner maintenance.

-            ) : null
-            // <item.icon className="h-4 w-4" />
-            }
+            ) : null}

1-1: Remove unused imports.

The icons MessageSquare, FileText, and Edit imported from 'lucide-react' are not used in this component. These may have been used previously with the now-commented-out icon rendering logic.

-import { MessageSquare, FileText, Edit } from 'lucide-react';

24-32: Consider redefining item type for better type safety.

Since you're transitioning to a more strongly-typed approach with trpc, you might want to define a type for the items in the array. Currently, the item.icon property referenced in the commented-out code doesn't appear in the item definition, which would cause TypeScript errors if the code were uncommented.

+ type TextButtonItem = {
+   name: string;
+   label: string;
+   action: () => void;
+   useImage: boolean;
+   imageSrc?: string;
+ };

  const items = [
    {
      name: 'chat-with-zero',
      label: 'Chat with Zero',
      action: handleChatWithAI,
      useImage: true,
      imageSrc: '/ai.svg',
    },
-  ];
+  ] as TextButtonItem[];
apps/mail/lib/email-templates.ts (4)

4-13: Consider Enhancing the <head> for Responsiveness

To improve rendering across diverse email clients—especially on mobile—consider adding:

  • <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  • A simple <title> tag

This can help clients scale the email correctly and improve accessibility.


14-15: Unify HTML Attribute Quotation

The <body> tag’s style attribute uses single quotes, while other inline attributes use double quotes. For consistency and readability, standardize on double quotes for all HTML attributes.


45-48: Improve Image Rendering Stability

Consider adding explicit width and height attributes to the <img> tag to help email clients reserve layout space before loading:

- <img
-   alt="Zero Early Access"
-   src="https://i.imgur.com/xBnWSpN.png"
+ <img
+   alt="Zero Early Access"
+   src="https://i.imgur.com/xBnWSpN.png"
+   width="600"
+   height="auto"
    style="width:100%;height:auto;object-fit:cover;border-radius:4px;display:block;outline:none;border:none;text-decoration:none" />

115-117: Use Dynamic Year for Maintainability

Hardcoding the year (2025) can quickly become outdated. Consider embedding a dynamic expression:

${new Date().getFullYear()}

within your template to keep this current automatically.

apps/mail/components/ui/page-header.tsx (1)

50-53: Consider properly fixing component types instead of using @ts-expect-error

Using @ts-expect-error comments to suppress TypeScript errors is a temporary solution that can hide potential issues. These annotations indicate there are type incompatibilities when attaching subcomponents to the PageHeader component.

Consider implementing proper TypeScript types for the compound component pattern. A common approach would be:

interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
+  Title?: typeof PageHeaderTitle;
+  Description?: typeof PageHeaderDescription;
}

const PageHeader = React.forwardRef<HTMLDivElement, PageHeaderProps>(
  ({ className, children, ...props }, ref) => {
    // Component implementation
  }
);

- // @ts-expect-error, fix types
PageHeader.Title = PageHeaderTitle;
- // @ts-expect-error, fix types
PageHeader.Description = PageHeaderDescription;
apps/mail/lib/brain.ts (1)

1-10: Add error logging and environment variable validation

The function correctly subscribes a connection to the brain service, but there are two improvement opportunities:

  1. The error case is silently handled, which might make debugging harder
  2. There's no validation that process.env.BRAIN_URL exists before using it
export const enableBrainFunction = async (connection: { id: string; providerId: string }) => {
+  if (!process.env.BRAIN_URL) {
+    console.warn('BRAIN_URL environment variable is not set');
+    return false;
+  }

  return await fetch(process.env.BRAIN_URL + `/subscribe/${connection.providerId}`, {
    body: JSON.stringify({
      connectionId: connection.id,
    }),
    method: 'PUT',
  })
    .then(() => true)
-    .catch(() => false);
+    .catch((error) => {
+      console.error('Failed to enable brain function:', error);
+      return false;
+    });
};
apps/mail/lib/resend.ts (1)

1-5: Improve type safety in mock implementation

The mock implementation uses any[] for arguments, which reduces type safety. Consider creating a more type-safe mock that matches the actual Resend client interface.

import { Resend } from 'resend';

export const resend = process.env.RESEND_API_KEY
  ? new Resend(process.env.RESEND_API_KEY)
-  : { emails: { send: async (...args: any[]) => console.log(args) } };
+  : {
+    emails: {
+      send: async (options: {
+        from: string;
+        to: string | string[];
+        subject: string;
+        html?: string;
+        text?: string;
+        [key: string]: any;
+      }) => {
+        console.log('Mock email send:', options);
+        return { id: 'mock-email-id' };
+      }
+    }
+  };
apps/mail/app/(routes)/settings/labels/page.tsx (2)

57-67: Consider awaiting the toast.promise call for better flow control

The logic for creating/updating labels is good, but you might want to await the toast.promise call directly rather than catching errors separately.

  const onSubmit = async (data: LabelType) => {
    try {
-     toast.promise(editingLabel ? updateLabel(editingLabel.id!, data) : createLabel(data), {
+     await toast.promise(editingLabel ? updateLabel(editingLabel.id!, data) : createLabel(data), {
        loading: 'Saving label...',
        success: 'Label saved successfully',
        error: 'Failed to save label',
      });
    } catch (error) {
      console.error('Error saving label:', error);
    } finally {
      await refetch();
      handleClose();
    }
  };

71-76: Same suggestion for delete operation toast promise

Similarly, consider awaiting the toast.promise call directly for better flow control in the delete operation.

  try {
-   toast.promise(deleteLabel({ id }), {
+   await toast.promise(deleteLabel({ id }), {
      loading: 'Deleting label...',
      success: 'Label deleted successfully',
      error: 'Failed to delete label',
    });
  }
apps/mail/components/create/email-composer.tsx (1)

100-102: Prefer the generated trpc.ai.generateBody.useMutation() helper

Using useMutation(trpc.ai.generateBody.mutationOptions()) works, but it duplicates boiler-plate that the codegen already solves.
Switching to the generated hook:

-const { mutateAsync: generateEmailBody } =
-  useMutation(trpc.ai.generateBody.mutationOptions());
+const { mutateAsync: generateEmailBody } =
+  trpc.ai.generateBody.useMutation();

yields identical behaviour while giving you:

  • Correct generic types without manually importing useMutation
  • Built-in error / loading state wiring
  • A terser diff when the procedure signature changes
apps/mail/hooks/use-stats.ts (1)

9-14: Leverage the generated query hook for conciseness

React-Query already wraps the options for you:

-const statsQuery = useQuery(
-  trpc.mail.count.queryOptions(void 0, {
-    enabled: !!session?.user.id,
-    staleTime: 1000 * 60 * 60, // 1 hour
-  }),
-);
+const statsQuery = trpc.mail.count.useQuery(undefined, {
+  enabled: !!session?.user.id,
+  staleTime: 60 * 60 * 1_000, // 1 h
+});

Same behaviour, less boiler-plate and one less direct dependency on
@tanstack/react-query.

apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (1)

100-102: items is unused – trim dependency noise

archiveEmail’s dependency array lists items, but the callback no longer
references it after the refactor. Retaining it will trigger needless
re-creations every time the list changes.

-}, [items, queryClient, t]);
+}, [queryClient, t]);
apps/mail/app/api/[...all]/route.ts (1)

18-29: Consider adding more detailed error handling for TRPC.

The error logging is good, but consider adding more structured error handling with proper error codes and responses to improve client-side error handling capabilities.

.all('/trpc/*', (c) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req: c.req.raw,
    router: appRouter,
    createContext: () => ({ c, session: c.var.session, db: c.var.db }),
    allowMethodOverride: true,
    onError: (opts) => {
      console.error('Error in TRPC handler:', opts.error);
+     // Log additional context about the request
+     const path = opts.path;
+     const type = opts.type;
+     console.error(`Path: ${path}, Type: ${type}`);
+     
+     // Return structured error response for better client handling
+     const statusCode = opts.error.code === 'UNAUTHORIZED' ? 401 : 
+                        opts.error.code === 'FORBIDDEN' ? 403 : 
+                        opts.error.code === 'NOT_FOUND' ? 404 : 500;
+     return new Response(JSON.stringify({
+       message: opts.error.message,
+       code: opts.error.code
+     }), { status: statusCode, headers: { 'content-type': 'application/json' } });
    },
  }),
)
apps/mail/components/create/ai-chat.tsx (1)

104-113: Avoid shadowing the outer-scope data variable

Inside handleSendMessage a new constant called data is declared (const data = await generateAISearch…).
Because there is already a data variable in the upper scope (returned from useConnections), the shadowing hinders readability and increases the chance of confusion or accidental misuse.

Rename the inner constant to something descriptive, e.g. aiResponse:

- const data = await generateAISearch({
+ const aiResponse = await generateAISearch({
    messages: [...messages, userMessage],
  });
…
- value: data.searchQuery,
+ value: aiResponse.searchQuery,
apps/mail/app/(routes)/settings/general/page.tsx (1)

120-127: Duplicate “saving” state can be derived from the mutation

You maintain a separate isSaving flag although saveUserSettings already exposes isPending.
Removing the extra state reduces complexity:

- const [isSaving, setIsSaving] = useState(false);- setIsSaving(true);
+ // no-op- } finally {
-   setIsSaving(false);
- }

and use isPending for button disabling and text.

apps/mail/app/(routes)/settings/danger-zone/page.tsx (1)

55-72: await mutateAsync + lifecycle callbacks is redundant

When you pass onSuccess, onError, and onSettled to mutateAsync, the promise returned by await resolves after those callbacks; thus await adds no benefit and may mislead future readers.

Two clearer alternatives:

  1. Use mutateAsync without callbacks and handle the resolved value in-line.
  2. Use mutate (non-async) with callbacks and drop await.

Either approach avoids mixing paradigms.

apps/mail/components/mail/mail-iframe.tsx (1)

30-30: Remove console.log statement before production.

This debug statement should be removed as it's not needed in production code.

- console.log(data?.settings);
apps/mail/components/create/voice.tsx (2)

14-15: Remove unused useTRPC import to avoid dead code & bundle bloat

useTRPC is imported but never referenced in this component. Tree-shakers may eliminate it, yet keeping the import is a lint error in most configs (eslint-plugin-unused-imports / TypeScript noUnusedLocals).

-import { trpcClient, useTRPC } from '@/providers/query-provider';
+import { trpcClient } from '@/providers/query-provider';

63-67: Gracefully handle missing sender / recipient names

latestMessage.sender.name and t.name may be undefined, yielding Subject: …\nFrom: undefined.
Guard with a fallback to the email address or "Unknown" to avoid confusing prompts.

apps/mail/lib/thread-actions.ts (1)

56-73: Nested ternaries reduce readability – consider a small helper

The triple-nest makes the logic error-prone. A helper like getRemoveLabel() or a mapping table will be easier to test and maintain.

apps/mail/components/ui/nav-user.tsx (3)

17-18: Remove unused/internal dataTagErrorSymbol import

dataTagErrorSymbol is an internal constant of TanStack Query and is not used in this component. Importing internal symbols couples the code to library internals that can change without notice and produces an unused-import warning.

-import { dataTagErrorSymbol, useMutation } from '@tanstack/react-query';
+import { useMutation } from '@tanstack/react-query';

58-60: handleClearCache no longer clears anything

Since the Indexed-DB cache was removed, this handler now only shows a success toast.
If the intention is to clear React-Query cache (or any other cache layer), call queryClient.clear() or remove the option entirely to avoid a misleading UX.


63-66: Rename mutation instance to follow JS/TS naming conventions

EnableBrain is a function reference, not a component/class, so camelCase (enableBrain) is preferred. This keeps the codebase consistent and prevents ESLint naming-convention warnings.

-const { mutateAsync: EnableBrain } = useMutation(trpc.brain.enableBrain.mutationOptions());
+const { mutateAsync: enableBrain } = useMutation(
+  trpc.brain.enableBrain.mutationOptions(),
+);
...
-const enabled = await EnableBrain({});
+const enabled = await enableBrain({});
apps/mail/trpc/routes/ai/chat.ts (3)

23-28: Redundant runtime check after schema validation

If the Zod schema ensures every content is a non-empty string, the BAD_REQUEST guard becomes unreachable. Either remove the guard or simplify the schema.


53-62: Prefer mutation() for costly, non-idempotent AI calls

AI completions are compute-heavy, rate-limited, and should not be automatically cached/retried like queries. Consider switching to .mutation() to signal side-effects and integrate better with React-Query’s invalidation patterns.


64-73: Handle JSON parsing failures explicitly

When JSON.parse fails, the response shape changes from {emailContent, subject, content} to {content}.
Return a discriminated union or include an ok flag so callers can reliably branch without in checks.

-          return { content: completion };
+          return { ok: false as const, content: completion };
...
-return { content: completion };
+return { ok: true as const, content: completion };
apps/mail/trpc/routes/ai/reply.ts (3)

6-13: Consider using mutation instead of query for content generation.

This procedure generates new content but is implemented as a query rather than a mutation. According to React Query and tRPC best practices, queries should be used for data fetching operations, while mutations should be used for operations that create, update, or delete data.

- .query(async ({ input, ctx }) => {
+ .mutation(async ({ input, ctx }) => {

17-19: Unused content processing comment.

The comment indicates content reduction, but the code simply assigns threadContent to processedContent without any processing. Either implement the processing logic or remove the misleading comment.

- // Use a more aggressive content reduction approach
- const processedContent = threadContent;
+ const processedContent = threadContent;

72-72: Remove or gate debug logging.

Console logs should not be present in production code. Either remove it or gate it behind a debug flag.

- console.log('Generating AI response with prompt:', prompt);
+ if (process.env.NODE_ENV !== 'production') {
+   console.log('Generating AI response with prompt:', prompt);
+ }
apps/mail/components/mail/mail.tsx (1)

85-87: Fix optional chaining in the filter callback

Consider using optional chaining when accessing potentially undefined properties.

-      const filtered = items.filter(
-        (item) => item.tags && item.tags.find((i) => i.name === filterTag),
-      );
+      const filtered = items.filter(
+        (item) => item.tags?.find((i) => i.name === filterTag),
+      );
🧰 Tools
🪛 Biome (1.9.4)

[error] 86-86: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/mail/trpc/routes/ai/email.ts (2)

31-42: Potential duplication in error handling logic

There's some duplication in the error handling logic between the "no response" case and the "missing body field" case. Consider extracting this to a helper function.

+ function createErrorResponse(errorMsg: string) {
+   return {
+     content: errorMsg,
+     jsonContent: createJsonContentFromBody(errorMsg),
+     type: 'system',
+   };
+ }
+
  // ...
  
  if (!response) {
    console.error(
      'AI Action Error (Body): Received no response array item from generateEmailBody',
    );
    const errorMsg = 'AI failed to generate a response.';
-   return {
-     content: errorMsg,
-     jsonContent: createJsonContentFromBody(errorMsg),
-     type: 'system',
-   };
+   return createErrorResponse(errorMsg);
  }
  
  // ...
  
  if (!responseBody) {
    console.error('AI Action Error (Body): Missing body field on response');
    const errorMsg = 'AI returned an unexpected format.';
-   return {
-     content: errorMsg,
-     jsonContent: createJsonContentFromBody(errorMsg),
-     type: 'system',
-   };
+   return createErrorResponse(errorMsg);
  }

Also applies to: 49-59


22-29: Consider adding rate limiting for AI operations

The procedure calls AI functions directly without any rate limiting. Since AI operations can be expensive and could be abused, consider implementing rate limiting.

You could add a rate limiter in the procedure to prevent excessive use:

import { rateLimit } from '@/lib/rate-limit';

// ...inside the mutation handler
const rateLimitResult = await rateLimit.check(ctx.session.user.id);
if (!rateLimitResult.success) {
  return {
    content: 'You've reached the AI generation limit. Please try again later.',
    jsonContent: createJsonContentFromBody('You've reached the AI generation limit. Please try again later.'),
    type: 'system',
  };
}

// Then proceed with AI generation
apps/mail/hooks/use-threads.ts (1)

34-44: Thread filtering logic has an unnecessary dependency.

The useMemo dependency array includes session but this variable isn't used within the memo function. This could cause unnecessary recalculations.

- [threadsQuery.data, session, backgroundQueue, isInQueue],
+ [threadsQuery.data, backgroundQueue, isInQueue],
apps/mail/components/mail/mail-list.tsx (2)

411-412: Minor UI style cleanup in Avatar component.

The Avatar component's className has been reorganized to place rounded-full and border classes in a more logical order. This is a minor improvement for code readability.


419-420: Consistent style organization in AvatarImage.

Similar to the Avatar component, the AvatarImage className has been reorganized to place the background class after the rounded-full class for consistency.

apps/mail/hooks/use-labels.ts (1)

1-25: Consider reducing stale time for more frequent label updates.

The current stale time of 1 hour may be too long for an application where labels could be frequently updated. This could lead to users seeing outdated label data for up to an hour after changes.

Consider reducing the stale time to something shorter, like 5 or 10 minutes, to balance performance with data freshness:

- staleTime: 1000 * 60 * 60, // 1 hour
+ staleTime: 1000 * 60 * 10, // 10 minutes
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01755a4 and d3e574b.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (107)
  • apps/mail/actions/ai-reply.ts (0 hunks)
  • apps/mail/actions/ai.ts (0 hunks)
  • apps/mail/actions/brain.ts (0 hunks)
  • apps/mail/actions/connections.ts (0 hunks)
  • apps/mail/actions/cookies.ts (0 hunks)
  • apps/mail/actions/drafts.ts (0 hunks)
  • apps/mail/actions/extractText.ts (0 hunks)
  • apps/mail/actions/getSummary.ts (0 hunks)
  • apps/mail/actions/mail.ts (0 hunks)
  • apps/mail/actions/notes.ts (0 hunks)
  • apps/mail/actions/send.ts (0 hunks)
  • apps/mail/actions/settings.ts (0 hunks)
  • apps/mail/actions/user.ts (0 hunks)
  • apps/mail/app/(full-width)/contributors/page.tsx (5 hunks)
  • apps/mail/app/(routes)/layout.tsx (1 hunks)
  • apps/mail/app/(routes)/mail/compose/page.tsx (2 hunks)
  • apps/mail/app/(routes)/settings/connections/page.tsx (3 hunks)
  • apps/mail/app/(routes)/settings/danger-zone/page.tsx (4 hunks)
  • apps/mail/app/(routes)/settings/general/page.tsx (4 hunks)
  • apps/mail/app/(routes)/settings/labels/page.tsx (5 hunks)
  • apps/mail/app/api/[...all]/route.ts (1 hunks)
  • apps/mail/app/api/ai-search/route.ts (0 hunks)
  • apps/mail/app/api/auth/[...all]/route.ts (0 hunks)
  • apps/mail/app/api/auth/early-access/count/route.ts (0 hunks)
  • apps/mail/app/api/auth/early-access/route.ts (0 hunks)
  • apps/mail/app/api/auth/settings/route.ts (0 hunks)
  • apps/mail/app/api/chat/route.ts (0 hunks)
  • apps/mail/app/api/driver/[id]/route.ts (0 hunks)
  • apps/mail/app/api/driver/connections/route.ts (0 hunks)
  • apps/mail/app/api/driver/count/route.ts (0 hunks)
  • apps/mail/app/api/driver/drafts/[id]/route.ts (0 hunks)
  • apps/mail/app/api/driver/notes/route.ts (0 hunks)
  • apps/mail/app/api/driver/route.ts (0 hunks)
  • apps/mail/app/api/golden-ticket/route.ts (0 hunks)
  • apps/mail/app/api/mailto-handler.ts (5 hunks)
  • apps/mail/app/api/notes/index.ts (0 hunks)
  • apps/mail/app/api/notes/types.ts (0 hunks)
  • apps/mail/app/api/utils.ts (0 hunks)
  • apps/mail/app/api/v1/email-aliases/route.ts (0 hunks)
  • apps/mail/app/api/v1/hotkeys/route.ts (1 hunks)
  • apps/mail/app/api/v1/labels/route.ts (0 hunks)
  • apps/mail/app/error.tsx (1 hunks)
  • apps/mail/app/layout.tsx (3 hunks)
  • apps/mail/app/manifest.ts (1 hunks)
  • apps/mail/components/context/thread-context.tsx (8 hunks)
  • apps/mail/components/cookies/toggle.tsx (2 hunks)
  • apps/mail/components/create/ai-assistant.tsx (7 hunks)
  • apps/mail/components/create/ai-chat.tsx (7 hunks)
  • apps/mail/components/create/create-email.tsx (2 hunks)
  • apps/mail/components/create/email-composer.tsx (14 hunks)
  • apps/mail/components/create/selectors/text-buttons.tsx (1 hunks)
  • apps/mail/components/create/voice.tsx (11 hunks)
  • apps/mail/components/golden.tsx (5 hunks)
  • apps/mail/components/home/hero.tsx (5 hunks)
  • apps/mail/components/mail/empty-state.tsx (1 hunks)
  • apps/mail/components/mail/mail-iframe.tsx (2 hunks)
  • apps/mail/components/mail/mail-list.tsx (12 hunks)
  • apps/mail/components/mail/mail-quick-actions.tsx (5 hunks)
  • apps/mail/components/mail/mail.tsx (11 hunks)
  • apps/mail/components/mail/nav-main.tsx (1 hunks)
  • apps/mail/components/mail/note-panel.tsx (12 hunks)
  • apps/mail/components/mail/render-labels.tsx (1 hunks)
  • apps/mail/components/mail/reply-composer.tsx (2 hunks)
  • apps/mail/components/mail/thread-display.tsx (4 hunks)
  • apps/mail/components/ui/animated-number.tsx (1 hunks)
  • apps/mail/components/ui/chart.tsx (1 hunks)
  • apps/mail/components/ui/input-otp.tsx (1 hunks)
  • apps/mail/components/ui/nav-main.tsx (4 hunks)
  • apps/mail/components/ui/nav-user.tsx (5 hunks)
  • apps/mail/components/ui/page-header.tsx (1 hunks)
  • apps/mail/hooks/use-ai-search.ts (0 hunks)
  • apps/mail/hooks/use-connections.ts (1 hunks)
  • apps/mail/hooks/use-contacts.ts (0 hunks)
  • apps/mail/hooks/use-drafts.ts (1 hunks)
  • apps/mail/hooks/use-email-aliases.ts (1 hunks)
  • apps/mail/hooks/use-labels.ts (1 hunks)
  • apps/mail/hooks/use-mail-navigation.ts (3 hunks)
  • apps/mail/hooks/use-notes.tsx (1 hunks)
  • apps/mail/hooks/use-settings.ts (1 hunks)
  • apps/mail/hooks/use-stats.ts (1 hunks)
  • apps/mail/hooks/use-summary.ts (1 hunks)
  • apps/mail/hooks/use-threads.ts (1 hunks)
  • apps/mail/lib/auth-client.ts (1 hunks)
  • apps/mail/lib/auth.ts (3 hunks)
  • apps/mail/lib/brain.ts (1 hunks)
  • apps/mail/lib/cookies.ts (2 hunks)
  • apps/mail/lib/driver-utils.ts (2 hunks)
  • apps/mail/lib/driver/google.ts (1 hunks)
  • apps/mail/lib/driver/microsoft.ts (3 hunks)
  • apps/mail/lib/driver/types.ts (1 hunks)
  • apps/mail/lib/email-templates.ts (1 hunks)
  • apps/mail/lib/email-utils.client.tsx (2 hunks)
  • apps/mail/lib/hotkeys/mail-list-hotkeys.tsx (4 hunks)
  • apps/mail/lib/idb.ts (1 hunks)
  • apps/mail/lib/notes-manager.ts (7 hunks)
  • apps/mail/lib/notes-utils.ts (4 hunks)
  • apps/mail/lib/resend.ts (1 hunks)
  • apps/mail/lib/thread-actions.ts (4 hunks)
  • apps/mail/lib/utils.ts (0 hunks)
  • apps/mail/package.json (6 hunks)
  • apps/mail/providers/query-provider.tsx (1 hunks)
  • apps/mail/trpc/hono.ts (1 hunks)
  • apps/mail/trpc/index.ts (1 hunks)
  • apps/mail/trpc/routes/ai/chat.ts (1 hunks)
  • apps/mail/trpc/routes/ai/email.ts (1 hunks)
  • apps/mail/trpc/routes/ai/index.ts (1 hunks)
  • apps/mail/trpc/routes/ai/reply.ts (1 hunks)
⛔ Files not processed due to max files limit (17)
  • apps/mail/trpc/routes/ai/search-v2.ts
  • apps/mail/trpc/routes/ai/search.ts
  • apps/mail/trpc/routes/brain/enable.ts
  • apps/mail/trpc/routes/brain/index.ts
  • apps/mail/trpc/routes/brain/summary.ts
  • apps/mail/trpc/routes/connections.ts
  • apps/mail/trpc/routes/cookies.ts
  • apps/mail/trpc/routes/drafts.ts
  • apps/mail/trpc/routes/early-access.ts
  • apps/mail/trpc/routes/label.ts
  • apps/mail/trpc/routes/mail.ts
  • apps/mail/trpc/routes/misc.ts
  • apps/mail/trpc/routes/notes.ts
  • apps/mail/trpc/routes/settings.ts
  • apps/mail/trpc/routes/user.ts
  • apps/mail/trpc/trpc.ts
  • apps/mail/types/index.ts
💤 Files with no reviewable changes (34)
  • apps/mail/lib/utils.ts
  • apps/mail/app/api/auth/[...all]/route.ts
  • apps/mail/actions/user.ts
  • apps/mail/actions/extractText.ts
  • apps/mail/app/api/auth/early-access/count/route.ts
  • apps/mail/hooks/use-contacts.ts
  • apps/mail/app/api/v1/email-aliases/route.ts
  • apps/mail/app/api/auth/settings/route.ts
  • apps/mail/app/api/driver/route.ts
  • apps/mail/actions/brain.ts
  • apps/mail/app/api/driver/notes/route.ts
  • apps/mail/app/api/notes/types.ts
  • apps/mail/app/api/driver/[id]/route.ts
  • apps/mail/actions/getSummary.ts
  • apps/mail/hooks/use-ai-search.ts
  • apps/mail/app/api/driver/connections/route.ts
  • apps/mail/app/api/driver/drafts/[id]/route.ts
  • apps/mail/app/api/chat/route.ts
  • apps/mail/actions/connections.ts
  • apps/mail/actions/drafts.ts
  • apps/mail/actions/send.ts
  • apps/mail/actions/settings.ts
  • apps/mail/actions/ai-reply.ts
  • apps/mail/app/api/notes/index.ts
  • apps/mail/app/api/auth/early-access/route.ts
  • apps/mail/actions/cookies.ts
  • apps/mail/app/api/golden-ticket/route.ts
  • apps/mail/app/api/driver/count/route.ts
  • apps/mail/actions/mail.ts
  • apps/mail/app/api/ai-search/route.ts
  • apps/mail/actions/ai.ts
  • apps/mail/actions/notes.ts
  • apps/mail/app/api/utils.ts
  • apps/mail/app/api/v1/labels/route.ts
🧰 Additional context used
🧬 Code Graph Analysis (25)
apps/mail/app/layout.tsx (1)
apps/mail/providers/query-provider.tsx (1)
  • QueryProvider (61-72)
apps/mail/lib/email-utils.client.tsx (1)
apps/mail/providers/query-provider.tsx (1)
  • trpcClient (50-59)
apps/mail/components/ui/nav-main.tsx (1)
apps/mail/hooks/use-labels.ts (1)
  • useLabels (5-13)
apps/mail/lib/driver-utils.ts (2)
packages/db/src/schema.ts (1)
  • session (19-30)
apps/mail/lib/auth.ts (1)
  • auth (330-333)
apps/mail/trpc/hono.ts (1)
apps/mail/lib/auth.ts (1)
  • auth (330-333)
apps/mail/lib/brain.ts (1)
packages/db/src/schema.ts (1)
  • connection (77-96)
apps/mail/components/mail/empty-state.tsx (1)
apps/mail/hooks/use-connections.ts (1)
  • useConnections (4-12)
apps/mail/lib/thread-actions.ts (2)
apps/mail/lib/utils.ts (1)
  • LABELS (21-28)
apps/mail/providers/query-provider.tsx (1)
  • trpcClient (50-59)
apps/mail/components/cookies/toggle.tsx (1)
apps/mail/lib/cookies.ts (1)
  • CookieCategory (4-4)
apps/mail/trpc/routes/ai/index.ts (5)
apps/mail/trpc/trpc.ts (1)
  • router (17-17)
apps/mail/trpc/routes/ai/reply.ts (1)
  • generateReply (6-83)
apps/mail/trpc/routes/ai/search.ts (1)
  • generateSearchQuery (4-15)
apps/mail/trpc/routes/ai/search-v2.ts (1)
  • generateSearchQueryEnhanced (25-161)
apps/mail/trpc/routes/ai/email.ts (1)
  • generateEmailSubject (79-100)
apps/mail/app/(routes)/settings/labels/page.tsx (1)
apps/mail/hooks/use-labels.ts (1)
  • useLabels (5-13)
apps/mail/components/mail/mail-quick-actions.tsx (2)
apps/mail/hooks/use-threads.ts (2)
  • useThread (60-79)
  • useThreads (11-58)
apps/mail/hooks/use-stats.ts (1)
  • useStats (5-17)
apps/mail/hooks/use-notes.tsx (1)
apps/mail/lib/notes-manager.ts (1)
  • Note (5-15)
apps/mail/app/(routes)/settings/general/page.tsx (2)
apps/mail/hooks/use-settings.ts (1)
  • useSettings (5-17)
packages/db/src/user_settings_default.ts (1)
  • userSettingsSchema (13-21)
apps/mail/components/create/voice.tsx (3)
apps/mail/hooks/use-threads.ts (1)
  • useThreads (11-58)
apps/mail/providers/query-provider.tsx (1)
  • trpcClient (50-59)
apps/mail/types/index.ts (1)
  • Sender (47-50)
apps/mail/components/mail/mail.tsx (2)
apps/mail/hooks/use-threads.ts (1)
  • useThreads (11-58)
apps/mail/lib/utils.ts (1)
  • cn (53-53)
apps/mail/components/create/ai-chat.tsx (2)
apps/mail/hooks/use-connections.ts (1)
  • useConnections (4-12)
packages/db/src/schema.ts (2)
  • connection (77-96)
  • session (19-30)
apps/mail/app/(routes)/settings/connections/page.tsx (2)
apps/mail/hooks/use-connections.ts (1)
  • useConnections (4-12)
packages/db/src/schema.ts (1)
  • connection (77-96)
apps/mail/app/api/mailto-handler.ts (2)
apps/mail/trpc/hono.ts (1)
  • HonoVariables (4-7)
apps/mail/trpc/index.ts (1)
  • serverTrpc (38-39)
apps/mail/lib/notes-utils.ts (2)
apps/mail/lib/notes-manager.ts (1)
  • Note (5-15)
packages/db/src/schema.ts (1)
  • note (110-122)
apps/mail/providers/query-provider.tsx (1)
apps/mail/trpc/index.ts (1)
  • AppRouter (33-33)
apps/mail/components/mail/mail-iframe.tsx (3)
apps/mail/hooks/use-settings.ts (1)
  • useSettings (5-17)
packages/db/src/user_settings_default.ts (1)
  • defaultUserSettings (3-11)
apps/mail/lib/timezones.ts (1)
  • getBrowserTimezone (2-2)
apps/mail/components/home/hero.tsx (1)
apps/mail/components/ui/animated-number.tsx (1)
  • AnimatedNumber (13-29)
apps/mail/components/create/email-composer.tsx (1)
apps/mail/components/icons/icons.tsx (1)
  • Sparkles (355-380)
apps/mail/components/mail/note-panel.tsx (2)
apps/mail/hooks/use-notes.tsx (1)
  • useThreadNotes (7-27)
apps/mail/lib/notes-manager.ts (4)
  • createNote (34-66)
  • updateNote (68-96)
  • deleteNote (98-112)
  • reorderNotes (114-169)
🪛 Biome (1.9.4)
apps/mail/components/mail/mail.tsx

[error] 86-86: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (175)
apps/mail/components/ui/animated-number.tsx (1)

19-19: Improved type safety by removing unnecessary type assertion.

This change removes the explicit type assertion (as keyof JSX.IntrinsicElements) and now properly leverages the as prop's type, which is already defined as React.ElementType. This is a positive improvement because:

  1. It eliminates redundant type casting while maintaining the same functionality
  2. React.ElementType is more flexible, allowing both intrinsic HTML elements and custom React components
  3. It aligns with modern TypeScript best practices by letting the type system work without manual assertions
apps/mail/app/manifest.ts (3)

8-9: Verify the PWA scope and start_url pairing
You’ve set scope: '/' with a start_url of /mail/inbox, which grants the PWA control over the entire site. If the PWA is meant only for the mail section, consider narrowing scope to '/mail/' so that navigations outside the mail app aren’t intercepted by the PWA shell.


15-17: Approve consistent string-literal formatting
The migration from double quotes to single quotes for src, sizes, and type values is consistent with the project’s style guide and improves readability.

Also applies to: 22-24, 29-31


33-33: Approve trailing comma in arrays
Adding a trailing comma after the closing bracket of icons aligns with the project’s formatting conventions and produces cleaner diffs on future edits.

apps/mail/components/create/selectors/text-buttons.tsx (1)

13-22: Placeholder implementation for AI chat functionality.

The handleChatWithAI function only logs to the console and doesn't implement the actual functionality. As part of your refactoring from server-actions/SWR to trpc/tanstack-query, consider implementing this using your new data fetching approach.

Is the AI chat functionality intended to be implemented in this PR, or is this placeholder intentionally left for future work?

apps/mail/lib/email-templates.ts (2)

1-3: Import and Export Structure Looks Good

The dedent import and the export const goldenTicketEmail declaration follow existing conventions and correctly set up the HTML template.


16-22: Verify Hidden Preview Text Characters

The hidden preview snippet includes a sequence of zero-width Unicode characters (‌‌​‍‎‏). Please confirm these are intentional and render as expected across major email clients, as malformed invisible characters can break the preview text or trigger client quirks.

apps/mail/lib/driver/google.ts (1)

3-3: Import path updated to align with new structure

The import path for deleteActiveConnection and FatalErrors has been updated to use @/lib/driver-utils instead of the previous path from actions. This change supports the move away from server actions to tRPC.

apps/mail/components/mail/render-labels.tsx (1)

8-8: Type import source updated correctly

The Label type is now imported from @/types instead of the previous hooks location, which aligns with the migration from SWR to tRPC and React Query for data management.

apps/mail/lib/auth-client.ts (1)

1-3: Import style standardized

The import statements have been updated to use single quotes consistently, which improves code style standardization across the codebase.

apps/mail/lib/driver/types.ts (1)

2-2: Type import updated for consistency

The Label type import has been updated to reference @/types and explicitly uses the type keyword, maintaining consistency with other components in the codebase during the migration to tRPC.

apps/mail/app/error.tsx (1)

23-32:

Details

✅ Verification successful

IndexedDB cache clearing removed as part of migration

The call to dexieStorageProvider().clear() has been removed from the error handler, which aligns with the broader removal of IndexedDB cache management in favor of React Query's caching system.

Since the PR mentions that "the indexed DB cache mechanism used in SWR has not yet been ported to the new version", please verify that removing this cache clearing behavior doesn't lead to stale data issues when users encounter errors and retry.


🏁 Script executed:

#!/bin/bash
# Check if any other components still use dexieStorageProvider
rg "dexieStorageProvider" --type ts --type tsx

Length of output: 74


I’ll re-run the search to cover both .ts and .tsx files explicitly.


🏁 Script executed:

#!/bin/bash
# Re-check for any remaining references to dexieStorageProvider in TS and TSX files
rg "dexieStorageProvider" --glob '*.ts' --glob '*.tsx' || true

Length of output: 128


No remaining Dexie cache references—removal is safe

I ran a workspace-wide grep for dexieStorageProvider and only saw its definition in apps/mail/lib/idb.ts; there are no imports or calls elsewhere. Removing the call to dexieStorageProvider().clear() in the error handler will not leave any unhandled IndexedDB cache logic in the current code. Feel free to merge.

apps/mail/app/layout.tsx (1)

13-13: Well-structured integration of React Query and tRPC

The addition of QueryProvider as a wrapper around the application is a clean implementation that properly establishes React Query and tRPC context throughout the app. This aligns perfectly with the PR objective of transitioning from server actions and SWR to tRPC and tanstack-query.

Also applies to: 57-67

apps/mail/lib/driver/microsoft.ts (1)

3-3: Import path update aligns with refactoring strategy

The change in import path from @/actions/utils to @/lib/driver-utils correctly supports the broader refactoring from direct action imports to the new architecture.

apps/mail/app/(routes)/mail/compose/page.tsx (2)

3-4: Import order change looks good.

The import order between headers and auth has been swapped, which doesn't affect functionality.


31-31: Redirect URL updated to align with new architecture.

The redirect path has been changed from what was likely /mail/compose/handle-mailto to /api/mailto-handler, which aligns with the PR's goal of decoupling from NextJS-specific features and moving to a more unified tRPC architecture.

apps/mail/lib/cookies.ts (2)

1-1: 'use client' directive is appropriate.

Adding the 'use client' directive aligns with the transition from server-side actions to client-side TRPC calls for cookie preferences management.


2-49: Quote style standardization looks good.

All string literals have been standardized to use single quotes throughout the file, which improves consistency. The formatting changes to the CookiePreferences interface are also consistent with the codebase style.

apps/mail/lib/email-utils.client.tsx (2)

4-4: Import change aligns with tRPC migration.

The import has been changed from direct server action import to tRPC client import, which aligns with the PR's goal of transitioning from server actions to tRPC.


43-53: Server action replaced with tRPC mutation.

The direct call to sendEmail server action has been replaced with trpcClient.mail.send.mutate, maintaining the same functionality while aligning with the new architecture. The parameters passed remain the same, ensuring consistent behavior.

apps/mail/components/ui/nav-main.tsx (3)

17-18: Import structure update looks good.

The separation of the Label type import from the useLabels hook follows good practice for type organization, moving the type to a centralized location.

Also applies to: 28-28


66-66: Correctly updated to match React Query pattern.

This change properly aligns with the React Query pattern where hooks return the full query object with properties like data, error, and isLoading.


224-224: Consistent data access update.

The change from labels?.map to data?.map aligns with the updated return structure from the useLabels hook which now returns the full React Query result object.

apps/mail/components/mail/nav-main.tsx (3)

1-1: Successful internationalization library migration.

The change from useTranslation (react-i18next) to useTranslations (next-intl) correctly implements the new internationalization approach.

Also applies to: 6-6


10-10: Improved code readability.

Adding parentheses around the function parameter and conditional expression enhances code clarity without changing functionality.


13-14: Simplified component structure.

The JSX return and export statement have been streamlined, improving code readability while maintaining functionality.

Also applies to: 16-16

apps/mail/trpc/hono.ts (1)

1-7: Well-structured type definition for Hono context.

The HonoVariables type provides strong typing for the Hono context, ensuring type safety when accessing session data and database operations. This supports the decoupling of backend code from NextJS-specific features.

apps/mail/lib/driver-utils.ts (2)

2-2: Appropriate reorganization of import path.

Moving from @/app/api/driver to @/lib/driver better organizes the code by placing driver creation logic in a more general library location rather than an API-specific one.


33-34: Successfully decoupled from NextJS-specific API.

Changing getActiveDriver to accept a Request object parameter instead of using headers() from next/headers improves flexibility and decouples the function from NextJS-specific APIs. This aligns with the PR objective of making the backend more separable from the frontend.

apps/mail/lib/idb.ts (1)

1-3: Ensure alternative caching strategy is documented

The removal of IndexedDB caching aligns with the PR description that mentions the SWR cache hasn't been ported yet. This explains why the application might initially feel slower as stated in the PR objectives.

Consider updating the comment to include:

  1. Expected timeline for reintroduction
  2. Current fallback caching mechanism
  3. Impact on performance and offline capabilities
/*
-Removed for now, In future more granular cache management will be added along with offline support
+Removed as part of migration from SWR to tanstack-query. A new cache implementation with
+more granular control and offline support will be added in a future PR.
+
+Note: Until this is reintroduced, the application may have reduced performance for repeat
+queries and limited offline functionality. The current tanstack-query implementation uses
+in-memory caching only.
*/
apps/mail/package.json (1)

21-21:

Details

❓ Verification inconclusive

Dependencies align well with architectural shift

The added dependencies appropriately support the migration from server actions/SWR to tRPC/tanstack-query. The inclusion of Hono for API routing and supporting libraries like superjson and dedent all fit the new architecture.

Let's verify the latest versions of these critical packages:

Also applies to: 47-47, 58-60, 77-77, 86-86, 113-113, 120-120


🌐 Web query:

What are the latest versions of @tanstack/react-query, @trpc/server, and hono?

💡 Result:

Here are the latest versions of the requested libraries as of April 27, 2025:

@tanstack/react-query

  • Latest version: 5.74.6
  • Release date: April 22, 2025[2]

@trpc/server

  • Latest version: 11.1.0
  • Release date: Announced March 21, 2025, with continued maintenance updates[6][10]

hono

  • Latest version: 4.7.5
  • Release date: (Most recent version as of current date)[7]

These are the most up-to-date stable releases for each package based on official sources and package repositories.

Citations:


🏁 Script executed:

#!/bin/bash
# Check current versions of critical dependencies in apps/mail/package.json
grep -n '"@tanstack/react-query"' apps/mail/package.json || echo "No @tanstack/react-query dependency found"
grep -n '"@trpc/server"' apps/mail/package.json || echo "No @trpc/server dependency found"
grep -n '"hono":' apps/mail/package.json || echo "No hono dependency found"

Length of output: 253


Please verify dependency versions against the latest stable patches

The added dependencies align well with the tRPC/tanstack-query architectural shift, but a few are not on the most recent patch releases:

• apps/mail/package.json:47 – @tanstack/react-query is ^5.74.4 (latest 5.74.6)
• apps/mail/package.json:59 – @trpc/server is ^11.1.1 (latest reported 11.1.0) – please confirm that 11.1.1 is published or adjust to 11.1.0
• apps/mail/package.json:86 – hono is ^4.7.7 (latest 4.7.5)

Everything else fits the new architecture. Once you’ve verified these versions, feel free to bump them to the current stable patches.

apps/mail/components/mail/empty-state.tsx (3)

108-108: Destructuring optimization aligns with React Query patterns

Good update to destructure only the needed properties from the tRPC query result. This follows best practices with React Query by directly accessing what the component needs.


110-112: Enhanced dependency array with proper optional chaining

The update to use data?.connections with optional chaining is good practice. The dependency array is also correctly updated to watch data?.connections instead of the previous structure.


115-115: Clean loading state handling

The simplified reference to isLoading makes the code more readable and maintainable, properly leveraging React Query's standardized return values.

apps/mail/hooks/use-summary.ts (1)

1-9: Well-structured migration to tRPC and React Query

This refactoring cleanly implements the transition from server actions to tRPC. The hook now:

  1. Properly uses the tRPC client
  2. Leverages React Query's useQuery hook with the appropriate options
  3. Returns the full query result object giving components access to loading, error states, etc.

The implementation follows best practices by using the queryOptions pattern from tRPC for type safety.

apps/mail/components/cookies/toggle.tsx (3)

4-6: Good adoption of tRPC and React Query for imports

Clean import structure that brings in the necessary hooks for the refactoring.


17-20: Well-structured mutation setup

Good implementation of the tRPC mutation using the proper patterns. The destructuring of mutateAsync makes the code more readable.


24-24: Updated mutation call signature

The mutation call is properly updated to use an object parameter structure ({ category: key, enabled: checked }) which is more maintainable and clear than separate arguments.

apps/mail/components/create/create-email.tsx (4)

5-7: Imports updated for TRPC and React Query integration

Good addition of the necessary imports for the new data-fetching architecture using TRPC and React Query.


29-30: Updated hook destructuring for new data patterns

Correctly updated destructuring from data fetching hooks to match the new return structure from TRPC/React Query hooks.


33-34: Implemented TRPC mutation for email sending

Good implementation of the TRPC-based mutation for sending emails. This properly replaces the previous direct server action call with the new mutation pattern.


36-39: Updated reference to connections with proper optional chaining

The updated reference to data.connections with optional chaining is correct and helps prevent potential null reference errors.

apps/mail/trpc/routes/ai/index.ts (2)

1-6: Good modular imports for AI functionality

Well-structured imports that keep the AI functionality modular and organized by importing from separate files.


7-13: Well-organized TRPC router for AI functionality

Great job creating a centralized TRPC router for AI functionality. This approach:

  1. Improves type safety
  2. Centralizes all AI capabilities under a single namespace
  3. Makes these functions easily accessible throughout the application
  4. Facilitates the decoupling from NextJS-specific features mentioned in the PR objectives
apps/mail/app/(routes)/settings/labels/page.tsx (5)

22-25: Updated imports for TRPC and React Query

Good update of imports to support the new data-fetching architecture.


39-39: Properly destructured label query with refetch capability

Good update to the hook usage, now properly destructuring the returned query object to access data, loading state, error, and the refetch function.


48-51: Implemented TRPC mutations for label operations

Well-implemented TRPC mutations for label creation, updating, and deletion operations. This is a clean way to handle these operations with proper type safety.


194-195: Good error handling and null checking

The error handling has been improved by displaying the actual error message, and the null checking with optional chaining is correctly implemented.


200-200: Proper optional chaining in the map function

Good use of optional chaining in the map function to handle the possibility of undefined labels.

apps/mail/components/home/hero.tsx (5)

4-8: Updated imports for TRPC and React Query

Good update of imports to support the new data-fetching architecture.


29-33: Well-implemented TRPC query and mutation for early access functionality

The implementation of TRPC query for count data and mutation for registration is clean and effective. Good job setting the initialData to prevent null reference issues before data loads.


47-50: Updated to use TRPC mutation for registration

Good replacement of what was likely a direct API call with the new TRPC mutation.


62-62: Using refetch to update count after registration

Good approach using refetch to update the count after a successful registration rather than manually incrementing a local state variable.


166-175: Updated count display with proper data reference

Good update to reference the count directly from the query data and pass it to the AnimatedNumber component.

apps/mail/components/golden.tsx (3)

12-14: Updated imports to support TRPC with React Query

The imports have been properly updated to support the migration from server actions to TRPC with React Query for data fetching and mutations.


43-46: Proper TRPC setup for the golden ticket claim mutation

The code correctly initializes TRPC and sets up the mutation for claiming a golden ticket using TRPC's mutation options pattern.


55-75: Refactored form submission to use TRPC mutation with proper error handling

The form submission has been well-refactored to use a TRPC mutation instead of direct fetch calls. The implementation includes:

  • Toast ID tracking for updating the loading toast
  • Proper success and error callbacks
  • Session refetching and router refresh on success
  • Modal closure on successful submission

This is a good implementation of the React Query mutation pattern.

apps/mail/app/(full-width)/contributors/page.tsx (8)

30-30: Updated import for React Query

Correctly imported useQuery from @tanstack/react-query to replace SWR.


58-64: Improved formatting for excludedUsernames array

The array has been reformatted for better readability while maintaining the same content.


68-71: Enhanced type declaration for specialRoles

The type declaration for specialRoles has been improved for better readability and maintainability.


144-150: Replaced SWR with useQuery for initial contributors

Correctly implemented React Query's useQuery hook to fetch the initial contributors from GitHub API.


152-159: Smart conditional query for additional contributors

Well-implemented conditional query that only runs when the initial contributors count equals 100, suggesting there might be more pages to fetch.


171-174: Replaced SWR with useQuery for repository data

All GitHub API requests have been standardized to use React Query with appropriate query keys and functions.

Also applies to: 176-182, 184-190


192-206: Refactored useMemo implementation for filteredCoreTeam

The useMemo implementation has been refactored to a more concise arrow function style while preserving the same filtering and sorting logic.


855-855: Fixed spacing in Avatar className

Minor formatting fix to ensure consistent className formatting.

apps/mail/hooks/use-connections.ts (2)

1-2: Updated imports for TRPC and React Query

Correctly replaced the old SWR imports with TRPC and React Query imports.


4-12: Refactored useConnections hook to use TRPC with React Query

The hook has been completely refactored to:

  1. Initialize TRPC client
  2. Use React Query's useQuery with TRPC's connection list query
  3. Set an infinite stale time to prevent unnecessary refetches
  4. Return the full query object instead of just the data

This implementation aligns with the architectural shift from SWR to React Query and provides consumers with access to all React Query features like refetching, loading states, and error handling.

apps/mail/components/mail/mail-quick-actions.tsx (5)

6-8: Updated imports for TRPC and React Query

Correctly added imports for TRPC client and React Query's useMutation hook.


36-39: Setup TRPC mutations for mail operations

Properly initialized TRPC client and created mutations for markAsRead and markAsUnread operations using TRPC's mutation options pattern.


40-45: Updated hook usage for refetch capabilities

Properly destructured the query results to access refetch methods from thread, threads, and stats queries, which is essential for refreshing data after mutations.


82-82: Optimized cache refresh with Promise.all

Efficiently refreshes both thread list and stats data in parallel after an archive operation using Promise.all.


119-149: Replaced direct function calls with TRPC mutations

Effectively replaced direct markAsRead/markAsUnread function calls with TRPC mutations that include proper success, error, and settled callbacks.

The implementation provides:

  • Better type safety through TRPC
  • Improved error handling with specific error messages
  • Proper UI feedback via toast notifications
  • Automatic thread data refreshing on success
apps/mail/app/api/[...all]/route.ts (2)

1-32: LGTM: Well-structured route handler consolidation.

This new route handler effectively consolidates multiple API routes into a single unified handler using Hono. The implementation properly extracts user sessions, provides authentication routes, TRPC integration, and mail handler functionality.

The clean organization with middleware for session and database handling will simplify maintenance and ensure consistent behavior across endpoints.


32-42: LGTM: Uniform HTTP method handling approach.

Exporting the same handler for all HTTP methods is elegant and ensures consistent behavior. This approach also aligns well with the goal of decoupling from NextJS-specific features.

apps/mail/hooks/use-email-aliases.ts (2)

1-13: LGTM: Successfully migrated from SWR to React Query.

The hook has been successfully migrated from useSWRImmutable to React Query's useQuery. The implementation properly sets up a 1-hour stale time and provides appropriate initial data.

The return type change (returning the full query object) gives consumers more flexibility to access additional React Query features like isRefetching, refetch, etc.


6-10:

Details

✅ Verification successful

Verify clients consuming this hook can handle the changed return type.

The hook now returns the full React Query object instead of the previous custom object with aliases, isLoading, etc. This change in return type might break components that expect the previous structure.


🏁 Script executed:

#!/bin/bash
# Search for components using useEmailAliases
echo "Searching for components using useEmailAliases..."
rg -A 3 -B 3 "useEmailAliases\(\)" --type tsx --type jsx

Length of output: 195


Let's retry the search without relying on built-in rg file types—use globs instead to catch all .tsx/.jsx usages:


🏁 Script executed:

#!/bin/bash
echo "Searching components for useEmailAliases usage (TSX/JSX globs)…"
rg -n -A3 -B3 "useEmailAliases(" -g "*.tsx" -g "*.jsx"

Length of output: 276


Let’s search both the imports and the literal hook calls across all TS/JS files:


🏁 Script executed:

#!/bin/bash
echo "Searching for imports of useEmailAliases in .ts/.tsx/.js/.jsx files..."
rg -n "import.*useEmailAliases" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx"

echo
echo "Searching for literal calls of useEmailAliases(...) in .ts/.tsx/.js/.jsx files..."
rg -F -n -A3 -B3 "useEmailAliases(" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx"

Length of output: 2597


Verified safe usage of useEmailAliases return shape

I checked all imports and calls of useEmailAliases – it’s only used in:

  • apps/mail/components/mail/reply-composer.tsx (line 28)
  • apps/mail/components/create/create-email.tsx (line 30)

In both places the hook’s return value is destructured as

const { data: aliases, isLoading: isLoadingAliases } = useEmailAliases();

which matches the new React Query object shape (data and isLoading). No components rely on the old custom { aliases, … } return type, so this change won’t break any existing consumers.

apps/mail/components/create/ai-assistant.tsx (6)

1-14: LGTM: Updated imports for TRPC and React Query integration.

The import statements have been updated to use the new TRPC and React Query hooks, which aligns with the architecture changes described in the PR.


209-214: LGTM: Successfully migrated to TRPC mutations for AI operations.

The component now properly uses TRPC mutation hooks for email body and subject generation, making it compatible with the new architecture.


287-294: Improved typed request structure for generating email body.

The function call now uses a properly structured object for the request parameters, providing better type safety.


321-322: Simplified API call for subject generation.

The subject generation mutation call has been simplified, improving code readability.


335-338: Improved error handling and reporting for empty content.

The error handling has been enhanced with more explicit error messages and proper error types, which will make debugging easier.


343-357: Well-structured error handling with appropriate user feedback.

The improved error handling now properly differentiates between specific error types and provides appropriate user feedback through toast notifications. This enhancement will improve the user experience when errors occur.

apps/mail/lib/auth.ts (5)

16-18: LGTM: Updated imports for better modularity.

The import changes support better modularity by separating brain functionality into its own module and updating the driver utilities import path.


21-21: LGTM: Simplified resend email client import.

The simplified import of the resend email client from a dedicated module improves modularity and separation of concerns.


87-88: Added proper request validation in deleteUser hook.

The updated hook now correctly validates the request object and passes it to getActiveDriver, aligning with the function's updated signature.


274-274: Simplified brain enablement function call.

The brain enablement function call has been simplified with a flatter object structure, making the code more readable and maintainable.


87-101: Verify that user account deletion works correctly with the updated driver.

The user deletion flow has been modified to use the new getActiveDriver signature. Ensure that this critical functionality works correctly after these changes.

Given the changes to how active drivers are obtained, this is a critical flow to test as it involves revoking tokens and deleting user data.

apps/mail/components/mail/mail-iframe.tsx (2)

23-24: LGTM: Good use of tRPC mutations.

The implementation correctly uses React Query's mutation hook with tRPC, which provides better type safety and consistency compared to direct action imports.


37-42: LGTM: Proper handling of trustedSenders array.

The code correctly handles the case where trustedSenders might be undefined by using optional chaining and providing a fallback.

apps/mail/hooks/use-drafts.ts (1)

8-14: LGTM: Good query configuration.

The implementation correctly:

  1. Uses proper typing with tRPC
  2. Enables the query only when both session and draft ID exist
  3. Sets a reasonable stale time of 1 hour for draft data

This is an improvement over the previous SWR implementation.

apps/mail/app/(routes)/settings/connections/page.tsx (3)

29-29: LGTM: Good use of explicit renaming.

Explicitly renaming refetch to refetchConnections improves code readability by making the purpose of the function clear.


36-49: LGTM: Improved error handling and user feedback.

The implementation correctly:

  1. Uses the tRPC mutation hook
  2. Provides proper error handling with console logging and user notification
  3. Shows success messages to the user
  4. Refreshes both connections and session data to ensure UI consistency

This is more robust than the previous implementation.


76-78: LGTM: Consistent data structure access.

The code correctly updates references to connection data to use the new data structure from React Query.

apps/mail/hooks/use-settings.ts (2)

9-14: Consider the implications of Infinity staleTime.

Setting staleTime: Infinity means the data will never become stale automatically, which is appropriate for relatively static user settings. However, be aware that you'll need to manually trigger a refetch whenever settings change in other parts of the application.

Do you have a strategy for ensuring settings are refreshed when modified in other components or contexts? For example, when a different component changes user settings, how will this be reflected where useSettings is used?


16-16: LGTM: Returning the full query object provides flexibility.

Returning the complete React Query result object gives consumers access to additional useful properties like isLoading, isError, error, and refetch, making this hook more versatile.

apps/mail/components/create/voice.tsx (1)

52-83: Avoid N×M re-fetch storm by memoising already-downloaded threads

Every change to threads re-runs fetchThreadContents, re-querying the same 20 IDs (even if none changed), hammering the API and slowing the UI. Cache thread IDs you have already fetched or compare threads length/IDs before re-running.

Pseudo-fix:

-  useEffect(() => {
-    if (!threads || threads.length === 0) return;
+  const fetchedRef = useRef<Set<string>>(new Set());
+
+  useEffect(() => {
+    if (!threads?.length) return;
     ...
-          threads.slice(0, 20).map(async (thread) => {
+          threads
+            .slice(0, 20)
+            .filter((t) => !fetchedRef.current.has(t.id))
+            .map(async (thread) => {
               ...
+              fetchedRef.current.add(thread.id);
             }),

[ suggest_optional_refactor ]
[ request_verification ]

apps/mail/lib/thread-actions.ts (1)

91-95:

Details

❌ Incorrect review comment

Parameter name mismatch may break the TRPC call

modifyLabels expects (per previous API) threadIds: string[], but the call supplies a singular key threadId. If the router still expects the plural form the mutation will fail at runtime.

-    return trpcClient.mail.modifyLabels.mutate({
-      threadId: threadIds,
+    return trpcClient.mail.modifyLabels.mutate({
+      threadIds,
       addLabels: addLabel ? [addLabel] : [],
       removeLabels: removeLabel ? [removeLabel] : [],
     });

Please verify the server-side procedure signature.


🏁 Script executed:

#!/bin/bash
set -e
rg --color=never -n "modifyLabels" .

Length of output: 608


🏁 Script executed:

#!/bin/bash
set -e
rg -n -A5 -B5 "modifyLabels" apps/mail/trpc/routes/mail.ts

Length of output: 1181


No change needed: client is already using the correct threadId parameter

The server-side procedure in apps/mail/trpc/routes/mail.ts declares:

.input(
  z.object({
    threadId: z.string().array(),
    addLabels: z.string().array().optional().default([]),
    removeLabels: z.string().array().optional().default([]),
  })
)

…so calling

trpcClient.mail.modifyLabels.mutate({
  threadId: threadIds,
  addLabels: addLabel ? [addLabel] : [],
  removeLabels: removeLabel ? [removeLabel] : [],
});

matches the expected schema. The suggested rename to threadIds would break the call.

Likely an incorrect or invalid review comment.

apps/mail/components/context/thread-context.tsx (1)

132-141: Background-queue IDs include the thread: prefix – ensure backend expects it

targets are built as thread:${id}, but other TRPC calls (e.g., deleteThread) pass raw IDs. Mixing formats may break analytics or cache keys.

Confirm the expected identifier format and normalise across the codebase.

apps/mail/trpc/index.ts (1)

38-40: LGTM – clean serverTrpc helper

Neat helper; context typing is correct and keeps the caller construction DRY. 👍

apps/mail/hooks/use-notes.tsx (3)

1-5: Clean import structuring.

The imports have been properly reorganized to accommodate the new React Query and tRPC architecture, replacing the previous SWR-based approach.


10-24: Well-implemented React Query transition with appropriate configurations.

The migration from SWR to React Query with tRPC is well structured. The query configuration includes:

  • Conditional enabling based on session and threadId presence
  • A reasonable 5-minute stale time for caching notes data
  • Proper error handling via the meta customError property
  • Appropriate initial data to prevent UI flickers

26-26: Return value is now more feature-rich.

The hook now returns the full React Query result object instead of a custom return structure, providing access to additional query state flags and functions like isLoading, isFetching, refetch, etc.

apps/mail/components/mail/reply-composer.tsx (4)

6-8: Clean import updates for new architecture.

The imports have been properly updated to support the migration from server actions to tRPC and React Query.


24-31: Correctly migrated hook usage patterns.

The component now properly uses the updated hook patterns:

  • Destructuring refetch from useThread instead of mutate
  • Using the full data response pattern with data: aliases from useEmailAliases
  • Setting up tRPC and the email sending mutation correctly

138-157: Email sending mutation properly configured.

The email sending functionality has been successfully migrated to use the tRPC mutation while maintaining all the necessary parameters and headers for proper email threading.


161-161: Thread refetching correctly updated.

The code now uses await refetch() instead of the previous SWR mutate() call, properly aligning with the React Query paradigm.

apps/mail/lib/notes-manager.ts (7)

1-15: Well-structured Note interface definition.

The Note interface is now explicitly defined locally with appropriate types, including allowing isPinned to be null. This provides clear type definitions for the module.


17-17: Improved export style.

Changed from exporting an interface to a directly implemented object, which is cleaner and more straightforward.


38-40: Consistent parameter defaults.

Default values for color and isPinned are now consistently formatted.


71-71: Type-safe parameter using Partial and Omit.

The update data parameter is now properly typed using Partial<Omit<Note, ...>>, ensuring better type safety when updating note properties.


116-116: Extended isPinned type to include null.

Updated the type definition to allow null values for isPinned in the reorderNotes function, ensuring compatibility with the Note interface.


127-135: Improved SQL query readability.

The SQL query construction has been refactored for better readability with proper indentation and structure.


145-168: Well-structured transaction logic.

The transaction logic in reorderNotes has been refactored for better readability and maintainability:

  • Clear separation of transaction steps
  • Proper error handling with informative messages
  • Conditional property updating for isPinned
apps/mail/components/mail/mail.tsx (5)

226-226: Updated hook usage for React Query compatibility

The code has been modified to destructure isLoading and isFetching from the hook result instead of the previous isLoading and isValidating. This change aligns with the transition from SWR to React Query (TanStack Query) as mentioned in the PR objectives.


280-281: URL path updated for mailto protocol handler

The mailto protocol handler path has been changed from NextJS-specific route to a more generic API endpoint (/api/mailto-handler). This aligns with the PR's goal of decoupling the backend from NextJS-specific features, potentially allowing the backend to be separated from the frontend in the future.


363-363: Improved UI layout for categories

The category selection has been moved to its own line with a margin-top, which improves UI spacing and makes the interface more readable.


369-370: Loading indicator now uses isFetching instead of isValidating

The loading indicator now checks isFetching instead of isValidating to determine when to show the loading animation. This is consistent with the React Query (TanStack Query) API naming conventions.


596-597: CSS order updates in category icons

The conditional class application for category icons has been reworked for consistency across all categories. This improves maintainability by following a consistent pattern.

Also applies to: 609-610, 624-625, 637-638

apps/mail/components/mail/note-panel.tsx (8)

71-77: Updated imports for tRPC and React Query integration

The component now correctly imports tRPC and React Query hooks to align with the new data fetching architecture. The type import for Note has also been updated to reference the appropriate location.


246-250: Updated hook usage pattern for thread notes

The useThreadNotes hook usage has been refactored to properly destructure notes from data and use refetch for invalidating the query cache, which follows React Query patterns. This is more explicit than the previous approach and aligns with the overall architectural changes.


264-268: Implemented tRPC mutations for note operations

The component now uses tRPC mutations via React Query's useMutation hook for all note operations (create, update, delete, reorder). This provides better type safety and aligns with the new backend architecture.


305-312: Improved note creation state management

The note creation flow has been improved to properly manage loading state before and after the operation. The state is set to loading before the operation starts and cleared after refetching data, which provides a better user experience by preventing multiple submissions.


342-348: Updated note update mutation with named parameters

The note update mutation now uses a more robust named parameter object pattern instead of positional parameters. This improves code readability and maintainability while reducing the risk of parameter order mistakes.


376-377: Consistent parameter pattern for delete operation

The delete note operation now uses a named parameter object pattern consistent with other mutations. This uniformity improves maintainability and reduces cognitive load when working with different mutations.


394-407: Improved pin toggle implementation

The toggle pin operation has been updated to use named parameters and now returns the refetch promise, allowing for proper sequence control. The toast message is also dynamically updated based on the current pin state.


454-464: Consistent note reordering with proper refetching

Both pinned and unpinned note reordering operations now use a consistent parameter pattern and properly await the refetch operation. This ensures the UI stays in sync with the backend data.

Also applies to: 475-485

apps/mail/app/api/mailto-handler.ts (5)

1-3: Updated imports for Hono framework integration

The imports have been updated to use Hono framework types and tRPC, aligning with the new backend architecture that decouples from NextJS-specific features.


82-84: Updated function signature for Hono context

The createDraftFromMailto function signature has been updated to accept a Hono context, enabling seamless integration with the new backend architecture.


92-103: Improved HTML content generation from plain text

The email body formatter has been enhanced to properly convert plain text to HTML with appropriate paragraph and line break handling. It now:

  1. Normalizes line breaks to \n
  2. Splits text into paragraphs
  3. Replaces line breaks with
    tags
  4. Replaces multiple spaces with   to preserve whitespace
  5. Wraps paragraphs in

    tags

This provides a much better rendering of plain text emails in HTML editors.


118-119: Migrated to tRPC for draft creation

Draft creation now uses the tRPC caller with the Hono context, providing better type safety and aligning with the new backend architecture.


133-155: Updated handler function for Hono integration

The handler function has been completely refactored to:

  1. Accept a Hono context
  2. Check session from context variables
  3. Access URL parameters using Hono's API
  4. Use Hono's redirect method for navigation

This maintains the same functionality while aligning with the new backend architecture.

apps/mail/trpc/routes/ai/email.ts (6)

1-5: Appropriate imports for AI email generation

The file correctly imports the necessary functions and types, including the AI generation functions from the lib, tRPC procedures, JSON content type from Novel, and zod for input validation.


6-21: Well-structured tRPC procedure with input validation

The generateEmailBody procedure is properly defined with comprehensive input validation using zod. It accepts all necessary parameters like prompt, current content, subject, recipients, and conversation ID, which provides good flexibility for different use cases.


22-60: Robust error handling and logging

The email body generation process includes thorough error handling:

  1. Checks for null/undefined responses
  2. Provides fallback error messages
  3. Includes comprehensive logging for debugging
  4. Handles missing body field cases

This robust approach will help with troubleshooting and ensure users receive appropriate feedback.


68-77: Comprehensive error capturing

The try-catch block properly catches any unhandled exceptions during body generation and returns a user-friendly error message. The error is also logged for debugging purposes.


79-100: Streamlined subject generation

The subject generation procedure is well implemented with:

  1. Input validation using zod
  2. Empty body check to prevent unnecessary API calls
  3. Proper error handling
  4. Appropriate logging

This provides an efficient way to generate subjects from email bodies.


102-116: Effective JSON content creation helper

The createJsonContentFromBody helper function efficiently converts plain text into a structured JSON format compatible with rich text editors. It includes a fallback message if the input is empty, ensuring the UI always has something to display.

apps/mail/providers/query-provider.tsx (6)

1-10: Well-structured imports and initial client configuration.

The imports are properly organized and the makeQueryClient function correctly sets up global error handling with toast notifications, while respecting the noGlobalError flag. The query configuration also includes proper performance settings like disabling refetchOnWindowFocus and query retries by default, which aligns with the PR objectives of improving the architecture.


11-30: Query client customization improves user-specific caching.

Including the userId in the query key hash function is an excellent approach to ensure per-user cache isolation. This addresses one of the issues mentioned in the PR objectives about improper cache keying when handling multiple connections.


32-41: Good singleton pattern implementation for client-side performance.

The singleton pattern for browser query client ensures that only one instance is maintained on the client side, which is memory-efficient, while creating new instances per request on the server side to prevent cross-request data leakage.


48-48: Type-safe TRPC context creation.

The TRPC context is correctly created with type safety using the AppRouter type, ensuring that all TRPC queries and mutations will have proper type checking.


50-59: TRPC client configuration includes appropriate links and transformers.

The TRPC client is configured with logging (always enabled) and HTTP batch link with superjson for data transformation. This setup optimizes network requests through batching and ensures proper serialization/deserialization of complex data types.


61-72: QueryProvider properly composes React Query and TRPC providers.

The QueryProvider component correctly obtains the current user session to create a user-scoped query client, and then wraps children with both QueryClientProvider and TRPCProvider. This composition ensures that all data-fetching throughout the application benefits from both libraries' capabilities.

apps/mail/hooks/use-threads.ts (5)

1-5: Updated imports align with the new architecture.

The imports have been updated to use React Query and TRPC hooks, replacing the previous SWR-based approach. This change is consistent with the architectural shift described in the PR objectives.


11-32: Infinite query implementation with appropriate configuration.

The implementation of useInfiniteQuery with TRPC correctly sets up cursor-based pagination. The query configuration includes:

  • Setting initial cursor
  • Proper extraction of the next page token
  • A reasonable stale time (60 seconds)
  • Enabling refetchOnMount

This configuration balances data freshness with performance.


47-51: Clear end-of-data detection logic.

The isReachingEnd calculation correctly handles both empty results and missing next page token, ensuring proper pagination behavior in the UI.


52-55: Load more function with fetching guard.

The loadMore function correctly prevents concurrent fetching operations by checking the loading and fetching states before triggering the next page fetch.


60-79: Single thread query implementation with appropriate caching.

The useThread implementation properly:

  • Uses the TRPC query hook
  • Conditionally enables the query based on ID and session availability
  • Sets a long stale time (12 hours) which is appropriate for thread data that changes infrequently

The return of the full query object gives consumers access to loading states, error information, and refetch capabilities.

apps/mail/components/mail/mail-list.tsx (10)

15-16: Updated imports for component modularity.

The imports have been adjusted to use the more specific named imports, which improves code maintainability and bundle size through better tree-shaking.


20-30: Improved hook import organization.

The additional import for useThreadLabels from the labels hook and the import for useQueryClient from React Query show proper organization of dependencies according to functionality.


151-152: Updated hook usage with destructuring pattern.

The useThreads hook usage has been updated to destructure only the refetch function from the first element of the returned tuple, which aligns with the new hook implementation.


157-165: Optimized thread prefetching implementation.

The prefetching logic has been improved to use React Query's prefetchQuery with TRPC. This approach is more efficient and leverages the React Query cache more effectively than the previous implementation.


516-517: Updated callback to match new refetch pattern.

The refreshCallback has been updated to use refetch() instead of the previous pattern, which aligns with the React Query paradigm.


564-565: Updated destructuring pattern for useThreads hook.

The destructuring of the useThreads hook return value has been updated to match the new implementation, correctly extracting the query object with its properties and the additional returned values.


604-605: Updated dependency array in event listener effect.

The dependency array for the refresh event listener effect has been updated to include only the refetch function, ensuring the event listener is properly updated when the refetch function changes.


634-637: Improved scroll handling with query state checks.

The scroll handler now correctly checks for loading, fetching, and hasNextPage states before triggering the loadMore function, which prevents unnecessary API calls and provides a better user experience.


749-749: Updated pagination condition with hasNextPage.

The condition for displaying the "Load More" button has been updated to use the hasNextPage property from React Query, which provides a more accurate representation of whether more data is available.


773-773: Comprehensive loading state handling.

The loading indicator now correctly accounts for both initial loading and background fetching states, showing the spinner in either case to provide appropriate feedback to the user.

apps/mail/hooks/use-labels.ts (3)

1-2: Updated imports for the new data fetching approach.

The imports have been updated to use React Query and TRPC hooks instead of the previous approach, which is consistent with the architectural changes in the PR.


5-13: Simplified useLabels hook with appropriate caching.

The useLabels hook has been refactored to use TRPC with React Query, setting a one-hour stale time which is reasonable for label data that doesn't change frequently. The hook now returns the full query result object, giving consumers access to loading states, error information, and refetch capabilities.


15-17: Updated useThreadLabels to work with the new hook return type.

The useThreadLabels function has been adjusted to properly destructure the data from the new useLabels hook return type, maintaining backward compatibility for consumers.

apps/mail/lib/notes-utils.ts (7)

1-1: LGTM: Import path updated for the Note type.

The import statement has been updated to use a relative path, which is appropriate since the Note type is defined in a neighboring file.


5-55: Code formatting improvements applied.

The NOTE_COLORS constant has been reformatted to use consistent single quotes and better whitespace organization, which improves readability while maintaining the same functionality.


58-66: Consistent string formatting applied.

The record definitions for note color translation keys now consistently use single quotes, which aligns with the project's coding style.


73-74: Arrow function parameter parentheses added consistently.

The callback functions in find() methods now consistently use parentheses around parameters, which improves code style consistency.

Also applies to: 78-79


90-97: Improved spacing for better readability.

Added appropriate line breaks to improve code readability in the formatting functions, which makes the code easier to scan and understand.

Also applies to: 116-123


134-139: Consistent arrow function formatting in sortNotes.

The arrow functions and spacing in the sortNotes function have been standardized, improving code readability.


166-190: Improved formatting of updateNotesWithNewOrders function.

The parameter format and internal function structure have been improved for better readability while maintaining the same functionality.

apps/mail/components/mail/thread-display.tsx (7)

33-34: Added tRPC and TanStack Query imports.

These new imports support the shift from server actions to tRPC and TanStack Query for data fetching and mutations, aligning with the PR's stated objective.


148-149: Updated hook usage to align with new data fetching pattern.

The hooks now use refetch from React Query instead of mutate from SWR, properly implementing the migration from SWR to React Query.

Also applies to: 155-155


161-162: Implemented tRPC mutation for markAsRead.

Correctly sets up the tRPC mutation hook for marking emails as read, replacing the previous server action approach.


214-230: LGTM: Successful migration to tRPC mutation.

The effect that marks emails as read has been successfully migrated to use the tRPC mutation with proper error handling and cache invalidation.


276-290: Simplified star toggle implementation.

The handleToggleStar function has been cleaned up and reformatted while maintaining the same functionality, correctly using the new data refetching pattern.


401-407: Improved className formatting for better readability.

The Star component's conditional class application has been reformatted for better readability while maintaining the same styling logic.


412-414: Improved ternary expression formatting.

The conditional expression for the tooltip content has been reformatted to use a more readable multi-line approach.

@BlankParticle BlankParticle force-pushed the feat/tanstack-query-trpc branch 2 times, most recently from 7a88a10 to d015b70 Compare April 28, 2025 07:04
@BlankParticle BlankParticle marked this pull request as ready for review April 28, 2025 14:52
@BlankParticle BlankParticle force-pushed the feat/tanstack-query-trpc branch from d015b70 to 4132f03 Compare April 29, 2025 13:53
@BlankParticle BlankParticle marked this pull request as draft April 29, 2025 18:38
@BlankParticle BlankParticle force-pushed the feat/tanstack-query-trpc branch 2 times, most recently from b9857a6 to 9cdf57f Compare May 3, 2025 09:47
@BlankParticle BlankParticle marked this pull request as ready for review May 3, 2025 09:48
@BlankParticle BlankParticle force-pushed the feat/tanstack-query-trpc branch from 9cdf57f to 5082eda Compare May 3, 2025 13:02
@MrgSub MrgSub merged commit f7c0c01 into Mail-0:staging May 3, 2025
1 of 2 checks passed
@BlankParticle BlankParticle deleted the feat/tanstack-query-trpc branch May 4, 2025 04:47
@coderabbitai coderabbitai bot mentioned this pull request May 7, 2025
8 tasks
@coderabbitai coderabbitai bot mentioned this pull request Jun 3, 2025
34 tasks
This was referenced Jun 27, 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.

2 participants