Skip to content

Toolbar on hover#791

Merged
MrgSub merged 2 commits intostagingfrom
toolbar-on-hover
Apr 29, 2025
Merged

Toolbar on hover#791
MrgSub merged 2 commits intostagingfrom
toolbar-on-hover

Conversation

@nizzyabi
Copy link
Collaborator

@nizzyabi nizzyabi commented Apr 29, 2025

when you hover, toolbar

Summary by CodeRabbit

  • New Features

    • Added Microsoft email provider support, enabling Microsoft account integration and mail functionality.
    • Introduced AI-powered email search and chat assistant for composing, searching, and summarizing emails.
    • Added a modal-based email composer and reply interface with AI assistance, improved attachment handling, and validation.
    • Implemented a writing style analysis service and seeding scripts for personalized email composition.
  • Enhancements

    • Improved mail thread display with quick actions (star, archive, delete), advanced label handling, and attachment downloads.
    • Enhanced sidebar, navigation, and settings UI for better usability and theme consistency.
    • Added new icons and updated category visuals for a modernized interface.
    • Improved date formatting to respect user timezones.
  • Bug Fixes

    • Fixed label and tag handling for threads and messages to use structured objects.
    • Improved toast notifications and form submission controls to prevent duplicate actions.
  • Refactor

    • Consolidated and simplified email composition and reply components.
    • Updated hooks and state management for drafts, labels, and threads for better performance and reliability.
    • Migrated advanced email template rendering to a secure, client-side approach.
  • Chores

    • Added utility scripts and documentation for seeding and managing writing style data.
    • Updated dependencies and configuration for enhanced AI, Microsoft integration, and UI libraries.
  • Documentation

    • Added detailed README for utility scripts system and usage instructions.
  • Style

    • Updated color palette, badge, and tooltip styles for improved appearance and accessibility.
  • Tests

    • No explicit test changes, but improved type safety and validation throughout the app.

@vercel
Copy link

vercel bot commented Apr 29, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
0 ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 29, 2025 3:46am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 29, 2025

Important

Review skipped

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

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

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

Walkthrough

This update introduces a wide-ranging refactor and feature expansion across the mail application. Major changes include: a new Microsoft mail driver and authentication provider, a comprehensive overhaul of the email composition UI (with the introduction of a modular EmailComposer component), a new AI-powered assistant and chat sidebar, and a robust writing style analysis and seeding system. The tags system for emails is restructured from string arrays to label objects. The mail list, thread, and draft management UIs are redesigned for improved usability and state management. Many utility hooks, background queue state, and toast feedback mechanisms are updated or added. The codebase also gains new scripts for seeding writing styles, extended icon libraries, enhanced navigation, and improved workflow and build configurations.

Changes

File(s) / Group Change Summary
.github/workflows/ci.yml, .github/workflows/main.yml Updated GitHub Actions workflows: added concurrency controls, switched to actions/setup-node@v4, enabled bun caching, changed runner image to ubuntu-latest, set timeouts, and improved environment variable usage.
.gitignore Added apps/mail/scripts.ts to ignore list.
.npmrc Added save-exact=true to enforce exact dependency versions.
apps/mail/actions/ai.ts Changed AI email body generation call signature to use an argument object; minor formatting changes.
apps/mail/actions/extractText.ts Deleted function for extracting plain text from HTML using Cheerio.
apps/mail/actions/mail.ts Updated starred thread detection logic to use tag objects with case-insensitive name checks.
apps/mail/actions/send.ts Refactored sendEmail to use a typed interface, defaulted attachments to empty array, and added a post-send callback for writing style updates.
apps/mail/actions/utils.ts Added utility functions: fromBase64Url, fromBinary, and findHtmlBody.
apps/mail/app/(full-width)/about/page.tsx Added a new About page with dynamic sections and navigation.
apps/mail/app/(routes)/layout.tsx Changed container classes for layout, commented out SWR provider assignment.
apps/mail/app/(routes)/mail/create/page.tsx Refactored to accept/await searchParams, now redirects with query params instead of rendering UI.
apps/mail/app/(routes)/mail/draft/page.tsx Deleted simple wrapper component for drafts layout.
apps/mail/app/(routes)/mail/layout.tsx Changed background color classes to theme-based, removed some padding.
apps/mail/app/(routes)/settings/danger-zone/page.tsx Refactored delete account dialog to use toast.promise, updated translation keys to camelCase.
apps/mail/app/(routes)/settings/labels/page.tsx Refactored label mutation logic to use direct imports and toast.promise, updated error handling.
apps/mail/app/api/ai-search/route.ts Added new POST API route for AI-powered email search using OpenAI and Gmail-compatible queries.
apps/mail/app/api/chat/route.ts Added new POST API route for chat requests, supporting email and non-email AI completions.
apps/mail/app/api/driver/drafts/[id]/route.ts Added new GET API route for fetching a draft by ID with rate limiting.
apps/mail/app/api/driver/google.ts Refactored base64 decoding, label mapping, message parsing, and thread label accumulation; updated draft and label methods.
apps/mail/app/api/driver/index.ts Added support for Microsoft mail driver.
apps/mail/app/api/driver/microsoft.ts New Microsoft Graph API mail driver implementing the MailManager interface.
apps/mail/app/api/driver/route.ts Added conditional logic to handle draft folder requests separately.
apps/mail/app/api/driver/types.ts Extended interfaces for drafts, labels, and added getIdType to MailManager.
apps/mail/app/api/utils.ts Added delay and withExponentialBackoff utility functions for async retries.
apps/mail/app/api/v1/labels/route.ts Switched to imported Label type, removed optional chaining, changed error status to 400.
apps/mail/app/api/v1/thread-labels/route.ts Deleted GET API route for fetching thread labels.
apps/mail/app/globals.css Changed .dark background CSS variable to numeric value.
apps/mail/app/layout.tsx Added suppressHydrationWarning to <body>, removed CookieProvider.
apps/mail/components/context/thread-context.tsx Updated starred logic, background queue integration, and toast usage; refactored handlers.
apps/mail/components/create/ai-assistant.tsx Refactored props to require more fields, changed callback prop, updated export style.
apps/mail/components/create/ai-chat.tsx Major refactor: new message structure, search API integration, improved UI, file upload, and stateful chat logic.
apps/mail/components/create/ai-textarea.tsx Added 'use client', simplified textarea classes, changed displayName.
apps/mail/components/create/create-email.tsx Refactored to delegate all composition UI/logic to new EmailComposer component.
apps/mail/components/create/editor.tsx Added readOnly and hideToolbar props; implemented read-only mode and toolbar hiding.
apps/mail/components/create/email-composer.tsx New component for modular, validated email composition with AI, attachments, and recipient management.
apps/mail/components/create/prosemirror.css Changed font size, removed font family, commented out placeholder rule.
apps/mail/components/create/voice.tsx New voice chat component integrating ElevenLabs conversation API and email context.
apps/mail/components/draft/drafts-list.tsx, apps/mail/components/draft/drafts.tsx Deleted drafts list and layout components.
apps/mail/components/golden.tsx Improved invite modal: submission prevention, keyboard shortcut, and UI updates.
apps/mail/components/home/HomeContent.tsx Rewritten as client-side component with static landing page, tabbed preview, and signup form.
apps/mail/components/home/home.tsx New homepage component with tabbed navigation and dynamic glow effect.
apps/mail/components/home/navbar.tsx Redirects authenticated users to /mail, always shows "Get Started" for unauthenticated.
apps/mail/components/icons/animated/alert.tsx Added new animated alert icon with animation control via ref.
apps/mail/components/icons/animated/archive.tsx, apps/mail/components/icons/animated/trash.tsx Removed fixed class from SVG elements.
apps/mail/components/icons/icons.tsx Added many new SVG icon components for UI use.
apps/mail/components/mail/demo.json Changed "tags" field format from array of strings to array of objects with id and name.
apps/mail/components/mail/mail-display.tsx Major UI refactor: new label rendering, attachment handling, reply actions, popover details, and improved layout.
apps/mail/components/mail/mail-iframe.tsx Refactored to async template rendering, improved state logic, removed legacy code.
apps/mail/components/mail/mail-list.tsx Refactored: added draft support, enhanced thread actions, improved selection, and empty state UI.
apps/mail/components/mail/mail-quick-actions.tsx Added query state logic for activeReplyId and clearing on thread close.
apps/mail/components/mail/mail.tsx Refactored categories to animated button group, updated layout, and simplified tabs.
apps/mail/components/mail/nav-main.tsx New placeholder component for main navigation.
apps/mail/components/mail/render-labels.tsx Refactored to accept label objects, added filter toggle, updated styling.
apps/mail/components/mail/reply-composer.tsx Refactored to delegate all composition logic to EmailComposer, simplified props and mode handling.
apps/mail/components/mail/search-bar.tsx Removed advanced filter popover, simplified search bar, reset on route change.
apps/mail/components/mail/thread-display.tsx Major refactor: added thread navigation, starring, improved action buttons, and UI restructuring.
apps/mail/components/providers/editor-provider.tsx New context provider for TipTap editor instance management.
apps/mail/components/settings/settings-card.tsx Added 'use client' directive, reordered class names.
apps/mail/components/theme/toast.tsx Added new SuccessEmailToast component for styled success notifications.
apps/mail/components/ui/ai-sidebar.tsx Refactored to resizable, persistent sidebar with keyboard shortcuts and placeholder UI.
apps/mail/components/ui/app-sidebar.tsx Switched compose flow to modal dialog, updated footer, and button styling.
apps/mail/components/ui/badge.tsx Removed background and hover color classes from badge variants except "secondary," "destructive," and "outline".
apps/mail/components/ui/dialog.tsx Changed overlay and content background colors, added unused icon import.
apps/mail/components/ui/nav-main.tsx Integrated sidebar state for conditional rendering, simplified label section, removed modal.
apps/mail/components/ui/nav-user.tsx Refactored account dropdown for sidebar state, simplified theme toggle, updated layout.
apps/mail/components/ui/navigation-menu.tsx New customizable navigation menu component using Radix primitives.
apps/mail/components/ui/scroll-area.tsx Added hideScrollbar prop to optionally hide scrollbars.
apps/mail/components/ui/settings-content.tsx Adjusted container padding and height for layout.
apps/mail/components/ui/sidebar-toggle.tsx Switched icon to custom PanelLeftOpen with theme-based fill.
apps/mail/components/ui/sidebar.tsx Updated background color classes, refined tooltip rendering logic.
apps/mail/components/ui/toast.tsx Removed theme prop, changed toast position to bottom-center.
apps/mail/components/ui/tooltip.tsx Changed base classes, added sidebar variant with arrow, updated content rendering.
apps/mail/config/navigation.ts Reorganized navigation config, updated icons, added bottom nav items.
apps/mail/hooks/use-debounce.ts New hook for creating debounced callbacks.
apps/mail/hooks/use-drafts.ts Removed infinite pagination, simplified draft fetching to single draft by ID.
apps/mail/hooks/use-labels.ts Refactored: moved mutation functions out, simplified thread label retrieval, switched to axios.
apps/mail/hooks/use-mail-navigation.ts Simplified thread navigation to use message IDs only.
apps/mail/hooks/use-settings.ts Added 'use client' directive, reformatted imports, minor log change.
apps/mail/hooks/use-stats.ts Debounced the mutate function using new debounce hook.
apps/mail/hooks/use-threads.ts Integrated background queue, debounced mutate, added group thread detection, cleaned up exports.
apps/mail/lib/ai.ts Refactored AI email generation: new prompt structure, llama model, style matrix extraction, and post-processing.
apps/mail/lib/auth-providers.ts Enabled Google prompt param, added Microsoft auth provider config.
apps/mail/lib/auth.ts Changed user deletion to use a transaction including hotkeys.
apps/mail/lib/constants.ts Added/updated constants for sidebar, templates, and providers; updated template CSS.
apps/mail/lib/email-utils.client.tsx New email templating with CSP, nonce, and inline styles using @react-email/components.
apps/mail/lib/email-utils.ts Removed template function, updated fallback sender name.
apps/mail/lib/prompts.ts Added new prompt generators for style-aware AI, extraction, and summary updating.
apps/mail/lib/providers.tsx Added unused EditorProvider import, commented out AISidebarProvider.
apps/mail/lib/timezones.ts Added 'use client' directive.
apps/mail/lib/utils.ts Made date formatting timezone-aware using date-fns-tz.
apps/mail/locales/en.json Updated/added localization strings, renamed keys to camelCase.
apps/mail/package.json Updated scripts, dependencies, and devDependencies; added AI, Microsoft, and UI packages.
apps/mail/scripts/README.md New documentation for the scripts system and style seeding.
apps/mail/scripts/run.ts New CLI entry point using cmd-ts for running scripts.
apps/mail/scripts/seed-style/seeder.ts New CLI script for seeding/resetting writing style matrices with concurrency and retry logic.
apps/mail/scripts/seed-style/styles/*.json Added large datasets of email templates for various writing styles (professional, persuasive, genz, concise, friendly).
apps/mail/services/writing-style-service.ts New module for maintaining/updating writing style matrices using Welford’s algorithm and top-count metrics.
apps/mail/store/backgroundQueue.ts New Jotai atom state for managing a background queue of thread IDs.
apps/mail/tailwind.config.ts Added new custom colors for theming.
apps/mail/types/index.ts Added ISendEmail interface, made Sender.name optional, changed ParsedMessage.tags to label objects.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant AI Sidebar
    participant API
    participant EmailDriver
    participant WritingStyleService

    User->>UI: Compose or reply to email
    UI->>AI Sidebar: (optional) Request suggestion
    AI Sidebar->>API: /api/ai-search or /api/chat
    API->>AI Sidebar: AI-powered suggestion or search results
    User->>UI: Accept suggestion, send email
    UI->>API: sendEmail (with ISendEmail object)
    API->>EmailDriver: Send email
    API->>WritingStyleService: updateWritingStyleMatrix(connectionId, emailBody)
    WritingStyleService-->>API: Update style matrix in DB
    API->>UI: Email sent confirmation
Loading
sequenceDiagram
    participant ScriptUser
    participant CLI
    participant SeederScript
    participant DB
    participant WritingStyleService

    ScriptUser->>CLI: bun scripts seed-style seed --connection-id ...
    CLI->>SeederScript: Parse args, prompt if missing
    SeederScript->>DB: Validate connection ID
    SeederScript->>WritingStyleService: updateWritingStyleMatrix for each email
    WritingStyleService->>DB: Update style matrix
    SeederScript->>CLI: Output progress and results
Loading

Possibly related PRs

  • Mail-0/Zero#478: Both PRs modify mail-list.tsx and involve changes to selection mode and key press handling, directly improving user interaction and state management for mail selection.
  • Mail-0/Zero#595: Both PRs modify starred thread detection logic in mail.ts by changing tag checks, indicating a direct connection in how starred state and label handling are implemented.
  • Mail-0/Zero#692: Both PRs refactor the CreateEmail component, with the main PR extracting internal logic into a new EmailComposer component, showing a strong code-level relationship.

Suggested labels

design

Suggested reviewers

  • MrgSub
  • ahmetskilinc

Poem

In a warren of code, a rabbit hopped,
Through drafts and threads, it never stopped.
With new drivers and styles, and icons galore,
The compose flow's fresh, and AI does more!
Sidebar glows, emails shine bright,
Scripts and seeds, all working right.
🐇✨ "Ship it!" the rabbit cheered with delight.


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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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.

@nizzyabi nizzyabi changed the base branch from main to staging April 29, 2025 02:32
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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (54)
apps/mail/app/api/utils.ts (1)

73-76: 💡 Verification agent

🧩 Analysis chain

Verify rate limit detection logic with API documentation

The rate limit detection logic checks for HTTP 429 status code and specific error reasons. Please verify that these patterns match the actual error response formats from all the APIs being called.


🌐 Web query:

What are the common rate limit error response formats for Microsoft Graph API?

💡 Result:

Microsoft Graph API rate limits and error responses are designed to manage and control the usage of the API, ensuring reliability and resource efficiency. When these limits are exceeded, specific error responses are returned to the client. Here are the common error response formats for Microsoft Graph API rate limits:

Error Response Format

  1. HTTP Status Code 429 - Too Many Requests: This is the standard HTTP status code returned when rate limits are exceeded. It indicates that the client has made too many requests within a given timeframe[2][3].

  2. Retry-After Header: Along with the 429 status code, Microsoft Graph returns a Retry-After header. This header specifies the number of seconds the client should wait before making the next request[2][4].

  3. JSON Error Response Object: Error responses from Microsoft Graph API are typically returned in JSON format. This object provides detailed information about the error, which can help clients handle the error gracefully[3].

Example JSON Error Response

{
  "error": {
    "code": "Throttled",
    "message": "Too many requests. Please wait before sending more requests.",
    "innerError": {
      "date": "2025-01-01T00:00:00",
      "request-id": "request-id",
      "client-request-id": "client-request-id"
    }
  }
}

Additional Details

  • Request Types: The throttling behavior can vary based on the request type. For example, writes might be throttled more frequently than reads[2].
  • Service-Specific Limits: Different Microsoft Graph services have their own specific throttling limits, which can lead to varied error responses depending on the service being accessed[1].

In general, when you encounter rate limit errors with Microsoft Graph API, it's advisable to implement exponential backoff strategies or use features like Microsoft Graph Data Connect for large data extractions to avoid hitting these limits[2].

Citations:


🏁 Script executed:

#!/bin/bash
# Display the contents around the rate limit detection logic in utils.ts
sed -n '1,200p' apps/mail/app/api/utils.ts

Length of output: 2751


🏁 Script executed:

#!/bin/bash
# Search for usage of withExponentialBackoff across the codebase
rg -n "withExponentialBackoff" --color=never

Length of output: 394


🏁 Script executed:

#!/bin/bash
# Display the first 200 lines of the Microsoft Graph API driver to inspect error handling
sed -n '1,200p' apps/mail/app/api/driver/microsoft.ts

Length of output: 6951


Expand rate limit detection logic to cover Microsoft Graph API

The current check only covers HTTP 429 and Google’s rateLimitExceeded/userRateLimitExceeded reasons. Microsoft Graph returns a JSON error with a string code ("Throttled") (and typically sets statusCode or response.status to 429), so we should include those too.

• File: apps/mail/app/api/utils.ts
• Location: inside withExponentialBackoff (around lines 73–76)

Suggested diff:

-      const isRateLimit =
-        error?.code === 429 ||
-        error?.errors?.[0]?.reason === 'rateLimitExceeded' ||
-        error?.errors?.[0]?.reason === 'userRateLimitExceeded';
+      const isRateLimit =
+        // HTTP 429 from Google or generic fetch
+        error?.code === 429 ||
+        // HTTP status on Graph client error
+        error?.statusCode === 429 ||
+        error?.response?.status === 429 ||
+        // Google API retry reasons
+        error?.errors?.[0]?.reason === 'rateLimitExceeded' ||
+        error?.errors?.[0]?.reason === 'userRateLimitExceeded' ||
+        // Microsoft Graph throttling
+        error?.error?.code === 'Throttled';
📝 Committable suggestion

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

      const isRateLimit =
        // HTTP 429 from Google or generic fetch
        error?.code === 429 ||
        // HTTP status on Graph client error
        error?.statusCode === 429 ||
        error?.response?.status === 429 ||
        // Google API retry reasons
        error?.errors?.[0]?.reason === 'rateLimitExceeded' ||
        error?.errors?.[0]?.reason === 'userRateLimitExceeded' ||
        // Microsoft Graph throttling
        error?.error?.code === 'Throttled';
apps/mail/app/globals.css (1)

64-64: 💡 Verification agent

🧩 Analysis chain

Verify background CSS variable value

The --background CSS variable in the dark theme has been changed from an HSL color value to simply 1, which is not a valid CSS color format. This might cause rendering issues for elements using this variable.


🏁 Script executed:

#!/bin/bash
# Check where the --background variable is used in the codebase
rg -A 2 -B 2 "var\(--background\)" --type css --type tsx --type jsx

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Retry searching for uses of --background without relying on type filters
rg -A2 -B2 'var(--background)' --glob '*.css' --glob '*.tsx' --glob '*.jsx'

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Search entire repository for any usage of the --background CSS variable
rg -n 'var\(--background\)'

Length of output: 272


Fix invalid --background CSS variable value

The --background variable in the dark theme is currently set to 1, but it’s used throughout the code inside hsl(var(--background)). Supplying just 1 to hsl() is invalid (it expects three or four HSL components). This will break background colors in:

  • apps/mail/tailwind.config.ts (line 30)
    background: 'hsl(var(--background))',
  • apps/mail/app/globals.css (line 266)
    background-color: hsl(var(--background));
  • apps/mail/components/create/prosemirror.css (line 72)
    background-color: hsl(var(--background));

Please update the variable to a valid HSL triplet (e.g. --background: 210 10% 20%; or similar) so that all hsl(var(--background)) calls render correctly.

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

4-4: ⚠️ Potential issue

Unused import

The EditorProvider is imported but not used in the component. Consider either removing the unused import or implementing it as intended.

-import { EditorProvider } from '@/components/providers/editor-provider';
📝 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.


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

60-64: 💡 Verification agent

🧩 Analysis chain

Removed CookieProvider wrapper

You've removed the <CookieProvider> wrapper around the children and UI components. This change might impact how cookies or state are managed in the application. Make sure all components previously relying on the CookieProvider context still function correctly.


🏁 Script executed:

#!/bin/bash
# Check for components using CookieProvider context
echo "Searching for components that might depend on CookieProvider..."
rg -n "useCookies" --type=ts --type=tsx

Length of output: 204


🏁 Script executed:

#!/bin/bash
echo "Searching for CookieProvider references..."
rg -n "CookieProvider"
echo "Searching for useCookies references..."
rg -n "useCookies"
echo "Searching for react-cookie imports..."
rg -n "react-cookie"

Length of output: 1094


Restore <CookieProvider> in Mail Layout

The useCookies hook (used in cookie-dialog.tsx) relies on the CookieProvider context. Since you’ve removed it from apps/mail/app/layout.tsx, any component calling useCookies will now throw at runtime.

• File: apps/mail/app/layout.tsx (around lines 60–64)
Wrap these nodes in <CookieProvider>…</CookieProvider>.

Suggested diff:

-           {children}
-           {cookies}
-           <Toast />
-           <Analytics />
+           <CookieProvider>
+             {children}
+             {cookies}
+             <Toast />
+             <Analytics />
+           </CookieProvider>
📝 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.

            <CookieProvider>
              {children}
              {cookies}
              <Toast />
              <Analytics />
            </CookieProvider>
            {/* {isEuRegion && <CookieConsent />} */}

55-57: 💡 Verification agent

🧩 Analysis chain

Added suppressHydrationWarning to body element

Adding suppressHydrationWarning on the body element will prevent React from showing hydration mismatch warnings. While this resolves hydration warnings, it's important to understand why these warnings occur in the first place. If there are actual hydration mismatches, consider fixing the root cause rather than suppressing the warnings.


🏁 Script executed:

#!/bin/bash
# Check for components that might cause hydration issues

# Look for components that use browser-specific APIs
echo "Searching for components using browser APIs that might cause hydration issues..."
rg -n "typeof window !== 'undefined'" --type=ts --type=tsx
rg -n "document\." --type=ts --type=tsx
rg -n "window\." --type=ts --type=tsx

Length of output: 403


🏁 Script executed:

#!/bin/bash
echo "Re-running search for browser-specific APIs in TS/TSX files..."
rg -n "window\." -g '*.ts' -g '*.tsx'
rg -n "document\." -g '*.ts' -g '*.tsx'
rg -n "navigator\." -g '*.ts' -g '*.tsx'

Length of output: 11145


Ensure hydration consistency by isolating client-only logic

Adding suppressHydrationWarning on <body> will silence React’s hydration-mismatch warnings, but it doesn’t solve the underlying issue. Our search uncovered dozens of components and hooks reading window, document, or navigator during render, for example:
• apps/mail/lib/email-utils.client.tsx
• apps/mail/lib/hotkeys/mail-list-hotkeys.tsx
• apps/mail/hooks/use-mobile.tsx
• apps/mail/components/ui/nav-main.tsx
• apps/mail/components/mail/mail-iframe.tsx
(and many more)

To eliminate real mismatches, please:

• Mark any component that uses browser APIs at render time as a client component with a top-of-file use client directive.
• Defer side-effects and globals (window.*, document.*, navigator.*) to useEffect or event handlers, not the initial render.
• Ensure that server-rendered markup exactly matches the client’s first render; once you’ve corrected these, you can safely remove suppressHydrationWarning.

.github/workflows/ci.yml (1)

13-15: ⚠️ Potential issue

Fix environment variable usage in runs-on.

The current implementation using ${{ env.RUNNER_IMAGE }} in the runs-on field is not valid according to GitHub Actions syntax.

GitHub Actions doesn't allow the env context in the runs-on field. Change to either:

- env:
-   RUNNER_IMAGE: ubuntu-latest
- runs-on: ${{ env.RUNNER_IMAGE }}
+ runs-on: ubuntu-latest

Or if you need to reuse the runner configuration:

- env:
-   RUNNER_IMAGE: ubuntu-latest
- runs-on: ${{ env.RUNNER_IMAGE }}
+ runs-on: ubuntu-latest
🧰 Tools
🪛 actionlint (1.7.4)

15-15: context "env" is not allowed here. available contexts are "github", "inputs", "matrix", "needs", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

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

1-21: ⚠️ Potential issue

Component implementation is incomplete.

The file contains only a skeleton of the NavMain component with a placeholder comment for the actual content. This component sets up state management for collapsed/expanded views but doesn't implement any UI rendering.

The component should be completed by replacing the placeholder comment with the actual navigation elements. Consider implementing the toolbar mentioned in the PR objectives that appears on hover.

.github/workflows/main.yml (1)

24-27: ⚠️ Potential issue

Fix environment variable reference in runs-on configuration

The workflow is trying to use an environment variable in the runs-on field, but there's an issue with how it's referenced.

According to the static analysis tool, the "env" context is not allowed at the runs-on field level. GitHub Actions requires direct values or matrix variables here.

Fix by using a hardcoded value or matrix variable:

- timeout-minutes: 15
- env:
-   RUNNER_IMAGE: ubuntu-latest
- runs-on: ${{ env.RUNNER_IMAGE }}
+ timeout-minutes: 15
+ runs-on: ubuntu-latest
📝 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.

    timeout-minutes: 15
    runs-on: ubuntu-latest
🧰 Tools
🪛 actionlint (1.7.4)

27-27: context "env" is not allowed here. available contexts are "github", "inputs", "matrix", "needs", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

apps/mail/scripts/seed-style/seeder.ts (2)

80-92: 🛠️ Refactor suggestion

Add validation for command-line arguments.

Currently, validation is only performed for interactive prompts but not for command-line arguments. Consider adding similar validation for the command-line arguments to ensure consistency.

handler: async (inputs) => {
-  const connectionId = inputs.connectionId ?? await getConnectionId()
+  let connectionId = inputs.connectionId;
+  if (connectionId) {
+    // Validate command-line provided connectionId
+    const connection = await db.query.connection.findFirst({
+      where: (table, ops) => ops.eq(table.id, connectionId),
+      columns: { id: true },
+    });
+    
+    if (!connection) {
+      console.error('Invalid Connection ID provided via command line');
+      connectionId = await getConnectionId();
+    }
+  } else {
+    connectionId = await getConnectionId();
+  }
  const style = inputs.style ?? await getStyle()
  const resetStyleMatrix = inputs.resetStyleMatrix ?? await getResetStyleMatrix(connectionId)
  const size = inputs.size ?? await getNumberOfEmails(mapping[style].length)

  if (resetStyleMatrix) {
    await runResetStyleMatrix(connectionId)
  }

  await runSeeder(connectionId, style, size)
},
📝 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.

  },
  handler: async (inputs) => {
    let connectionId = inputs.connectionId;
    if (connectionId) {
      // Validate command-line provided connectionId
      const connection = await db.query.connection.findFirst({
        where: (table, ops) => ops.eq(table.id, connectionId),
        columns: { id: true },
      });

      if (!connection) {
        console.error('Invalid Connection ID provided via command line');
        connectionId = await getConnectionId();
      }
    } else {
      connectionId = await getConnectionId();
    }
    const style = inputs.style ?? await getStyle()
    const resetStyleMatrix = inputs.resetStyleMatrix ?? await getResetStyleMatrix(connectionId)
    const size = inputs.size ?? await getNumberOfEmails(mapping[style].length)

    if (resetStyleMatrix) {
      await runResetStyleMatrix(connectionId)
    }

    await runSeeder(connectionId, style, size)
  },

112-118: 🛠️ Refactor suggestion

Add error handling for database operations.

The reset operation doesn't have any error handling for the database delete operation, which could fail for various reasons.

if (confirmed) {
  console.warn('Resetting style matrix for connection', connectionId)
-  await runResetStyleMatrix(connectionId)
-  console.warn('Reset style matrix for connection', connectionId)
+  try {
+    await runResetStyleMatrix(connectionId)
+    console.log('Reset style matrix for connection', connectionId)
+  } catch (error) {
+    console.error('Failed to reset style matrix:', error)
+  }
} else {
  console.warn('Aborted reset')
}
📝 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.

    if (confirmed) {
      console.warn('Resetting style matrix for connection', connectionId)
      try {
        await runResetStyleMatrix(connectionId)
        console.log('Reset style matrix for connection', connectionId)
      } catch (error) {
        console.error('Failed to reset style matrix:', error)
      }
    } else {
      console.warn('Aborted reset')
    }
apps/mail/app/api/driver/types.ts (1)

12-19: 🛠️ Refactor suggestion

Address TODO comment for better type safety.

There's a TODO comment about adding a generic type parameter, which should be addressed to improve type safety.

export interface ParsedDraft {
  id: string;
  to?: string[];
  subject?: string;
  content?: string;
-  // todo: add <T>
-  rawMessage?: any;
+  rawMessage?: Record<string, unknown>;
}

Alternatively, if you need to maintain the flexibility of accepting different message formats:

export interface ParsedDraft<T = Record<string, unknown>> {
  id: string;
  to?: string[];
  subject?: string;
  content?: string;
-  // todo: add <T>
-  rawMessage?: any;
+  rawMessage?: T;
}
📝 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.

export interface ParsedDraft {
  id: string;
  to?: string[];
  subject?: string;
  content?: string;
  rawMessage?: Record<string, unknown>;
}
export interface ParsedDraft<T = Record<string, unknown>> {
  id: string;
  to?: string[];
  subject?: string;
  content?: string;
  rawMessage?: T;
}
apps/mail/components/create/editor.tsx (2)

90-96: 🛠️ Refactor suggestion

Good additions to the interface

The new props (onEditorReady, readOnly, and hideToolbar) improve the component's flexibility. However, I notice that while readOnly is used throughout the code, the hideToolbar property is defined but not actually implemented in the component logic.

You should implement the hideToolbar functionality by conditionally rendering the MenuBar component in the editor. Consider adding this to the Editor component:

-        <EditorContent
+        <EditorContent
+          slotBefore={!hideToolbar ? <MenuBar /> : undefined}
           immediatelyRender={false}
           initialContent={initialValue || defaultEditorContent}

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


351-351: ⚠️ Potential issue

Implementation of the hideToolbar prop

While you've added the hideToolbar prop to the component's props interface, it's not being used to actually hide the toolbar. The toolbar visibility should be conditional based on this prop.

Implement the toolbar hiding functionality by ensuring the MenuBar is conditionally rendered:

  return (
    <div
      className={`relative w-full ${className || ''}`}
      onClick={focusEditor}
      ...
    >
      <EditorRoot>
        <EditorContent
+         slotBefore={!hideToolbar ? <MenuBar /> : undefined}
          immediatelyRender={false}
          initialContent={initialValue || defaultEditorContent}
📝 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.

  return (
    <div
      className={`relative w-full ${className || ''}`}
      onClick={focusEditor}
      ...
    >
      <EditorRoot>
        <EditorContent
          slotBefore={!hideToolbar ? <MenuBar /> : undefined}
          immediatelyRender={false}
          initialContent={initialValue || defaultEditorContent}
apps/mail/app/(full-width)/about/page.tsx (1)

131-136: ⚠️ Potential issue

Email address mismatch

There's an inconsistency between the mailto link (using personal email "nizabizaher@gmail.com") and the displayed email address ("contact@0.email").

Consider updating the mailto link to use the same email that's displayed to users:

-            href="mailto:nizabizaher@gmail.com"
+            href="mailto:contact@0.email"
             className="inline-flex items-center text-blue-400 hover:text-blue-300"
📝 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.

            href="mailto:contact@0.email"
            className="inline-flex items-center text-blue-400 hover:text-blue-300"
          >
            <Mail className="mr-2 h-4 w-4" />
            contact@0.email
          </a>
apps/mail/components/ui/app-sidebar.tsx (1)

142-145: 🛠️ Refactor suggestion

Empty dialog title and description

The DialogTitle and DialogDescription components are rendered but empty. This could impact accessibility as screen readers rely on these elements.

Either add appropriate content to these elements or remove them:

-      <DialogTitle></DialogTitle>
-      <DialogDescription></DialogDescription>
apps/mail/components/icons/animated/alert.tsx (2)

67-72: 🛠️ Refactor suggestion

Missing className application

The component accepts a className prop, but it's not applied to any element in the component.

Apply the className prop to the container div:

      <div
-       className="hover:bg-accent flex cursor-pointer select-none items-center justify-center rounded-md transition-colors duration-200"
+       className={cn("hover:bg-accent flex cursor-pointer select-none items-center justify-center rounded-md transition-colors duration-200", className)}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
        {...props}
📝 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.

      <div
        className={cn(
          "hover:bg-accent flex cursor-pointer select-none items-center justify-center rounded-md transition-colors duration-200",
          className
        )}
        onMouseEnter={handleMouseEnter}
        onMouseLeave={handleMouseLeave}
        {...props}
      >

31-43: 🛠️ Refactor suggestion

Size prop is not applied to SVG

The component accepts a size prop (default 28), but it's not applied to the SVG element, which has hard-coded width and height attributes.

Apply the size prop to the SVG element:

        <motion.svg
          xmlns="http://www.w3.org/2000/svg"
-         width="15"
-         height="15"
+         width={size}
+         height={size}
          viewBox="0 0 24 24"
apps/mail/components/create/voice.tsx (3)

91-107: 🛠️ Refactor suggestion

Clean-up ElevenLabs connection on unmount

useConversation likely holds a WebSocket / media stream. If the component disappears while connected, the session and microphone stay open.

const conversation = useConversation({...});

useEffect(() => {
  return () => {
    if (conversation.status === 'connected') {
      conversation.endSession().catch(console.error);
    }
  };
}, [conversation]);
📝 Committable suggestion

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

  const conversation = useConversation({
    onConnect: () => {
      console.log("Connected to ElevenLabs");
    },
    onDisconnect: () => {
      console.log("Disconnected from ElevenLabs");
    },
    onMessage: (message) => {
      console.log("Received message:", message);
    },
    onError: (error: string | Error) => {
      setErrorMessage(typeof error === "string" ? error : error.message);
      console.error("Error:", error);
    },
  });

  useEffect(() => {
    return () => {
      if (conversation.status === 'connected') {
        conversation.endSession().catch(console.error);
      }
    };
  }, [conversation]);

  const { status, isSpeaking } = conversation;

60-71: ⚠️ Potential issue

Null-safety for optional sender / recipient fields

latestMessage.sender.name and t.name can be undefined, which will render literal “undefined” in the prompt.

From: ${latestMessage.sender.name ?? latestMessage.sender.email}
To: ${latestMessage.to.map((t: Sender) => t.name ?? t.email).join(', ')}
Subject: ${latestMessage.subject ?? '(No subject)'}

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


50-80: 🛠️ Refactor suggestion

Avoid state-updates after component unmount & reduce overfetching

fetchThreadContents fires 20 concurrent requests every time threads changes, but:

  1. If the component unmounts before all requests settle, the later setEmailContent will raise a React warning (“Can’t perform a React state update on an unmounted component”).
  2. Making up to 20 separate /api/driver/:id calls can quickly hit rate-limits. The driver already returns snippets – consider a batched endpoint or using the first page data instead.
  useEffect(() => {
-    if (!threads || threads.length === 0) return;
+    if (!threads?.length) return;
     ...
-    const fetchThreadContents = async () => {
+    const abort = new AbortController();
+    const fetchThreadContents = async () => {
         const threadContents = await Promise.all(
-          threads.slice(0, 20).map(async (thread) => {
+          threads.slice(0, 20).map(async (thread) => {
               try {
-                const response = await fetch(`/api/driver/${thread.id}`);
+                const response = await fetch(`/api/driver/${thread.id}`, { signal: abort.signal });

and return a cleanup:

-    fetchThreadContents();
+    fetchThreadContents();
+    return () => abort.abort(); // prevent setState after unmount

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

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

164-168: ⚠️ Potential issue

Sanitize HTML before using dangerouslySetInnerHTML

dangerouslySetInnerHTML is flagged by Biome because it can enable XSS if content is user-supplied.
Even though the CSP is strict, defense-in-depth is advisable. Consider running the HTML through a sanitizer such as DOMPurify before rendering:

-import DOMPurify from 'dompurify';
...
-<div ... dangerouslySetInnerHTML={{ __html: content }} />
+import DOMPurify from 'dompurify';
+...
+<div
+  ... 
+  dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}
+/>

If bundle size is a concern, a small allow-list sanitizer or server-side sanitisation can be used instead.

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

// At the top of apps/mail/lib/email-utils.client.tsx
import DOMPurify from 'dompurify';
// …other imports

// …later in your JSX render…
<Row style={{ background: 'transparent', padding: 0, margin: 0 }}>
  <Column style={{ background: 'transparent', padding: 0, margin: 0 }}>
    <div
      style={{ background: 'transparent', fontSize: '16px', lineHeight: '1.5' }}
      dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}
    />
  </Column>
</Row>
🧰 Tools
🪛 Biome (1.9.4)

[error] 166-166: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

apps/mail/app/api/ai-search/route.ts (1)

35-37: ⚠️ Potential issue

Validate request body shape with Zod

messages is assumed to be an array; malformed input will throw at runtime. Add zod validation before use:

const BodySchema = z.object({
  messages: z.array(z.object({ role: z.string(), content: z.string() }))
});
const parseResult = BodySchema.safeParse(await req.json());
if (!parseResult.success) {
  return new Response('Invalid body', { status: 400 });
}
const { messages } = parseResult.data;
apps/mail/components/golden.tsx (1)

155-160: ⚠️ Potential issue

Invalid Tailwind class mr- – missing value

mr- is not a valid Tailwind shorthand and will be ignored, leaving extra spacing un-applied.

-                <span className="mr-">Send invite</span>
+                <span className="mr-2">Send invite</span>

Pick a value (mr-1, mr-2, …) that matches adjacent spacing so the text isn’t glued to the shortcut hint.

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

              <Button disabled={!email} type="submit" className="h-7">
-                <span className="mr-">Send invite</span>
+                <span className="mr-2">Send invite</span>
                <div className="flex h-5 items-center justify-center gap-1 rounded-sm dark:bg-black/10 px-1 bg-white/10">
                  <Command className="h-2 w-2 text-black dark:text-[#929292]" />
                  <CurvedArrow className="mt-1.5 h-3 w-3 fill-black dark:fill-[#929292]" />
                </div>
apps/mail/components/ui/nav-user.tsx (1)

104-113: 🛠️ Refactor suggestion

⚠️ Potential issue

Unsafe optional-chaining → possible TypeError (.split on undefined)

If activeAccount is undefined (or both name & email are), the expression below short-circuits to undefined, then .split() is invoked – exactly what Biome flagged.

(activeAccount?.name || activeAccount?.email).split(' ')

Minimal fix:

- {(activeAccount?.name || activeAccount?.email)
+ {(activeAccount?.name ?? activeAccount?.email ?? '')
     .split(' ')

Recommended: extract a helper to de-duplicate the same pattern that appears three times in this component.

const getInitials = (name?: string | null, email?: string | null) =>
  (name ?? email ?? '')
    .split(' ')
    .map((n) => n[0])
    .join('')
    .toUpperCase()
    .slice(0, 2);

Then replace all inline calculations with getInitials(...).

🧰 Tools
🪛 Biome (1.9.4)

[error] 111-111: Unsafe usage of optional chaining.

If it short-circuits with 'undefined' the evaluation will throw TypeError here:

(lint/correctness/noUnsafeOptionalChaining)

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

4-10: ⚠️ Potential issue

Remove unused imports to avoid lint / build failures

useHotkeysContext, useSettings, and useDraft are imported but never referenced. Most TS/ESLint configs treat this as an error and break CI/CD.

-import { useHotkeysContext } from 'react-hotkeys-hook';
-import { useSettings } from '@/hooks/use-settings';
-import { useDraft } from '@/hooks/use-drafts';
📝 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.

import { DialogClose } from '@/components/ui/dialog';
import { EmailComposer } from './email-composer';
import { useSession } from '@/lib/auth-client';
import { useTranslations } from 'next-intl';

68-76: 🛠️ Refactor suggestion

Eliminate redundant console.log wrapper around posthog.capture

posthog.capture already records the event and returns void (or a boolean in older SDKs). Wrapping it in console.log yields undefined in the console and adds noise.

-console.log(posthog.capture('Create Email Sent with CC and BCC'));
+posthog.capture('Create Email Sent with CC and BCC');

Apply the same adjustment for the CC-only, BCC-only, and default branches.

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

      if (data.cc && data.bcc) {
        posthog.capture('Create Email Sent with CC and BCC');
      } else if (data.cc) {
        posthog.capture('Create Email Sent with CC');
      } else if (data.bcc) {
        posthog.capture('Create Email Sent with BCC');
      } else {
        posthog.capture('Create Email Sent');
      }
apps/mail/components/mail/mail-iframe.tsx (2)

72-90: ⚠️ Potential issue

Potential memory-leak: generated Blob URLs are never revoked

template() creates a new Blob URL on every html / imagesEnabled change.
Because the url variable lives only inside the then callback the cleanup section cannot access it (note that it’s commented out anyway), so every navigation / re-render leaves an orphaned object URL in memory.

-    template(html, imagesEnabled).then((htmlDoc) => {
-      if (!iframeRef.current) return;
-      const url = URL.createObjectURL(new Blob([htmlDoc], { type: 'text/html' }));
+    let url: string;
+    template(html, imagesEnabled).then((htmlDoc) => {
+      if (!iframeRef.current) return;
+      url = URL.createObjectURL(new Blob([htmlDoc], { type: 'text/html' }));
       iframeRef.current.src = url;
       …
     });
 
     return () => {
-      //   URL.revokeObjectURL(url);
+      if (url) URL.revokeObjectURL(url);
     };

Keeping the revoke ensures that the browser’s in-memory blob store is freed when the component unmounts or the dependencies change.

Also applies to: 91-94


50-55: 🛠️ Refactor suggestion

Effect overrides manual “show images” toggle for trusted senders

When the sender is trusted the effect always forces imagesEnabled to true. After the user clicks “Disable images” the value flips to false but will be set back to true on the next settings mutation (because isTrustedSender stays true).
Persist the user override, e.g.:

-  useEffect(() => {
-    if (isTrustedSender) {
-      setImagesEnabled(true);
-    }
-  }, [isTrustedSender]);
+  useEffect(() => {
+    if (isTrustedSender && !imagesEnabled) {
+      setImagesEnabled(true);
+    }
+  }, [isTrustedSender, imagesEnabled]);

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

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

83-174: ⚠️ Potential issue

Remove the unnecessary React Fragment flagged by lint

The fragment adds no semantic or styling value because it wraps a single ResizablePanel.
Dropping it silences noUselessFragments and avoids an extra element in the tree.

-        {open && (
-          <>
-            <ResizablePanel
+        {open && (
+            <ResizablePanel
               defaultSize={25}
               …
             >
               …
-            </ResizablePanel>
-          </>
+            </ResizablePanel>
         )}
📝 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.

        {open && (
            <ResizablePanel
              defaultSize={25}
              minSize={20}
              maxSize={45}
              className="bg-panelLight dark:bg-panelDark ml- mr-1.5 mt-1 h-[calc(98vh+9px)] border-[#E7E7E7] shadow-sm md:rounded-2xl md:border md:shadow-sm dark:border-[#252525]"
            >
              <div className={cn('h-[calc(98vh+15px)]', 'flex flex-col', '', className)}>
                <div className="flex h-full flex-col">
                  {/* ...rest of content... */}
                </div>
              </div>
            </ResizablePanel>
        )}
🧰 Tools
🪛 Biome (1.9.4)

[error] 84-173: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

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

148-153: ⚠️ Potential issue

handleKeyDown is defined but never used

The function listens for Cmd/Ctrl + Enter submission but is not bound to any element or event emitter, so the shortcut never fires.

Either attach it to the form element / relevant input, or remove the unused code to avoid dead-weight.


105-118: 🛠️ Refactor suggestion

Glow position isn’t recalculated on window resize / tab list layout change

handleTabChange is only executed on tab change and initial mount. If the window is resized (or orientation changes) the cached rect values become stale, causing the glow to mis-align.

Add a resize listener that recomputes the glow:

useEffect(() => {
+ const handler = () => handleTabChange(tabs.find(t => t.value === valueRef.current)?.value ?? '');
+ window.addEventListener('resize', handler);
   return () => window.removeEventListener('resize', handler);
-}, [handleTabChange]);
+}, [handleTabChange]);

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


68-70: 🛠️ Refactor suggestion

Over-restrictive email validation may reject valid addresses

z.string().email().min(9) requires at least 9 characters, so a perfectly valid address like a@b.co will fail.
Unless you have a business reason for this length check, consider removing the .min(9) constraint.

-  email: z.string().email().min(9),
+  email: z.string().email(),
📝 Committable suggestion

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

const betaSignupSchema = z.object({
  email: z.string().email(),
});
apps/mail/components/create/email-composer.tsx (2)

306-308: ⚠️ Potential issue

Empty stub: handleGenerateReply is unimplemented

Leaving an empty handler will confuse future maintainers and may lead to runtime issues if the button becomes callable.

Either implement the logic or remove the button until ready.


162-189: 🛠️ Refactor suggestion

Repeated setValue('bcc', …) inside loop can introduce duplicates & unnecessary renders

For every BCC recipient you call setValue, potentially causing:

  1. Duplicate addresses when a recipient already exists in the array.
  2. Multiple re-renders during a single effect.

Refactor to accumulate addresses first, then call setValue once:

- latestEmail.bcc?.forEach((recipient) => {
-
-   form.setValue('bcc', [...(bccEmails || []), recipient.email]);
-   setShowBcc(true);
- });
+ const newBcc = new Set(bccEmails ?? []);
+ latestEmail.bcc?.forEach(r => {
+   const e = r.email.toLowerCase();
+   if (e !== userEmail && !newBcc.has(r.email)) newBcc.add(r.email);
+ });
+ if (newBcc.size) {
+   form.setValue('bcc', Array.from(newBcc));
+   setShowBcc(true);
+ }

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

apps/mail/app/api/driver/google.ts (2)

30-33: 🛠️ Refactor suggestion

Add missing Base-64 padding before decoding

base64-js throws InvalidLengthError if the input length is not a multiple of four. Gmail’s URL-safe payloads frequently come without the = padding, so fromBinary will intermittently blow up on real-world messages.

-const bytes = toByteArray(str.replace(/-/g, '+').replace(/_/g, '/'));
+const normalized = str.replace(/-/g, '+').replace(/_/g, '/');
+const padded     = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
+const bytes      = toByteArray(padded);

This keeps the helper deterministic and prevents sporadic “invalid encoding” crashes.

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

function fromBinary(str: string) {
  const normalized = str.replace(/-/g, '+').replace(/_/g, '/');
  const padded     = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
  const bytes      = toByteArray(padded);
  return new TextDecoder().decode(bytes);
}

688-690: ⚠️ Potential issue

Remove hard-coded debug logging

console.log('decodedBody', bodyData); for a single thread ID will leak noisy logs in production and potentially expose user data.

-if (id === '196784c9e42c15cb') {
-  console.log('decodedBody', bodyData);
-}
📝 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.


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

131-136: ⚠️ Potential issue

toast.promise ignores the finally key – cleanup never runs

sonner’s toast.promise accepts only loading | success | error.
The finally callback here is silently ignored, so the background-queue never drains and SWR never revalidates.

Refactor to:

toast.promise(promise, {
  loading: t('common.actions.moving'),
  success: t('common.actions.moved'),
  error:  t('common.actions.failedToMove'),
}).finally(async () => {
  await Promise.all([mutate(), mutateStats()]);
  setMail({ ...mail, bulkSelected: [] });
  targets.forEach((threadId) => setBackgroundQueue({ type: 'delete', threadId }));
});

Apply the same fix to handleReadUnread.


99-104: ⚠️ Potential issue

isStarred can throw when threadData is undefined

threadData?.messages.some(...) calls .some on undefined when the data is not yet loaded, leading to a runtime error.

-return threadData?.messages.some((message) =>
+return threadData?.messages?.some((message) =>
   message.tags?.some((tag) => tag.name.toLowerCase() === 'starred'),
 );
apps/mail/components/create/ai-chat.tsx (1)

3-3: ⚠️ Potential issue

Incorrect icon component names – build will fail

lucide-react exports ArrowUp, Check, X (and so on) – not the *Icon suffixed variants. Importing the wrong identifiers will raise compile-time errors.

-import { ArrowUpIcon, Mic, CheckIcon, XIcon, Plus, Command } from 'lucide-react';
+import { ArrowUp, Mic, Check, X, Plus, Command } from 'lucide-react';

Update the corresponding JSX usages (<Check />, <X />, <ArrowUp /> …).

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

-import { ArrowUpIcon, Mic, CheckIcon, XIcon, Plus, Command } from 'lucide-react';
+import { ArrowUp, Mic, Check, X, Plus, Command } from 'lucide-react';
apps/mail/components/mail/mail.tsx (2)

32-35: ⚠️ Potential issue

Unused imports – will fail noUnusedLocals / eslint rules
Filter and X are no longer referenced (the only usages are commented-out).
Leaving them imported breaks the build if strict lint rules are enabled. Simply remove the names or the whole import line.


262-267: ⚠️ Potential issue

Missing dependency in handleClose callback
handleClose calls both setThreadId and setActiveReplyId but only lists setThreadId in its dependency array.
If the setter returned by useQueryState ever changes (e.g. during HMR) the callback will capture a stale reference.

-const handleClose = useCallback(() => {
-  setThreadId(null);
-  setActiveReplyId(null);
-}, [setThreadId]);
+const handleClose = useCallback(() => {
+  setThreadId(null);
+  setActiveReplyId(null);
+}, [setThreadId, setActiveReplyId]);
apps/mail/components/mail/mail-list.tsx (2)

464-514: 🛠️ Refactor suggestion

Quick-action toolbar accessibility & pointer-events

  1. The floating toolbar is absolutely positioned with top-[-1] when index !== 0.
    Negative pixel values can be brittle; consider using inset-y-1 or a relative transform so the toolbar never overlaps neighbouring rows.

  2. Buttons inside the hover-toolbar are the only focusable items rendered while it’s visible.
    For keyboard / screen-reader users the toolbar is unreachable because it is conditionally rendered only on mouse-hover.
    Consider keeping the toolbar in the DOM with opacity-0 and toggling pointer-events/opacity instead so it can still participate in the tab order, or expose the same actions via the context menu.


16-18: ⚠️ Potential issue

Users, Briefcase, StickyNote, and Star icons are used but never imported
Components referenced further down (<Users/>, <Briefcase/>, <StickyNote/>, <Star/>) will throw ReferenceError at compile-time. Add the missing imports, e.g.:

-import { Bell, GroupPeople, Lightning, People, Tag, User, Star2, Archive, Trash, Archive2, ChevronDown } from '../icons/icons';
+import {
+  Bell,
+  GroupPeople,
+  Lightning,
+  People,
+  Tag,
+  User,
+  Users,
+  Briefcase,
+  StickyNote,
+  Star,
+  Star2,
+  Archive,
+  Trash,
+  Archive2,
+  ChevronDown,
+} from '../icons/icons';
📝 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.

-import { Bell, GroupPeople, Lightning, People, Tag, User, Star2, Archive, Trash, Archive2, ChevronDown } from '../icons/icons';
+import {
+  Bell,
+  GroupPeople,
+  Lightning,
+  People,
+  Tag,
+  User,
+  Users,
+  Briefcase,
+  StickyNote,
+  Star,
+  Star2,
+  Archive,
+  Trash,
+  Archive2,
+  ChevronDown,
+} from '../icons/icons';
apps/mail/lib/prompts.ts (2)

158-168: ⚠️ Potential issue

Escape user-supplied context before embedding into XML

currentSubject, currentDraft, and every element of recipients are interpolated verbatim into the XML prompt.
Because these strings come from user input they can legally contain <, &, ", etc. – characters that break the XML and leak prompt-injection vectors.

-    const currentSubjectContent = currentSubject ? `\n\n<current_subject>${currentSubject}</current_subject>\n\n` : '';
-    const currentDraftContent  = currentDraft  ? `\n\n<current_draft>${currentDraft}</current_draft>\n\n`   : '';
-    const recipientsContent    = recipients    ? `\n\n<recipients>${recipients.join(', ')}</recipients>\n\n` : '';
+    const currentSubjectContent = currentSubject
+      ? `\n\n<current_subject>${escapeXml(currentSubject)}</current_subject>\n\n`
+      : '';
+    const currentDraftContent  = currentDraft
+      ? `\n\n<current_draft>${escapeXml(currentDraft)}</current_draft>\n\n`
+      : '';
+    const recipientsContent    = recipients
+      ? `\n\n<recipients>${recipients.map(escapeXml).join(', ')}</recipients>\n\n`
+      : '';

Failing to escape these fields can mangle the prompt and allow the user to smuggle instructions that override your system rules.
Please sanitize before release.

Also applies to: 170-176


178-183: 🛠️ Refactor suggestion

Escape & minify the injected JSON profile

styleProfileJSON is inserted raw. Any &, <, > or quote from future metric keys/values will invalidate the XML.
Additionally the pretty-print (null, 2) inflates the token count (~×3) for no functional gain.

-  const styleProfileJSON = JSON.stringify(styleProfile, null, 2);
+  const styleProfileJSON = escapeXml(JSON.stringify(styleProfile));

(You can keep indentation if you value readability, but escaping is mandatory.)

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

export const StyledEmailAssistantSystemPrompt = (userName: string, styleProfile: WritingStyleMatrix) => {
    const safeName = escapeXml(userName);
-    const styleProfileJSON = JSON.stringify(styleProfile, null, 2);
+    const styleProfileJSON = escapeXml(JSON.stringify(styleProfile));

    return `
   <system_prompt>
apps/mail/lib/ai.ts (1)

48-51: ⚠️ Potential issue

Environment-key mismatch: OPENAI_API_KEY vs Groq usage

generateText is called with groq('llama-3.1-8b-instant'), yet the guard still checks for process.env.OPENAI_API_KEY.
On a fresh deployment that only configures GROQ_API_KEY this branch will throw “OpenAI API key is not configured”.

Either:

- if (!process.env.OPENAI_API_KEY) {
-   throw new Error('OpenAI API key is not configured');
- }
+ if (!process.env.GROQ_API_KEY) {
+   throw new Error('Groq API key is not configured');
+ }

or remove the check entirely if the Groq SDK surfaces its own error.

apps/mail/services/writing-style-service.ts (1)

131-155: ⚠️ Potential issue

takeTopCoverage always returns an empty object when the first bucket meets the threshold

Because the predicate uses < coverage, a first entry that pushes running / total equal to 0.95 fails the takeWhile test and nothing is kept.

Example: counts = { hello: 95, hi: 5 }, coverage = 0.95
running becomes 95 → 95 / 100 = 0.95 not < 0.95 ⇒ entry dropped ⇒ {} returned.

-    takeWhile(([_, count]) => {
-      running += count
-      return running / total < coverage
-    }),
+    takeWhile(([_, count]) => {
+      running += count
+      return running / total <= coverage
+    }),

Use <= (or push the entry before the test) so the most-frequent tokens are retained up to the desired coverage.

📝 Committable suggestion

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

const takeTopCoverage = (data: Record<string, number>, coverage = TAKE_TOP_COVERAGE) => {
  const total = pipe(
    data,
    values(),
    sum(),
  )

  if (total === 0) {
    return {}
  }

  let running = 0

  return pipe(
    data,
    entries(),
    sortBy(([_, count]) => -count),
    takeWhile(([_, count]) => {
      running += count
      return running / total <= coverage
    }),
    fromEntries(),
  )
}
apps/mail/components/mail/thread-display.tsx (2)

271-285: 🛠️ Refactor suggestion

Star toggle only updates local state – server remains stale

handleToggleStar flips isStarred and fires a toast but never persists the change.
On the next refetch the UI will revert because emailData.latest.tags is still the source of truth.

-    setIsStarred(newStarredState);
-    if (newStarredState) {
+    try {
+      // Persist the change first
+      await modifyLabels([threadId], {
+        addLabels: newStarredState ? ['STARRED'] : [],
+        removeLabels: newStarredState ? [] : ['STARRED'],
+      });
+
+      setIsStarred(newStarredState);
+      if (newStarredState) {
         toast.custom((id) => <SuccessEmailToast message={t('common.actions.addedToFavorites')} />);
-    } else {
+      } else {
         toast.custom((id) => (
           <SuccessEmailToast message={t('common.actions.removedFromFavorites')} />
         ));
-    }
-    mutateThreads();
+      }
+      mutateThreads();
+    } catch {
+      toast.error(t('common.actions.failedToMove')); // Re-use an existing i18n key
+    }

[pflag_critical_issue]

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

  // Add handleToggleStar function
  const handleToggleStar = useCallback(async () => {
    if (!emailData || !threadId) return;

    const newStarredState = !isStarred;
    try {
      // Persist the change first
      await modifyLabels([threadId], {
        addLabels: newStarredState ? ['STARRED'] : [],
        removeLabels: newStarredState ? [] : ['STARRED'],
      });

      setIsStarred(newStarredState);
      if (newStarredState) {
        toast.custom((id) => <SuccessEmailToast message={t('common.actions.addedToFavorites')} />);
      } else {
        toast.custom((id) => (
          <SuccessEmailToast message={t('common.actions.removedFromFavorites')} />
        ));
      }
      mutateThreads();
    } catch {
      toast.error(t('common.actions.failedToMove')); // Re-use an existing i18n key
    }
  }, [emailData, threadId, isStarred, mutateThreads, t]);

240-266: 🛠️ Refactor suggestion

Background queue entry is never cleaned up

You enqueue the thread (setBackgroundQueue({ type: 'add', … })) but the counterpart delete call is commented out.
If the request fails – or even succeeds – the queue will grow indefinitely, breaking the visual “in-progress” cues.

Add the deletion in both finally and catch blocks:

        finally: async () => {
          await Promise.all([mutateStats(), mutateThreads()]);
-          //   setBackgroundQueue({ type: 'delete', threadId: `thread:${threadId}` });
+          setBackgroundQueue({ type: 'delete', threadId: `thread:${threadId}` });
        },
+       error: () => {
+         setBackgroundQueue({ type: 'delete', threadId: `thread:${threadId}` });
+       },

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

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

151-214: 🛠️ Refactor suggestion

MailDisplayLabels still expects string[], losing colour & id information

Everywhere else labels are now objects ({ id, name }).
Accepting plain strings forces upstream callers to map(t => t.name) and drops id, which will bite when you need it for actions (e.g. filter by label).

Refactor signature to labels: { id: string; name: string }[] and adjust mapping.


734-774: ⚠️ Potential issue

Large attachments decoded in the main thread

atob + array allocation copies the entire file into memory; for multi-MB files this freezes the UI.
Prefer the Streams API:

- const byteCharacters = atob(attachment.body);
-
- const blob = new Blob([byteArray], { type: attachment.mimeType });
+ const resp = await fetch(`data:${attachment.mimeType};base64,${attachment.body}`);
+ const blob = await resp.blob();

This keeps parsing in the browser’s decoding pipeline and is far more memory-efficient.

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

apps/mail/app/api/driver/microsoft.ts (3)

235-249: ⚠️ Potential issue

parseOutgoing is built but never used

You build a raw MIME message (parseOutgoing) yet discard it, sending Graph’s JSON payload instead.
Either:

  1. Remove the expensive call, or
  2. Switch to /me/sendMail’s saveToSentItems=true with the raw MIME in binary content to preserve headers.

Current form wastes CPU and slows sends.


419-431: ⚠️ Potential issue

modifyLabels ignores removeLabels and may duplicate categories

Graph patches replace the entire categories array.
If you only send addLabels, previously-assigned labels linger; duplicates are also possible.

- categories: options.addLabels,
+ categories: [
+   ...new Set([
+     ...(message.categories ?? []).filter((c) => !options.removeLabels.includes(c)),
+     ...options.addLabels,
+   ]),
+ ],

Make sure to fetch the current categories first or include them in the request body.

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


305-310: 🛠️ Refactor suggestion

Hard-coded .top(3) ignores maxResults

list() promises maxResults but always returns three items.
Replace with:

- .top(3)
+ .top(maxResults)

Also bump the skip calculation when you re-enable pagination.

📝 Committable suggestion

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

      const response = await client
        .api('/me/messages')
        // .filter(searchQuery)
-       .top(3)
+       .top(maxResults)
        // .skip(pageToken ? parseInt(pageToken.toString()) : 0)
        .get();

@MrgSub MrgSub merged commit 6318b05 into staging Apr 29, 2025
3 of 4 checks passed
@MrgSub MrgSub deleted the toolbar-on-hover branch April 29, 2025 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants