Conversation
feat: add helper cli
chore: add sentry proxy
forwarded from simplelogin
folders/labels for outlook and gmail
…nal-sender Use SimpleLogin Original Sender for the Email Sender Field
adds context menu back
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
zero-staging | 76ae73d | May 22 2025, 06:41 AM |
WalkthroughThis 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
Sequence Diagram(s)Environment Setup Flow with Nizzy CLIsequenceDiagram
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
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
AI Chat Tool InvocationsequenceDiagram
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
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 12
🔭 Outside diff range comments (2)
apps/mail/components/ui/nav-main.tsx (1)
80-82:⚠️ Potential issueFix missing query-string parsing –
searchParamsis always empty
new URLSearchParams()starts with an empty object, so every call tosearchParams.get(...)returnsnull.
That silently breaks:
- Back-navigation via the
fromparameter.- 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 suggestionIncomplete / dead tools inflate surface area
createEmail,_listEmails,writeEmail, andsendDraftare defined but either
- lack an
executeimplementation, 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 variableThe 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 configurationThe 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** - Runpnpm nizzy envto 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 Runpnpm nizzy envto 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.envfile. 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.envfile. And you have ranpnpm nizzy syncto 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: PreferuseQuery’s built-in typing instead of falling back toany[].
userLabelsis typed asunknown(orany) becausetrpc.labels.list.queryOptions()returns an un-annotated generic.
Two consequences:
checkLabelExists()later receivesany[], forfeiting compile-time safety.- 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.
checkLabelExistsis 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.USERmay 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.
toolResultsisundefinedwhen 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 detectionThe 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 validationThe
runCommandfunction 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 functionThe 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 valuesThe switch component uses a hardcoded color value
#0B84FEfor 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 formattingThe 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 colorsThe 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 whitespaceThere'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_toVITE_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
.envfileThe 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-interactiveflag 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
anytype for bothlabelandactiveAccountprops. 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
Accounttype 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 casesThe
processMailFoldersHierarchymethod has a defaultmaxDepthof 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 componentThe new
Bookmarkicon 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: Improvegroupstyping for clarity & safetyUsing
typeof datamakes every bucket potentiallyundefined, while the logic assumes arrays.
Explicitly typing asLabelType[](orArray<LabelType>) removes the need for casts and prevents
accidental runtimeundefinedchecks.-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 strayconsole.logused for debuggingLeftover 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
annualPricecould yield a floating-point like8.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: ImprovewebSearcherror handling & redaction
fetcherrors whenresponse.okis false (HTTP 4xx/5xx) are currently parsed as JSON and may throw.- Returned payload includes the full Perplexity response, which might expose billing metadata or internal traces to the caller.
- 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 vectorsRight 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
nullmasks infrastructure outages and makes debugging difficult.
471-486: Minor typos & colour-set drift increateLabel
- 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
colorsvia a shared constants module to guarantee synchronisation.
250-263:listEmailsviolates YAGNI – folded into_listEmails?You now maintain two almost-identical search helpers (
listEmailsand_listEmails). The latter enriches results with full thread data and seems more generally useful. Consider collapsing both into one function with aincludeThreadsflag 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
⛔ Files ignored due to path filters (2)
apps/mail/public/mail-pixel.svgis excluded by!**/*.svgpnpm-lock.yamlis 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 initializationThe 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 proxyThe implementation correctly intercepts requests to the
/monitoringpath and forwards them to the Sentry ingestion endpoint, preserving the original request properties. This works well with thetunnelconfiguration in the Sentry initialization.apps/mail/components/ui/gauge.tsx (1)
39-39: Responsive padding adjustment looks goodAdding 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 formattingThe 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 flowsThe improved error handling for session errors is a good practice. By logging errors to the console and returning
nullinstead 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_v1Good addition of the typed import to properly type the payload parameter in the new utility function.
100-106: Well-implemented SimpleLogin supportThe new
getSimpleLoginSenderutility 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-scrollbarutility
The.no-scrollbarclass 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
Themt-20utility 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 tohide-scrollbarelsewhere.Run this to confirm no leftover usages:
🏁 Script executed:
#!/bin/bash rg "hide-scrollbar" -nLength of output: 22
Scrollbar utility class rename confirmed
No remaining usages ofhide-scrollbarwere 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-prodflag 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
stagingtolocal, 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 fLength 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 envto setup your environment variables”- “Run
pnpm nizzy syncto sync your environment variables and types”- .github/CONTRIBUTING.md:
- “Run
pnpm nizzy envto setup your environment variables”- “Run
pnpm nizzy syncto sync your environment variables and types”No further action required.
packages/cli/package.json (1)
1-11: LGTM: Package configuration for the new CLI toolThe package configuration looks good with appropriate type definition and dependencies for a CLI tool. Using
@clack/promptsis a good choice for interactive CLI interfaces, andtiny-globwill be useful for file operations..github/CONTRIBUTING.md (1)
35-36: Great simplification of the environment setup processThe 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 componentThe addition of
@radix-ui/react-switchis 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 targetRemoving 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 stylingMoving 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 nameChanging from
hide-scrollbartono-scrollbarensures consistency with the global CSS changes throughout the application.apps/server/src/types.ts (2)
11-11: Good addition for nested label structuresAdding the recursive
labelsproperty to theLabeltype 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 toolsThe 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 emptyclassNameattribute 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: ReformattedSidebarHeaderclassNamefor readability
The reordering and line-breaks improve clarity, and the conditional padding (px-2vsmd:px-4) is applied correctly.
103-105:SidebarContentenhanced withoverflow-x-hidden
Addingoverflow-x-hiddenprevents horizontal scroll and aligns with the updated scrollbar styling. Layout and formatting changes are consistent.
119-123: RefactoredSidebarFooterpadding classes
Reorderingpx-0 pb-0and 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 allfocus-visiblering classes in favor offocus:ring-0 ring-0disables 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: ImportedgetSimpleLoginSenderutility
This import supports the new logic to prioritize the SimpleLogin original sender.packages/cli/src/commands/index.ts (2)
1-5: Define a standardizedCommandinterface
The newCommandtype correctly captures the essential CLI command shape. Consider whether you’ll need to pass arguments or a context object torunin the future, but this baseline is solid.
7-9: Centralized command exports
Re-exportingfixEnv,reinstallNodeModules, andsyncunder a commonCommandinterface cleans up imports elsewhere and keeps the CLI entrypoint modular.apps/mail/components/context/label-sidebar-context.tsx (3)
37-37: Interface change looks goodAdded optional
hideprop to theLabelSidebarContextMenuPropsinterface, enabling conditional rendering of the context menu.
40-40: Function signature update is correctUpdated component function signature to include the new
hideprop, maintaining backward compatibility.
58-59: Good implementation of conditional renderingThis 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 toolThe 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 environmentThe 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 environmentThe same AI and vectorization bindings are appropriately added to the staging environment, maintaining consistency across environments.
130-130: Updated staging URL to be more descriptiveChanging 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 environmentThe 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 thenizzyCLI 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 synccommand 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 theResponseas an exception, but many runtimes (including Hono) only forwardErrorsub-classes through error middleware. Preferreturning 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 sentThis avoids unexpected “Response is not Error” stack traces.
apps/mail/components/ui/switch.tsx (1)
1-3: LGTM on client directive and import changesThe 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 refactoringThe simplification to use a single
PricingCardcomponent instead of inline pricing cards improves maintainability and follows the component-based architecture pattern.
235-237: Using flexbox for footer positioningUsing
mt-autofor 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:flexclasses 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:
- A gauge showing message usage
- A clear upgrade path with a dedicated button
- Integration with the PricingDialog component
- 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-scrollbartono-scrollbarto 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-anywhereand 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
isSavingDraftstate 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
isLoadingorisSavingDraftis true, which prevents potential race conditions or duplicate sends during draft saving operations.
370-370: Enhanced draft saving state management.The
saveDraftfunction now properly manages theisSavingDraftstate:
- Sets it to true at the beginning of the operation
- 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-scrollbartono-scrollbarto 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
Linkcomponent 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
AskZeroMailboxtool 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
WebSearchtool 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
AskZeroThreadtool 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 managementThe
handleFilterByLabelfunction 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 behaviorsThe
handleFolderClickfunction 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 structureThe 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 identificationThe updated
getUserLabelsmethod 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 labelsThe 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 foldersThe 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 componentThe new
FolderOpenicon 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 renderingThe
Papericon's viewBox has been changed from a likely default value to1 -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 iconThe
ThickCheckicon has been reformatted for better readability and consistency with other icon components.
1727-1737: Properly reformatted PurpleThickCheck iconThe
PurpleThickCheckicon has been reformatted for better readability and consistency with other icon components.apps/server/src/routes/agent/tools.ts (1)
580-596:publicToolscurrently returns an empty objectAll tool definitions are commented out, so
publicToolsresolves 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.
| collapsible="icon" | ||
| {...props} | ||
| className={`bg-lightBackground dark:bg-darkBackground flex h-screen select-none flex-col items-center ${state === 'collapsed' ? '' : ''} pb-2`} | ||
| > |
There was a problem hiding this comment.
🛠️ 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.
| // 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'; |
There was a problem hiding this comment.
🛠️ 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.
| // 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.
| 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; | ||
| }), |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Refactored summary generation to use vector search and AI model
The implementation has been changed to use a two-step process:
- Retrieve content metadata using vector search
- 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.
| 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.
| await command.run(); | ||
| outro(`Done!`); | ||
| process.exit(0); |
There was a problem hiding this comment.
🛠️ 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.
| 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) }); |
There was a problem hiding this comment.
Hard-coded email makes publicChatHandler unsafe and unusable in prod.
Searching the DB with eq(connection.email, 'test@test.com'):
- Leaks an internal test address.
- Breaks for any real user.
- 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.
| 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.
| 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'); | ||
| }, |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| const isActive = searchValue.value.includes(`label:${label.name}`); | ||
| const isFolderActive = isActive || window.location.pathname.includes(`/mail/label/${label.id}`); |
There was a problem hiding this comment.
💡 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 10Length 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.tsxLength 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.locationdirectly:import { useLocation, useNavigate } from 'react-router';
- Update the
isFolderActivelogic to useuseLocation():- 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.
| import { FileIcon } from 'lucide-react'; | ||
| import { Accordion } from 'radix-ui'; |
There was a problem hiding this comment.
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.
| 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.
| <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} | ||
| > |
There was a problem hiding this comment.
onValueChange corrupts accordion state & duplicates IDs
value already contains the full list of expanded items.
Appending value[0] back into state:
- Loses collapse information (closed items stay “open” in state).
- 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.
| <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.
| execute: async (data) => { | ||
| try { | ||
| const { draftId, ...mail } = data; | ||
|
|
||
| if (draftId) { | ||
| await driver.sendDraft(draftId, { | ||
| ...mail, | ||
| attachments: [], | ||
| headers: {}, | ||
| }); | ||
| } else { | ||
| await driver.create({ | ||
| ...mail, | ||
| attachments: [], | ||
| headers: {}, | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores
Documentation