Skip to content

"Category" instead of "label" for outlook#696

Merged
elie222 merged 6 commits intomainfrom
feat/categorize-outlook-copy
Aug 20, 2025
Merged

"Category" instead of "label" for outlook#696
elie222 merged 6 commits intomainfrom
feat/categorize-outlook-copy

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Aug 19, 2025

Summary by CodeRabbit

  • New Features

    • Provider-aware terminology across the app (e.g., “Label” vs “Category”) in rules, badges, menus, side navigation, bulk-unsubscribe, summaries, and assistant text.
    • Provider-specific personas and example prompts in assistant dialogs and rule prompts.
    • Setup flow now shows the browser-extension step only for Google accounts.
  • Style

    • Wider account linking dialog, clearer button text, and minor spacing/copy tweaks.
  • Refactor

    • Centralized terminology and provider context propagated for consistent UI and prompt generation.

@claude
Copy link

claude bot commented Aug 19, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@vercel
Copy link

vercel bot commented Aug 19, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Aug 20, 2025 5:39am

@claude
Copy link

claude bot commented Aug 19, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 19, 2025

Walkthrough

Provider 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

Cohort / File(s) Summary
Terminology utility
apps/web/utils/terminology.ts
New getEmailTerminology(provider) returns provider-specific label/category words and casing variants.
Action display & summaries
apps/web/utils/action-display.ts, apps/web/app/(app)/[emailAccountId]/assistant/ActionSummaryCard.tsx
getActionDisplay(action, provider) and ActionSummaryCard use provider terminology for LABEL and TRACK_THREAD text; ActionSummaryCard gained a provider prop.
Assistant rule UI (forms/lists)
apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx, apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx, apps/web/app/(app)/[emailAccountId]/assistant/PersonaDialog.tsx
Components accept/propagate provider and personas; RuleForm/Rules pass provider into action displays and ActionSummaryCard; PersonaDialog now takes personas prop.
Personas & example prompts
apps/web/app/(app)/[emailAccountId]/assistant/examples.ts, apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx, apps/web/components/assistant-chat/examples-dialog.tsx
Static personas/examplePrompts replaced with getPersonas(provider) / getExamplePrompts(provider); components obtain personas/prompts by provider and pass them down.
Rule → prompt pipeline
apps/web/utils/actions/rule.ts, apps/web/utils/rule/prompt-file.ts, apps/web/utils/ai/rule/create-prompt-from-rule.ts, apps/web/utils/ai/rule/create-prompt-from-rule.test.ts, apps/web/utils/ai/rule/generate-prompt-on-update-rule.ts, apps/web/utils/ai/rule/generate-prompt-on-delete-rule.ts, apps/web/utils/ai/assistant/process-user-request.ts
provider threaded into rule create/update/delete flows and prompt-file updates; createPromptFromRule(rule, provider) uses terminology for LABEL wording; tests updated for new signature.
Bulk unsubscribe & menus
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/common.tsx, apps/web/components/LabelsSubMenu.tsx, apps/web/components/SideNav.tsx
Use getEmailTerminology(provider) to render label/category strings (singular/plural/capitalized) in UI copy and empty states.
Plan/action badges & email list plumbing
apps/web/components/PlanBadge.tsx, apps/web/components/*email-list*, apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx, apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx, apps/web/components/assistant-chat/tools.tsx
provider added/passed through PlanBadge/ActionBadge/ActionBadgeExpanded and email-list components; action labels/messages use terminology; several component signatures updated to accept provider.
Setup flow & context
apps/web/app/(app)/[emailAccountId]/setup/SetupContent.tsx, apps/web/app/(app)/[emailAccountId]/setup/page.tsx
SetupContent now reads provider via useAccount() (removed prop); provider passed to child components and used to conditionally render Google-specific step (isGoogleProvider).
Accounts dialog copy
apps/web/app/(app)/accounts/AddAccount.tsx
Dialog width increased and button labels clarified; no signature changes.
Debug rule history
apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
Prisma query includes provider; action label display uses terminology derived from rule's provider.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • edulelis

Poem

I hop through labels, new and old,
I learn each inbox's naming fold.
Prompts and personas now conform,
From Gmail "label" to Outlook "form".
A rabbit tweaks the tiny terms—hip hop, hooray! 🐇

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/categorize-outlook-copy

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@claude
Copy link

claude bot commented Aug 19, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 file

If existingPrompt or updatedRulePrompt is falsy, returning "" may overwrite a non-empty rulesPrompt with 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 compatibility

Checklist 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 Link

You’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 accessibility

Empty 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 fetch

If 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 standards

The 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 Outlook

For 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 good

Passing emailAccount.account.provider into 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 failure

To align with AI-utils guidelines: add try/catch, log context, apply retry (withRetry), and fall back to existingPrompt to 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 generateObject in withRetry(...) 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 switch

All 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 payloads

Given the earlier note in generate-prompt-on-update-rule, consider avoiding overwriting rulesPrompt with an empty string. If updatedPrompt is 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 rulesPrompt in 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 fields

Using label.id! and label.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 providers

This 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

isMicrosoftProvider accepts string | 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-specific

Placeholder/empty-state strings still say “label”. For Outlook, this should be “category”.

Introduce provider-aware copy here (will require passing provider down or passing terminology):

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

We avoid console in app code. Errors are already surfaced via toastError.

Replace both console.error(res); with a scoped debug logger or remove entirely:

// Remove console.error; rely on toastError which is already present

Also applies to: 261-263

apps/web/components/assistant-chat/examples-dialog.tsx (1)

83-86: Guard against invalid persona when provider changes

If the user switches accounts (and thus providers), a previously selected persona may no longer exist in the next provider’s personas, causing personas[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.other assumes 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 actions

Examples 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 strings

The onboarding strings still literally say “Label all ...”. Since provider is 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 replacement

The current implementation uses simple regex replacements which might have edge cases:

  1. The regex \bLabel\b might miss cases with special characters
  2. 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 performance

The getPersonas function 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
Copy link

claude bot commented Aug 20, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 Outlook

This 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-aware

convertLabelsToDisplay (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 term

Affected locations:

  • apps/web/utils/mention.ts: change convertLabelsToDisplay(text: string): stringconvertLabelsToDisplay(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 pitfall

Using 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 render

getPersonas(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” bullets

If 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-aware

The 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 type

Forwarding provider into ActionBadgeExpanded is correct and aligns with provider-specific terminology. Consider narrowing the prop type from string to the actual provider type for better type-safety and autocomplete. You can derive it from useAccount without 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.

📥 Commits

Reviewing files that changed from the base of the PR and between f339c4c and 047b6e0.

📒 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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/components/email-list/EmailListItem.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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 LoadingContent component 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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/email-list/EmailList.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements 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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/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.tsx
  • apps/web/components/email-list/EmailPanel.tsx
  • apps/web/components/email-list/PlanExplanation.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/components/email-list/EmailList.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/web/app/**

📄 CodeRabbit Inference Engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/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.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPrompt.tsx
apps/web/app/**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/project-structure.mdc)

Components with onClick must be client components with use client directive

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/ExecutedRulesTable.tsx
  • apps/web/app/(app)/[emailAccountId]/debug/rule-history/[ruleId]/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx
  • apps/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/context

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} : 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 correct

Importing 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 correct

Passing personas and examplePrompts down keeps the form decoupled and testable.


93-94: PersonaDialog now receiving personas: LGTM

Matches the updated PersonaDialog API.


358-364: Examples panel wiring looks good

Passing examplePrompts and onSelect keeps concerns separated. Nice.


369-375: PureExamples prop signature update is consistent

Accepting 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 include provider prop
Verified via code search that there are no <UpdatedRuleActions> usages missing the required provider prop. No further changes needed.

apps/web/app/(app)/[emailAccountId]/assistant/Pending.tsx (2)

72-72: Provider pulled from useAccount: good propagation

Adding provider to the destructure keeps PendingTable aligned with provider-aware UI. No issues here.


194-197: All ActionItemsCell usages include the provider prop

  • Verified the active call in Pending.tsx now passes provider.
  • The only other occurrence in History.tsx is commented out, so no action needed.

No further updates required.

@claude
Copy link

claude bot commented Aug 20, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 UI

Right 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 formatActions

Avoid 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 types

If 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” label

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

📥 Commits

Reviewing files that changed from the base of the PR and between 047b6e0 and 5f4b3d2.

📒 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
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

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 LoadingContent component 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}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import 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 use elements 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 import

Importing getEmailTerminology here is appropriate for provider-specific wording.

@elie222 elie222 merged commit c1fbe94 into main Aug 20, 2025
12 of 13 checks passed
@elie222 elie222 deleted the feat/categorize-outlook-copy branch December 18, 2025 23:01
@coderabbitai coderabbitai bot mentioned this pull request Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments