Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughIntroduces 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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 <button> inside <button> 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 "draft" 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.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (20)
apps/web/app/(app)/[emailAccountId]/automation/page.tsx (1)
129-129: A11y: mark decorative SVG icon as hiddenAdd 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 iconsOther 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 conventionsThe 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 stateThe 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
useStatefrom React and ensureRulesPromptaccepts an optionalonSuccesscallback to close the dialog after successful rule creation.apps/web/utils/action-display.tsx (1)
89-91: Consider providing a fallback icon for better resilienceWhile 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 typeOptional 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 clicksAdd 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 getActionColorActionBadgeExpanded 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 colorPlan 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_FOLDERFor 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 nitUsing 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
undefinedis 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.
isMicrosoftProvideracceptsstring | 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
ActionBadgesto 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
📒 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.tsapps/web/components/ExpandableText.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-sort.tsapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/utils/ai/rule/create-rule-schema.tsapps/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.tsapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/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.tsversion.txtapps/web/components/ExpandableText.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-sort.tsapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/utils/ai/rule/create-rule-schema.tsapps/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.tsapps/web/utils/action-sort.tsapps/web/utils/ai/rule/create-rule-schema.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport Prisma in the project using
import prisma from "@/utils/prisma";
**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.
Files:
apps/web/app/(app)/[emailAccountId]/assistant/examples.tsapps/web/components/ExpandableText.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-sort.tsapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/utils/ai/rule/create-rule-schema.tsapps/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.tsapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...
Files:
apps/web/app/(app)/[emailAccountId]/assistant/examples.tsapps/web/components/ExpandableText.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-sort.tsapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/utils/ai/rule/create-rule-schema.tsapps/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.tsversion.txtapps/web/components/ExpandableText.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-sort.tsapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/utils/ai/rule/create-rule-schema.tsapps/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
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/components/ExpandableText.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/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
LoadingContentcomponent to handle loading and error states consistently in data-fetching components.Use PascalCase for components (e.g.
components/Button.tsx)
Files:
apps/web/components/ExpandableText.tsxapps/web/components/PremiumAlert.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/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.tsxapps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/components/PremiumAlert.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/components/PlanBadge.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/utils/action-display.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsxapps/web/app/(app)/*/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
If you need to use onClick in a component, that component is a client component and file must start with 'use client'
Files:
apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsxapps/web/app/(app)/*/**/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/page-structure.mdc)
If we're in a deeply nested component we will use swr to fetch via API
Files:
apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsxapps/web/app/**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Components with
onClickmust be client components withuse clientdirectiveFiles:
apps/web/app/(app)/[emailAccountId]/assistant/AvailableActionsPanel.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/AddRuleDialog.tsxapps/web/app/(app)/[emailAccountId]/assistant/RuleForm.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsxapps/web/app/(app)/[emailAccountId]/bulk-unsubscribe/BulkUnsubscribeSection.tsxapps/web/app/(app)/[emailAccountId]/automation/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/Rules.tsxapps/web/app/(app)/[emailAccountId]/assistant/ExamplesList.tsxapps/web/app/(landing)/components/page.tsxapps/web/app/(app)/[emailAccountId]/assistant/RulesPromptNew.tsxapps/web/utils/**
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Create utility functions in
utils/folder for reusable logicFiles:
apps/web/utils/action-sort.tsapps/web/utils/action-display.tsxapps/web/utils/ai/rule/create-rule-schema.tsapps/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 sizeFiles:
apps/web/utils/action-sort.tsapps/web/utils/ai/rule/create-rule-schema.tsapps/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 featuresFiles:
apps/web/utils/ai/rule/create-rule-schema.tsapps/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 specificationsApplied 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 separateApplied 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 contextApplied 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 functionsApplied 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 commentsApplied to files:
apps/web/app/(app)/[emailAccountId]/assistant/RuleDialog.tsxapps/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 featuresApplied 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 promptsApplied 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 validationApplied 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 goodMoving spacing onto DismissibleVideoCard via className="my-4" is cleaner and prevents stray gaps after dismissal.
134-136: Resolved — VideoCard thumbnail and iframe title verifiedapps/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 cardUsing 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 wrapperThe component cleanly separates the trigger button from the dialog content, following good composition patterns. The use of
max-w-4xlprovides appropriate width for the rules form.apps/web/components/PremiumAlert.tsx (1)
84-110: LGTM! Consistent button styling improvementsThe changes to use
variant="primaryBlack"across all action buttons provide visual consistency. The use of theasChildprop 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 guidanceThe 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 repliesThe 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 displayThe format change from "Label: " to "Label as ''" reads more naturally and clearly indicates the label being applied.
53-55: Clearer folder action descriptionThe 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 — verifiedgetActionIcon 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 orderIf 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 acknowledgedLooks 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: goodUsing enums for condition/action/operator is a solid improvement and matches the rest of the system.
52-53: Confirm useRule handles empty ID without fetchinguseRule(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 greatExhaustiveness via never is a good guard.
apps/web/app/(app)/[emailAccountId]/assistant/RulesTabNew.tsx (1)
8-27: Tabs-based layout reads cleanerDefaulting 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 wellGood separation of concerns.
54-55: onHideExamples callback is a nice UX touchKeeps the form focused when needed.
Also applies to: 73-79
206-221: Right-rail and examples placement improve scannabilityGood 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/getActionColoris 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"; | |||
There was a problem hiding this comment.
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.
| storageKey={`video-dismissible-${Date.now()}`} | ||
| /> |
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
🧩 Analysis chain
Duplicate React keys and non-canonical provider.
- Two actions have
id: "draft"→ duplicate keys. provider="gmail"may not matchisGoogleProviderchecks (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=tsLength 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.
| <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.
| 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.` : ""}`, | ||
| ), |
There was a problem hiding this comment.
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.
| 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.
Summary by CodeRabbit
New Features
UI/UX
Bug Fixes
Chores