Skip to content

Simplify rules tab and clean up ui#803

Merged
elie222 merged 10 commits intomainfrom
feat/update-ui
Sep 19, 2025
Merged

Simplify rules tab and clean up ui#803
elie222 merged 10 commits intomainfrom
feat/update-ui

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Sep 19, 2025

Summary by CodeRabbit

  • New Features

    • Tabbed Rules view (List and Prompt).
    • “Add Rule” dialog and Available Actions panel.
    • New Examples grid with action icons.
  • UI/UX

    • Action icons added across rules and examples; clearer action wording and ordering.
    • Refreshed Rules header and dropdown actions; removed legacy threads column.
    • Improved placeholders and labels; cold-email label shows full name.
    • Updated layouts for prompt screen; spacing/margins refined.
    • Styling update: primary black buttons.
    • Landing page showcases video and action badges.
  • Bug Fixes

    • Prevented accidental parent clicks from ExpandableText and rule rows.
  • Chores

    • Version updated to v2.10.1.

@vercel
Copy link

vercel bot commented Sep 19, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Sep 19, 2025 0:58am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

Walkthrough

Introduces a tabbed Rules UI (List/Prompt), adds AddRuleDialog and AvailableActionsPanel, updates Rules and RulesPrompt layouts, integrates action icons/colors, refactors action availability helpers, adjusts action priority and display text, tweaks various UI strings and spacing, expands landing page demo components, and bumps version.

Changes

Cohort / File(s) Summary of changes
Assistant UI structure
apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx, .../RulesPromptNew.tsx, .../Rules.tsx
Adds two-tab layout (List/Prompt), integrates AvailableActionsPanel and ExamplesGrid, updates headers and menus, row click handling, and removes legacy threads UI; prompts/layout reorganized.
Rule creation dialogs
.../AddRuleDialog.tsx, .../RuleDialog.tsx, .../RuleForm.tsx
New AddRuleDialog trigger; default new-rule values typed (AI condition, LABEL action, AND operator); minor copy updates in RuleForm.
Examples and actions panel
.../AvailableActionsPanel.tsx, .../ExamplesList.tsx, .../examples.ts
New panel listing available/extra actions with icons; examples now show inferred action icons/colors and a 2-column ExamplesGrid; minor prompt order tweak.
Action utilities
apps/web/utils/action-display.tsx, apps/web/utils/action-sort.ts, apps/web/utils/ai/rule/create-rule-schema.ts
Adds getActionIcon and display text tweaks; reorders action priority; introduces getAvailableActions(provider) and getExtraActions() and refactors schema enumeration.
Landing page demos
apps/web/app/(landing)/components/page.tsx
Adds DismissibleVideoCard, IconCircle, and ActionBadges demo; imports ActionType; includes varied sample actions.
Video card spacing/API
apps/web/app/(app)/[emailAccountId]/automation/page.tsx, .../bulk-unsubscribe/BulkUnsubscribeSection.tsx, apps/web/src/components/VideoCard.tsx
Moves margin from wrappers to DismissibleVideoCard via new optional className prop; removes wrappers.
Shared components
apps/web/components/ExpandableText.tsx, apps/web/components/PlanBadge.tsx, apps/web/components/PremiumAlert.tsx
Stop event propagation on expand toggle; expand getActionColor mappings with exhaustive default; buttons switch to variant="primaryBlack".
Version
version.txt
Updates version from v2.9.48 to v2.10.1.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant RT as RulesTab (List/Prompt)
  participant R as Rules (List)
  participant P as RulesPromptFormat (Prompt)
  participant ARD as AddRuleDialog
  participant RD as RuleDialog

  U->>RT: Open Rules
  RT->>R: Show "List" tab by default
  U->>ARD: Click "Add Rule"
  ARD->>RD: Open dialog (new rule defaults)
  RD-->>U: Edit/Save

  U->>RT: Switch to "Prompt" tab
  RT->>P: Render prompt UI
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant PF as RulesPromptForm
  participant AP as AvailableActionsPanel
  participant EG as ExamplesGrid
  participant RD as RuleDialog

  U->>PF: Type prompt/edit
  PF-->>AP: Display available/extra actions (read-only)
  PF-->>EG: Show examples (optional)
  U->>EG: Select example
  EG-->>PF: Populate editor input
  U->>PF: Add rule
  PF->>RD: Open RuleDialog with parsed defaults
  RD-->>U: Confirm/Save
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • edulelis

Poem

Hoppity hop, I tidy the rules,
Tabs gleam neat like polished tools.
Icons wink, priorities align,
Prompts now dance in a two-column design.
Click—a dialog blooms in view,
Carrots crossed, I ship v2.10.1 too! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Simplify rules tab and clean up ui" is concise and accurately reflects the primary intent of the changeset — the RulesTab and RulesPrompt refactor to a tabbed layout and multiple UI cleanups across related components (ExamplesGrid, AvailableActionsPanel, icons, labels, dialogs). It highlights the main change without listing files or implementation details, so a teammate scanning history can understand the PR's purpose. The phrasing is clear and relevant to the modifications shown in the summary.
✨ 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/update-ui

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 19 files

Prompt for AI agents (all 3 issues)

Understand the root cause of the following 3 issues and fix them.


<file name="apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx">

<violation number="1" location="apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx:9">
DialogTrigger should use asChild when wrapping a Button to avoid nesting &lt;button&gt; inside &lt;button&gt; and to make the Button the actual trigger.</violation>
</file>

<file name="apps/web/app/(landing)/components/page.tsx">

<violation number="1" location="apps/web/app/(landing)/components/page.tsx:236">
Unstable storageKey uses Date.now(), so the dismiss state will not persist. Use a stable key.</violation>

<violation number="2" location="apps/web/app/(landing)/components/page.tsx:272">
Duplicate action id &quot;draft&quot; used twice in the array; ensure IDs are unique to avoid collisions.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

@elie222 elie222 merged commit 549e14c into main Sep 19, 2025
18 of 19 checks passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (20)
apps/web/app/(app)/[emailAccountId]/automation/page.tsx (1)

129-129: A11y: mark decorative SVG icon as hidden

Add aria-hidden to the icon so it isn’t announced by screen readers.

-            icon={<SparklesIcon className="h-5 w-5" />}
+            icon={<SparklesIcon className="h-5 w-5" aria-hidden="true" />}
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (1)

239-239: Consistency: use h-5 w-5 instead of size-5 for icons

Other icons use h-5 w-5; align here for consistency (and broader Tailwind compatibility).

-        icon={<ArchiveIcon className="size-5" />}
+        icon={<ArchiveIcon className="h-5 w-5" aria-hidden="true" />}
version.txt (1)

1-1: Consider following semantic versioning conventions

The version jump from v2.9.48 to v2.10.1 suggests a minor release with a patch. Given the extensive UI refactoring and new components introduced (AddRuleDialog, AvailableActionsPanel, etc.), ensure this aligns with your versioning strategy. If these changes include breaking changes to the UI that might affect integrations or user workflows, consider if this warrants a major version bump.

apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx (1)

9-13: Consider adding accessibility attributes and managing dialog state

The DialogTrigger button should manage dialog state properly and include accessibility attributes for better screen reader support.

 export function AddRuleDialog() {
+  const [open, setOpen] = useState(false);
   return (
-    <Dialog>
-      <DialogTrigger>
+    <Dialog open={open} onOpenChange={setOpen}>
+      <DialogTrigger asChild>
         <Button size="sm" Icon={PlusIcon}>
           Add Rule
         </Button>
       </DialogTrigger>
       <DialogContent className="max-w-4xl">
-        <RulesPrompt />
+        <RulesPrompt onSuccess={() => setOpen(false)} />
       </DialogContent>
     </Dialog>
   );
 }

You'll also need to import useState from React and ensure RulesPrompt accepts an optional onSuccess callback to close the dialog after successful rule creation.

apps/web/utils/action-display.tsx (1)

89-91: Consider providing a fallback icon for better resilience

While the exhaustive check is good for catching unmapped types during development, consider providing a fallback icon for production resilience.

     default: {
       const exhaustiveCheck: never = actionType;
-      return exhaustiveCheck;
+      // This should never happen in production, but provide a fallback
+      console.error(`Unmapped action type: ${actionType}`);
+      return FileTextIcon; // Generic document icon as fallback
     }
apps/web/app/(app)/[emailAccountId]/assistant/examples.ts (1)

17-22: Ensure boolean return type

Optional chaining can yield undefined. Coerce to boolean for clearer typing.

-  return (
-    rule.condition.static?.to?.includes("@company.com") ||
-    rule.condition.static?.from?.includes("@mycompany.com") ||
-    rule.actions.some((a) => a.content?.includes("cal.com/example"))
-  );
+  return Boolean(
+    rule.condition.static?.to?.includes("@company.com") ||
+      rule.condition.static?.from?.includes("@mycompany.com") ||
+      rule.actions.some((a) => a.content?.includes("cal.com/example")),
+  );
apps/web/components/ExpandableText.tsx (1)

45-48: Good call: stopPropagation prevents parent row clicks

Add basic a11y to the toggle button.

-      <motion.button
+      <motion.button
         type="button"
         onClick={(e) => {
           e.stopPropagation();
           setIsExpanded(!isExpanded);
         }}
+        aria-expanded={isExpanded}
         className="mt-1 flex items-center text-xs text-muted-foreground hover:text-primary"
apps/web/utils/action-sort.ts (1)

44-49: Incorrect Lodash API usage (sortBy extra arg ignored, potential confusion)

sortBy doesn’t take a directions array; the third arg here is a no-op iteratee. Use a single iteratee, or switch to orderBy if you need direction.

-  return sortBy(
-    actions,
-    [(action) => getActionTypePriority(action.type)],
-    ["asc"],
-  );
+  return sortBy(actions, (action) => getActionTypePriority(action.type));
apps/web/components/PlanBadge.tsx (2)

102-147: UI inconsistency: hardcoded colors vs getActionColor

ActionBadgeExpanded hardcodes colors (indigo/pink) for REPLY/FORWARD/SEND_EMAIL/DRAFT_EMAIL, which conflicts with getActionColor (green). Delegate color to getActionColor for consistency.

   case ActionType.REPLY:
-      return (
-        <div>
-          <Badge color="indigo">Reply</Badge>
-          <ActionContent action={action} />
-        </div>
-      );
+      return (
+        <div>
+          <Badge color={getActionColor(ActionType.REPLY)}>
+            {getActionLabel(ActionType.REPLY, provider)}
+          </Badge>
+          <ActionContent action={action} />
+        </div>
+      );
   case ActionType.SEND_EMAIL:
-      return (
-        <div>
-          <Badge color="indigo">Send email</Badge>
-          <ActionContent action={action} />
-        </div>
-      );
+      return (
+        <div>
+          <Badge color={getActionColor(ActionType.SEND_EMAIL)}>
+            {getActionLabel(ActionType.SEND_EMAIL, provider)}
+          </Badge>
+          <ActionContent action={action} />
+        </div>
+      );
   case ActionType.FORWARD:
-      return (
-        <div>
-          <Badge color="indigo">Forward email</Badge>
-          <ActionContent action={action} />
-        </div>
-      );
+      return (
+        <div>
+          <Badge color={getActionColor(ActionType.FORWARD)}>
+            {getActionLabel(ActionType.FORWARD, provider)}
+          </Badge>
+          <ActionContent action={action} />
+        </div>
+      );
   case ActionType.DRAFT_EMAIL:
-      return (
-        <div>
-          <Badge color="pink">Draft reply</Badge>
-          <ActionContent action={action} />
-        </div>
-      );
+      return (
+        <div>
+          <Badge color={getActionColor(ActionType.DRAFT_EMAIL)}>
+            {getActionLabel(ActionType.DRAFT_EMAIL, provider)}
+          </Badge>
+          <ActionContent action={action} />
+        </div>
+      );

232-249: Plan badge color for LABEL differs from action color

Plan color for LABEL is purple but getActionColor returns blue. If intentional, ignore; otherwise align for consistency.

apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx (1)

26-41: Provider-aware labels for Label/MOVE_FOLDER

For better terminology (Label vs Category, Move to folder vs Move), derive names from provider where applicable.

-import { SectionHeader } from "@/components/Typography";
+import { SectionHeader } from "@/components/Typography";
+import { getEmailTerminology } from "@/utils/terminology";
@@
-export function AvailableActionsPanel() {
-  const { provider } = useAccount();
+export function AvailableActionsPanel() {
+  const { provider } = useAccount();
   return (
@@
-          <ActionSection
-            actions={getAvailableActions(provider)}
-            title="Available Actions"
-          />
-          <ActionSection actions={getExtraActions()} title="Extra" />
+          <ActionSection
+            actions={getAvailableActions(provider)}
+            title="Available Actions"
+            provider={provider}
+          />
+          <ActionSection
+            actions={getExtraActions()}
+            title="Extra"
+            provider={provider}
+          />
-function ActionSection({
-  title,
-  actions,
-}: {
-  title: string;
-  actions: ActionType[];
-}) {
+function ActionSection({
+  title,
+  actions,
+  provider,
+}: {
+  title: string;
+  actions: ActionType[];
+  provider: string;
+}) {
   return (
@@
-        {actions.map((actionType) => {
+        {actions.map((actionType) => {
           const Icon = getActionIcon(actionType);
+          const name =
+            actionType === ActionType.LABEL
+              ? getEmailTerminology(provider).label.action
+              : actionNames[actionType];
           return (
             <div key={actionType} className="flex items-center gap-2">
               <Icon className="size-3.5 text-muted-foreground" />
-              <span className="text-sm">{actionNames[actionType]}</span>
+              <span className="text-sm">{name}</span>
             </div>
           );
         })}
apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx (1)

153-201: Prompt form restructure looks solid; minor a11y nit

Using Label as a section header is slightly semantically off. Prefer a heading (e.g., h3) or a SectionHeader component for non-form labels.

-            <Label className="font-cal text-xl leading-7">Add new rules</Label>
+            <h3 className="font-cal text-xl leading-7">Add new rules</h3>
apps/web/utils/ai/rule/create-rule-schema.ts (3)

52-55: Return a tuple from getExtraActions for stronger typing.

Keeps the tuple type through spreads.

-export const getExtraActions = () => [
-  ActionType.DIGEST,
-  ActionType.CALL_WEBHOOK,
-];
+export const getExtraActions = (): [ActionType, ...ActionType[]] => [
+  ActionType.DIGEST,
+  ActionType.CALL_WEBHOOK,
+];

38-50: Drop unnecessary .filter(isDefined) here.

No undefined is produced in this array; the filter and import are redundant.

-import { isDefined } from "@/utils/types";
...
-  ].filter(isDefined);
+  ];

Also applies to: 9-9


38-38: Loosen provider param type to match helpers.

isMicrosoftProvider accepts string | null | undefined. Consider mirroring to reduce casts downstream.

-export function getAvailableActions(provider: string) {
+export function getAvailableActions(provider: string | null | undefined) {
...
-const actionSchema = (provider: string) =>
+const actionSchema = (provider: string | null | undefined) =>

Also applies to: 57-57

apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (1)

247-256: Make clickable TableRow accessible (keyboard + semantics).

Rows with onClick should be focusable and respond to Enter/Space.

-<TableRow
+<TableRow
   key={rule.id}
   className={!rule.enabled ? "bg-muted opacity-60" : ""}
-  onClick={() => {
+  role="button"
+  tabIndex={0}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      if (isColdEmailBlocker) coldEmailDialog.onOpen();
+      else ruleDialog.onOpen({ ruleId: rule.id, editMode: false });
+    }
+  }}
+  onClick={() => {
     if (isColdEmailBlocker) {
       coldEmailDialog.onOpen();
     } else {
       ruleDialog.onOpen({
         ruleId: rule.id,
         editMode: false,
       });
     }
   }}
>
apps/web/app/(landing)/components/page.tsx (1)

34-35: Avoid deep import from an app-scoped file for shared UI.

Consider moving ActionBadges to a shared components module to prevent cross-app coupling.

apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx (3)

68-77: Prop type includes className but it’s not accepted/used.

Either accept and wire it, or drop from the type.

-function PureExamplesGrid({
-  examples,
-  onSelect,
-  provider,
-}: {
+function PureExamplesGrid({
+  examples,
+  onSelect,
+  provider,
+  className,
+}: {
   examples: string[];
   onSelect: (example: string) => void;
   provider: string;
   className?: string;
 }) {
-  const examplePrompts = getExamplePrompts(provider, examples);
+  const examplePrompts = getExamplePrompts(provider, examples);

And apply className={cn("grid grid-cols-2 gap-4", className)} on the container.


114-140: Make action detection stricter to avoid false positives.

"mark" matches many words (“bookmark”, “marketing”). Use word-boundary regexes and ordered checks.

-function getActionType(example: string): ActionType | null {
+function getActionType(example: string): ActionType | null {
   const lowerExample = example.toLowerCase();
-
-  if (lowerExample.includes("forward")) {
+  if (/\bforward\b/.test(lowerExample)) {
     return ActionType.FORWARD;
   }
-  if (lowerExample.includes("draft")) {
+  if (/\bdraft\b/.test(lowerExample)) {
     return ActionType.DRAFT_EMAIL;
   }
-  if (lowerExample.includes("reply")) {
+  if (/\breply\b/.test(lowerExample)) {
     return ActionType.REPLY;
   }
-  if (lowerExample.includes("archive")) {
+  if (/\barchive\b/.test(lowerExample)) {
     return ActionType.ARCHIVE;
   }
-  if (lowerExample.includes("spam")) {
+  if (/\bspam\b/.test(lowerExample)) {
     return ActionType.MARK_SPAM;
   }
-  if (lowerExample.includes("mark")) {
+  if (/\bmark\b/.test(lowerExample) && /\bread\b/.test(lowerExample)) {
     return ActionType.MARK_READ;
   }
-  if (lowerExample.includes("label") || lowerExample.includes("categorize")) {
+  if (/\b(label|categorize)\b/.test(lowerExample)) {
     return ActionType.LABEL;
   }
-
   return null;
 }

38-43: Keyed by full example text; ensure uniqueness.

              <Button
                key={example}
                variant="outline"
                onClick={() => onSelect(example)}
                className="h-auto w-full justify-start text-wrap py-2 text-left"
              >

Using key={example} is fragile — getExamplePrompts() can yield identical strings (shared commonPrompts / repeated calendar links). If duplicates are possible, use a stable unique key (index+text or a short hash) or attach explicit ids to the example items.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e5b1d4 and 73859f5.

📒 Files selected for processing (19)
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx (2 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx (2 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (7 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx (4 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/assistant/examples.ts (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx (1 hunks)
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (1 hunks)
  • apps/web/app/(landing)/components/page.tsx (2 hunks)
  • apps/web/components/ExpandableText.tsx (1 hunks)
  • apps/web/components/PlanBadge.tsx (1 hunks)
  • apps/web/components/PremiumAlert.tsx (2 hunks)
  • apps/web/utils/action-display.tsx (3 hunks)
  • apps/web/utils/action-sort.ts (1 hunks)
  • apps/web/utils/ai/rule/create-rule-schema.ts (2 hunks)
  • version.txt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (20)
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/app/(app)/[emailAccountId]/assistant/examples.ts
  • apps/web/components/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-sort.ts
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/examples.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/app/(app)/[emailAccountId]/assistant/examples.ts
  • version.txt
  • apps/web/components/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-sort.ts
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/app/(app)/[emailAccountId]/assistant/examples.ts
  • apps/web/utils/action-sort.ts
  • apps/web/utils/ai/rule/create-rule-schema.ts
**/*.{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/app/(app)/[emailAccountId]/assistant/examples.ts
  • apps/web/components/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-sort.ts
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/examples.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/app/(app)/[emailAccountId]/assistant/examples.ts
  • apps/web/components/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-sort.ts
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/app/(app)/[emailAccountId]/assistant/examples.ts
  • version.txt
  • apps/web/components/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-sort.ts
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/ExpandableText.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/components/PlanBadge.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/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/ExpandableText.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/components/PremiumAlert.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/components/PlanBadge.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/utils/action-display.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.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/AvailableActionsPanel.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
  • apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx
  • apps/web/app/(app)/[emailAccountId]/automation/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx
  • apps/web/app/(landing)/components/page.tsx
  • apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/action-sort.ts
  • apps/web/utils/action-display.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/action-sort.ts
  • apps/web/utils/ai/rule/create-rule-schema.ts
apps/web/utils/ai/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

apps/web/utils/ai/**/*.{ts,tsx}: Place main LLM feature implementations under apps/web/utils/ai/
LLM feature functions should follow the provided TypeScript pattern (separate system/user prompts, use createGenerateObject, Zod schema validation, early validation, return result.object)
Keep system prompts and user prompts separate
System prompt should define the LLM's role and task specifications
User prompt should contain the actual data and context
Always define a Zod schema for response validation
Make Zod schemas as specific as possible to guide LLM output
Use descriptive scoped loggers for each feature
Log inputs and outputs with appropriate log levels and include relevant context
Implement early returns for invalid inputs
Use proper error types and logging for failures
Implement fallbacks for AI failures
Add retry logic for transient failures using withRetry
Use XML-like tags to structure data in prompts
Remove excessive whitespace and truncate long inputs in prompts
Format prompt data consistently across similar functions
Use TypeScript types for all parameters and return values in LLM features
Define clear interfaces for complex input/output structures in LLM features

Files:

  • apps/web/utils/ai/rule/create-rule-schema.ts
apps/web/utils/{ai,llms}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)

Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Files:

  • apps/web/utils/ai/rule/create-rule-schema.ts
🧠 Learnings (11)
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : System prompt should define the LLM's role and task specifications

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/examples.ts
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Keep system prompts and user prompts separate

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/examples.ts
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : User prompt should contain the actual data and context

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/examples.ts
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Format prompt data consistently across similar functions

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/examples.ts
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
  • apps/web/utils/ai/rule/create-rule-schema.ts
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use TypeScript types for all parameters and return values in LLM features

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsx
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use XML-like tags to structure data in prompts

Applied to files:

  • apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Implement all server actions using the `next-safe-action` library for type safety, input validation, context management, and error handling. Refer to `apps/web/utils/actions/safe-action.ts` for client definitions (`actionClient`, `actionClientUser`, `adminActionClient`).

Applied to files:

  • apps/web/utils/ai/rule/create-rule-schema.ts
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define input validation schemas using Zod in the corresponding `.validation.ts` file. These schemas are used by `next-safe-action` (`.schema()`) and can also be reused on the client for form validation.

Applied to files:

  • apps/web/utils/ai/rule/create-rule-schema.ts
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-07-18T15:05:16.146Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod schemas for validation in dedicated files and use them for both client and server validation.

Applied to files:

  • apps/web/utils/ai/rule/create-rule-schema.ts
📚 Learning: 2025-09-17T22:05:28.616Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.616Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Always define a Zod schema for response validation

Applied to files:

  • apps/web/utils/ai/rule/create-rule-schema.ts
🧬 Code graph analysis (10)
apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx (3)
apps/web/providers/EmailAccountProvider.tsx (1)
  • useAccount (72-82)
apps/web/utils/ai/rule/create-rule-schema.ts (2)
  • getAvailableActions (38-50)
  • getExtraActions (52-55)
apps/web/utils/action-display.tsx (1)
  • getActionIcon (62-93)
apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx (1)
apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx (1)
  • RulesPrompt (32-65)
apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx (3)
apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx (1)
  • AddRuleDialog (6-19)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (1)
  • Rules (66-482)
apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptFormat.tsx (1)
  • RulesPromptFormat (18-86)
apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (1)
apps/web/components/VideoCard.tsx (1)
  • DismissibleVideoCard (20-40)
apps/web/app/(app)/[emailAccountId]/automation/page.tsx (1)
apps/web/components/VideoCard.tsx (1)
  • DismissibleVideoCard (20-40)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (3)
apps/web/utils/label.ts (1)
  • inboxZeroLabels (32-68)
apps/web/utils/action-display.tsx (1)
  • getActionIcon (62-93)
apps/web/components/PlanBadge.tsx (1)
  • getActionColor (206-230)
apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx (5)
apps/web/utils/action-display.tsx (1)
  • getActionIcon (62-93)
apps/web/components/PlanBadge.tsx (1)
  • getActionColor (206-230)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/app/(app)/[emailAccountId]/assistant/examples.ts (1)
  • getExamplePrompts (70-78)
apps/web/utils/mention.ts (1)
  • convertLabelsToDisplay (14-16)
apps/web/app/(landing)/components/page.tsx (3)
apps/web/components/VideoCard.tsx (1)
  • DismissibleVideoCard (20-40)
apps/web/app/(app)/[emailAccountId]/onboarding/IconCircle.tsx (1)
  • IconCircle (82-99)
apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (1)
  • ActionBadges (484-518)
apps/web/utils/ai/rule/create-rule-schema.ts (2)
apps/web/utils/email/provider-types.ts (1)
  • isMicrosoftProvider (5-7)
apps/web/utils/types.ts (1)
  • isDefined (8-10)
apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx (5)
apps/web/components/Input.tsx (1)
  • Label (116-132)
apps/web/components/LoadingContent.tsx (1)
  • LoadingContent (13-27)
apps/web/components/editor/SimpleRichTextEditor.tsx (1)
  • SimpleRichTextEditor (28-158)
apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx (1)
  • AvailableActionsPanel (26-41)
apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx (1)
  • ExamplesGrid (112-112)
🔇 Additional comments (26)
apps/web/app/(app)/[emailAccountId]/automation/page.tsx (2)

127-137: Margin shift to card-level looks good

Moving spacing onto DismissibleVideoCard via className="my-4" is cleaner and prevents stray gaps after dismissal.


134-136: Resolved — VideoCard thumbnail and iframe title verified

apps/web/components/VideoCard.tsx uses next/image for the thumbnail, the iframe includes a meaningful title (Video: {title}), and className is passed through.

apps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsx (2)

237-247: Good move: margin now lives on the card

Using className="my-4" on DismissibleVideoCard removes the need for a wrapper and avoids leftover spacing when dismissed.


245-245: img.youtube.com allowed in next.config.ts
images.remotePatterns in apps/web/next.config.ts contains { protocol: "https", hostname: "img.youtube.com" }, so no config change required for the thumbnail to render.

apps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsx (1)

6-19: LGTM! Clean implementation of the dialog wrapper

The component cleanly separates the trigger button from the dialog content, following good composition patterns. The use of max-w-4xl provides appropriate width for the rules form.

apps/web/components/PremiumAlert.tsx (1)

84-110: LGTM! Consistent button styling improvements

The changes to use variant="primaryBlack" across all action buttons provide visual consistency. The use of the asChild prop with Link for the API key button maintains proper navigation semantics.

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

611-611: Improved placeholder text for better user guidance

The new placeholder "e.g. Newsletters, regular content from publications, blogs, or services I've subscribed to" provides much clearer guidance than the previous generic example. This helps users understand the types of conditions they can create.


1224-1226: Clearer AI description for draft replies

The updated text "Our AI will generate a reply based on your email history and knowledge base" is more accurate and informative than the previous version, helping users understand the AI's capabilities better.

apps/web/utils/action-display.tsx (3)

37-39: Improved readability of label action display

The format change from "Label: " to "Label as ''" reads more naturally and clearly indicates the label being applied.


53-55: Clearer folder action description

The change from "Folder: " to "Move to '' folder" better describes the action being performed and follows consistent formatting with quotes around the folder name.


62-93: Well-structured icon mapping with exhaustive type checking — verified

getActionIcon returns lucide-react icon components and is consumed in:

  • apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (line ~503)
  • apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx (line ~55)
  • apps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsx (lines ~34, ~84)

Exhaustive default case present; no changes required.

apps/web/app/(app)/[emailAccountId]/assistant/examples.ts (1)

47-48: Order tweak looks fine; confirm UI respects array order

If ExamplesGrid applies its own sorting, this reorder might not surface. Verify the grid renders in insertion order.

apps/web/utils/action-sort.ts (1)

9-25: Priority reorder acknowledged

Looks consistent with the UI intent (archive earlier; spam/webhook after digest).

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

83-96: Typed defaults for new rules: good

Using enums for condition/action/operator is a solid improvement and matches the rest of the system.


52-53: Confirm useRule handles empty ID without fetching

useRule(ruleId || "") will invoke the hook with an empty string when creating. Ensure the hook treats empty keys as “no fetch”.

Would you like a small patch to guard the hook (e.g., pass null and skip fetch) if it currently requests on empty IDs?

apps/web/components/PlanBadge.tsx (1)

206-229: Color mapping: exhaustive check is great

Exhaustiveness via never is a good guard.

apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx (1)

8-27: Tabs-based layout reads cleaner

Defaulting to “list” and wiring the AddRuleDialog in the header looks good.

apps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx (3)

25-31: New imports (ExamplesGrid, AvailableActionsPanel) integrate well

Good separation of concerns.


54-55: onHideExamples callback is a nice UX touch

Keeps the form focused when needed.

Also applies to: 73-79


206-221: Right-rail and examples placement improve scannability

Good responsive grid; ExamplesGrid rendering is conditional and lightweight.

apps/web/app/(app)/[emailAccountId]/assistant/Rules.tsx (4)

133-139: Cold email label: good correction.

Using the full label (inboxZeroLabels.cold_email.name) is clearer and avoids brittle splits.


227-232: Header “Add Rule” button placement looks good.

Inline, minimal, and behind showAddRuleButton.


261-273: Good: stopPropagation on inner button.

Prevents row handler from firing; keeps UX predictable.


498-516: Action badges with icons: solid improvement.

Icon + color mapping centralization via getActionIcon/getActionColor is tidy.

Also applies to: 503-504, 511-513

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

33-36: Icons for examples: nice clarity boost.

Action-derived icon + color improves scannability.

Also applies to: 45-52


142-159: Icon color mapping looks fine.

Consistent with Badge palette.

@@ -0,0 +1,66 @@
import { ActionType } from "@prisma/client";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing "use client" — this component uses hooks

This file calls useAccount(); it must be a Client Component in Next.js app router.

+ "use client";
+
 import { ActionType } from "@prisma/client";

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

🤖 Prompt for AI Agents
In apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsx at
line 1, the component uses React hooks (useAccount) but is missing the Next.js
Client Component directive; add the string "use client" as the very first line
of the file (before any imports) so the component is treated as a Client
Component, then keep the existing imports and code unchanged.

Comment on lines +236 to +237
storageKey={`video-dismissible-${Date.now()}`}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

storageKey should be stable; Date.now() breaks dismissal persistence.

A changing key prevents the card from staying dismissed across sessions.

-  storageKey={`video-dismissible-${Date.now()}`}
+  storageKey="video-dismissible-getting-started-ai-assistant"

Also applies to: 228-237

🤖 Prompt for AI Agents
In apps/web/app/(landing)/components/page.tsx around lines 228 to 237, the
storageKey currently uses Date.now() making it change every render so dismissal
can't persist; replace the dynamic Date.now() with a stable key (for example a
constant string like "video-dismissible" or a versioned key like
"video-dismissible-v1" or derive from a stable video id/feature flag) so the
dismissed state is stored under a consistent key across sessions.

Comment on lines +251 to +323
<ActionBadges
actions={[
{
type: ActionType.LABEL,
label: "Label",
id: "label",
},
{
type: ActionType.MOVE_FOLDER,
label: "Move to folder",
id: "move_folder",
folderName: "Marketing",
},
{
type: ActionType.ARCHIVE,
label: "Archive",
id: "archive",
},
{
type: ActionType.DRAFT_EMAIL,
label: "Draft",
id: "draft",
},
{
type: ActionType.DRAFT_EMAIL,
label: "Draft",
id: "draft",
content: "Hi, I'd like to discuss the project with you.",
},
{
type: ActionType.REPLY,
label: "Reply",
id: "reply",
},
{
type: ActionType.SEND_EMAIL,
label: "Send",
id: "send",
},
{
type: ActionType.FORWARD,
label: "Forward",
id: "forward",
},
{
type: ActionType.MARK_SPAM,
label: "Mark as spam",
id: "mark_spam",
},
{
type: ActionType.MARK_READ,
label: "Mark as read",
id: "mark_read",
},
{
type: ActionType.CALL_WEBHOOK,
label: "Call webhook",
id: "call_webhook",
},
{
type: ActionType.TRACK_THREAD,
label: "Track thread",
id: "track_thread",
},
{
type: ActionType.DIGEST,
label: "Digest",
id: "digest",
},
]}
provider="gmail"
/>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Duplicate React keys and non-canonical provider.

  • Two actions have id: "draft" → duplicate keys.
  • provider="gmail" may not match isGoogleProvider checks (likely "google").
-  provider="gmail"
+  provider="google"
@@
-  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft" },
+  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft-1" },
@@
-  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft", content: "Hi, I'd like to discuss the project with you." },
+  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft-2", content: "Hi, I'd like to discuss the project with you." },

🏁 Script executed:

#!/bin/bash
# Verify expected provider literals
rg -n -C2 'function\s+isGoogleProvider|export\s+function\s+isGoogleProvider|isGoogleProvider\(' --type=ts

Length of output: 9547


Fix duplicate React keys and provider literal (apps/web/app/(landing)/components/page.tsx:251-323)

  • Two actions share id: "draft" — give each a unique id.
  • provider="gmail" should be provider="google" to match isGoogleProvider (checks for "google").
-  provider="gmail"
+  provider="google"
@@
-  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft" },
+  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft-1" },
@@
-  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft", content: "Hi, I'd like to discuss the project with you." },
+  { type: ActionType.DRAFT_EMAIL, label: "Draft", id: "draft-2", content: "Hi, I'd like to discuss the project with you." },
📝 Committable suggestion

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

Suggested change
<ActionBadges
actions={[
{
type: ActionType.LABEL,
label: "Label",
id: "label",
},
{
type: ActionType.MOVE_FOLDER,
label: "Move to folder",
id: "move_folder",
folderName: "Marketing",
},
{
type: ActionType.ARCHIVE,
label: "Archive",
id: "archive",
},
{
type: ActionType.DRAFT_EMAIL,
label: "Draft",
id: "draft",
},
{
type: ActionType.DRAFT_EMAIL,
label: "Draft",
id: "draft",
content: "Hi, I'd like to discuss the project with you.",
},
{
type: ActionType.REPLY,
label: "Reply",
id: "reply",
},
{
type: ActionType.SEND_EMAIL,
label: "Send",
id: "send",
},
{
type: ActionType.FORWARD,
label: "Forward",
id: "forward",
},
{
type: ActionType.MARK_SPAM,
label: "Mark as spam",
id: "mark_spam",
},
{
type: ActionType.MARK_READ,
label: "Mark as read",
id: "mark_read",
},
{
type: ActionType.CALL_WEBHOOK,
label: "Call webhook",
id: "call_webhook",
},
{
type: ActionType.TRACK_THREAD,
label: "Track thread",
id: "track_thread",
},
{
type: ActionType.DIGEST,
label: "Digest",
id: "digest",
},
]}
provider="gmail"
/>
</div>
<ActionBadges
actions={[
{
type: ActionType.LABEL,
label: "Label",
id: "label",
},
{
type: ActionType.MOVE_FOLDER,
label: "Move to folder",
id: "move_folder",
folderName: "Marketing",
},
{
type: ActionType.ARCHIVE,
label: "Archive",
id: "archive",
},
{
type: ActionType.DRAFT_EMAIL,
label: "Draft",
id: "draft-1",
},
{
type: ActionType.DRAFT_EMAIL,
label: "Draft",
id: "draft-2",
content: "Hi, I'd like to discuss the project with you.",
},
{
type: ActionType.REPLY,
label: "Reply",
id: "reply",
},
{
type: ActionType.SEND_EMAIL,
label: "Send",
id: "send",
},
{
type: ActionType.FORWARD,
label: "Forward",
id: "forward",
},
{
type: ActionType.MARK_SPAM,
label: "Mark as spam",
id: "mark_spam",
},
{
type: ActionType.MARK_READ,
label: "Mark as read",
id: "mark_read",
},
{
type: ActionType.CALL_WEBHOOK,
label: "Call webhook",
id: "call_webhook",
},
{
type: ActionType.TRACK_THREAD,
label: "Track thread",
id: "track_thread",
},
{
type: ActionType.DIGEST,
label: "Digest",
id: "digest",
},
]}
provider="google"
/>
</div>
🤖 Prompt for AI Agents
In apps/web/app/(landing)/components/page.tsx around lines 251 to 323, two
Action objects use the same id "draft" and the provider prop is set to the
literal "gmail" while the code expects "google"; fix by giving each action a
unique id (e.g., "draft" and "draft_with_content" or similar) so React keys are
unique, and change provider="gmail" to provider="google" to match
isGoogleProvider checks.

Comment on lines 57 to +63
const actionSchema = (provider: string) =>
z.object({
type: z
.enum(
isMicrosoftProvider(provider)
? (Object.values(ActionType) as [ActionType, ...ActionType[]])
: (Object.values(ActionType).filter(
(type) => type !== ActionType.MOVE_FOLDER,
) as [ActionType, ...ActionType[]]),
)
.describe("The type of the action"),
.enum([...getAvailableActions(provider), ...getExtraActions()])
.describe(
`The type of the action. '${ActionType.DIGEST}' means emails will be added to the digest email the user receives. ${isMicrosoftProvider(provider) ? `'${ActionType.LABEL}' means emails will be categorized in Outlook.` : ""}`,
),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix z.enum typing to avoid tuple widening (can break type-checking).

When spreading a tuple with an array, the result widens to ActionType[], which z.enum doesn’t accept. Materialize the union first and pass a tuple.

Apply:

-const actionSchema = (provider: string) =>
-  z.object({
-    type: z
-      .enum([...getAvailableActions(provider), ...getExtraActions()])
-      .describe(
+const actionSchema = (provider: string) => {
+  const allowed = [
+    ...getAvailableActions(provider),
+    ...getExtraActions(),
+  ] as [ActionType, ...ActionType[]];
+  return z.object({
+    type: z
+      .enum(allowed)
+      .describe(
         `The type of the action. '${ActionType.DIGEST}' means emails will be added to the digest email the user receives. ${isMicrosoftProvider(provider) ? `'${ActionType.LABEL}' means emails will be categorized in Outlook.` : ""}`,
       ),
     fields: z
       .object({
         ...
       }),
     delayInMinutes: delayInMinutesSchema,
-  });
+  });
+};
📝 Committable suggestion

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

Suggested change
const actionSchema = (provider: string) =>
z.object({
type: z
.enum(
isMicrosoftProvider(provider)
? (Object.values(ActionType) as [ActionType, ...ActionType[]])
: (Object.values(ActionType).filter(
(type) => type !== ActionType.MOVE_FOLDER,
) as [ActionType, ...ActionType[]]),
)
.describe("The type of the action"),
.enum([...getAvailableActions(provider), ...getExtraActions()])
.describe(
`The type of the action. '${ActionType.DIGEST}' means emails will be added to the digest email the user receives. ${isMicrosoftProvider(provider) ? `'${ActionType.LABEL}' means emails will be categorized in Outlook.` : ""}`,
),
const actionSchema = (provider: string) => {
const allowed = [
...getAvailableActions(provider),
...getExtraActions(),
] as [ActionType, ...ActionType[]];
return z.object({
type: z
.enum(allowed)
.describe(
`The type of the action. '${ActionType.DIGEST}' means emails will be added to the digest email the user receives. ${isMicrosoftProvider(provider) ? `'${ActionType.LABEL}' means emails will be categorized in Outlook.` : ""}`,
),
fields: z
.object({
...
}),
delayInMinutes: delayInMinutesSchema,
});
};
🤖 Prompt for AI Agents
In apps/web/utils/ai/rule/create-rule-schema.ts around lines 57 to 63, the
spread of getAvailableActions(provider) and getExtraActions() into z.enum widens
to ActionType[] which breaks z.enum typing; fix by materializing a readonly
tuple first (e.g. const actions = [...getAvailableActions(provider),
...getExtraActions()] as const) and pass that tuple into z.enum(actions) so
TypeScript preserves literal types; if those helper functions don't return
readonly literal tuples, explicitly cast their results to literal tuples when
building the actions tuple.

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