Skip to content

Comments

Staging#1040

Merged
MrgSub merged 37 commits intomainfrom
staging
May 22, 2025
Merged

Staging#1040
MrgSub merged 37 commits intomainfrom
staging

Conversation

@MrgSub
Copy link
Collaborator

@MrgSub MrgSub commented May 22, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a CLI tool ("Nizzy") for environment setup, syncing variables and types, and reinstalling node modules.
    • Added a fully featured, accessible tree view component for nested folders and files.
    • Enhanced pricing options with annual billing, a free trial, and an Enterprise plan.
    • Added mailbox and thread search tools, and integrated web search via Perplexity AI.
  • Improvements

    • Streamlined environment setup and syncing across documentation and scripts.
    • Refined sidebar navigation with recursive folder rendering and improved label/folder management.
    • Updated UI elements for better accessibility, styling, and usability, including input focus, button styles, and switch components.
    • Improved AI chat analytics and tool integration.
    • Enhanced error handling and draft-saving logic in email composition.
    • Updated prompts for more accurate AI responses and tool usage.
  • Bug Fixes

    • Improved folder validation and error handling in mail navigation.
    • Fixed state management issues during draft saving and email sending.
  • Chores

    • Refactored and removed legacy scripts, dependencies, and unused code.
    • Updated configuration files for environment bindings and scripts.
  • Documentation

    • Simplified and unified setup instructions in README and contributor guides.

@cloudflare-workers-and-pages
Copy link

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
zero-staging 76ae73d May 22 2025, 06:41 AM

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 22, 2025

Walkthrough

This update introduces a new CLI tool ("Nizzy") for streamlined environment setup and synchronization, refactors environment management scripts, and removes manual environment variable copying. It modularizes pricing card features, adds an enterprise tier, overhauls label/folder sidebar rendering, and introduces vector-based mailbox tools and web search. Numerous UI and styling adjustments are included.

Changes

File(s) / Group Change Summary
.env.example Removed four environment variable placeholders: GROQ_API_KEY, DEFAULT_BRAIN_LIMIT, BRAIN_URL, and GOOGLE_GENERATIVE_AI_API_KEY.
.github/CONTRIBUTING.md, README.md Updated environment setup instructions: replaced manual .env copying and Cloudflare setup with pnpm nizzy env and pnpm nizzy sync commands; removed manual variable listing.
package.json, apps/mail/package.json, apps/server/package.json Replaced postinstall scripts with new nizzy CLI, removed cf-install, added @radix-ui/react-switch and tsx dependencies.
turbo.json Removed "postinstall" task configuration.
apps/mail/app/(full-width)/pricing.tsx, apps/mail/components/pricing/pricing-card.tsx, apps/mail/components/pricing/comparision.tsx Refactored and modularized pricing cards, added Enterprise tier, unified feature rendering, updated button texts and layout.
apps/mail/app/(routes)/mail/[folder]/page.tsx Replaced immediate folder validation with async TRPC query; added label hierarchy validation and loading/error states.
apps/mail/app/globals.css, apps/mail/components/create/editor.tsx, apps/mail/components/create/email-composer.tsx, apps/mail/components/mail/mail-list.tsx Replaced .hide-scrollbar class with .no-scrollbar for scrollbar hiding; updated relevant component usage.
apps/mail/components/context/label-sidebar-context.tsx Added optional hide prop to context menu component to conditionally disable menu rendering.
apps/mail/components/create/ai-chat.tsx Updated chat UI styling, placeholder text, and button colors; expanded status check for shimmer effect.
apps/mail/components/create/create-email.tsx Removed console.log calls around event tracking; no logic changes.
apps/mail/components/create/email-composer.tsx Added isSavingDraft state to separate draft-saving from general loading; updated guards and error handling.
apps/mail/components/home/HomeContent.tsx Added session-aware sign-in button logic; removed "Contact Us" button; updated navigation and prompt text.
apps/mail/components/home/footer.tsx Removed useInView hook; reordered classNames; removed empty className.
apps/mail/components/home/speed.tsx Deleted unused Speed component.
apps/mail/components/icons/icons.tsx Added FolderOpen and Bookmark icons; refactored and reformatted existing icons.
apps/mail/components/magicui/file-tree.tsx, apps/mail/components/ui/recursive-folder.tsx Added reusable recursive tree and folder components for label/folder sidebar rendering.
apps/mail/components/mail/mail.tsx, apps/mail/components/mail/thread-display.tsx Updated autolabeling toggle to a switch; adjusted UI and button rendering logic.
apps/mail/components/ui/ai-sidebar.tsx Added PostHog analytics tracking for AI chat; updated upgrade button and sidebar panel sizing.
apps/mail/components/ui/app-sidebar.tsx Reformatted sidebar component JSX for readability; no logic changes.
apps/mail/components/ui/gauge.tsx Added horizontal padding to gauge container.
apps/mail/components/ui/input.tsx, apps/mail/components/ui/switch.tsx Updated input and switch focus/active styles; switched to @radix-ui/react-switch.
apps/mail/components/ui/nav-main.tsx Refactored sidebar: replaced dropdown/context menus with recursive folder tree; provider-aware label/folder rendering.
apps/mail/components/ui/navigation-menu.tsx Added conditional fill color classes to icons for theme support.
apps/mail/components/ui/pricing-dialog.tsx Added monthly/annual billing toggle, controlled open state, dynamic price display, and improved feature list styling.
apps/mail/instrument.ts, apps/mail/worker.ts Added Sentry proxying via /monitoring route; Sentry only initializes outside dev; added tunnel config.
apps/mail/lib/auth-proxy.ts Changed session error handling to log and return null instead of throwing.
apps/mail/lib/constants.tsx, apps/server/src/lib/auth-providers.ts Comment formatting improvements for provider config blocks.
apps/server/src/lib/driver/google.ts, apps/server/src/lib/driver/utils.ts Added getSimpleLoginSender utility; updated sender extraction in Google driver.
apps/server/src/lib/driver/microsoft.ts Refactored label fetching: now recursively traverses mail folder hierarchy; improved error handling and label fetching logic.
apps/server/src/lib/prompts.ts Added new guideline for plural search; updated AI chat prompt, added new tools (AskZeroMailbox, WebSearch, AskZeroThread).
apps/server/src/main.ts Commented out /public-chat POST route.
apps/server/src/routes/agent/tools.ts Refactored all mail tools to accept driver; added vector-based mailbox/thread tools and web search tool; modularized tool creation.
apps/server/src/routes/chat.ts Updated to use dynamic tools; added publicChatHandler for public chat endpoint.
apps/server/src/trpc/routes/brain.ts Changed summary generation to use vector store and AI model for short summaries only.
apps/server/src/types.ts Extended Label type to support nested labels; added new tool enums.
apps/server/wrangler.jsonc Added AI and vectorize bindings; updated staging app URL.
packages/cli/package.json, packages/cli/tsconfig.json Added new CLI package manifest and TypeScript config.
packages/cli/src/cli.ts, packages/cli/src/commands/*, packages/cli/src/utils.ts Introduced new CLI tool with commands for environment setup (env), syncing (sync), and reinstalling node modules (reinstall).

Sequence Diagram(s)

Environment Setup Flow with Nizzy CLI

sequenceDiagram
    participant User
    participant CLI (Nizzy)
    participant Project Files

    User->>CLI (Nizzy): Run `pnpm nizzy env`
    CLI (Nizzy)->>Project Files: Read .env.example and .env
    CLI (Nizzy)->>User: Prompt for missing/required env variables
    User-->>CLI (Nizzy): Provide input or cancel
    CLI (Nizzy)->>Project Files: Write/Update .env
    CLI (Nizzy)->>User: Confirm setup complete

    User->>CLI (Nizzy): Run `pnpm nizzy sync`
    CLI (Nizzy)->>Project Files: Copy .env to targets (.dev.vars, etc.)
    CLI (Nizzy)->>CLI (Nizzy): Run type generation scripts
    CLI (Nizzy)->>User: Confirm sync complete
Loading

Label/Folder Sidebar Rendering (Provider-Aware)

sequenceDiagram
    participant Sidebar
    participant useSession
    participant useConnections
    participant RecursiveFolder
    participant Tree

    Sidebar->>useSession: Get session
    Sidebar->>useConnections: Get connections
    Sidebar->>Sidebar: Determine active account/provider

    alt Google provider
        Sidebar->>Tree: Render labels/folders as nested tree
        Tree->>RecursiveFolder: Recursively render label/folder nodes
    else Microsoft provider
        Sidebar->>Tree: Render folders flatly
        Tree->>RecursiveFolder: Render each folder as flat node
    end
Loading

AI Chat Tool Invocation

sequenceDiagram
    participant User
    participant UI (Chat)
    participant PostHog
    participant Server (AI)
    participant Tools

    User->>UI (Chat): Send message
    UI (Chat)->>PostHog: Track event (AI Chat Response/Tool Call/Error)
    UI (Chat)->>Server (AI): Send message, context, and tools
    Server (AI)->>Tools: Call appropriate tool (e.g., AskZeroMailbox, WebSearch)
    Tools-->>Server (AI): Return tool result
    Server (AI)-->>UI (Chat): Stream AI response
    UI (Chat)-->>User: Display AI reply
Loading

Possibly related PRs

  • Mail-0/Zero#1030: Both PRs refactor and expand the pricing card component, modularizing features and improving layout, making their changes closely related.
  • Mail-0/Zero#941: Both PRs modify the DEFAULT_BRAIN_LIMIT environment variable in .env.example, but in opposite directions (removal vs addition).
  • Mail-0/Zero#813: Both PRs modify environment variable placeholders in .env.example, with this PR removing and the other adding placeholders, indicating a direct connection.

Suggested reviewers

  • ahmetskilinc
  • needleXO
  • nizzyabi

Poem

🐇
Nizzy hops in with a CLI so bright,
Syncing envs and types, making setup light.
Pricing cards multiply, features now neat,
Sidebars grow trees, labels nested deep.
AI tools expand, web search in hand—
Zero’s new burrow, the best in the land!
🌱✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

@MrgSub MrgSub merged commit a863cfd into main May 22, 2025
3 of 4 checks passed
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: 12

🔭 Outside diff range comments (2)
apps/mail/components/ui/nav-main.tsx (1)

80-82: ⚠️ Potential issue

Fix missing query-string parsing – searchParams is always empty

new URLSearchParams() starts with an empty object, so every call to searchParams.get(...) returns null.
That silently breaks:

  • Back-navigation via the from parameter.
  • Active-link detection in isUrlActive.
  • Category preservation for the Inbox link.
-const searchParams = new URLSearchParams();
+// Use the real query-string so we don’t lose `from`, `category`, etc.
+const searchParams = new URLSearchParams(location.search);
apps/server/src/routes/agent/tools.ts (1)

217-248: 🛠️ Refactor suggestion

Incomplete / dead tools inflate surface area

createEmail, _listEmails, writeEmail, and sendDraft are defined but either

  • lack an execute implementation, or
  • are never exported via tools / publicTools.

Dead code increases cognitive load and may become stale or incorrect. Please either remove these helpers or finish & wire them up.

Example minimal fix for createEmail (if you intend to keep it):

 const createEmail = tool({
   description: 'Create and send a new email',
   parameters: z.object({ /* … */ }),
-});
+execute: async (data) => {
+  const mailManager = await getMailManager();
+  return mailManager.create({ ...data, attachments: data.attachments ?? [], headers: data.headers ?? {} });
+},
+});

If they’re not needed, deleting them is preferable.

Also applies to: 265-284, 303-346

🧹 Nitpick comments (32)
apps/mail/worker.ts (2)

22-22: Consider extracting Sentry URL to an environment variable

The Sentry URL contains sensitive information (DSN key) hardcoded in the source. Consider moving this to an environment variable or configuration file for better security and maintainability.


22-34: Consider DRY approach for Sentry configuration

The Sentry DSN information appears to be duplicated between this file and instrument.ts. Consider extracting this common configuration to a shared module or environment variables to maintain a single source of truth.

- const sentryUrl = `https://o4509328786915328.ingest.us.sentry.io/api/4509328795303936/envelope/?sentry_version=7&sentry_key=03f6397c0eb458bf1e37c4776a31797c&sentry_client=sentry.javascript.react%2F9.19.0`;
+ import { SENTRY_DSN, SENTRY_ORG_ID, SENTRY_PROJECT_ID } from '../config/sentry';
+ const sentryUrl = `https://${SENTRY_ORG_ID}.ingest.us.sentry.io/api/${SENTRY_PROJECT_ID}/envelope/?sentry_version=7&sentry_key=${SENTRY_DSN.split('@')[0].split('//')[1]}&sentry_client=sentry.javascript.react%2F9.19.0`;
apps/server/src/main.ts (1)

31-31: Optional: Clean up or document the commented-out route
The .post('/public-chat', publicChatHandler) line is commented out with an informal “ssshhhh” note. Consider removing it entirely or replacing the comment with a clear // TODO: … explaining why the route is disabled.

.github/CONTRIBUTING.md (1)

35-35: Fix grammatical error: "setup" vs "set up"

"Setup" is a noun, while "set up" is the verb form. Since you're describing an action, you should use the verb form.

- Run `pnpm nizzy env` to setup your environment variables
+ Run `pnpm nizzy env` to set up your environment variables
🧰 Tools
🪛 LanguageTool

[grammar] ~35-~35: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ker:db:up - Runpnpm nizzy envto setup your environment variables - Runpn...

(NOUN_VERB_CONFUSION)

README.md (3)

78-79: Grammar correction needed in environment setup instructions.

The word "setup" is a noun, but it's being used as a verb here. When used as a verb, it should be written as two words: "set up".

-   - Run `pnpm nizzy env` to setup your environment variables
+   - Run `pnpm nizzy env` to set up your environment variables
-   - Run `pnpm nizzy sync` to sync your environment variables and types
+   - Run `pnpm nizzy sync` to sync your environment variables and types
🧰 Tools
🪛 LanguageTool

[grammar] ~78-~78: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ronment** - Run pnpm nizzy env to setup your environment variables - Run `pn...

(NOUN_VERB_CONFUSION)


149-149: Fix grammar in environment variables section.

Similar to the previous comment, "setup" is used as a verb here, but should be written as "set up".

-Run `pnpm nizzy env` to setup your environment variables. It will copy the `.env.example` file to `.env` and fill in the variables for you.
+Run `pnpm nizzy env` to set up your environment variables. It will copy the `.env.example` file to `.env` and fill in the variables for you.
🧰 Tools
🪛 LanguageTool

[grammar] ~149-~149: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ment Variables Run pnpm nizzy env to setup your environment variables. It will cop...

(NOUN_VERB_CONFUSION)


173-173: Fix grammar and add missing article.

There are two issues in this sentence: a missing article "the" before ".env" and incorrect past participle form "ran" instead of "run".

-Make sure your database connection string is in `.env` file. And you have ran `pnpm nizzy sync` to sync the latest env.
+Make sure your database connection string is in the `.env` file. And you have run `pnpm nizzy sync` to sync the latest env.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~173-~173: You might be missing the article “the” here.
Context: ...e your database connection string is in .env file. And you have ran `pnpm nizzy...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~173-~173: It appears that the past participle should be used here.
Context: ... string is in .env file. And you have ran pnpm nizzy sync to sync the latest en...

(HAVE_PART_AGREEMENT)

packages/cli/src/commands/reinstall-node-modules.ts (2)

15-19: Consider adding error handling for the removal process.

The command currently doesn't handle potential errors during the node_modules removal process. If any removal fails, the Promise.all might reject and stop the entire process without cleaning up properly.

- await Promise.all(
-   nodeModuleFolders.map((folder) => rm(join(root, folder), { recursive: true, force: true })),
- );
+ try {
+   await Promise.all(
+     nodeModuleFolders.map(async (folder) => {
+       try {
+         await rm(join(root, folder), { recursive: true, force: true });
+       } catch (error) {
+         log.warn(`Failed to remove ${folder}: ${error.message}`);
+       }
+     }),
+   );
+ } catch (error) {
+   removePackagesSpinner.stop('Encountered some issues removing node_modules');
+   log.error(`Error during node_modules removal: ${error.message}`);
+ }

20-22: Add error handling for the install process.

The command should handle potential errors during the reinstallation process to provide better feedback to the user and avoid leaving the system in an inconsistent state.

- log.step('Reinstalling node_modules');
- await runCommand('pnpm', ['install']);
- log.step('Reinstalled node_modules');
+ log.step('Reinstalling node_modules');
+ try {
+   await runCommand('pnpm', ['install']);
+   log.step('Reinstalled node_modules');
+ } catch (error) {
+   log.error(`Failed to reinstall node_modules: ${error.message}`);
+   log.info('You may need to run "pnpm install" manually to complete the setup.');
+ }
apps/mail/app/(routes)/mail/[folder]/page.tsx (3)

30-33: Prefer useQuery’s built-in typing instead of falling back to any[].
userLabels is typed as unknown (or any) because trpc.labels.list.queryOptions() returns an un-annotated generic.
Two consequences:

  1. checkLabelExists() later receives any[], forfeiting compile-time safety.
  2. You lose IntelliSense on deeply-nested label.labels.

Consider giving the query a proper output type:

-const { data: userLabels, isLoading: isLoadingLabels } = useQuery(
-  trpc.labels.list.queryOptions(void 0),
-);
+type LabelTree = { id: string; labels?: LabelTree[] };
+const { data: userLabels, isLoading: isLoadingLabels } = useQuery<LabelTree[]>(
+  trpc.labels.list.queryOptions(void 0),
+);

This keeps the recursion strongly typed and prevents accidental property typos.


43-52: Move the recursive search out of the render cycle.
checkLabelExists is recreated on every render, which is unnecessary and can hinder performance on very large label trees.

-  useEffect(() => {
-
-    if (userLabels) {
-      const checkLabelExists = (labels: any[]): boolean => {
-
-      };
-      const labelExists = checkLabelExists(userLabels);
+  const checkLabelExists = useCallback((labels: LabelTree[]): boolean => {
+    for (const label of labels) {
+      if (label.id === folder) return true;
+      if (label.labels?.length && checkLabelExists(label.labels)) return true;
+    }
+    return false;
+  }, [folder]);
+
+  useEffect(() => {
+
+    if (userLabels) {
+      const labelExists = checkLabelExists(userLabels);

Even if the current tree is small, this keeps the effect focused and avoids re-allocating the function.


58-63: Ensure the timeout is always cleared to avoid memory leaks.
The cleanup function is only returned when the label does not exist. If the folder later changes before the 2-second timeout fires, the previous timer will persist.

-      if (!labelExists) {
-        const timer = setTimeout(() => {
-          navigate('/mail/inbox');
-        }, 2000);
-        return () => clearTimeout(timer);
-      }
+      const timer =
+        !labelExists &&
+        setTimeout(() => {
+          navigate('/mail/inbox');
+        }, 2000);
+      return () => {
+        if (timer) clearTimeout(timer as number);
+      };

This guarantees we always provide a cleanup function and never leave stray timeouts.

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

4-6: process.env.USER may be undefined on some shells/OSes.
Relying on it can lead to the question “Hey undefined, what do you want to do?” on Windows or CI runners.

-  message: `Hey ${process.env.USER}, what do you want to do?`,
+  message: `Hey ${process.env.USER ?? 'friend'}, what do you want to do?`,

A small DX polish that avoids awkward greetings.

apps/server/src/routes/chat.ts (1)

108-111: toolResults.map((r) => r.result) can crash when no tools executed.
toolResults is undefined when the model never called a tool. Guard against it:

-return c.json({ response: result.text, toolResults: result.toolResults.map((r) => r.result) });
+return c.json({
+  response: result.text,
+  toolResults: result.toolResults?.map((r) => r.result) ?? [],
+});

Prevents “Cannot read properties of undefined (reading ‘map’)”.

packages/cli/src/utils.ts (3)

6-16: Improve project root detection

The function relies solely on the package name being "zero" for root detection, which is fragile. Consider adding additional checks for more robust validation.

 export const getProjectRoot = async () => {
   const cwd = process.cwd();
   const packageJson = await readFile(join(cwd, 'package.json'), 'utf8').catch(() => '{}');
   const packageJsonObject = JSON.parse(packageJson);
   const rootName = packageJsonObject.name;
-  if (!rootName || rootName !== 'zero') {
+  // Check multiple indicators of project root
+  const isProjectRoot = 
+    rootName === 'zero' && 
+    (await readFile(join(cwd, 'pnpm-workspace.yaml'), 'utf8').catch(() => null) !== null);
+  
+  if (!isProjectRoot) {
     log.error(`Please run this command from the root of the project.`);
     process.exit(0);
   }
   return cwd;
 };

18-24: Enhance runCommand with input validation

The runCommand function should validate inputs and include better error handling.

 export const runCommand = async (command: string, args: string[], options: SpawnOptions = {}) => {
+  if (!command) {
+    throw new Error('Command is required');
+  }
+  
   const child = spawn(command, args, { stdio: 'inherit', ...options });
-  await new Promise((resolve, reject) => {
+  return new Promise((resolve, reject) => {
     child.once('close', resolve);
     child.once('error', reject);
   });
 };

33-43: Improve error handling in parseEnv function

The parseEnv function should handle additional edge cases for more robust .env parsing.

       const equalIndex = line.indexOf('=');
-      if (equalIndex === -1) return null;
+      if (equalIndex === -1) {
+        // Log a warning for malformed lines
+        console.warn(`Skipping malformed line in .env file: ${line}`);
+        return null;
+      }

       const key = line.slice(0, equalIndex).trim();
+      if (!key) {
+        console.warn(`Skipping line with empty key in .env file: ${line}`);
+        return null;
+      }
+      
       let value = line.slice(equalIndex + 1).trim();

       if (
         (value.startsWith('"') && value.endsWith('"')) ||
         (value.startsWith("'") && value.endsWith("'"))
       ) {
         value = value.slice(1, -1);
       }
apps/mail/components/ui/switch.tsx (2)

14-14: Use CSS variables for colors instead of hardcoded values

The switch component uses a hardcoded color value #0B84FE for the checked state instead of using a CSS variable from your theme, which could lead to inconsistency.

-      'focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-[#0B84FE] data-[state=unchecked]:bg-input peer inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
+      'focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input peer inline-flex h-4 w-7 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',

22-22: Improve class organization with better formatting

The thumb element's className is a long string that mixes positioning, styling, and state classes in a hard-to-read order.

-        'bg-background dark:bg-white pointer-events-none block h-3 w-3 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 data-[state=checked]:right-1 relative',
+        'pointer-events-none relative block h-3 w-3 rounded-full bg-background shadow-lg ring-0 transition-transform dark:bg-white data-[state=checked]:right-1 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
apps/mail/app/(full-width)/pricing.tsx (2)

139-140: Use theme variables for button styling instead of hardcoded colors

The button style uses hardcoded colors which may not adapt well to theme changes.

-              className="h-8 text-black hover:bg-white hover:text-black bg-white"
+              className="h-8 bg-white text-black hover:bg-white/90 hover:text-black"

208-209: Remove unnecessary whitespace

There's an empty whitespace line that should be removed.

                      alt={resource.platform}
                      width={20}
                      height={20}
-                      
packages/cli/src/commands/fix-env.ts (1)

1-94: Well-structured environment variable management command.

This new CLI command provides a streamlined approach to setting up and maintaining environment variables with several key benefits:

  • Automatically migrates legacy environment variables from NEXT_PUBLIC_ to VITE_PUBLIC_ prefix
  • Interactively prompts for required variables like Google OAuth credentials
  • Identifies and prompts for missing variables from .env.example
  • Properly formats and writes variables back to the .env file

The implementation follows good practices with proper error handling, user prompts with validation, and clean file I/O operations.

One potential improvement could be adding a --non-interactive flag for CI environments:

+  id: 'env',
+  description: 'Setup/Fix your environment variables',
+  options: [
+    {
+      name: '--non-interactive',
+      description: 'Run in non-interactive mode, using default values',
+      type: 'boolean',
+      default: false,
+    },
+  ],
-  run: async () => {
+  run: async ({ options }) => {
+    const nonInteractive = options?.['non-interactive'] || false;
apps/mail/components/ui/recursive-folder.tsx (1)

11-11: Consider using more specific types instead of 'any'

The component uses any type for both label and activeAccount props. This could lead to type safety issues and makes the component harder to maintain.

-export const RecursiveFolder = ({ label, activeAccount }: { label: any; activeAccount?: any }) => {
+export const RecursiveFolder = ({ label, activeAccount }: { label: LabelType; activeAccount?: Account }) => {

Note: You would need to import the Account type from your types file. This will provide better type safety and IDE support.

apps/server/src/lib/driver/microsoft.ts (1)

756-809: Consider limiting maximum recursion depth for edge cases

The processMailFoldersHierarchy method has a default maxDepth of 99, which might be excessive. For extremely nested folder structures, this could potentially lead to performance issues or stack overflow.

-  private async processMailFoldersHierarchy(
-    folders: MailFolder[],
-    systemFolderNames: string[],
-    depth: number = 0,
-    maxDepth: number = 99,
-  ): Promise<Label[]> {
+  private async processMailFoldersHierarchy(
+    folders: MailFolder[],
+    systemFolderNames: string[],
+    depth: number = 0,
+    maxDepth: number = 15, // More reasonable maximum depth
+  ): Promise<Label[]> {
apps/mail/components/icons/icons.tsx (1)

380-397: Well-structured new Bookmark icon component

The new Bookmark icon follows the same pattern as existing icons and is well-implemented. There's a minor issue though - the SVG attributes should use camelCase for React.

-      fill-rule="evenodd"
-      clip-rule="evenodd"
+      fillRule="evenodd"
+      clipRule="evenodd"
apps/mail/components/ui/nav-main.tsx (1)

447-452: Improve groups typing for clarity & safety

Using typeof data makes every bucket potentially undefined, while the logic assumes arrays.
Explicitly typing as LabelType[] (or Array<LabelType>) removes the need for casts and prevents
accidental runtime undefined checks.

-const groups = {
-  brackets: [] as typeof data,
-  other: [] as typeof data,
-  folders: {} as Record<string, typeof data>,
-};
+const groups: {
+  brackets: LabelType[];
+  other:    LabelType[];
+  folders: Record<string, LabelType[]>;
+} = { brackets: [], other: [], folders: {} };
apps/mail/components/magicui/file-tree.tsx (1)

379-383: Remove stray console.log used for debugging

Leftover logging adds noise and can leak internal state in production builds.

-  useEffect(() => {
-    console.log(expandAll);
+  useEffect(() => {
apps/mail/components/pricing/pricing-card.tsx (1)

82-86: Consider rounding the computed annual price for display

annualPrice could yield a floating-point like 8.333333.
If pricing ever changes, format to two decimals to avoid UI artefacts.

-const annualPrice = monthlyPrice * PRICING_CONSTANTS.ANNUAL_DISCOUNT;
+const annualPrice = +(monthlyPrice * PRICING_CONSTANTS.ANNUAL_DISCOUNT).toFixed(2);
apps/server/src/routes/agent/tools.ts (4)

529-559: Improve webSearch error handling & redaction

  1. fetch errors when response.ok is false (HTTP 4xx/5xx) are currently parsed as JSON and may throw.
  2. Returned payload includes the full Perplexity response, which might expose billing metadata or internal traces to the caller.
  3. The Bearer token is concatenated directly; logging elsewhere could leak it.
-    const options = {
+    const options: RequestInit = {
       method: 'POST',
       headers: {
         Authorization: `Bearer ${env.PERPLEXITY_API_KEY}`,
         'Content-Type': 'application/json',
       },
@@
-      const response = await fetch('https://api.perplexity.ai/chat/completions', options);
-      const data = (await response.json()) as any;
-      return { result: data };
+      const response = await fetch('https://api.perplexity.ai/chat/completions', options);
+
+      if (!response.ok) {
+        const body = await response.text();
+        console.error('WebSearch failed:', response.status, body);
+        throw new Error(`Perplexity returned ${response.status}`);
+      }
+
+      const { choices } = (await response.json()) as { choices: Array<{ message: { content: string } }> };
+      return { answer: choices?.[0]?.message?.content ?? 'No answer returned' };
     } catch (error) {

This guards against non-200 responses and only returns the useful answer text.
[security]


84-104: getEmbeddingVector – propagate errors or cache vectors

Right now the helper swallows any exception and returns null, leaving the caller with an ambiguous “Failed to get embedding” message.
Consider:

  • Exposing the original error (wrapped) so callers can surface actionable diagnostics.
  • Caching vectors when gatewayId === 'vectorize-save' to avoid repeated model calls for identical input.
-  } catch (error) {
-    console.log('[getEmbeddingVector] failed', error);
-    return null;
+  } catch (error) {
+    console.error('[getEmbeddingVector] failed:', error);
+    throw error instanceof Error ? error : new Error(String(error));
   }

Returning null masks infrastructure outages and makes debugging difficult.


471-486: Minor typos & colour-set drift in createLabel

  • Description contains “does nto exist”.
  • Validation message says “one of the predefined colors” but the actual list lives in a different module—any divergence will break requests.

Nit-level patch:

-    description: 'Create a new label with custom colors, if it does nto exist already',
+    description: 'Create a new label with custom colours if it does not exist already',

You may also want to expose colors via a shared constants module to guarantee synchronisation.


250-263: listEmails violates YAGNI – folded into _listEmails?

You now maintain two almost-identical search helpers (listEmails and _listEmails). The latter enriches results with full thread data and seems more generally useful. Consider collapsing both into one function with a includeThreads flag to reduce duplication.

This will also remove one item from the public API surface.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 802c705 and 76ae73d.

⛔ Files ignored due to path filters (2)
  • apps/mail/public/mail-pixel.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (57)
  • .env.example (0 hunks)
  • .github/CONTRIBUTING.md (1 hunks)
  • README.md (4 hunks)
  • apps/mail/app/(full-width)/pricing.tsx (6 hunks)
  • apps/mail/app/(routes)/mail/[folder]/page.tsx (2 hunks)
  • apps/mail/app/globals.css (1 hunks)
  • apps/mail/components/context/label-sidebar-context.tsx (2 hunks)
  • apps/mail/components/create/ai-chat.tsx (7 hunks)
  • apps/mail/components/create/create-email.tsx (1 hunks)
  • apps/mail/components/create/editor.tsx (1 hunks)
  • apps/mail/components/create/email-composer.tsx (7 hunks)
  • apps/mail/components/home/HomeContent.tsx (9 hunks)
  • apps/mail/components/home/footer.tsx (1 hunks)
  • apps/mail/components/home/speed.tsx (0 hunks)
  • apps/mail/components/icons/icons.tsx (5 hunks)
  • apps/mail/components/magicui/file-tree.tsx (1 hunks)
  • apps/mail/components/mail/mail-list.tsx (2 hunks)
  • apps/mail/components/mail/mail.tsx (5 hunks)
  • apps/mail/components/mail/thread-display.tsx (1 hunks)
  • apps/mail/components/pricing/comparision.tsx (2 hunks)
  • apps/mail/components/pricing/pricing-card.tsx (7 hunks)
  • apps/mail/components/ui/ai-sidebar.tsx (7 hunks)
  • apps/mail/components/ui/app-sidebar.tsx (3 hunks)
  • apps/mail/components/ui/gauge.tsx (1 hunks)
  • apps/mail/components/ui/input.tsx (1 hunks)
  • apps/mail/components/ui/nav-main.tsx (6 hunks)
  • apps/mail/components/ui/navigation-menu.tsx (1 hunks)
  • apps/mail/components/ui/pricing-dialog.tsx (5 hunks)
  • apps/mail/components/ui/recursive-folder.tsx (1 hunks)
  • apps/mail/components/ui/switch.tsx (2 hunks)
  • apps/mail/instrument.ts (1 hunks)
  • apps/mail/lib/auth-proxy.ts (1 hunks)
  • apps/mail/lib/constants.tsx (1 hunks)
  • apps/mail/package.json (1 hunks)
  • apps/mail/worker.ts (1 hunks)
  • apps/server/package.json (1 hunks)
  • apps/server/src/lib/auth-providers.ts (1 hunks)
  • apps/server/src/lib/driver/google.ts (2 hunks)
  • apps/server/src/lib/driver/microsoft.ts (1 hunks)
  • apps/server/src/lib/driver/utils.ts (2 hunks)
  • apps/server/src/lib/prompts.ts (3 hunks)
  • apps/server/src/main.ts (1 hunks)
  • apps/server/src/routes/agent/tools.ts (4 hunks)
  • apps/server/src/routes/chat.ts (4 hunks)
  • apps/server/src/trpc/routes/brain.ts (1 hunks)
  • apps/server/src/types.ts (2 hunks)
  • apps/server/wrangler.jsonc (4 hunks)
  • package.json (2 hunks)
  • packages/cli/package.json (1 hunks)
  • packages/cli/src/cli.ts (1 hunks)
  • packages/cli/src/commands/fix-env.ts (1 hunks)
  • packages/cli/src/commands/index.ts (1 hunks)
  • packages/cli/src/commands/reinstall-node-modules.ts (1 hunks)
  • packages/cli/src/commands/sync.ts (1 hunks)
  • packages/cli/src/utils.ts (1 hunks)
  • packages/cli/tsconfig.json (1 hunks)
  • turbo.json (0 hunks)
💤 Files with no reviewable changes (3)
  • turbo.json
  • apps/mail/components/home/speed.tsx
  • .env.example
🧰 Additional context used
🧬 Code Graph Analysis (10)
apps/mail/lib/auth-proxy.ts (1)
packages/db/src/schema.ts (1)
  • session (27-38)
apps/server/src/lib/driver/google.ts (1)
apps/server/src/lib/driver/utils.ts (1)
  • getSimpleLoginSender (104-106)
apps/mail/components/mail/mail-list.tsx (2)
apps/mail/lib/utils.ts (1)
  • cn (53-53)
apps/mail/components/ui/scroll-area.tsx (1)
  • ScrollArea (50-50)
apps/mail/app/(full-width)/pricing.tsx (2)
apps/mail/components/pricing/pricing-card.tsx (1)
  • PricingCard (82-286)
apps/mail/components/pricing/comparision.tsx (1)
  • Comparision (7-388)
apps/server/src/routes/chat.ts (5)
apps/server/src/lib/server-utils.ts (2)
  • getActiveConnection (7-35)
  • connectionToDriver (37-50)
apps/server/src/routes/agent/tools.ts (2)
  • tools (562-578)
  • publicTools (580-596)
apps/server/src/ctx.ts (1)
  • HonoContext (13-13)
packages/db/src/schema.ts (1)
  • connection (85-104)
apps/server/src/lib/prompts.ts (1)
  • AiChatPrompt (288-607)
apps/server/src/types.ts (1)
apps/mail/types/index.ts (1)
  • Label (1-9)
apps/mail/components/ui/switch.tsx (1)
apps/mail/lib/utils.ts (1)
  • cn (53-53)
apps/mail/components/magicui/file-tree.tsx (3)
apps/mail/lib/utils.ts (1)
  • cn (53-53)
apps/mail/components/ui/scroll-area.tsx (1)
  • ScrollArea (50-50)
apps/mail/components/icons/icons.tsx (2)
  • Folder (336-356)
  • Bookmark (380-397)
apps/server/src/lib/driver/microsoft.ts (1)
apps/server/src/types.ts (1)
  • Label (3-12)
apps/mail/components/pricing/pricing-card.tsx (3)
apps/mail/components/icons/icons.tsx (2)
  • PurpleThickCheck (1726-1737)
  • ThickCheck (1681-1692)
apps/mail/components/ui/pricing-switch.tsx (1)
  • PricingSwitch (27-27)
apps/mail/lib/utils.ts (1)
  • cn (53-53)
🪛 LanguageTool
.github/CONTRIBUTING.md

[grammar] ~35-~35: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ker:db:up - Runpnpm nizzy envto setup your environment variables - Runpn...

(NOUN_VERB_CONFUSION)

README.md

[grammar] ~78-~78: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ronment** - Run pnpm nizzy env to setup your environment variables - Run `pn...

(NOUN_VERB_CONFUSION)


[grammar] ~149-~149: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ment Variables Run pnpm nizzy env to setup your environment variables. It will cop...

(NOUN_VERB_CONFUSION)


[uncategorized] ~173-~173: You might be missing the article “the” here.
Context: ...e your database connection string is in .env file. And you have ran `pnpm nizzy...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~173-~173: It appears that the past participle should be used here.
Context: ... string is in .env file. And you have ran pnpm nizzy sync to sync the latest en...

(HAVE_PART_AGREEMENT)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Workers Builds: zero-server-staging
  • GitHub Check: Workers Builds: zero-staging
🔇 Additional comments (104)
apps/mail/instrument.ts (1)

3-13: Good addition of environment check for Sentry initialization

The conditional initialization of Sentry only in non-development environments is a good practice. This prevents development data from being sent to Sentry and avoids unnecessary traffic during local development.

The addition of the tunnel: '/monitoring' parameter works well with the changes in worker.ts to route monitoring data through a proxy path.

apps/mail/worker.ts (1)

26-34: Good implementation of Sentry monitoring proxy

The implementation correctly intercepts requests to the /monitoring path and forwards them to the Sentry ingestion endpoint, preserving the original request properties. This works well with the tunnel configuration in the Sentry initialization.

apps/mail/components/ui/gauge.tsx (1)

39-39: Responsive padding adjustment looks good

Adding horizontal padding on mobile (px-2) and removing it on larger screens (md:px-0) improves the component's responsive behavior across different viewport sizes.

apps/mail/lib/constants.tsx (1)

21-25: Improved comment formatting

The indentation adjustment for the commented-out Outlook provider improves code readability by aligning it with the rest of the array elements.

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

19-22: Enhanced error handling for authentication flows

The improved error handling for session errors is a good practice. By logging errors to the console and returning null instead of letting exceptions propagate, the application can gracefully handle authentication issues.

This change will help maintain a smoother user experience when session-related problems occur, preventing potential crashes and allowing the UI to display appropriate feedback to users.

apps/server/src/lib/driver/utils.ts (2)

3-3: Type import for gmail_v1

Good addition of the typed import to properly type the payload parameter in the new utility function.


100-106: Well-implemented SimpleLogin support

The new getSimpleLoginSender utility function is a good addition that improves email handling for users of SimpleLogin forwarding addresses. The function is well-documented and correctly extracts the original sender from the SimpleLogin header.

This enhancement will maintain the connection between forwarded emails and their original senders, providing a better user experience for SimpleLogin users.

apps/server/src/lib/auth-providers.ts (1)

53-79: Stylistic indentation adjustment for commented Microsoft provider
The commented Microsoft provider block has been unindented to align with surrounding entries. No functional or logical changes were introduced.

apps/mail/app/globals.css (1)

336-346: Approve new .no-scrollbar utility
The .no-scrollbar class provides the intended cross-browser scrollbar-hiding styles and replaces the removed .hide-scrollbar. Implementation looks correct.

apps/mail/components/pricing/comparision.tsx (2)

41-41: Added top margin to comparison section
The mt-20 utility increases spacing above the component for better visual separation. Looks good.


283-283: Updated button label to “Start free trial”
The call-to-action aligns with updated pricing terminology. No issues found.

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

481-481:

Details

✅ Verification successful

Renamed scrollbar utility class
Changed from "hide-scrollbar" to "no-scrollbar" to match the global CSS updates. Please verify there are no remaining references to hide-scrollbar elsewhere.

Run this to confirm no leftover usages:


🏁 Script executed:

#!/bin/bash
rg "hide-scrollbar" -n

Length of output: 22


Scrollbar utility class rename confirmed
No remaining usages of hide-scrollbar were found in the codebase.

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

389-391: Improved accessibility of AI chat functionality.

The "Chat with Zero AI" button is now always visible in the empty thread view, regardless of whether the sidebar is open or closed. This makes the AI feature more discoverable and accessible.

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

122-129: Removed unnecessary console.logs from PostHog event tracking.

Cleaning up the console logging for analytics events improves code cleanliness and reduces browser console clutter.

apps/mail/components/ui/navigation-menu.tsx (1)

138-138: Enhanced icon theme compatibility.

Added conditional fill colors for icons to ensure proper visibility in both light and dark themes.

apps/server/package.json (3)

6-6: Enabled experimental vector binding in development environment.

Added the --experimental-vectorize-bind-to-prod flag to support vector-based features during local development. This aligns with the vector-based mailbox tools mentioned in the PR summary.


8-8: Improved type generation consistency.

Updated the environment for type generation from staging to local, which provides better consistency between development and type checking environments.


1-63:

Details

✅ Verification successful

Verify if postinstall script removal affects environment setup.

According to the PR summary, manual environment variable copying has been replaced with a new CLI tool. Please ensure that new contributors are provided clear instructions for environment setup now that the postinstall script has been removed.


🏁 Script executed:

#!/bin/bash
# Check if there are updated environment setup instructions

# Look for updated setup instructions in README or CONTRIBUTING docs
grep -i -e "environment" -e "env" -e "nizzy" README.md .github/CONTRIBUTING.md

# Check for the new CLI tool mentioned in the PR summary
find . -name "nizzy*" -type f

Length of output: 2577


Environment Setup Instructions Verified

The README.md and CONTRIBUTING.md have been updated with clear steps using the new CLI:

  • README.md:
    • “Run pnpm nizzy env to setup your environment variables”
    • “Run pnpm nizzy sync to sync your environment variables and types”
  • .github/CONTRIBUTING.md:
    • “Run pnpm nizzy env to setup your environment variables”
    • “Run pnpm nizzy sync to sync your environment variables and types”

No further action required.

packages/cli/package.json (1)

1-11: LGTM: Package configuration for the new CLI tool

The package configuration looks good with appropriate type definition and dependencies for a CLI tool. Using @clack/prompts is a good choice for interactive CLI interfaces, and tiny-glob will be useful for file operations.

.github/CONTRIBUTING.md (1)

35-36: Great simplification of the environment setup process

The new CLI-based approach streamlines the contributor onboarding process by replacing manual environment configuration with automated commands. This aligns well with modern developer experience practices.

🧰 Tools
🪛 LanguageTool

[grammar] ~35-~35: The word “setup” is a noun. The verb is spelled with a white space.
Context: ...ker:db:up - Runpnpm nizzy envto setup your environment variables - Runpn...

(NOUN_VERB_CONFUSION)

apps/mail/package.json (1)

23-23: LGTM: Adding Radix UI Switch component

The addition of @radix-ui/react-switch is appropriate for enhancing UI toggle components. Radix UI provides accessible and customizable components that follow WAI-ARIA design patterns.

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

450-451: Improved hover styling target

Removing the hover classes from the outer container is a good change that prepares for the new hover behavior.


457-458: Better hover UX with properly scoped styling

Moving the hover styles to the inner content container provides a more precise and visually appealing hover effect that focuses on the actual thread content rather than the entire row.


963-963: Consistent scrollbar styling with updated class name

Changing from hide-scrollbar to no-scrollbar ensures consistency with the global CSS changes throughout the application.

apps/server/src/types.ts (2)

11-11: Good addition for nested label structures

Adding the recursive labels property to the Label type enables hierarchical organization of labels, which is a useful feature for more complex mail organization systems.


154-156: Well-structured enum additions for new AI tools

The new Tools enum members appropriately represent the new AI-powered functionality. These additions follow the existing naming pattern and enable type-safe references to these tools throughout the codebase.

apps/mail/components/home/footer.tsx (1)

29-30: Styling adjustments on the outer and nested <div> elements
The className on the outer <div> has been reordered (from centering-first to margin-first) and the empty className attribute on the nested <div> was removed. These are purely cosmetic; ensure they align with your project’s Tailwind class ordering conventions.

apps/mail/components/ui/app-sidebar.tsx (3)

86-88: Reformatted SidebarHeader className for readability
The reordering and line-breaks improve clarity, and the conditional padding (px-2 vs md:px-4) is applied correctly.


103-105: SidebarContent enhanced with overflow-x-hidden
Adding overflow-x-hidden prevents horizontal scroll and aligns with the updated scrollbar styling. Layout and formatting changes are consistent.


119-123: Refactored SidebarFooter padding classes
Reordering px-0 pb-0 and the conditional MD padding for collapsed/expanded state is correct and aligns with other sidebar sections.

apps/mail/components/ui/input.tsx (1)

18-18: Ensure keyboard focus indicator remains accessible
The removal of all focus-visible ring classes in favor of focus:ring-0 ring-0 disables the visible focus outline for keyboard users. Verify that an alternative focus style is applied globally (or via a wrapper) to maintain WCAG-compliant keyboard navigation.

apps/server/src/lib/driver/google.ts (1)

7-7: Imported getSimpleLoginSender utility
This import supports the new logic to prioritize the SimpleLogin original sender.

packages/cli/src/commands/index.ts (2)

1-5: Define a standardized Command interface
The new Command type correctly captures the essential CLI command shape. Consider whether you’ll need to pass arguments or a context object to run in the future, but this baseline is solid.


7-9: Centralized command exports
Re-exporting fixEnv, reinstallNodeModules, and sync under a common Command interface cleans up imports elsewhere and keeps the CLI entrypoint modular.

apps/mail/components/context/label-sidebar-context.tsx (3)

37-37: Interface change looks good

Added optional hide prop to the LabelSidebarContextMenuProps interface, enabling conditional rendering of the context menu.


40-40: Function signature update is correct

Updated component function signature to include the new hide prop, maintaining backward compatibility.


58-59: Good implementation of conditional rendering

This early return pattern is clean and efficiently handles the case when the context menu should be hidden while still rendering children.

packages/cli/tsconfig.json (1)

1-12: TypeScript configuration looks appropriate for a CLI tool

The configuration uses modern settings that are well-suited for a CLI package:

  • Module system set to ESNext with bundler resolution
  • Strict type checking enabled
  • Modern JavaScript target (ESNext)
  • Appropriate compilation settings with noEmit and skipLibCheck

This aligns well with the introduction of the new "Nizzy" CLI tool mentioned in the PR summary.

apps/server/wrangler.jsonc (4)

9-21: AI and vectorization bindings added for local environment

The addition of AI and vectorization bindings is consistent with the PR's introduction of vector-based mailbox tools and AI features. The configuration correctly specifies the binding names and vector indexes.


79-91: AI and vectorization bindings added for staging environment

The same AI and vectorization bindings are appropriately added to the staging environment, maintaining consistency across environments.


130-130: Updated staging URL to be more descriptive

Changing from "app.0.email" to "staging.0.email" makes it clearer that this is a staging environment.


152-164: AI and vectorization bindings added for production environment

The same AI and vectorization bindings are consistently applied to the production environment, ensuring feature parity across all environments.

package.json (3)

8-9: Good addition of the nizzy CLI tool script!

This provides a clean way to run the new CLI tool using tsx for executing TypeScript directly.


9-9: Nice upgrade to the postinstall flow!

Replacing the previous Turbo-based command with the new nizzy sync command aligns with the PR's objectives of streamlining environment setup.


39-39: Good addition of tsx dependency.

Adding the tsx package as a dev dependency with a specific version is appropriate since it's now required by the nizzy script.

README.md (1)

58-58: Simplified heading improves readability.

Removing the "Option 1:" prefix from the heading makes the documentation cleaner since there doesn't appear to be an "Option 2" anymore.

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

211-226: Improved toggle UI with Switch component.

Replacing the previous indicator div with a proper Switch component improves accessibility and user experience. The implementation correctly disables the switch during state transitions and maintains the checked state based on the brain state.


231-231: Better dialog spacing.

Adding margin bottom to the dialog description improves the visual spacing and makes the content more readable.


262-262: Updated Trash icon with explicit sizing and fill.

The icon now has explicit sizing and fill color, which ensures consistent rendering across different environments.


290-290: Updated Plus icon with explicit sizing and fill.

Similar to the Trash icon, the Plus icon now has explicit sizing and fill color for consistent rendering.


450-450: Added vertical divider for better UI separation.

The addition of a vertical divider before the refresh button creates a visual separation between the autolabeling settings and the refresh functionality, improving the UI layout.

packages/cli/src/commands/reinstall-node-modules.ts (1)

8-24: Good implementation of the reinstall command.

The command properly detects the project root, finds all node_modules directories, removes them concurrently, and reinstalls dependencies. The spinner and logging provide clear feedback to the user.

apps/server/src/routes/chat.ts (1)

58-64: throw c.json() returns a Response, not an Error.
throw c.json(...) short-circuits the normal error chain and treats the Response as an exception, but many runtimes (including Hono) only forward Error sub-classes through error middleware. Prefer returning the response instead:

-const _conn = await getActiveConnection().catch((err) => {
-  console.error('Error in getActiveConnection:', err);
-  throw c.json({ error: 'Failed to get active connection' }, 500);
-});
+const _conn = await getActiveConnection().catch((err) => {
+  console.error('Error in getActiveConnection:', err);
+  return c.json({ error: 'Failed to get active connection' }, 500);
+});
+if (!_conn) return; // response already sent

This avoids unexpected “Response is not Error” stack traces.

apps/mail/components/ui/switch.tsx (1)

1-3: LGTM on client directive and import changes

The addition of the 'use client' directive and the import change to use the scoped package correctly marks the component as client-side and follows best practices.

apps/mail/app/(full-width)/pricing.tsx (2)

228-230: LGTM on pricing card refactoring

The simplification to use a single PricingCard component instead of inline pricing cards improves maintainability and follows the component-based architecture pattern.


235-237: Using flexbox for footer positioning

Using mt-auto for the footer is a good practice for ensuring it stays at the bottom of the page, even with variable content height.

apps/mail/components/ui/ai-sidebar.tsx (8)

49-49: Good addition of PostHog analytics tracking.

Adding PostHog analytics will help track user interactions with the AI chat functionality and provide valuable insights for improving the user experience.


75-75: Nice state management for the pricing dialog.

Creating a dedicated state variable for controlling the pricing dialog visibility follows best practices for React component state management.


128-132: Good responsive design adjustment.

Hiding the toggle view mode button on smaller screens using the hidden md:flex classes improves the mobile UI by removing less critical controls when space is limited.


147-175: Well-implemented upgrade flow for non-pro users.

The updated UI for non-pro users now includes:

  1. A gauge showing message usage
  2. A clear upgrade path with a dedicated button
  3. Integration with the PricingDialog component
  4. Proper event propagation stopping

This provides a more seamless upgrade experience while clearly communicating usage limits.


418-426: Comprehensive error tracking with PostHog.

The error tracking implementation captures detailed context about the error, including thread ID, current folder, filter, and messages. This will be valuable for diagnosing issues in production.


429-436: Good response analytics tracking.

Tracking AI chat responses with relevant context allows for measuring the performance and usage patterns of the chat feature, which can help identify improvement opportunities.


442-449: Excellent tool call tracking.

The tool call tracking provides visibility into how users are leveraging AI automation capabilities, which will help prioritize future tool development based on actual usage.


509-512: Panel sizing adjustment improves UI consistency.

Setting fixed values for defaultSize, minSize, and maxSize (all 24) ensures the sidebar has consistent dimensions, preventing unexpected resizing behavior that could disrupt the user experience.

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

186-187: CSS class update for scrollbar styling consistency.

Changed from hide-scrollbar to no-scrollbar to maintain consistency with the global CSS class renaming in the app's stylesheet.


189-196: Improved pricing dialog presentation.

The updated pricing dialog now has better alignment, improved text formatting with TextShimmer for emphasis, and a more prominent upgrade button.


236-239: Enhanced chat bubble styling.

The updated styling for chat bubbles improves text wrapping with overflow-wrap-anywhere and ensures better spacing and contrast between user and assistant messages.


254-254: Better AI thinking status indicator.

The "thinking" state now appears during both the "submitted" and "streaming" statuses, providing users with immediate feedback that their request is being processed.


271-271: Improved input container styling.

The background color adjustment for the input container enhances visual contrast and aligns with the app's overall design system.


283-284: Updated placeholder text and input styling.

Changing from "Ask AI to do anything..." to "Ask Zero to do anything..." creates brand consistency. The refined input styling improves visual hierarchy and accessibility.


293-295: Refined send button styling.

The updated styling for the send button creates better visual balance and improves the clickable area while maintaining a clean interface.


303-305: Improved stop button styling.

The simplified styling for the stop button removes unnecessary background elements while maintaining clear visibility of the stop action.

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

96-96: Good addition of separate draft saving state.

Adding a dedicated isSavingDraft state variable to track draft saving separately from the general loading state improves state management and allows for more precise control of UI feedback.


292-294: Improved email sending guard condition.

The updated condition prevents sending an email if either isLoading or isSavingDraft is true, which prevents potential race conditions or duplicate sends during draft saving operations.


370-370: Enhanced draft saving state management.

The saveDraft function now properly manages the isSavingDraft state:

  1. Sets it to true at the beginning of the operation
  2. Explicitly resets it in both error and finally blocks

This ensures the state is always properly reset, even if an error occurs during saving.

Also applies to: 389-394


455-459: CSS class update for scrollbar styling consistency.

Changed from hide-scrollbar to no-scrollbar to align with the global CSS class renaming, ensuring consistent scrollbar hiding behavior across the application.


943-943: Cleaner button JSX by removing empty className attributes.

Removing unnecessary empty className attributes and simplifying the span element improves code readability and reduces DOM complexity.

Also applies to: 980-981

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

47-49: Updated imports for authentication and routing.

The changes correctly import the necessary hooks for authentication (useSession) and navigation (useNavigate), which are needed for the enhanced sign-in button functionality.


136-137: Properly initialized hooks for session management and navigation.

The hooks are correctly declared and will enable conditional navigation based on authentication state.


233-249: Enhanced sign-in button with session-aware navigation.

This is a good UX improvement that conditionally handles navigation based on authentication state:

  • For logged-in users: Direct navigation to inbox
  • For guests: Social sign-in flow with error handling

The button styling has also been updated to a white background with black text for better visibility.


277-279: Improved client-side routing in mobile navigation.

Replaced anchor tag with React Router's Link component for the pricing page, ensuring consistent client-side navigation behavior.


1513-1513: Updated placeholder text for brand consistency.

Changed from "Ask AI to do anything..." to "Ask Zero to do anything..." which aligns with the product branding.

apps/mail/components/ui/pricing-dialog.tsx (6)

12-16: Implemented billing period toggle with controlled/uncontrolled dialog state.

The implementation adds support for both monthly and annual pricing options with proper state management:

  • Added annual/monthly toggle state with 50% discount for annual billing
  • Implemented URL query parameter sync for dialog state
  • Properly handles both controlled and uncontrolled component patterns

This is a well-structured enhancement that improves pricing flexibility.

Also applies to: 24-33


34-45: Added robust dialog state synchronization.

The implementation properly synchronizes the dialog's open state with the URL query parameter when in uncontrolled mode, ensuring consistent UI state across page navigations.


52-53: Dynamic product selection based on billing period.

The upgrade handler now selects the appropriate product ID based on the billing period, supporting the new annual billing option.


97-107: Added billing period toggle with visual feedback.

The UI now includes a toggle for switching between billing periods with clear labeling, a "Save 50%" badge, and proper styling.


112-116: Dynamic price display with comparative pricing.

When annual billing is selected, the UI helpfully displays both the discounted price and the original monthly price (struck through), making the savings clear to users.


199-199: Updated CTA text to emphasize free trial.

Changed button text from "Get Zero Pro" to "Start free trial," which better communicates the value proposition and may improve conversion rates.

apps/server/src/lib/prompts.ts (5)

279-281: Improved search logic for plural terms.

Added new guideline that ensures plural search terms also match singular forms using the OR operator, which will improve search recall and relevance.


292-294: Updated assistant identity and clarified system capabilities.

Changed the assistant's name from "Zero" to "Fred" and added context that clarifies Zero as a tool with complete mailbox history access. This separation between the assistant identity and the tool capabilities creates a clearer mental model for users.


341-361: Added mailbox-wide query capability.

Implemented the AskZeroMailbox tool that enables natural language queries about the entire mailbox with:

  • Clear parameter definitions including optional result limit (topK)
  • Comprehensive usage examples showing successful queries and fallback behavior
  • Appropriate error handling that gracefully transitions to web search when information isn't found

This addition significantly enhances the assistant's ability to answer broad questions about email history and patterns.


363-372: Added web search integration.

The new WebSearch tool enables the assistant to access external information via Perplexity AI when needed, with:

  • Clear description of when to use this tool (for information not in the inbox)
  • Simple parameter structure with string query
  • Diverse usage examples covering different search types

This addition bridges the gap between inbox knowledge and external information, making the assistant more versatile.


374-377: Added thread-specific query capability.

The new AskZeroThread tool complements the mailbox-wide query by enabling focused questions about specific email threads, with parameters for thread ID and question. This provides a more granular level of interaction with specific conversations.

apps/mail/components/ui/recursive-folder.tsx (3)

20-41: Well-implemented filter handler with proper state management

The handleFilterByLabel function is well implemented, correctly handling the toggling of label filters in the search value and properly updating the state with setSearchValue.


43-64: Good handling of different provider behaviors

The handleFolderClick function appropriately handles different behaviors based on the account provider, with specific logic for Microsoft accounts versus others. The mobile sidebar state is also properly managed.


66-88: Well-implemented recursive component structure

The component recursively renders child labels correctly, with proper props passing and context handling. The conditional rendering based on account provider and child presence is appropriate.

apps/server/src/lib/driver/microsoft.ts (3)

717-748: Improved folder hierarchy processing with clear system folder identification

The updated getUserLabels method now correctly fetches only root mail folders and processes their hierarchy. The system folder names list is comprehensive and well-defined.


798-803: Good use of optional chaining for child labels

The code correctly handles the case where a folder might have child labels by using conditional assignment. This prevents unnecessary empty arrays in the label structure.


803-806: Well-implemented error handling for individual folders

The error handling within the folder processing loop is well done, catching and logging errors for individual folders without failing the entire process.

apps/mail/components/icons/icons.tsx (4)

358-378: Well-structured new FolderOpen icon component

The new FolderOpen icon follows the same pattern as existing icons, with proper SVG attributes and path definitions. It will integrate well with the existing icon system.


1550-1556: Check if Paper icon viewBox change affects rendering

The Paper icon's viewBox has been changed from a likely default value to 1 -3 14 28. Verify that this change doesn't negatively impact how the icon renders in the UI.

Are there specific design requirements that necessitated this viewBox change? It's unusual to have negative y-coordinates in a viewBox.


1682-1691: Properly reformatted ThickCheck icon

The ThickCheck icon has been reformatted for better readability and consistency with other icon components.


1727-1737: Properly reformatted PurpleThickCheck icon

The PurpleThickCheck icon has been reformatted for better readability and consistency with other icon components.

apps/server/src/routes/agent/tools.ts (1)

580-596: publicTools currently returns an empty object

All tool definitions are commented out, so publicTools resolves to {}.
If that is intentional, add a comment explaining why. Otherwise uncomment the desired entries or delete the helper to avoid confusion for future maintainers.

Comment on lines +81 to +84
collapsible="icon"
{...props}
className={`bg-lightBackground dark:bg-darkBackground flex h-screen select-none flex-col items-center ${state === 'collapsed' ? '' : ''} pb-2`}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify redundant conditional in Sidebar className
The template literal includes ${state === 'collapsed' ? '' : ''}, which will always be an empty string. Remove this ternary or replace it with the intended collapsed/expanded classes to avoid dead code.

🤖 Prompt for AI Agents
In apps/mail/components/ui/app-sidebar.tsx around lines 81 to 84, the className
string contains a redundant ternary expression `${state === 'collapsed' ? '' :
''}` that always results in an empty string. Remove this ternary entirely or
replace it with the correct class names that reflect the collapsed or expanded
state to eliminate dead code and properly style the sidebar based on its state.

Comment on lines +731 to +737
// If there's a SimpleLogin Header, use it as the sender
const simpleLoginSender = getSimpleLoginSender(payload);

const sender =
payload?.headers?.find((h) => h.name?.toLowerCase() === 'from')?.value || 'Failed';
simpleLoginSender ||
payload?.headers?.find((h) => h.name?.toLowerCase() === 'from')?.value ||
'Failed';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make SimpleLogin header lookup case-insensitive
Relying on a case-sensitive match ('X-SimpleLogin-Original-From') can miss headers with different casing. Update the utility in apps/server/src/lib/driver/utils.ts to normalize header names to lowercase:

-export function getSimpleLoginSender(payload: gmail_v1.Schema$Message['payload']) {
-  return payload?.headers?.find((h) => h.name === 'X-SimpleLogin-Original-From')?.value || null;
-}
+export function getSimpleLoginSender(payload: gmail_v1.Schema$Message['payload']) {
+  return payload?.headers?.find(
+    (h) => h.name?.toLowerCase() === 'x-simplelogin-original-from'
+  )?.value || null;
+}
📝 Committable suggestion

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

Suggested change
// If there's a SimpleLogin Header, use it as the sender
const simpleLoginSender = getSimpleLoginSender(payload);
const sender =
payload?.headers?.find((h) => h.name?.toLowerCase() === 'from')?.value || 'Failed';
simpleLoginSender ||
payload?.headers?.find((h) => h.name?.toLowerCase() === 'from')?.value ||
'Failed';
-export function getSimpleLoginSender(payload: gmail_v1.Schema$Message['payload']) {
- return payload?.headers?.find((h) => h.name === 'X-SimpleLogin-Original-From')?.value || null;
-}
+export function getSimpleLoginSender(payload: gmail_v1.Schema$Message['payload']) {
+ return payload?.headers?.find(
+ (h) => h.name?.toLowerCase() === 'x-simplelogin-original-from'
+ )?.value || null;
+}
🤖 Prompt for AI Agents
In apps/server/src/lib/driver/google.ts around lines 731 to 737, the code uses
getSimpleLoginSender to find the SimpleLogin header but this lookup is
case-sensitive and can miss headers with different casing. To fix this, update
the getSimpleLoginSender function in apps/server/src/lib/driver/utils.ts to
normalize all header names to lowercase before checking for
'x-simplelogin-original-from', ensuring the header lookup is case-insensitive.

Comment on lines +70 to 83
const response = await env.VECTORIZE.getByIds([threadId]);
if (response.length && response?.[0]?.metadata?.['content']) {
const content = response[0].metadata['content'] as string;
const shortResponse = await env.AI.run('@cf/facebook/bart-large-cnn', {
input_text: content,
});
return {
data: {
short: shortResponse.summary,
},
};
};
}
return null;
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactored summary generation to use vector search and AI model

The implementation has been changed to use a two-step process:

  1. Retrieve content metadata using vector search
  2. Generate a summary using an AI model

However, there's no error handling for the AI model call. If env.AI.run fails, the error will propagate up.

Consider adding error handling:

      const { threadId } = input;
      const response = await env.VECTORIZE.getByIds([threadId]);
      if (response.length && response?.[0]?.metadata?.['content']) {
        const content = response[0].metadata['content'] as string;
-        const shortResponse = await env.AI.run('@cf/facebook/bart-large-cnn', {
-          input_text: content,
-        });
-        return {
-          data: {
-            short: shortResponse.summary,
-          },
-        };
+        try {
+          const shortResponse = await env.AI.run('@cf/facebook/bart-large-cnn', {
+            input_text: content,
+          });
+          return {
+            data: {
+              short: shortResponse.summary,
+            },
+          };
+        } catch (error) {
+          console.error(`[GENERATE_SUMMARY] Error generating summary for thread ${threadId}:`, error);
+          return null;
+        }
      }
      return null;
📝 Committable suggestion

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

Suggested change
const response = await env.VECTORIZE.getByIds([threadId]);
if (response.length && response?.[0]?.metadata?.['content']) {
const content = response[0].metadata['content'] as string;
const shortResponse = await env.AI.run('@cf/facebook/bart-large-cnn', {
input_text: content,
});
return {
data: {
short: shortResponse.summary,
},
};
};
}
return null;
}),
const response = await env.VECTORIZE.getByIds([threadId]);
if (response.length && response?.[0]?.metadata?.['content']) {
const content = response[0].metadata['content'] as string;
try {
const shortResponse = await env.AI.run('@cf/facebook/bart-large-cnn', {
input_text: content,
});
return {
data: {
short: shortResponse.summary,
},
};
} catch (error) {
console.error(
`[GENERATE_SUMMARY] Error generating summary for thread ${threadId}:`,
error
);
return null;
}
}
return null;
}),
🤖 Prompt for AI Agents
In apps/server/src/trpc/routes/brain.ts around lines 70 to 83, the call to
env.AI.run lacks error handling, so any failure will propagate and potentially
crash the process. Wrap the env.AI.run call in a try-catch block to catch
errors, log or handle them appropriately, and return a fallback response or null
to prevent unhandled exceptions.

Comment on lines +46 to +48
await command.run();
outro(`Done!`);
process.exit(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Wrap command execution in a try/catch to provide friendly error output and non-zero exit codes.

If a command rejects, the process will crash with an unhandled promise rejection and still exit with code 0. Capture the error, log it, and exit with 1.

-await command.run();
-outro(`Done!`);
-process.exit(0);
+try {
+  await command.run(args.slice(1)); // pass remaining args if relevant
+  outro('Done!');
+  process.exit(0);
+} catch (err) {
+  log.error(
+    typeof err === 'string' ? err : (err as Error).message ?? 'Unknown error',
+  );
+  outro('Command failed ☹️');
+  process.exit(1);
+}

This improves robustness and makes CI pipelines detect failures.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/cli/src/cli.ts around lines 46 to 48, the command execution is not
wrapped in a try/catch block, causing unhandled promise rejections and an exit
code of 0 even on failure. Wrap the await command.run() call in a try block,
catch any errors, log the error message, and call process.exit(1) to indicate
failure. Ensure the outro("Done!") and process.exit(0) calls remain in the try
block to only run on success.

Comment on lines +90 to +111
const c = getContext<HonoContext>();
const { message } = await c.req.json<{ message: string; query: string }>();
const _connection = await c.var.db.query.connection.findFirst({
where: eq(connection.email, 'test@test.com'),
});
if (!_connection) {
return c.json({ error: 'Connection not found' }, 404);
}
const driver = connectionToDriver(_connection);
const result = await generateText({
model: openai('gpt-4o'),
system: AiChatPrompt('', '', ''),
messages: [
{
role: 'user',
content: message,
},
],
tools: { ...publicTools(driver, _connection.id), buildGmailSearchQuery },
});

return c.json({ response: result.text, toolResults: result.toolResults.map((r) => r.result) });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Hard-coded email makes publicChatHandler unsafe and unusable in prod.
Searching the DB with eq(connection.email, 'test@test.com'):

  1. Leaks an internal test address.
  2. Breaks for any real user.
  3. Prevents multi-tenant isolation.

Replace the magic string with a parameter or env variable and add proper validation.

-const _connection = await c.var.db.query.connection.findFirst({
-  where: eq(connection.email, 'test@test.com'),
-});
+const email = c.req.header('x-connection-email') ?? process.env.DEFAULT_PUBLIC_EMAIL;
+if (!email) return c.json({ error: 'No email provided' }, 400);
+
+const _connection = await c.var.db.query.connection.findFirst({
+  where: eq(connection.email, email),
+});

Also consider verifying that the retrieved connection has valid tokens before using it.

📝 Committable suggestion

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

Suggested change
const c = getContext<HonoContext>();
const { message } = await c.req.json<{ message: string; query: string }>();
const _connection = await c.var.db.query.connection.findFirst({
where: eq(connection.email, 'test@test.com'),
});
if (!_connection) {
return c.json({ error: 'Connection not found' }, 404);
}
const driver = connectionToDriver(_connection);
const result = await generateText({
model: openai('gpt-4o'),
system: AiChatPrompt('', '', ''),
messages: [
{
role: 'user',
content: message,
},
],
tools: { ...publicTools(driver, _connection.id), buildGmailSearchQuery },
});
return c.json({ response: result.text, toolResults: result.toolResults.map((r) => r.result) });
const c = getContext<HonoContext>();
const { message } = await c.req.json<{ message: string; query: string }>();
const email = c.req.header('x-connection-email') ?? process.env.DEFAULT_PUBLIC_EMAIL;
if (!email) {
return c.json({ error: 'No email provided' }, 400);
}
const _connection = await c.var.db.query.connection.findFirst({
where: eq(connection.email, email),
});
if (!_connection) {
return c.json({ error: 'Connection not found' }, 404);
}
const driver = connectionToDriver(_connection);
const result = await generateText({
model: openai('gpt-4o'),
system: AiChatPrompt('', '', ''),
messages: [
{
role: 'user',
content: message,
},
],
tools: { ...publicTools(driver, _connection.id), buildGmailSearchQuery },
});
return c.json({ response: result.text, toolResults: result.toolResults.map((r) => r.result) });
🤖 Prompt for AI Agents
In apps/server/src/routes/chat.ts between lines 90 and 111, the code uses a
hard-coded email 'test@test.com' to query the connection, which is unsafe and
not suitable for production. Replace this hard-coded string with a dynamic
parameter, such as extracting the email from the authenticated user's session or
request context, or use an environment variable if appropriate. Additionally,
add validation to ensure the retrieved connection exists and has valid
authentication tokens before proceeding to use it in the driver and text
generation steps.

Comment on lines +10 to +29
run: async () => {
const root = await getProjectRoot();
const envFile = await readFile(join(root, '.env'), 'utf8').catch(() => null);

if (!envFile) {
log.step('No .env file exists, creating one using `pnpm nizzy env`');
process.exit(0);
}

log.step('Syncing environment variables');
cp(join(root, '.env'), join(root, 'apps/mail/.dev.vars'));
cp(join(root, '.env'), join(root, 'apps/mail/.env'));
cp(join(root, '.env'), join(root, 'apps/server/.dev.vars'));

log.step('Syncing frontend types');
await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/mail') });
log.step('Syncing backend types');
await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/server') });
log.success('Synced environment variables and types');
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Insufficient error handling

The code lacks error handling for file copying operations and command execution. If any of these operations fail, the error isn't properly caught or reported to the user.

Consider adding try/catch blocks to handle potential errors:

   run: async () => {
     const root = await getProjectRoot();
     const envFile = await readFile(join(root, '.env'), 'utf8').catch(() => null);

     if (!envFile) {
-      log.step('No .env file exists, creating one using `pnpm nizzy env`');
+      log.step('No .env file exists. Please create one using `pnpm nizzy env` first.');
       process.exit(0);
     }

     log.step('Syncing environment variables');
-    cp(join(root, '.env'), join(root, 'apps/mail/.dev.vars'));
-    cp(join(root, '.env'), join(root, 'apps/mail/.env'));
-    cp(join(root, '.env'), join(root, 'apps/server/.dev.vars'));
+    try {
+      await cp(join(root, '.env'), join(root, 'apps/mail/.dev.vars'));
+      await cp(join(root, '.env'), join(root, 'apps/mail/.env'));
+      await cp(join(root, '.env'), join(root, 'apps/server/.dev.vars'));
+    } catch (error) {
+      log.error(`Failed to copy environment files: ${error.message}`);
+      process.exit(1);
+    }

     log.step('Syncing frontend types');
-    await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/mail') });
-    log.step('Syncing backend types');
-    await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/server') });
-    log.success('Synced environment variables and types');
+    try {
+      await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/mail') });
+      log.step('Syncing backend types');
+      await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/server') });
+      log.success('Synced environment variables and types');
+    } catch (error) {
+      log.error(`Failed to sync types: ${error.message}`);
+      process.exit(1);
+    }
   },
📝 Committable suggestion

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

Suggested change
run: async () => {
const root = await getProjectRoot();
const envFile = await readFile(join(root, '.env'), 'utf8').catch(() => null);
if (!envFile) {
log.step('No .env file exists, creating one using `pnpm nizzy env`');
process.exit(0);
}
log.step('Syncing environment variables');
cp(join(root, '.env'), join(root, 'apps/mail/.dev.vars'));
cp(join(root, '.env'), join(root, 'apps/mail/.env'));
cp(join(root, '.env'), join(root, 'apps/server/.dev.vars'));
log.step('Syncing frontend types');
await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/mail') });
log.step('Syncing backend types');
await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/server') });
log.success('Synced environment variables and types');
},
run: async () => {
const root = await getProjectRoot();
const envFile = await readFile(join(root, '.env'), 'utf8').catch(() => null);
if (!envFile) {
log.step('No .env file exists. Please create one using `pnpm nizzy env` first.');
process.exit(0);
}
log.step('Syncing environment variables');
try {
await cp(join(root, '.env'), join(root, 'apps/mail/.dev.vars'));
await cp(join(root, '.env'), join(root, 'apps/mail/.env'));
await cp(join(root, '.env'), join(root, 'apps/server/.dev.vars'));
} catch (error) {
log.error(`Failed to copy environment files: ${error.message}`);
process.exit(1);
}
log.step('Syncing frontend types');
try {
await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/mail') });
log.step('Syncing backend types');
await runCommand('pnpm', ['run', 'types'], { cwd: join(root, 'apps/server') });
log.success('Synced environment variables and types');
} catch (error) {
log.error(`Failed to sync types: ${error.message}`);
process.exit(1);
}
},
🤖 Prompt for AI Agents
In packages/cli/src/commands/sync.ts around lines 10 to 29, the file copying and
command execution operations lack error handling, so failures are not caught or
reported. Wrap the cp calls and runCommand calls in try/catch blocks to catch
any errors, log meaningful error messages to the user, and handle failures
gracefully without crashing silently.

Comment on lines +13 to +14
const isActive = searchValue.value.includes(`label:${label.name}`);
const isFolderActive = isActive || window.location.pathname.includes(`/mail/label/${label.id}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Be cautious with window.location direct access

Direct usage of window.location.pathname might cause issues with server-side rendering and tests. Consider using the router's location information instead.


🏁 Script executed:

#!/bin/bash
# Check if the codebase uses Next.js or similar frameworks that have SSR
grep -r "getServerSideProps\|getStaticProps\|next" --include="*.tsx" --include="*.ts" . | head -n 10

Length of output: 1245


🏁 Script executed:

#!/bin/bash
# Display the top of the component to check for "use client" directive
sed -n '1,20p' apps/mail/components/ui/recursive-folder.tsx

Length of output: 1109


🏁 Script executed:

#!/bin/bash
# Check for "use client" directive and useLocation import in the component
grep -n -E "^['\"]use client['\"]" apps/mail/components/ui/recursive-folder.tsx || echo "No 'use client' directive found"
grep -n "useLocation" apps/mail/components/ui/recursive-folder.tsx || echo "No useLocation import/usage found"

Length of output: 308


Add “use client” and replace window.location with router location

  • At the top of apps/mail/components/ui/recursive-folder.tsx, add the "use client" directive to opt this file into client‐side rendering.
  • Import the router’s location hook instead of reading window.location directly:
    import { useLocation, useNavigate } from 'react-router';
  • Update the isFolderActive logic to use useLocation():
    - const isFolderActive = isActive || window.location.pathname.includes(`/mail/label/${label.id}`);
    + const { pathname } = useLocation();
    + const isFolderActive = isActive || pathname.includes(`/mail/label/${label.id}`);

This ensures the component runs only on the client and avoids SSR/test failures.

🤖 Prompt for AI Agents
In apps/mail/components/ui/recursive-folder.tsx around lines 13-14, replace the
direct usage of window.location.pathname with the router's location hook to
avoid SSR and testing issues. Add the "use client" directive at the top of the
file to enable client-side rendering, import useLocation from 'react-router',
and update the isFolderActive assignment to use the pathname from useLocation()
instead of window.location.pathname.

Comment on lines +18 to +19
import { FileIcon } from 'lucide-react';
import { Accordion } from 'radix-ui';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect Radix import – will crash at runtime

Accordion is not exported from the "radix-ui" umbrella package.
Import from @radix-ui/react-accordion, otherwise Accordion.Root will be undefined.

-import { Accordion } from 'radix-ui';
+import * as Accordion from '@radix-ui/react-accordion';
📝 Committable suggestion

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

Suggested change
import { FileIcon } from 'lucide-react';
import { Accordion } from 'radix-ui';
import { FileIcon } from 'lucide-react';
import * as Accordion from '@radix-ui/react-accordion';
🤖 Prompt for AI Agents
In apps/mail/components/magicui/file-tree.tsx at lines 18 to 19, the import of
Accordion from "radix-ui" is incorrect and will cause a runtime crash because
Accordion is not exported from that package. Change the import to come from
"@radix-ui/react-accordion" to ensure Accordion.Root and related components are
properly defined and avoid runtime errors.

Comment on lines +150 to +158
<Accordion.Root
{...props}
type="multiple"
defaultValue={expandedItems}
value={expandedItems}
className="flex flex-col gap-1"
onValueChange={(value) => setExpandedItems((prev) => [...(prev ?? []), value[0]])}
dir={dir as Direction}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

onValueChange corrupts accordion state & duplicates IDs

value already contains the full list of expanded items.
Appending value[0] back into state:

  1. Loses collapse information (closed items stay “open” in state).
  2. Introduces duplicates ([...prev, value[0]]).

Just store the array Radix gives you.

-onValueChange={(value) => setExpandedItems((prev) => [...(prev ?? []), value[0]])}
+onValueChange={(value) => setExpandedItems(value)}
📝 Committable suggestion

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

Suggested change
<Accordion.Root
{...props}
type="multiple"
defaultValue={expandedItems}
value={expandedItems}
className="flex flex-col gap-1"
onValueChange={(value) => setExpandedItems((prev) => [...(prev ?? []), value[0]])}
dir={dir as Direction}
>
<Accordion.Root
{...props}
type="multiple"
defaultValue={expandedItems}
value={expandedItems}
className="flex flex-col gap-1"
onValueChange={(value) => setExpandedItems(value)}
dir={dir as Direction}
>
🤖 Prompt for AI Agents
In apps/mail/components/magicui/file-tree.tsx around lines 150 to 158, the
onValueChange handler incorrectly appends value[0] to the previous state,
causing duplicate IDs and losing collapse information. Fix this by directly
setting the expandedItems state to the value array provided by Radix without
appending or merging with previous state.

Comment on lines +441 to +456
execute: async (data) => {
try {
const { draftId, ...mail } = data;

if (draftId) {
await driver.sendDraft(draftId, {
...mail,
attachments: [],
headers: {},
});
} else {
await driver.create({
...mail,
attachments: [],
headers: {},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Attachments & headers are silently dropped in sendEmail

attachments and headers that the caller supplies are overwritten with empty collections before the call to driver.create / driver.sendDraft.
All files will be sent without attachments and without custom headers, which is very likely not what the caller expects.

-        if (draftId) {
-          await driver.sendDraft(draftId, {
-            ...mail,
-            attachments: [],
-            headers: {},
-          });
-        } else {
-          await driver.create({
-            ...mail,
-            attachments: [],
-            headers: {},
-          });
+        const { attachments = [], headers = {}, ...rest } = mail;
+
+        if (draftId) {
+          await driver.sendDraft(draftId, { ...rest, attachments, headers });
+        } else {
+          await driver.create({ ...rest, attachments, headers });
+        }

This keeps the default behaviour (empty arrays / records) while preserving user-supplied data.

📝 Committable suggestion

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

Suggested change
execute: async (data) => {
try {
const { draftId, ...mail } = data;
if (draftId) {
await driver.sendDraft(draftId, {
...mail,
attachments: [],
headers: {},
});
} else {
await driver.create({
...mail,
attachments: [],
headers: {},
});
execute: async (data) => {
try {
const { draftId, ...mail } = data;
const { attachments = [], headers = {}, ...rest } = mail;
if (draftId) {
await driver.sendDraft(draftId, { ...rest, attachments, headers });
} else {
await driver.create({ ...rest, attachments, headers });
}
🤖 Prompt for AI Agents
In apps/server/src/routes/agent/tools.ts around lines 441 to 456, the code
overwrites the attachments and headers properties with empty arrays and objects
before calling driver.create and driver.sendDraft, causing user-supplied
attachments and headers to be dropped. To fix this, modify the code to preserve
any attachments and headers provided in the input data by merging or directly
passing them instead of replacing them with empty collections, while still
defaulting to empty arrays or objects if none are supplied.

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.

5 participants