"Category" instead of "label" for outlook#696
Conversation
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
WalkthroughProvider context and provider-aware email terminology were added across UI, assistant personas/examples, rule prompt generation, and utilities; many components/functions now accept or derive a provider and use getEmailTerminology(provider) to render label/category wording and generate provider-specific prompts. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as RuleForm / Rules UI
participant AccountCtx as useAccount()
participant Term as getEmailTerminology(provider)
participant API as createRuleAction / updatePromptFileOnRuleCreated
participant PF as PromptFile (appendRulePrompt)
User->>UI: Create/modify rule
UI->>AccountCtx: read provider
AccountCtx-->>UI: provider
UI->>Term: getEmailTerminology(provider)
Term-->>UI: terminology
UI->>API: createRule(payload + provider)
API->>PF: updatePromptFileOnRuleCreated({ emailAccountId, provider, rule })
PF->>Term: getEmailTerminology(provider)
Term-->>PF: terminology
PF->>PF: createPromptFromRule(rule, provider)
PF-->>API: appended
API-->>UI: success
sequenceDiagram
autonumber
actor User
participant App as App Navigation
participant Comp as UI Component
participant Term as getEmailTerminology(provider)
User->>App: Navigate to mailbox UI
App->>Comp: provide provider via useAccount()
Comp->>Term: getEmailTerminology(provider)
Term-->>Comp: terminology
Comp-->>User: render labels/categories using terminology
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/utils/ai/rule/generate-prompt-on-update-rule.ts (1)
28-30: Bug: Returning "" can wipe the entire prompt fileIf
existingPromptorupdatedRulePromptis falsy, returning""may overwrite a non-emptyrulesPromptwith an empty string downstream. Safer to return the original prompt unchanged.- if (!existingPrompt) return ""; - if (!updatedRulePrompt) return ""; + if (!existingPrompt) return existingPrompt; + if (!updatedRulePrompt) return existingPrompt;
🧹 Nitpick comments (24)
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx (2)
168-169: Widen provider prop type to match context and guard compatibilityChecklist only needs provider for a boolean gate, and isGoogleProvider accepts string | null | undefined. Typing provider as string risks a mismatch with useAccount() if provider can be null/undefined.
Apply this diff to make the type safer and consistent:
function Checklist({ emailAccountId, - provider, + provider, @@ }: { emailAccountId: string; - provider: string; + provider?: string | null; completedCount: number; totalSteps: number;Also applies to: 177-177
237-249: Optional: Use an anchor for external URLs instead of Next.js LinkYou’re passing EXTENSION_URL (external) to StepItem which always renders a Next.js Link. Next recommends plain for external links. Consider making StepItem render when href is external.
Outside this range, update StepItem to switch based on href:
// Replace the Link-only wrapper inside StepItem with: const isExternal = /^https?:\/\//.test(href); return isExternal ? ( <a className={`border-b border-border last:border-0 ${completed ? "opacity-60" : ""}`} href={href} target={linkProps?.target ?? "_blank"} rel={linkProps?.rel ?? "noopener noreferrer"} > {/* existing inner content */} </a> ) : ( <Link className={`border-b border-border last:border-0 ${completed ? "opacity-60" : ""}`} href={href} > {/* existing inner content */} </Link> );apps/web/app/(app)/accounts/AddAccount.tsx (5)
127-131: Provide meaningful alt text for accessibilityEmpty alt text hides the logo from screen readers. Since this icon conveys provider context, prefer a descriptive alt.
Apply this diff:
- <Image src={image} alt="" width={24} height={24} unoptimized /> + <Image + src={image} + alt={`${name} logo`} + width={24} + height={24} + unoptimized + />
29-37: Handle non-2xx responses from fetchIf the endpoint returns a non-OK status, response.json() may throw or produce an unexpected shape. Guard with response.ok and surface a toast error.
Apply this diff:
const handleMergeGoogle = async () => { const response = await fetch("/api/google/linking/auth-url", { method: "GET", headers: { "Content-Type": "application/json" }, }); + if (!response.ok) { + throw new Error(`Google linking auth-url failed: ${response.status}`); + } + const data: GetAuthLinkUrlResponse = await response.json(); window.location.href = data.url; };
48-56: Handle non-2xx responses from fetch (Microsoft)Mirror the response.ok handling for the Outlook flow to keep behavior consistent.
Apply this diff:
const handleMergeMicrosoft = async () => { const response = await fetch("/api/outlook/linking/auth-url", { method: "GET", headers: { "Content-Type": "application/json" }, }); + if (!response.ok) { + throw new Error(`Outlook linking auth-url failed: ${response.status}`); + } + const data: GetOutlookAuthLinkUrlResponse = await response.json(); window.location.href = data.url; };
97-103: Avoid console usage per project standardsThe coding guidelines forbid console.* in the codebase. Since you already surface a toast, dropping the console line is sufficient.
Apply this diff:
} catch (error) { - console.error(`Error initiating ${name} link:`, error); toastError({ title: `Error initiating ${name} link`, description: "Please try again or contact support", }); }
112-118: Avoid console usage per project standards (merge flow)Same rationale as above, for the merge path.
Apply this diff:
} catch (error) { - console.error(`Error initiating ${name} link:`, error); toastError({ title: `Error initiating ${name} link`, description: "Please try again or contact support", }); }apps/web/components/SideNav.tsx (1)
298-301: Potential duplicate “Categories” headings for OutlookFor Outlook, terminology.label.pluralCapitalized becomes “Categories”, which duplicates the static “Categories” group above (Lines 293–295). Consider disambiguating or conditionally rendering the system categories block only for Gmail to avoid two “Categories” sections.
One approach (outside this diff range):
// Around the static categories group const { provider } = useAccount(); {isGoogleProvider(provider) && ( <SidebarGroup> <SidebarGroupLabel>Categories</SidebarGroupLabel> <SideNavMenu items={bottomMailLinks} activeHref={path} /> </SidebarGroup> )}Or rename the user-defined section for Outlook to “Custom Categories”.
apps/web/utils/ai/rule/generate-prompt-on-update-rule.ts (2)
19-26: Provider threading into prompt generation looks goodPassing
emailAccount.account.providerinto both prompt builders aligns with the updated API and the PR’s provider-aware copy objective. Minor nit: cache the provider in a local const to avoid repeating the chain and to make nullability checks easier if needed later.- const currentRulePrompt = createPromptFromRule( - currentRule, - emailAccount.account.provider, - ); - const updatedRulePrompt = createPromptFromRule( - updatedRule, - emailAccount.account.provider, - ); + const provider = emailAccount.account.provider; + const currentRulePrompt = createPromptFromRule(currentRule, provider); + const updatedRulePrompt = createPromptFromRule(updatedRule, provider);
64-75: Add error handling and retries around LLM call; return original prompt on failureTo align with AI-utils guidelines: add try/catch, log context, apply retry (withRetry), and fall back to
existingPromptto prevent data loss if the AI call fails.- const aiResponse = await generateObject({ - ...modelOptions, - system, - prompt, - schema: z.object({ - updatedPrompt: z - .string() - .describe("The updated prompt file with the rule updated"), - }), - }); - - return aiResponse.object.updatedPrompt; + try { + const aiResponse = await generateObject({ + ...modelOptions, + system, + prompt, + schema: z.object({ + updatedPrompt: z + .string() + .describe("The updated prompt file with the rule updated"), + }), + }); + return aiResponse.object.updatedPrompt; + } catch (err) { + // Consider using a scoped logger and withRetry for transient errors. + return existingPrompt; + }Outside this range, consider initializing a scoped logger at the top and wrapping
generateObjectinwithRetry(...)to handle transient failures consistently.apps/web/utils/ai/rule/create-prompt-from-rule.test.ts (1)
12-210: Tests updated correctly; add Outlook-specific assertions to cover terminology switchAll test calls updated to
createPromptFromRule(rule, "google")and expectations still pass. To fully validate this PR’s objective, add at least one test using an Outlook/Microsoft provider and assert “categorize as …”.Example test to add (location: anywhere in this suite):
it("uses provider-specific terminology for LABEL on Outlook", () => { const rule = { from: "updates@microsoft.com", actions: [{ type: "LABEL", label: "Work" }] as unknown as Action[], } as unknown as Rule & { actions: Action[] }; expect(createPromptFromRule(rule, "microsoft")).toBe( 'For emails from "updates@microsoft.com", categorize as "Work"', ); });If your provider id differs (e.g., “outlook” or “microsoft-outlook”), adjust accordingly.
apps/web/utils/rule/prompt-file.ts (1)
44-55: Guard against empty updated prompt payloadsGiven the earlier note in generate-prompt-on-update-rule, consider avoiding overwriting
rulesPromptwith an empty string. IfupdatedPromptis falsy, keep the existing prompt unchanged.- await prisma.emailAccount.update({ - where: { id: emailAccountId }, - data: { rulesPrompt: updatedPrompt }, - }); + await prisma.emailAccount.update({ + where: { id: emailAccountId }, + data: { rulesPrompt: updatedPrompt || emailAccount.rulesPrompt || "" }, + });Note: This requires selecting
rulesPromptin the same scope or passing it along.apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx (3)
320-323: Avoid non-null assertions on label fieldsUsing
label.id!andlabel.name!violates our TS guideline (no non-null assertions) and risks runtime errors when data is incomplete.Apply a guard and pass only when both are present:
- await onAutoArchiveAndLabel(label.id!, label.name!); + if (label.id && label.name) { + await onAutoArchiveAndLabel(label.id, label.name); + } else { + toastError({ description: "Invalid label. Please try again." }); + }
447-451: Verify Gmail-specific action on non-Google providersThis submenu uses Labels +
createFilterAction, which appears Gmail-specific, yet it’s shown for all providers. For Outlook, you likely want either a different action (rules/categories) or to gate this menu to Google.Proposed gating (if there’s no Outlook equivalent yet):
{isGoogleProvider(provider) && ( <DropdownMenuSub> <DropdownMenuSubTrigger> <TagIcon className="mr-2 size-4" /> <span>{terminology.label.singularCapitalized} future emails</span> </DropdownMenuSubTrigger> <DropdownMenuPortal> <LabelsSubMenu labels={labels} onClick={async (label) => { const res = await createFilterAction(emailAccountId, { from: item.name, gmailLabelId: label.id, }); if (res?.serverError) { toastError({ title: "Error", description: `Failed to add ${item.name} to ${label.name}. ${res.serverError || ""}`, }); } else { toastSuccess({ title: "Success!", description: `Added ${item.name} to ${label.name}`, }); } }} /> </DropdownMenuPortal> </DropdownMenuSub> )}If an Outlook equivalent exists, wire that into the else branch instead of gating.
104-107: Make tooltip copy provider-agnostic“Auto archive emails using Gmail filters.” will show for Outlook too. Prefer generic copy.
Suggestion:
content={hasUnsubscribeAccess ? "Skip Inbox automatically using filters." : undefined}apps/web/utils/terminology.ts (1)
17-18: Widen parameter type to match helper and callsites
isMicrosoftProvideracceptsstring | null | undefined. Widening here avoids unnecessary casts upstream and is safer if provider is not yet set.Apply this diff:
-export function getEmailTerminology(provider: string): EmailTerminology { +export function getEmailTerminology( + provider: string | null | undefined, +): EmailTerminology {apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2)
1392-1434: Follow-up: “Label” copy in combobox is Gmail-specificPlaceholder/empty-state strings still say “label”. For Outlook, this should be “category”.
Introduce provider-aware copy here (will require passing
providerdown or passingterminology):
- placeholder:
Select a ${terminology.label.singular}- empty title:
No ${terminology.label.plural}- toasts/button text: use
${terminology.label.action.toLowerCase()}(e.g., “Creating category …”, “Created category …”,Create "${search}" ${terminology.label.singular})
If helpful, I can draft a patch once you confirm the preferred approach (pass provider vs. terminology prop).
233-236: Remove console usage in UI codeWe avoid
consolein app code. Errors are already surfaced viatoastError.Replace both
console.error(res);with a scoped debug logger or remove entirely:// Remove console.error; rely on toastError which is already presentAlso applies to: 261-263
apps/web/components/assistant-chat/examples-dialog.tsx (1)
83-86: Guard against invalid persona when provider changesIf the user switches accounts (and thus providers), a previously selected
personamay no longer exist in the next provider’s personas, causingpersonas[selectedPersona]?.promptArray[...]to be undefined at Lines 64–70. Reset the selection when the available persona keys change.Apply this diff to add the guard:
- import { useState } from "react"; + import { useEffect, useState } from "react";const [selectedPersona, setSelectedPersona] = useQueryState( "persona", parseAsStringEnum(Object.keys(personas)), ); + + // Reset if selected persona is not available for the current provider/personas set + useEffect(() => { + if (selectedPersona && !(selectedPersona in personas)) { + setSelectedPersona(null); + setSelectedExamples([]); + } + }, [selectedPersona, personas, setSelectedPersona]);Also applies to: 64-70, 3-3
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx (2)
276-283: Avoid hardcoding 'other' persona in placeholder
personas.otherassumes a key that may not exist across providers. Safer to pick the first persona’s examples.Add a fallback near the top of
RulesPromptForm:const editorRef = useRef<SimpleRichTextEditorRef>(null); + const defaultExamples = + Object.values(personas)[0]?.promptArray?.slice(0, 2) ?? [];Then update the placeholder:
- * ${personas.other.promptArray[0]} - * ${personas.other.promptArray[1]} + * ${defaultExamples[0] ?? ""} + * ${defaultExamples[1] ?? ""}Also applies to: 135-136
410-435: Heuristic tweak: recognize "category/categorize" as label-like actionsExamples for Outlook will say “categorize” or “category” rather than “label”. Including those keeps coloring/types consistent.
- if (lowerExample.includes("label")) { + if ( + lowerExample.includes("label") || + lowerExample.includes("categor") // category / categorize + ) { return { type: "label", color }; }apps/web/utils/actions/rule.ts (1)
488-498: Use provider-aware terminology in onboarding prompt stringsThe onboarding strings still literally say “Label all ...”. Since
provideris available, switch to provider-aware wording (e.g., “Categorize” for Outlook) to keep user-facing copy consistent.Example approach:
- Import terminology:
import { getEmailTerminology } from "@/utils/terminology";
- Inside
createRulesOnboardingAction, derive:const terminology = getEmailTerminology(provider); const actionVerb = terminology.label.action; // "Label" (Gmail) / "Categorize" (Outlook) const singular = terminology.label.singular; // "label" / "category"
- Replace prompt templates, e.g.:
// Before: "Label all newsletters as @[Newsletter]" // After: `${actionVerb} all newsletters as @[Newsletter]`I can push a patch updating all occurrences if you’d like.
Also applies to: 685-697, 703-714, 720-731, 737-748, 754-765, 770-785, 793-797
apps/web/app/(app)/[emailAccountId]/assistant/examples.ts (2)
28-43: Consider more robust terminology replacementThe current implementation uses simple regex replacements which might have edge cases:
- The regex
\bLabel\bmight miss cases with special characters- The double replacement approach could potentially cause issues if the terminology itself contains the word "label"
Consider a more robust implementation that handles edge cases:
function processPromptsWithTerminology( prompts: string[], provider: string, ): string[] { const terminology = getEmailTerminology(provider); return prompts.map((prompt) => { - // Replace "Label" at the beginning of sentences or after punctuation - let processed = prompt.replace(/\bLabel\b/g, terminology.label.action); - // Replace lowercase "label" in the middle of sentences - processed = processed.replace( - /\blabel\b/g, - terminology.label.action.toLowerCase(), - ); - return processed; + // Use a single regex with case-insensitive flag and callback function + return prompt.replace(/\blabel\b/gi, (match) => { + // Preserve the original case + return match[0] === match[0].toUpperCase() + ? terminology.label.action + : terminology.label.action.toLowerCase(); + }); }); }
85-348: Consider memoizing persona generation for performanceThe
getPersonasfunction creates a new object with computed properties on every call. Since the provider doesn't change frequently during a session, this could be optimized.Consider memoizing the result to avoid recomputing on every call:
+const personasCache = new Map<string, ReturnType<typeof getPersonas>>(); + export function getPersonas(provider: string) { + const cached = personasCache.get(provider); + if (cached) { + return cached; + } + const personas = { founder: { label: "🚀 Founder", promptArray: processPromptsWithTerminology(founderPromptArray, provider), get prompt() { return formatPromptArray(this.promptArray); }, }, // ... rest of the personas }; + + personasCache.set(provider, personas); + return personas; }
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx (2)
300-346: Hard-coded “Gmail inbox” copy is incorrect for OutlookThis user-facing string will mislead Outlook users. Either generalize (“your inbox”) or render provider-specific copy.
Two simple fixes:
- Generic: “Our AI will analyze your inbox and create a customized prompt…”
- Provider-aware (requires passing provider to this component):
const providerName = provider === "outlook" ? "Outlook" : "Gmail"; <Tooltip content={`Our AI will analyze your ${providerName} inbox and create a customized prompt for your assistant.`}>
382-399: Make convertLabelsToDisplay provider-awareconvertLabelsToDisplay (apps/web/utils/mention.ts) currently only quotes mentions and has no knowledge of the email provider’s terminology (e.g. “label” vs “category”). To ensure examplePrompts render provider-specific wording, update it (or wrap its output) to:
• Accept the current provider as an argument
• Invoke getEmailTerminology(provider) to choose between label/category and apply that termAffected locations:
- apps/web/utils/mention.ts: change
convertLabelsToDisplay(text: string): string→convertLabelsToDisplay(text: string, provider: string): string(or create a thin wrapper)- apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx: pass down the provider when calling convertLabelsToDisplay(example, provider)
♻️ Duplicate comments (1)
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx (1)
106-117: Prop typing uses exported Personas alias — fixes earlier typeof-on-type pitfallUsing the exported Personas type avoids the invalid ReturnType<typeof on a type-only import pattern flagged previously.
🧹 Nitpick comments (10)
apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx (1)
196-203: Nit: compute terminology once to avoid repeated calls inside the map.getEmailTerminology(rule.emailAccount.account.provider) is invoked for every action render. Since provider is constant per rule, compute once for readability and micro-optimization.
Apply this diff within the current block:
- <span> - { - getEmailTerminology( - rule.emailAccount.account.provider, - ).label.action - } - : {action.label} - </span> + <span>{actionLabelPrefix}: {action.label}</span>Add this line after validating
rule(e.g., right after Line 42):const actionLabelPrefix = getEmailTerminology( rule.emailAccount.account.provider, ).label.action;apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx (5)
43-57: Memoize provider-derived personas/prompts to avoid costly re-computation on every rendergetPersonas(provider) and getExamplePrompts(provider) build sizable data structures. Recompute only when provider changes.
Apply this diff in the selected range:
- const personas = getPersonas(provider); - const examplePrompts = getExamplePrompts(provider); + const personas = useMemo(() => getPersonas(provider), [provider]); + const examplePrompts = useMemo(() => getExamplePrompts(provider), [provider]);And add useMemo to your React import (outside the selected range):
import { useCallback, useEffect, useState, memo, useRef, useMemo } from "react";
278-283: Guard placeholder examples to avoid “undefined” bulletsIf other.promptArray has fewer than 2 items, the placeholder renders “undefined”. Use optional chaining and defaults.
Apply this diff:
- placeholder={`Here's an example of what your prompt might look like: - -* ${personas.other.promptArray[0]} -* ${personas.other.promptArray[1]} + placeholder={`Here's an example of what your prompt might look like: + +* ${personas.other?.promptArray?.[0] ?? ""} +* ${personas.other?.promptArray?.[1] ?? ""} * If someone asks about pricing, reply with: > Hi NAME! > I'm currently offering a 10% discount. Let me know if you're interested!`}
231-247: Make “@Label … for labels” tooltip provider-awareThe copy always says “@Label … for labels”. For Outlook it should read “@category … for categories”. Aligns with the PR objective.
Implementation options:
- Derive terminology via a central helper (e.g., getEmailTerminology(provider)) and render the appropriate token and noun.
- Or render the token via a small transformer and switch the noun based on provider.
Example (high level):
const t = getEmailTerminology(provider); // e.g., returns correct tokens/nouns // Use t in the tooltip: // <span>@{t.labelToken}</span> for {t.labelsNoun}You’ll need to pass provider into RulesPromptForm or compute the text one level up and pass it as props.
411-435: Action-type detection should recognize “category” as well as “label”The classifier only checks for “label”. For Outlook, examples will contain “category”, and will currently fall through to “other”.
Minimal update:
if (lowerExample.includes("label") || lowerExample.includes("category")) { return { type: "label", color }; }Alternatively, derive the term from a shared terminology helper.
44-48: Consider scoping the SWR key by emailAccountId (and/or provider)Using a static key risks cross-account cache bleed if the API’s response depends on the selected account. Safer:
Example:
const { data, isLoading, error, mutate } = useSWR<RulesPromptResponse, { error: string }>( ["/api/user/rules/prompt", emailAccountId], );This preserves the global fetcher and isolates caches by account.
apps/web/components/assistant-chat/tools.tsx (3)
222-225: Prefer the noun (“Category/Label”), not the verb (“Categorize”), for field prefixes.action.fields.label is a value (noun). Showing “Categorize: Finance” reads awkwardly. Use singularCapitalized to render “Category: Finance” for Outlook and “Label: Finance” for Gmail.
Apply this diff:
- if (action.fields?.label) - parts.push( - `${getEmailTerminology(provider).label.action}: ${action.fields.label}`, - ); + if (action.fields?.label) + parts.push( + `${getEmailTerminology(provider).label.singularCapitalized}: ${action.fields.label}`, + );Optional: hoist terminology to avoid recomputing per item:
const terminology = getEmailTerminology(provider); // ... if (action.fields?.label) parts.push(`${terminology.label.singularCapitalized}: ${action.fields.label}`);
512-553: Make renderActionFields provider-aware (hardcoded “Label”).This still prints “Label” for Outlook. For consistency with the PR goal, render the provider-specific noun (“Category” for Outlook, “Label” for Gmail).
Apply this diff within this function:
-function renderActionFields(fields: { +function renderActionFields( + fields: { label?: string | null; content?: string | null; to?: string | null; cc?: string | null; bcc?: string | null; subject?: string | null; url?: string | null; webhookUrl?: string | null; -}) { +}, + provider: string, +) { const fieldEntries = []; // Only add fields that have actual values - if (fields.label) fieldEntries.push(["Label", fields.label]); + const terminology = getEmailTerminology(provider); + if (fields.label) + fieldEntries.push([terminology.label.singularCapitalized, fields.label]); if (fields.subject) fieldEntries.push(["Subject", fields.subject]); if (fields.to) fieldEntries.push(["To", fields.to]); if (fields.cc) fieldEntries.push(["CC", fields.cc]); if (fields.bcc) fieldEntries.push(["BCC", fields.bcc]); if (fields.content) fieldEntries.push(["Content", fields.content]); if (fields.url || fields.webhookUrl) fieldEntries.push(["URL", fields.url || fields.webhookUrl]);Update call sites (outside this range) to pass provider:
// In UpdatedRuleActions (Line ~268) {actionItem.fields && renderActionFields(actionItem.fields, provider)}CreatedRuleToolCard also uses renderActionFields but doesn’t accept provider. Consider adding provider to its props for consistency:
-export function CreatedRuleToolCard({ - args, - ruleId, -}: { - args: CreateRuleTool["input"]; - ruleId?: string; -}) { +export function CreatedRuleToolCard({ + args, + ruleId, + provider, +}: { + args: CreateRuleTool["input"]; + ruleId?: string; + provider: string; +}) { // ... - {action.fields && renderActionFields(action.fields)} + {action.fields && renderActionFields(action.fields, provider)}If adding provider to CreatedRuleToolCard is too broad for this PR, at minimum ensure UpdatedRuleActions passes provider to renderActionFields to avoid mixed terminology within the same view.
214-241: Minor: avoid recomputing terminology per action in formatActions.Micro-optimization and readability: compute terminology once per component render or once per formatActions invocation.
Example:
const terminology = getEmailTerminology(provider); const formatActions = <T extends { type: string; fields: Record<string, string | null> }>(actions: T[]) => { return actions .map((action) => { const parts = [`Type: ${action.type}`]; if (action.fields?.label) parts.push(`${terminology.label.singularCapitalized}: ${action.fields.label}`); // ... return parts.join(", "); }) .join("\n"); };apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1)
149-160: ActionItemsCell now provider-aware — tighten the prop typeForwarding
providerintoActionBadgeExpandedis correct and aligns with provider-specific terminology. Consider narrowing the prop type fromstringto the actual provider type for better type-safety and autocomplete. You can derive it fromuseAccountwithout introducing new global types.Apply this diff:
export function ActionItemsCell({ actionItems, - provider, + provider, }: { actionItems: PendingExecutedRules["executedRules"][number]["actionItems"]; - provider: string; + provider: ReturnType<typeof useAccount>["provider"]; }) { return ( <div className="mt-2 flex flex-wrap gap-1"> {actionItems.map((item) => ( <ActionBadgeExpanded key={item.id} action={item} provider={provider} /> ))} </div> ); }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx(1 hunks)apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx(2 hunks)apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx(7 hunks)apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx(3 hunks)apps/web/components/PlanBadge.tsx(6 hunks)apps/web/components/assistant-chat/tools.tsx(3 hunks)apps/web/components/email-list/EmailList.tsx(2 hunks)apps/web/components/email-list/EmailListItem.tsx(3 hunks)apps/web/components/email-list/EmailPanel.tsx(3 hunks)apps/web/components/email-list/PlanExplanation.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/components/PlanBadge.tsx
🧰 Additional context used
📓 Path-based instructions (16)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/web/**/*.tsx
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
Use React Hook Form with Zod validation for form handling
Use the
LoadingContentcomponent to handle loading and error states consistently in data-fetching components.Use PascalCase for components (e.g.
components/Button.tsx)
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/email-list/EmailList.tsx
!{.cursor/rules/*.mdc}
📄 CodeRabbit Inference Engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)
**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/web/components/!(ui)/**
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
All other components are in
components/
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/email-list/EmailList.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
**/*.{jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.
Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
**/*.{html,jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...Files:
apps/web/components/email-list/EmailListItem.tsxapps/web/components/email-list/EmailPanel.tsxapps/web/components/email-list/PlanExplanation.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/components/assistant-chat/tools.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/components/email-list/EmailList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsxapps/web/app/**
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
NextJS app router structure with (app) directory
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsxapps/web/app/(app)/*/**
📄 CodeRabbit Inference Engine (.cursor/rules/page-structure.mdc)
Components for the page are either put in page.tsx, or in the apps/web/app/(app)/PAGE_NAME folder
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsxapps/web/app/(app)/*/**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/page-structure.mdc)
If you need to use onClick in a component, that component is a client component and file must start with 'use client'
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsxapps/web/app/(app)/*/**/**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/page-structure.mdc)
If we're in a deeply nested component we will use swr to fetch via API
Files:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsxapps/web/app/**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
Components with
onClickmust be client components withuse clientdirectiveFiles:
apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsxapps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Pending.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx🧠 Learnings (3)
📚 Learning: 2025-08-17T16:57:25.825Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/llm.mdc:0-0 Timestamp: 2025-08-17T16:57:25.825Z Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Keep system and user prompts separate; system defines role/task, user contains data/contextApplied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-07-20T09:03:06.318Z Learning: Applies to **/*.{ts,tsx} : Use import type for types.Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR PR: elie222/inbox-zero#0 File: .cursor/rules/ultracite.mdc:0-0 Timestamp: 2025-07-20T09:03:06.318Z Learning: Applies to **/*.{ts,tsx} : Don't use primitive type aliases or misleading types.Applied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx🧬 Code Graph Analysis (8)
apps/web/components/email-list/EmailListItem.tsx (1)
apps/web/components/PlanBadge.tsx (1)
PlanBadge(19-80)apps/web/components/email-list/EmailPanel.tsx (1)
apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(72-82)apps/web/components/email-list/PlanExplanation.tsx (1)
apps/web/components/PlanBadge.tsx (1)
PlanBadge(19-80)apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (2)
apps/web/app/api/user/planned/route.ts (1)
PendingExecutedRules(9-9)apps/web/components/PlanBadge.tsx (1)
ActionBadgeExpanded(94-147)apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx (1)
apps/web/utils/terminology.ts (1)
getEmailTerminology(17-42)apps/web/components/assistant-chat/tools.tsx (2)
apps/web/utils/ai/assistant/chat.ts (1)
UpdateRuleActionsTool(479-481)apps/web/utils/terminology.ts (1)
getEmailTerminology(17-42)apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx (2)
apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(72-82)apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx (1)
ActionItemsCell(149-163)apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx (2)
apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(72-82)apps/web/app/(app)/[emailAccountId]/assistant/examples.ts (3)
getPersonas(85-348)getExamplePrompts(70-72)Personas(3-3)⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (15)
apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx (2)
14-14: Good move: provider-aware terminology import added.Importing getEmailTerminology aligns this page with provider-specific wording.
32-40: Prisma select now includes provider — correct and minimal.Selecting rule.id, rule.name, and nested account.provider is sufficient for the new terminology usage without over-fetching.
apps/web/components/email-list/EmailListItem.tsx (1)
29-29: Provider prop is correctly threaded and used by PlanBadge.
- Public props updated to accept provider: string.
- Destructuring includes provider.
- PlanBadge receives provider to render provider-specific action labels.
This keeps EmailListItem decoupled and consistent with the provider-aware UI.
Also applies to: 48-48, 181-181
apps/web/components/email-list/EmailPanel.tsx (1)
9-9: Provider pulled from context and passed down — solid integration.useAccount() provides provider which is passed to PlanExplanation. This keeps the panel consistent with the provider-aware terminology without prop-drilling from higher levels.
Also applies to: 34-34, 79-79
apps/web/components/email-list/EmailList.tsx (1)
181-181: Provider extracted from context and forwarded to list items — consistent with the new pattern.
- useAccount now includes provider, destructured alongside userEmail and emailAccountId.
- EmailListItem receives provider, enabling downstream provider-aware components (PlanBadge).
Looks good and maintains clean boundaries.
Also applies to: 480-481
apps/web/components/email-list/PlanExplanation.tsx (1)
9-9: PlanExplanation now provider-aware via prop — matches PlanBadge requirements.
- Added provider: string to public props and destructured it.
- Passed provider to PlanBadge so action messages honor provider terminology.
This aligns PlanExplanation with the rest of the provider-propagation in the app.
Also applies to: 16-16, 25-25
apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx (5)
24-27: Provider-aware examples/personas import and type-only usage look correctImporting provider-based getters and the exported Personas type alias is the right direction. Type-only import is used correctly.
78-80: Propagating provider-aware data into RulesPromptForm is correctPassing personas and examplePrompts down keeps the form decoupled and testable.
93-94: PersonaDialog now receiving personas: LGTMMatches the updated PersonaDialog API.
358-364: Examples panel wiring looks goodPassing examplePrompts and onSelect keeps concerns separated. Nice.
369-375: PureExamples prop signature update is consistentAccepting examplePrompts here simplifies the mapping and keeps the component pure.
apps/web/components/assistant-chat/tools.tsx (2)
22-22: Terminology import is correct and aligns with PR objective.Import path and usage intent look good.
198-205: All UpdatedRuleActions call sites includeproviderprop
Verified via code search that there are no<UpdatedRuleActions>usages missing the requiredproviderprop. No further changes needed.apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx (2)
72-72: Provider pulled from useAccount: good propagationAdding provider to the destructure keeps PendingTable aligned with provider-aware UI. No issues here.
194-197: AllActionItemsCellusages include theproviderprop
- Verified the active call in
Pending.tsxnow passesprovider.- The only other occurrence in
History.tsxis commented out, so no action needed.No further updates required.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/components/assistant-chat/tools.tsx (1)
204-212: Plumb provider into renderActionFields to surface “Category” vs “Label” in UIRight now only the diff text in formatActions uses provider-aware wording. The visible UI list still shows “Label” via renderActionFields. To fully meet the PR goal (“Category” instead of “label” for Outlook), pass provider into renderActionFields and use terminology there. Also wire provider into CreatedRuleToolCard.
Apply the following diff:
@@ export function CreatedRuleToolCard({ args, ruleId, }: { args: CreateRuleTool["input"]; ruleId?: string; }) { + const { provider } = useAccount(); const conditionsArray = [ args.condition.aiInstructions, args.condition.static, ].filter(Boolean); @@ {args.actions.map((action, i) => ( <div key={i} className="rounded-md bg-muted p-2 text-sm"> <div className="font-medium capitalize"> {action.type.toLowerCase().replace("_", " ")} </div> - {action.fields && renderActionFields(action.fields)} + {action.fields && renderActionFields(provider, action.fields)} </div> ))} @@ return ( <div key={i} className="rounded-md bg-muted p-2 text-sm"> <div className="font-medium capitalize"> {actionItem.type.toLowerCase().replace("_", " ")} </div> - {actionItem.fields && renderActionFields(actionItem.fields)} + {actionItem.fields && renderActionFields(provider, actionItem.fields)} </div> ); })} @@ -// Helper function to render action fields -function renderActionFields(fields: { +// Helper function to render action fields +function renderActionFields( + provider: string, + fields: { label?: string | null; content?: string | null; to?: string | null; cc?: string | null; bcc?: string | null; subject?: string | null; url?: string | null; webhookUrl?: string | null; -}) { + }, +) { const fieldEntries = []; // Only add fields that have actual values - if (fields.label) fieldEntries.push(["Label", fields.label]); + if (fields.label) + fieldEntries.push([ + getEmailTerminology(provider).label.singularCapitalized, + fields.label, + ]); if (fields.subject) fieldEntries.push(["Subject", fields.subject]); if (fields.to) fieldEntries.push(["To", fields.to]); if (fields.cc) fieldEntries.push(["CC", fields.cc]); if (fields.bcc) fieldEntries.push(["BCC", fields.bcc]); if (fields.content) fieldEntries.push(["Content", fields.content]); if (fields.url || fields.webhookUrl) - fieldEntries.push(["URL", fields.url || fields.webhookUrl]); + fieldEntries.push(["URL", fields.url || fields.webhookUrl]); if (fieldEntries.length === 0) return null;Also applies to: 90-97, 258-269, 511-553, 32-43
🧹 Nitpick comments (3)
apps/web/components/assistant-chat/tools.tsx (3)
221-224: Minor: precompute terminology once in formatActionsAvoid repeated getEmailTerminology calls inside the map. Precomputing improves readability.
const formatActions = < T extends { type: string; fields: Record<string, string | null> }, >( actions: T[], ) => { - return actions + const terminology = getEmailTerminology(provider); + return actions .map((action) => { const parts = [`Type: ${action.type}`]; - if (action.fields?.label) - parts.push( - `${getEmailTerminology(provider).label.action}: ${action.fields.label}`, - ); + if (action.fields?.label) + parts.push(`${terminology.label.action}: ${action.fields.label}`);Also applies to: 213-219
92-95: Nit: replace only swaps first underscore; use replaceAll for multi-word typesIf action types contain multiple underscores, replace("_", " ") only changes the first occurrence.
- {action.type.toLowerCase().replace("_", " ")} + {action.type.toLowerCase().replaceAll("_", " ")}- {actionItem.type.toLowerCase().replace("_", " ")} + {actionItem.type.toLowerCase().replaceAll("_", " ")}Also applies to: 264-267
231-235: Consistency: align “Webhook” vs “URL” labelformatActions uses “Webhook” while the UI list uses “URL”. Consider a single “Webhook/URL” label to avoid confusion.
- if (action.fields?.webhookUrl || action.fields?.url) - parts.push( - `Webhook: ${action.fields.webhookUrl || action.fields.url}`, - ); + if (action.fields?.webhookUrl || action.fields?.url) + parts.push( + `Webhook/URL: ${action.fields.webhookUrl || action.fields.url}`, + );- if (fields.url || fields.webhookUrl) - fieldEntries.push(["URL", fields.url || fields.webhookUrl]); + if (fields.url || fields.webhookUrl) + fieldEntries.push(["Webhook/URL", fields.webhookUrl || fields.url]);Also applies to: 531-533
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/web/components/assistant-chat/tools.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
apps/web/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use@/for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX
Files:
apps/web/components/assistant-chat/tools.tsx
apps/web/**/*.tsx
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/components/assistant-chat/tools.tsx
apps/web/components/**/*.tsx
📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)
Use React Hook Form with Zod validation for form handling
Use the
LoadingContentcomponent to handle loading and error states consistently in data-fetching components.Use PascalCase for components (e.g.
components/Button.tsx)
Files:
apps/web/components/assistant-chat/tools.tsx
!{.cursor/rules/*.mdc}
📄 CodeRabbit Inference Engine (.cursor/rules/cursor-rules.mdc)
Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location
Files:
apps/web/components/assistant-chat/tools.tsx
**/*.tsx
📄 CodeRabbit Inference Engine (.cursor/rules/form-handling.mdc)
**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields
Files:
apps/web/components/assistant-chat/tools.tsx
**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/components/assistant-chat/tools.tsx
apps/web/components/!(ui)/**
📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)
All other components are in
components/
Files:
apps/web/components/assistant-chat/tools.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/components/assistant-chat/tools.tsx
!pages/_document.{js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.
Files:
apps/web/components/assistant-chat/tools.tsx
**/*.{jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.
Files:
apps/web/components/assistant-chat/tools.tsx
**/*.{html,jsx,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)
**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...Files:
apps/web/components/assistant-chat/tools.tsx🧬 Code Graph Analysis (1)
apps/web/components/assistant-chat/tools.tsx (2)
apps/web/providers/EmailAccountProvider.tsx (1)
useAccount(72-82)apps/web/utils/terminology.ts (1)
getEmailTerminology(17-42)🔇 Additional comments (1)
apps/web/components/assistant-chat/tools.tsx (1)
22-22: Good addition: provider-aware terminology importImporting getEmailTerminology here is appropriate for provider-specific wording.
Summary by CodeRabbit
New Features
Style
Refactor